diff --git a/.gitignore b/.gitignore
index f73e2fd..976055a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,6 +4,7 @@ __pycache__
/**/.ipynb_checkpoints
**/*.ipynb_checkpoints/
venv/
+mesoSPIM/test/fixtures/
*.log
*.bin
.idea
@@ -16,14 +17,9 @@ prototypes/python-essentials/temp\.tif
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/*
mesoSPIM/config/*
!mesoSPIM/config/demo_config.py
-
-mesoSPIM/config/etl_parameters/*.*
-!mesoSPIM/config/etl_parameters/ETL-parameters.csv
-mesoSPIM/src/mesoSPIM_DemoSerial\.py
mesoSPIM/src/devices/zoom/dynamixel\.zip
mesoSPIM/acq/
diff --git a/CHANGELOG.md b/CHANGELOG.md
index b311e46..9c95701 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,55 @@
+## Latest version
+* :gem: Support of multiple PI single-axis controllers, thanks to #52 by @drchrisch.
+Note the changes in config file: single multi-axis controller (C-884) is initialized by `'PI_1controllerNstages'`,
+while multiple single-axis controllers (C-663) by `'PI_NcontrollersNstages'`.
+
+## Version [0.1.5]
+* :gem: Improved Tiling Wizard:
+ * buttons `x-start, x-end, y-start, y-end` added for easier navigation:
+ no need to search for corners of imaginary box around the sample.
+ * `left, then right` illuminations can be created automatically for each tile: no need for manual duplication
+ and changing the illumination directions in the Acquisition Manager.
+
+* :gem: Improved saving options in Fiji/BigStitcher H5 format:
+ * `laser`, `illumination`, `angle` attributes are saved in the BigStitcher XML file.
+ * (optional) downsampling and compression are supported.
+* :gem: Image window got `Adjust levels` button for automatic intensity adjustment.
+* :gem: Image window got optional `Box overlay` to help measure sample dimensions.
+* :mag: Tests for tiling and serial communication are created.
+* :bug: **Bugfix:** long-standing `permission denied` issues with serial communication
+to filter wheel and zoom servo are fixed.
+The fix opens serial ports once and keeps them open during the session.
+The root cause was due to laser control software polling serial ports regularly, thus blocking access to them.
+
+## Version [0.1.4]
+### Features & updates
+* :warning: **Config files need to be updated** Please note: Updating to this version requires updating your microscope configuration file. Please copy the new configuration options from the `demo.cfg` file into your config files.
+* :warning: :gem: **New handling of config files** - If there is a single config file (without a 'demo' prefix in the filename and apart from the `demo_config.py`-file) in the config folder, the software will automatically load this file. Otherwise, the config selection GUI is opened. This is especially helpful when operating a mesoSPIM with multiple users. Thanks to @raacampbell for this feature!
+* :gem: **New: Writing HDF5** - If all rows in the acquistion manager contain the same file name (ending in `.h5`), the entire acquisition list will be saved in a single hdf5 file and a XML created automatically. Both can then be loaded into [Bigstitcher](https://imagej.net/BigStitcher) for stitching & multiview fusion.
+For this, the `npy2bdv` package by @nvladimus needs to be installed via `python -m pip install npy2bdv`
+* :gem: **New: Dark mode** - If the `dark_mode` option in the config file is set to `True`, the user interface appears in a dark mode. For this, the `qdarkstyle` package needs to be installed via `python -m pip install qdarkstyle`.
+* :gem: **New: Camera and Acquisition Manager Windows can be reopened** - A new menu allows the camera and acquisition manager windows to be reopened in case they get closed. The same menu bar allows exiting the program as well.
+* :gem: **New: Disabling arrow buttons** - To allow mesoSPIM configurations with less than 5 motorized stages, the arrow buttons in the main window can now be disabled in the configuration file. Typical examples are a mesoSPIM without a rotation stage or a mesoSPIM using only a single motorized z-stage. This feature can also be useful if the serial connection to the stages is too slow and pressing the arrow buttons leads to incorrect movements.
+* :gem: **Interactive IPython console** - If the software is launched via `python mesoSPIM-control.py -C`, an interactive IPython console is launched for debugging. Feature by @raacampbell.
+* :gem: **Command-line demo mode option** - If the software is launched via `python mesoSPIM-control.py -D`, it launches automatically into demo mode. Feature by @raacampbell.
+* :gem: **New: Support for PCO cameras** - PCO cameras with lightsheet mode are now supported. For this the `pco` Python package needs to be installed via `python -m pip install pco`. Currently, the only tested camera is the PCO panda 4.2 bi with lightsheet firmware.
+* :gem: **New: Support for Sutter Lambda 10B Filter Controller** Thanks to Kevin Dean @AdvancedImagingUTSW, Sutter filter wheels are now supported.
+* :gem: **New: Support for Physik Instrumente stepper motor stages in a XYZ configuration** Thanks to @drchrisch, a mesoSPIM configuration ('PI_xyz') using stepper motor stages for sample movement is now supported. Please note that this is currently not supporting focus movements or sample rotations.
+* :gem: **New: Support for Physik Instrumente C-863 controller in a single-stage config** To allow setting up a simplified mesoSPIM using only a single motorized z-stage (all other stages need to be manually operated), the combination of the C-863 motor controller and L-509 stage is now supported ('PI_z')
+* :sparkles: **Improvement:** **Disabling movement buttons in the GUI** By modifying the `ui_options` dictionary in the configuration file, the X,Y,Z, focus, rotation, and load/unload buttons can be disabled. This allows modifing the UI for mesoSPIM setups which do not utilize the full set of 5 axes. Disabled buttons are greyed out.
+* :sparkles: **Improvement:** **Updated multicolor tiling wizard** The tiling wizard now displays the FOV size and calculates the X and Y FOV offsets using a percentage setting. For this, the pixel size settings in the configuration file need to be set correctly.
+* :sparkles: **Improvement:** **Physik Instrumente stages now report their referencing status after startup in the logfile** This allows for easier diagnosis of unreferenced stages during startup. Feature by @raacampbell.
+* :bug: **Bugfix:** Binning was not working properly with all cameras.
+* :bug: **Bugfix:** Removed unnecessary imports.
+* :bug: **Bugfix:** Laser power setting `max_laser_voltage` was always 10V, ignoring the config file. This can damage some lasers that operate on lower command voltage.
+
+### Contributors
+* Fabian Voigt (@ffvoigt)
+* Nikita Vladimirov (@nvladimus)
+* Kevin Dean (@AdvancedImagingUTSW)
+* Christian Schulze (@drchrisch)
+* Rob Campbell (@raacampbell)
+
## Version [0.1.3] - March 13, 2020
* :warning: **Depending on your microscope configuration, this release breaks backward compatibility with previous configuration files. If necessary, update your configuration file using `demo_config.py` as an example.**
* :warning: **There are new startup parameters in the config file - make sure to update your config files accordingly**. For example, `average_frame_rate` has been added.
@@ -27,7 +79,7 @@ to f_start and at z_end, the detection path focus is at z_end. This allows imagi
* :bug: **Bugfix #34:** Fixed: Last frame in a stack is blank due to an off-by-one error
* :bug: **Bugfix #35:** Fixed: Software crashes when one folder (to save data in) in the acquisition list does not exist
----
+--
## Version [0.1.2] - August 19th, 2019
* **New:** Logging is now supported. Logfiles go in the `log` folder.
diff --git a/README.md b/README.md
index 65c63a5..08afaf0 100644
--- a/README.md
+++ b/README.md
@@ -14,7 +14,7 @@ If you are updating `mesoSPIM-control` from a previous version: Please have a cl
### Prerequisites
* Windows 7 or Windows 10
-* Python >3.6
+* Python >=3.6
#### Device drivers
* [Hamamatsu DCAM API](https://dcam-api.com/) when using Hamamatsu Orca Flash 4.0 V2 or V3 sCMOS cameras. To test camera functionality, [HCImage](https://dcam-api.com/hamamatsu-software/) can be used.
@@ -24,26 +24,31 @@ If you are updating `mesoSPIM-control` from a previous version: Please have a cl
* [Robotis DynamixelSDK](https://github.com/ROBOTIS-GIT/DynamixelSDK/releases) for Dynamixel Zoom servos. Make sure you download version 3.5.4 of the SDK.
#### Python
-mesoSPIM-control is usually running with [Anaconda](https://www.anaconda.com/download/) using a >3.6 Python. For a clean python install, the following packages are necessary (part of Anaconda):
-
-* csv
-* traceback
-* pprint
-* numpy
-* scipy
-* ctypes
-* importlib
-* PyQt5 (if there are problems with PyQt5 such as `ModuleNotFoundError: No module named 'PyQt5.QtWinExtras` after starting `mesoSPIM-control`, try reinstalling PyQt5 by: `python -m pip install --user -I PyQt5` and `python -m pip install --user -I PyQt5-sip`)
-
-In addition (for Anaconda), the following packages need to be installed:
-* nidaqmx (`python -m pip install nidaqmx`)
-* indexed (`python -m pip install indexed`)
-* serial (`python -m pip install pyserial`)
-* pyqtgraph (`python -m pip install pyqtgraph`)
-* pywinusb (`python -m pip install pywinusb`)
-* PIPython (part of the Physik Instrumente software collection. Unzip it, `cd` to the directory with the Anaconda terminal as an admin user, then install with `python setup.py install`. Test install with test installation with `import pipython`). You can also download PIPython [here](https://github.com/royerlab/pipython)
-* tifffile (`python -m pip install tifffile`)
-* ([PyVCAM when using Photometrics cameras](https://github.com/Photometrics/PyVCAM)
+mesoSPIM-control is usually running with [Anaconda](https://www.anaconda.com/download/) using a >=3.6 Python.
+##### Anaconda
+(optional) Create and activate a Python 3.6 environment from Anaconda prompt (you can use any name instead of `py36`):
+```
+conda create -n py36 python=3.6
+conda activate py36
+```
+The step above is optional because the latest Python 3.8 is backward compatible with Python 3.6 code.
+
+Many libraries are already included in Anaconda.
+Install mesoSPIM-specific libraries:
+```
+pip install -r requirements-anaconda.txt
+```
+
+##### Clean python
+For a clean (non-Anaconda) python interpreter, install all required libraries:
+```
+pip install -r requirements-clean-python.txt
+```
+
+##### Additional libraries
+Camera libraries are not hosted on PyPi and need to be installed manually:
+* [PyVCAM when using a Photometrics camera](https://github.com/Photometrics/PyVCAM)
+* pco (`python -m pip install pco`) when using a PCO camera ([Link](https://pypi.org/project/pco/)). A Version ≥0.1.3 is recommended.
#### Preparing python bindings for device drivers
* For PI stages, copy `C:\ProgramData\PI\GCSTranslator\PI_GCS2_DLL_x64.dll` in the main mesoSPIM folder: `PI_GCS2_DLL_x64.dll`
@@ -63,12 +68,30 @@ At time of writing that means the master trigger out (`PXI6259/port0/line1`) sho
Use BNC T connectors to split each analog output line to both lasers.
* You will need to set the ThorLabs shutter controllers to run on TTL input mode.
-#### Run the software.
+## Launching
+#### From Anaconda prompt
```
+conda activate py36
python mesoSPIM_Control.py
```
-After launch, it will prompt you for a configuration file. Please choose a file
-with demo devices (e.g. `DemoStage`) for testing.
+The software will now start. If you have multiple configuration files you will be prompted to choose one.
+
+#### From start_mesoSPIM.bat file
+Open the `start_mesoSPIM.bat` file in text editor and configure Anaconda and `py36` path to your own.
+Once done, launch mesoSPIM by double-clicking the file.
+Optionally, create a Windows shortcut (via right-click menu) and place it e.g. on your desktop.
+Using shortcut saves a lot of time.
+
+#### Starting with interactive console
+You may also run the software with an interactive IPython console for de-bugging:
+```
+python mesoSPIM_Control.py -C
+```
+For example, executing `mSpim.state.__dict__` in this console will show the current mesoSPIM state.
+
+## Troubleshooting
+If there are problems with PyQt5 such as `ModuleNotFoundError: No module named 'PyQt5.QtWinExtras` after starting
+`mesoSPIM-control`, try reinstalling PyQt5 by: `python -m pip install --user -I PyQt5` and `python -m pip install --user -I PyQt5-sip`)
-#### Documentation for users
+## Documentation for users
For instructions on how to use mesoSPIM-control, please check out the documentation [here](https://github.com/mesoSPIM/mesoSPIM-powerpoint-documentation).
diff --git a/mesoSPIM/config/demo_config.py b/mesoSPIM/config/demo_config.py
index 5b218fa..375924a 100644
--- a/mesoSPIM/config/demo_config.py
+++ b/mesoSPIM/config/demo_config.py
@@ -8,6 +8,18 @@
(The extension has to be .py).
'''
+'''
+Dark mode: Renders the UI dark
+'''
+ui_options = {'dark_mode' : True, # Dark mode: Renders the UI dark if enabled
+ 'enable_x_buttons' : True, # Here, specific sets of UI buttons can be disabled
+ 'enable_y_buttons' : True,
+ 'enable_z_buttons' : True,
+ 'enable_f_buttons' : True,
+ 'enable_rotation_buttons' : True,
+ 'enable_loading_buttons' : True,
+ }
+
'''
Waveform output for Galvos, ETLs etc.
'''
@@ -186,9 +198,12 @@
and safety limits. The rotation position defines a XYZ position (in absolute coordinates)
where sample rotation is safe. Additional hardware dictionaries (e.g. pi_parameters)
define the stage configuration details.
+All positions are absolute.
+
+'stage_type' options: 'DemoStage', 'PI_1controllerNstages' (former 'PI'), 'PI_NcontrollersNstages'
'''
-stage_parameters = {'stage_type' : 'DemoStage', # 'DemoStage' or 'PI' or other configs found in mesoSPIM_serial.py
+stage_parameters = {'stage_type' : 'DemoStage', # 'DemoStage'. 'PI_1controllerNstages', 'PI_NcontrollersNstages', see below
'startfocus' : -10000,
'y_load_position': -86000,
'y_unload_position': -120000,
@@ -198,8 +213,8 @@
'y_min' : -160000,
'z_max' : 99000,
'z_min' : -99000,
- 'f_max' : 99000,
- 'f_min' : -99000,
+ 'f_max' : 10000,
+ 'f_min' : -10000,
'theta_max' : 999,
'theta_min' : -999,
'x_rot_position': 0,
@@ -207,45 +222,31 @@
'z_rot_position': 66000,
}
-'''
-Depending on the stage hardware, further dictionaries define further details of the stage configuration
-
-For a standard mesoSPIM V4 with PI stages, the following pi_parameters are necessary (replace the
-serialnumber with the one of your controller):
-
+''''
+If 'stage_type' = 'PI_1controllerNstages' (vanilla mesoSPIM V5 with single 6-axis controller):
pi_parameters = {'controllername' : 'C-884',
- 'stages' : ('M-112K033','L-406.40DG10','M-112K033','M-116.DG','M-406.4PD','NOSTAGE'),
+ 'stages' : ('L-509.20DG10','L-509.40DG10','L-509.20DG10','M-060.DG','M-406.4PD','NOSTAGE'),
'refmode' : ('FRF',),
- 'serialnum' : ('118015797'),
+ 'serialnum' : ('118075764'),
}
-For a standard mesoSPIM V5 with PI stages, the following pi_parameters are necessary (replace the
-serialnumber with the one of your controller):
-
-pi_parameters = {'controllername' : 'C-884',
- 'stages' : ('L-509.20DG10','L-509.40DG10','L-509.20DG10','M-060.DG','M-406.4PD','NOSTAGE'),
- 'refmode' : ('FRF',),
- 'serialnum' : ('118015799'),
+If 'stage_type' = 'PI_NcontrollersNstages' (mesoSPIM V5 with multiple single-axis controllers):
+pi_parameters = {'axes_names': ('x', 'y', 'z', 'theta', 'f'),
+ 'stages': ('L-509.20SD00', 'L-509.40SD00', 'L-509.20SD00', None, 'MESOSPIM_FOCUS'),
+ 'controllername': ('C-663', 'C-663', 'C-663', None, 'C-663'),
+ 'serialnum': ('**********', '**********', '**********', None, '**********'),
+ 'refmode': ('FRF', 'FRF', 'FRF', None, 'RON')
+ }
'''
'''
Filterwheel configuration
-'''
-
-'''
-For a DemoFilterWheel, no COMport needs to be specified, for a Ludl Filterwheel,
-a valid COMport is necessary.
+For a DemoFilterWheel, no COMport needs to be specified.
+For a Ludl Filterwheel, a valid COMport is necessary. Ludl marking 10 = position 0.
'''
filterwheel_parameters = {'filterwheel_type' : 'DemoFilterWheel', # 'DemoFilterWheel' or 'Ludl'
'COMport' : 'COM53'}
-# Ludl marking 10 = position 0
-
-'''
-
-A Ludl double filter wheel can be
-'''
-
filterdict = {'Empty-Alignment' : 0, # Every config should contain this
'405-488-647-Tripleblock' : 1,
'405-488-561-640-Quadrupleblock' : 2,
@@ -259,9 +260,6 @@
'''
Zoom configuration
-'''
-
-'''
For the DemoZoom, servo_id, COMport and baudrate do not matter. For a Dynamixel zoom,
these values have to be there
'''
@@ -302,10 +300,18 @@
'6.3x' : 1.03}
'''
-Initial acquisition parameters
+ HDF5 parameters, if this format is used for data saving (optional).
+Downsampling and compression slows down writing by 5x - 10x, use with caution.
+Imaris can open these files if no subsampling and no compression is used.
+'''
+hdf5 = {'subsamp': ((1, 1, 1),), #((1, 1, 1),) no subsamp, ((1, 1, 1), (1, 4, 4)) for 2-level (z,y,x) subsamp.
+ 'compression': None, # None, 'gzip', 'lzf'
+ 'flip_xyz': (True, True, False) # match BigStitcher coordinates to mesoSPIM axes.
+ }
+'''
+Initial acquisition parameters
Used as initial values after startup
-
When setting up a new mesoSPIM, make sure that:
* 'max_laser_voltage' is correct (5 V for Toptica MLEs, 10 V for Omicron SOLE)
* 'galvo_l_amplitude' and 'galvo_r_amplitude' (in V) are correct (not above the max input allowed by your galvos)
@@ -369,4 +375,4 @@
'camera_binning':'1x1',
'camera_sensor_mode':'ASLM',
'average_frame_rate': 4.969,
-}
+}
\ No newline at end of file
diff --git a/mesoSPIM/config/etl_parameters/ETL-parameters.csv b/mesoSPIM/config/etl_parameters/ETL-parameters.csv
index 35e9dea..070ef24 100644
--- a/mesoSPIM/config/etl_parameters/ETL-parameters.csv
+++ b/mesoSPIM/config/etl_parameters/ETL-parameters.csv
@@ -1,134 +1,67 @@
-Objective;Wavelength;Zoom;ETL-Left-Offset;ETL-Left-Amp;ETL-Right-Offset;ETL-Right-Amp
-
-1x;405 nm;0.63x;2.4900000000000038;0.6950000000000021;2.655999999999999;1.2649999999999966
-
-1x;405 nm;0.8x;2.4950000000000037;0.7649999999999995;2.6500000000000004;0.725
-
-1x;405 nm;1x;2.4900000000000038;0.61;2.6550000000000002;0.5949999999999999
-
-1x;405 nm;1.25x;2.418000000000004;0.6830000000000002;2.6609999999999996;0.5439999999999998
-
-1x;405 nm;1.6x;2.465999999999994;0.43899999999999995;2.687999999999999;0.42499999999999993
-
-1x;405 nm;2x;2.498999999999993;0.2859999999999996;2.6669999999999994;0.312
-
-1x;405 nm;2.5x;2.4839999999999955;0.2900000000000001;2.667;0.20799999999999985
-
-1x;405 nm;3.2x;2.4809999999999928;0.243;2.67;0.21900000000000014
-
-1x;405 nm;4x;2.512999999999999;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.336000000000007;1.3;2.5560000000000027;1.24
-
-1x;488 nm;0.8x;2.334000000000004;0.942;2.5919999999999996;0.936
-
-1x;488 nm;1x;2.3939999999999975;0.8880000000000002;2.4920000000000018;0.804
-
-1x;488 nm;1.25x;2.3960000000000017;0.692;2.5759999999999974;0.6260000000000001
-
-1x;488 nm;1.6x;2.3880000000000026;0.524;2.547999999999999;0.449
-
-1x;488 nm;2x;2.397000000000001;0.4610000000000001;2.5450000000000004;0.366
-
-1x;488 nm;2.5x;2.386999999999998;0.3990000000000001;2.5870000000000006;0.26999999999999996
-
-1x;488 nm;3.2x;2.3499999999999965;0.251;2.5459999999999994;0.23
-
-1x;488 nm;4x;2.398000000000001;0.158;2.7359999999999944;0.09600000000000002
-
-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.422999999999997;1.123;2.4930000000000025;1.1460000000000001
-
-1x;515 nm;0.8x;2.3970000000000007;0.81;2.571999999999999;0.8360000000000001
-
-1x;515 nm;1x;2.3919999999999986;0.782;2.4500000000000024;0.702
-
-1x;515 nm;1.25x;2.396999999999999;0.7190000000000001;2.5840000000000014;0.5319999999999998
-
-1x;515 nm;1.6x;2.3340000000000067;0.41400000000000003;2.58899999999999;0.45099999999999996
-
-1x;515 nm;2x;2.3899999999999992;0.388;2.584;0.392
-
-1x;515 nm;2.5x;2.3920000000000075;0.317;2.59199999999999;0.26199999999999996
-
-1x;515 nm;3.2x;2.3910000000000076;0.20499999999999974;2.58999999999999;0.174
-
-1x;515 nm;4x;2.388;0.219;2.596999999999998;0.17100000000000004
-
-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.3620000000000014;0.87;2.547;0.75
-
-1x;561 nm;1x;2.399999999999999;0.744;2.7329999999999974;0.594
-
-1x;561 nm;1.25x;2.3959999999999995;0.688;2.5409999999999955;0.586
-
-1x;561 nm;1.6x;2.3800000000000017;0.584;2.628999999999992;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.4259999999999997;0.243;2.626;0.184
-
-1x;561 nm;4x;2.410999999999999;0.216;2.611999999999997;0.158
-
-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.307999999999999;1.314;2.492000000000001;1.14
-
-1x;594 nm;0.8x;2.4180000000000015;1.12;2.537;0.78
-
-1x;594 nm;1x;2.4180000000000015;0.9000000000000004;2.5949999999999984;0.8300000000000001
-
-1x;594 nm;1.25x;2.403000000000002;0.6150000000000002;2.5949999999999984;0.5299999999999999
-
-1x;594 nm;1.6x;2.4080000000000017;0.44000000000000006;2.5999999999999983;0.35999999999999976
-
-1x;594 nm;2x;2.403000000000002;0.37000000000000005;2.5519999999999996;0.31
-
-1x;594 nm;2.5x;2.398000000000002;0.3900000000000002;2.5999999999999983;0.24499999999999966
-
-1x;594 nm;3.2x;2.4040000000000084;0.23799999999999957;2.6029999999999998;0.21500000000000002
-
-1x;594 nm;4x;2.405000000000008;0.26400000000000007;2.6060000000000008;0.1419999999999999
-
-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;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;5x;2.415000000000002;0.14500000000000002;2.6099999999999923;0.12499999999999994
-
-1x;647 nm;6.3x;2.481;0.07400000000000001;2.7779999999999996;0.092
-
+Objective;Wavelength;Zoom;ETL-Left-Offset;ETL-Left-Amp;ETL-Right-Offset;ETL-Right-Amp
+1x;405 nm;0.63x;2.4900000000000038;0.6950000000000021;2.655999999999999;1.2649999999999966
+1x;405 nm;0.8x;2.4950000000000037;0.7649999999999995;2.6500000000000004;0.725
+1x;405 nm;1x;2.4900000000000038;0.61;2.6550000000000002;0.5949999999999999
+1x;405 nm;1.25x;2.418000000000004;0.6830000000000002;2.6609999999999996;0.5439999999999998
+1x;405 nm;1.6x;2.465999999999994;0.43899999999999995;2.687999999999999;0.42499999999999993
+1x;405 nm;2x;2.498999999999993;0.2859999999999996;2.6669999999999994;0.312
+1x;405 nm;2.5x;2.4839999999999955;0.2900000000000001;2.667;0.20799999999999985
+1x;405 nm;3.2x;2.4809999999999928;0.243;2.67;0.21900000000000014
+1x;405 nm;4x;2.512999999999999;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.204000000000013;1.0;2.5040000000000053;1.0
+1x;488 nm;0.8x;2.334000000000004;0.942;2.5919999999999996;0.936
+1x;488 nm;1x;2.3939999999999975;0.8880000000000002;2.4920000000000018;0.804
+1x;488 nm;1.25x;2.3960000000000017;0.692;2.5759999999999974;0.6260000000000001
+1x;488 nm;1.6x;2.3880000000000026;0.524;2.547999999999999;0.449
+1x;488 nm;2x;2.397000000000001;0.4610000000000001;2.5450000000000004;0.366
+1x;488 nm;2.5x;2.386999999999998;0.3990000000000001;2.5870000000000006;0.26999999999999996
+1x;488 nm;3.2x;2.2040000000000157;0.2;2.5140000000000025;0.2
+1x;488 nm;4x;2.398000000000001;0.158;2.7359999999999944;0.09600000000000002
+1x;488 nm;5x;2.3949999999999987;0.21100000000000008;2.593000000000001;0.13299999999999998
+1x;488 nm;6.3x;2.2000000000000175;0.1;2.5240000000000045;0.1
+1x;515 nm;0.63x;2.422999999999997;1.123;2.4930000000000025;1.1460000000000001
+1x;515 nm;0.8x;2.3970000000000007;0.81;2.571999999999999;0.8360000000000001
+1x;515 nm;1x;2.3919999999999986;0.782;2.4500000000000024;0.702
+1x;515 nm;1.25x;2.396999999999999;0.7190000000000001;2.5840000000000014;0.5319999999999998
+1x;515 nm;1.6x;2.3340000000000067;0.41400000000000003;2.58899999999999;0.45099999999999996
+1x;515 nm;2x;2.3899999999999992;0.388;2.584;0.392
+1x;515 nm;2.5x;2.3920000000000075;0.317;2.59199999999999;0.26199999999999996
+1x;515 nm;3.2x;2.3910000000000076;0.20499999999999974;2.58999999999999;0.174
+1x;515 nm;4x;2.388;0.219;2.596999999999998;0.17100000000000004
+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.3620000000000014;0.87;2.547;0.75
+1x;561 nm;1x;2.399999999999999;0.744;2.7329999999999974;0.594
+1x;561 nm;1.25x;2.3959999999999995;0.688;2.5409999999999955;0.586
+1x;561 nm;1.6x;2.3800000000000017;0.584;2.628999999999992;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.4259999999999997;0.243;2.626;0.184
+1x;561 nm;4x;2.410999999999999;0.216;2.611999999999997;0.158
+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.307999999999999;1.314;2.492000000000001;1.14
+1x;594 nm;0.8x;2.4180000000000015;1.12;2.537;0.78
+1x;594 nm;1x;2.4180000000000015;0.9000000000000004;2.5949999999999984;0.8300000000000001
+1x;594 nm;1.25x;2.403000000000002;0.6150000000000002;2.5949999999999984;0.5299999999999999
+1x;594 nm;1.6x;2.4080000000000017;0.44000000000000006;2.5999999999999983;0.35999999999999976
+1x;594 nm;2x;2.403000000000002;0.37000000000000005;2.5519999999999996;0.31
+1x;594 nm;2.5x;2.398000000000002;0.3900000000000002;2.5999999999999983;0.24499999999999966
+1x;594 nm;3.2x;2.4040000000000084;0.23799999999999957;2.6029999999999998;0.21500000000000002
+1x;594 nm;4x;2.405000000000008;0.26400000000000007;2.6060000000000008;0.1419999999999999
+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;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;5x;2.415000000000002;0.14500000000000002;2.6099999999999923;0.12499999999999994
+1x;647 nm;6.3x;2.481;0.07400000000000001;2.7779999999999996;0.092
diff --git a/mesoSPIM/gui/mesoSPIM_CameraWindow.ui b/mesoSPIM/gui/mesoSPIM_CameraWindow.ui
index 23fe68e..3cbc412 100644
--- a/mesoSPIM/gui/mesoSPIM_CameraWindow.ui
+++ b/mesoSPIM/gui/mesoSPIM_CameraWindow.ui
@@ -6,8 +6,8 @@
0
0
- 1114
- 1015
+ 1200
+ 1080
@@ -17,6 +17,53 @@
-
+ -
+
+
-
+
+
+ Adjust levels
+
+
+
+ -
+
+
+
+ 110
+ 0
+
+
+
+ Image overlay
+
+
-
+
+ Overlay: none
+
+
+ -
+
+ Box roi
+
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+
+
diff --git a/mesoSPIM/gui/mesoSPIM_MainWindow.ui b/mesoSPIM/gui/mesoSPIM_MainWindow.ui
index 07e0c75..25adacc 100644
--- a/mesoSPIM/gui/mesoSPIM_MainWindow.ui
+++ b/mesoSPIM/gui/mesoSPIM_MainWindow.ui
@@ -52,8 +52,8 @@
0
0
- 924
- 1222
+ 927
+ 1205
@@ -424,6 +424,11 @@
51
+
+
+ 16
+
+
false
@@ -658,6 +663,11 @@
401
+
+
+ 16
+
+
XY
@@ -846,6 +856,11 @@
401
+
+
+ 16
+
+
Z
@@ -964,6 +979,7 @@
+ 16
50
false
@@ -1005,6 +1021,11 @@
41
+
+
+ 16
+
+
µm
@@ -1029,6 +1050,7 @@
+ 16
50
false
@@ -1170,6 +1192,11 @@
41
+
+
+ 16
+
+
µm
@@ -1327,6 +1354,11 @@
41
+
+
+ 16
+
+
°
@@ -1354,6 +1386,7 @@
+ 16
50
false
false
@@ -1468,6 +1501,11 @@ Position
631
+
+
+ 14
+
+
Tunable lenses
@@ -1480,6 +1518,11 @@ Position
31
+
+
+ 14
+
+
Offset
@@ -1493,6 +1536,11 @@ Position
31
+
+
+ 14
+
+
Amplitude
@@ -1509,6 +1557,11 @@ Position
31
+
+
+ 14
+
+
V
@@ -1534,6 +1587,11 @@ Position
31
+
+
+ 14
+
+
Left
@@ -1547,6 +1605,11 @@ Position
31
+
+
+ 14
+
+
Right
@@ -1563,6 +1626,11 @@ Position
31
+
+
+ 14
+
+
V
@@ -1591,6 +1659,11 @@ Position
31
+
+
+ 14
+
+
V
@@ -1619,6 +1692,11 @@ Position
31
+
+
+ 14
+
+
V
@@ -1689,6 +1767,11 @@ Position
31
+
+
+ 14
+
+
Increment
@@ -1705,6 +1788,11 @@ Position
31
+
+
+ 14
+
+
V
@@ -1730,6 +1818,11 @@ Position
131
+
+
+ 14
+
+
false
@@ -1746,6 +1839,11 @@ Position
41
+
+
+ 14
+
+
Config File:
@@ -1818,6 +1916,7 @@ Position
+ 14
50
false
@@ -1845,6 +1944,11 @@ Position
91
+
+
+ 14
+
+
Sweep
@@ -1860,6 +1964,11 @@ Position
31
+
+
+ 14
+
+
ms
@@ -1885,6 +1994,11 @@ Position
31
+
+
+ 14
+
+
Sweeptime
@@ -1899,6 +2013,11 @@ Position
191
+
+
+ 14
+
+
Camera parameters
@@ -1911,6 +2030,11 @@ Position
31
+
+
+ 14
+
+
Delay
@@ -1924,6 +2048,11 @@ Position
31
+
+
+ 14
+
+
Pulselength
@@ -1940,6 +2069,11 @@ Position
31
+
+
+ 14
+
+
%
@@ -1965,6 +2099,11 @@ Position
31
+
+
+ 14
+
+
%
@@ -1987,6 +2126,11 @@ Position
31
+
+
+ 14
+
+
Exposure
@@ -2003,6 +2147,11 @@ Position
31
+
+
+ 14
+
+
ms
@@ -2028,6 +2177,11 @@ Position
31
+
+
+ 14
+
+
@@ -2038,6 +2192,11 @@ Position
31
+
+
+ 14
+
+
Binning
@@ -2052,6 +2211,11 @@ Position
201
+
+
+ 14
+
+
Laser pulse
@@ -2064,6 +2228,11 @@ Position
31
+
+
+ 14
+
+
Left
@@ -2077,6 +2246,11 @@ Position
31
+
+
+ 14
+
+
Right
@@ -2090,6 +2264,11 @@ Position
31
+
+
+ 14
+
+
Delay
@@ -2106,6 +2285,11 @@ Position
31
+
+
+ 14
+
+
%
@@ -2131,6 +2315,11 @@ Position
31
+
+
+ 14
+
+
%
@@ -2153,6 +2342,11 @@ Position
31
+
+
+ 14
+
+
Pulselength
@@ -2169,6 +2363,11 @@ Position
31
+
+
+ 14
+
+
%
@@ -2194,6 +2393,11 @@ Position
31
+
+
+ 14
+
+
%
@@ -2216,6 +2420,11 @@ Position
31
+
+
+ 14
+
+
Max Amplitude
@@ -2232,6 +2441,11 @@ Position
31
+
+
+ 14
+
+
%
@@ -2257,6 +2471,11 @@ Position
31
+
+
+ 14
+
+
%
@@ -2283,6 +2502,11 @@ Position
221
+
+
+ 14
+
+
Galvo (Shadow reduction)
@@ -2295,6 +2519,11 @@ Position
31
+
+
+ 14
+
+
Frequency
@@ -2311,6 +2540,11 @@ Position
31
+
+
+ 14
+
+
Hz
@@ -2336,6 +2570,11 @@ Position
31
+
+
+ 14
+
+
Left
@@ -2349,6 +2588,11 @@ Position
31
+
+
+ 14
+
+
Right
@@ -2362,6 +2606,11 @@ Position
31
+
+
+ 14
+
+
Offset
@@ -2375,6 +2624,11 @@ Position
31
+
+
+ 14
+
+
Amplitude
@@ -2391,6 +2645,11 @@ Position
31
+
+
+ 14
+
+
V
@@ -2416,6 +2675,11 @@ Position
31
+
+
+ 14
+
+
V
@@ -2444,6 +2708,11 @@ Position
31
+
+
+ 14
+
+
V
@@ -2469,6 +2738,11 @@ Position
31
+
+
+ 14
+
+
Phase
@@ -2485,6 +2759,11 @@ Position
31
+
+
+ 14
+
+
360.000000000000000
@@ -2507,6 +2786,11 @@ Position
31
+
+
+ 14
+
+
360.000000000000000
@@ -2530,6 +2814,11 @@ Position
251
+
+
+ 14
+
+
Tunable lenses
@@ -2542,6 +2831,11 @@ Position
41
+
+
+ 14
+
+
For offset & amplitude see
ETL tab
@@ -2556,6 +2850,11 @@ ETL tab
31
+
+
+ 14
+
+
Left
@@ -2569,6 +2868,11 @@ ETL tab
31
+
+
+ 14
+
+
Right
@@ -2582,6 +2886,11 @@ ETL tab
31
+
+
+ 14
+
+
Delay
@@ -2595,6 +2904,11 @@ ETL tab
31
+
+
+ 14
+
+
Ramp rising
@@ -2608,6 +2922,11 @@ ETL tab
31
+
+
+ 14
+
+
Ramp falling
@@ -2624,6 +2943,11 @@ ETL tab
31
+
+
+ 14
+
+
%
@@ -2649,6 +2973,11 @@ ETL tab
31
+
+
+ 14
+
+
%
@@ -2674,6 +3003,11 @@ ETL tab
31
+
+
+ 14
+
+
%
@@ -2699,6 +3033,11 @@ ETL tab
31
+
+
+ 14
+
+
%
@@ -2724,6 +3063,11 @@ ETL tab
31
+
+
+ 14
+
+
%
@@ -2749,6 +3093,11 @@ ETL tab
31
+
+
+ 14
+
+
%
@@ -2806,6 +3155,11 @@ ETL tab
81
+
+
+ 14
+
+
false
@@ -2823,6 +3177,11 @@ ETL tab
151
+
+
+ 14
+
+
Display Subsampling
@@ -2835,6 +3194,11 @@ ETL tab
31
+
+
+ 14
+
+
@@ -2845,6 +3209,11 @@ ETL tab
31
+
+
+ 14
+
+
During Live display:
@@ -2858,6 +3227,11 @@ ETL tab
31
+
+
+ 14
+
+
When Snapping Images:
@@ -2871,6 +3245,11 @@ ETL tab
31
+
+
+ 14
+
+
During Acquisitions:
@@ -2884,6 +3263,11 @@ ETL tab
31
+
+
+ 14
+
+
@@ -2894,6 +3278,11 @@ ETL tab
31
+
+
+ 14
+
+
-1
@@ -2962,6 +3351,46 @@ ETL tab
+
Close
@@ -2977,6 +3406,38 @@ ETL tab
Script Window
+
+
+ Open Camera Window
+
+
+ Opens a new Camera Window
+
+
+
+
+ Open Acquisition Manager
+
+
+ Opens a new Acquisition Manager
+
+
+
+
+ Exit
+
+
+ Close mesoSPIM-control
+
+
+ Ctrl+Q
+
+
+
+
+ About
+
+
diff --git a/mesoSPIM/mesoSPIM_Control.py b/mesoSPIM/mesoSPIM_Control.py
index 533b7c1..50f3cfb 100644
--- a/mesoSPIM/mesoSPIM_Control.py
+++ b/mesoSPIM/mesoSPIM_Control.py
@@ -4,9 +4,16 @@
The core module of the mesoSPIM software
'''
+__author__ = "Fabian Voigt"
+__license__ = "GPL v3"
+__maintainer__ = "Fabian Voigt"
+
+
''' Configuring the logging module before doing anything else'''
import time
import logging
+import argparse
+import glob
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')
@@ -23,25 +30,22 @@
logger.info('Modules loaded')
-def load_config():
+def load_config_UI(current_path):
'''
- Import microscope configuration at startup
+ Bring up a GUI that allows the user to select a microscope configuration to import
'''
''' This needs an placeholder QApplication to work '''
cfg_app = QtWidgets.QApplication(sys.argv)
+
current_path = os.path.abspath('./config')
global_config_path = ''
- global_config_path , _ = QtWidgets.QFileDialog.getOpenFileName(None,\
- 'Open microscope configuration file',current_path)
+ global_config_path , _ = QtWidgets.QFileDialog.getOpenFileName(None,
+ 'Open microscope configuration file',current_path)
if global_config_path != '':
- ''' Using importlib to load the config file '''
- 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}')
+ config = load_config_from_file(global_config_path)
return config
else:
''' Application shutdown '''
@@ -52,6 +56,16 @@ def load_config():
sys.exit(cfg_app.exec_())
+def load_config_from_file(path_to_config):
+ '''
+ Load a microscope configuration from a file using importlib
+ '''
+ spec = importlib.util.spec_from_file_location('module.name', path_to_config)
+ config = importlib.util.module_from_spec(spec)
+ spec.loader.exec_module(config)
+ logger.info(f'Configuration file loaded: {path_to_config}')
+ return config
+
def stage_referencing_check(cfg):
'''
Due to problems with some PI stages loosing reference information
@@ -75,19 +89,90 @@ def stage_referencing_check(cfg):
else:
return True
-def main():
+def get_parser():
+ """
+ Parse command-line input arguments
+
+ :return: The argparse parser object
+ """
+ parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter)
+
+ parser.add_argument('-C', '--console', action='store_true', # store_true makes it False by default
+ help='Start a ipython console')
+ parser.add_argument('-D', '--demo', action='store_true',
+ help='Start in demo mode')
+ return parser
+
+def dark_mode_check(cfg, app):
+ if (hasattr(cfg, 'dark_mode') and cfg.dark_mode) or (hasattr(cfg, 'ui_options') and cfg.ui_options['dark_mode']):
+ import qdarkstyle
+ app.setStyleSheet(qdarkstyle.load_stylesheet(qt_api='pyqt5'))
+
+
+def main(embed_console=False, demo_mode=False):
"""
Main function
"""
+ print('Starting control software')
+
logging.info('mesoSPIM Program started.')
- cfg = load_config()
+
+ # Load a configuration file according to the following rules:
+ # 1. If the user did not ask for demo mode and there is only one config file in the path then load that.
+ # 2. If the user did not ask for demo mode and there are multiple config files in the path, then bring up the UI loader.
+ # 3. If the user asked for demo mode and there is only one demo file in path: load it.
+ # 4. If the user asked for demo mode and there are multiple demo files in the path: bring up the UI loader
+ # 5. Otherwise bring up the UI loader
+
+ current_path = os.path.abspath('./config')
+
+ cfgLoaded = False
+ if demo_mode:
+ demo_fname = glob.glob(os.path.join(current_path, '*demo*.py'))
+ if len(demo_fname) == 1:
+ cfg = load_config_from_file(demo_fname[0])
+ print(f'Demo settings are loaded from file {demo_fname[0]}')
+ cfgLoaded = True
+ else:
+ all_configs = glob.glob(os.path.join(current_path,'*.py')) # All possible config files
+ # Strip the paths so when we remove "demo" files we do so based only on the file name itself
+ strip_path = [tFile.replace(os.path.commonprefix(all_configs), '') for tFile in all_configs]
+ all_configs_no_demo = list(filter(lambda tFile: str.find(tFile, 'demo') < 0, strip_path))
+
+ # If only one file left, we load it
+ if len(all_configs_no_demo) == 1:
+ cfg = load_config_from_file(os.path.join(current_path, all_configs_no_demo[0]))
+ cfgLoaded = True
+
+ if not cfgLoaded:
+ # Otherwise bring up the UI loader
+ cfg = load_config_UI(current_path)
+
app = QtWidgets.QApplication(sys.argv)
+
+ dark_mode_check(cfg, app)
stage_referencing_check(cfg)
ex = mesoSPIM_MainWindow(cfg)
ex.show()
ex.display_icons()
- sys.exit(app.exec_())
+ print('Done!')
+
+ if embed_console:
+ from traitlets.config import Config
+ cfg = Config()
+ cfg.InteractiveShellApp.gui = 'qt5'
+ import IPython
+ IPython.start_ipython(config=cfg, argv=[], user_ns=dict(mSpim=ex, app=app))
+ else:
+ sys.exit(app.exec_())
+
+
+def run():
+ args = get_parser().parse_args()
+ main(embed_console=args.console,demo_mode=args.demo)
+
+
if __name__ == '__main__':
- main()
+ run()
diff --git a/mesoSPIM/src/devices/filter_wheels/ludlcontrol.py b/mesoSPIM/src/devices/filter_wheels/ludlcontrol.py
index 5921572..ba0e2af 100644
--- a/mesoSPIM/src/devices/filter_wheels/ludlcontrol.py
+++ b/mesoSPIM/src/devices/filter_wheels/ludlcontrol.py
@@ -1,17 +1,17 @@
"""
mesoSPIM Module for controlling Ludl filterwheels
-Author: Fabian Voigt
+Authors: Fabian Voigt, Nikita Vladimirov
#TODO
"""
-import serial as Serial
-import io as Io
+import serial
+import io
import time
'''PyQt5 Imports'''
-from PyQt5 import QtWidgets, QtCore, QtGui
+from PyQt5 import QtCore
class LudlFilterwheel(QtCore.QObject):
@@ -43,7 +43,9 @@ def __init__(self, COMport, filterdict, baudrate=9600):
self.baudrate = baudrate
self.filterdict = filterdict
self.double_wheel = False
-
+ self.ser = None
+ self.sio = None
+ self._connect()
''' Delay in s for the wait until done function '''
self.wait_until_done_delay = 0.5
@@ -59,6 +61,20 @@ def __init__(self, COMport, filterdict, baudrate=9600):
if type(self.filterdict[self.first_item_in_filterdict]) is tuple:
self.double_wheel = True
+ def _connect(self):
+ """"Note: Only one connection should be done per session. Connecting frequently is error-prone,
+ because COM port can be scanned by another program (e.g. laser control) and thus be permission-denied at random
+ times."""
+ try:
+ self.ser = serial.Serial(self.COMport,
+ self.baudrate,
+ parity=serial.PARITY_NONE,
+ timeout=0, write_timeout=0,
+ xonxoff=False,
+ stopbits=serial.STOPBITS_TWO)
+ self.sio = io.TextIOWrapper(io.BufferedRWPair(self.ser, self.ser))
+ except serial.SerialException as e:
+ print(f"ERROR: Serial connection to Ludl filter wheel failed: {e}")
def _check_if_filter_in_filterdict(self, filter):
'''
@@ -73,23 +89,12 @@ def _check_if_filter_in_filterdict(self, filter):
def set_filter(self, filter, wait_until_done=False):
'''
Moves filter using the pyserial command set.
-
No checks are done whether the movement is completed or
finished in time.
-
-
'''
if self._check_if_filter_in_filterdict(filter) is True:
- self.ser = Serial.Serial(self.COMport,
- self.baudrate,
- parity=Serial.PARITY_NONE,
- timeout=0,
- xonxoff=False,
- stopbits=Serial.STOPBITS_TWO)
- self.sio = Io.TextIOWrapper(Io.BufferedRWPair(self.ser, self.ser))
"""
Check for double or single wheel
-
TODO: A bit of repeating code in here. Might be better to
spin the create and send commands off.
"""
@@ -98,10 +103,10 @@ def set_filter(self, filter, wait_until_done=False):
# Get the filter position from the filterdict:
self.filternumber = self.filterdict[filter]
# Rotat is the Ludl high-level command for moving a filter wheel
+ self.ser.flush()
self.ludlstring = 'Rotat S M ' + str(self.filternumber) + '\n'
self.sio.write(str(self.ludlstring))
self.sio.flush()
- self.ser.close()
if wait_until_done:
''' Wait a certain number of seconds. This is a hack
@@ -131,9 +136,13 @@ def set_filter(self, filter, wait_until_done=False):
self.ludlstring1 = 'Rotat S A ' + str(self.filternumber[1]) + '\n'
self.sio.write(str(self.ludlstring1))
self.sio.flush()
- self.ser.close()
if wait_until_done:
time.sleep(self.wait_until_done_delay)
else:
print(f'Filter {filter} not found in configuration.')
+
+
+ def __del__(self):
+ self.sio.flush()
+ self.ser.close()
diff --git a/mesoSPIM/src/devices/filter_wheels/sutterLambdaControl.py b/mesoSPIM/src/devices/filter_wheels/sutterLambdaControl.py
new file mode 100644
index 0000000..b37ce1c
--- /dev/null
+++ b/mesoSPIM/src/devices/filter_wheels/sutterLambdaControl.py
@@ -0,0 +1,106 @@
+"""
+mesoSPIM Module for controlling Sutter Lambda Filter Wheels
+
+Author: Kevin Dean,
+Basically 100% stolen from Andrew York's GitHub Account :)
+"""
+
+import serial
+import time
+
+
+class Lambda10B:
+ def __init__(self, comport, filterdict, baudrate=9600, read_on_init=True):
+ 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 = 0.5
+
+ self.first_item_in_filterdict = list(self.filterdict.keys())[0]
+ if type(self.filterdict[self.first_item_in_filterdict]) is tuple:
+ self.double_wheel = True
+
+ # Open Serial Port
+ try:
+ self.serial = serial.Serial(self.COMport, self.baudrate, timeout=.25)
+ except serial.SerialException:
+ raise UserWarning('Could not open the serial port to the Sutter Lambda 10-B.')
+
+ # Place Controller Into Online Mode
+ self.serial.write(bytes.fromhex('ee'))
+
+ # Check to see if the initialization sequence has finished.
+ if read_on_init:
+ self.read(2) # class 'bytes'
+ self.init_finished = True
+ print('Done initializing filter wheel')
+ else:
+ self.init_finished = False
+ self.filternumber = 0
+
+ def __enter__(self):
+ return self
+
+ def __exit__(self, type, value, traceback):
+ self.close()
+
+ def _check_if_filter_in_filterdict(self, filterposition):
+ # Checks if the filter designation (string) given as argument exists in the filterdict
+ if filterposition in self.filterdict:
+ return True
+ else:
+ raise ValueError('Filter designation not in the configuration')
+
+ def set_filter(self, filterposition=0, speed=0, wait_until_done=False):
+
+ # Confirm that the filter is present in the filter dictionary
+ if self._check_if_filter_in_filterdict(filterposition) is True:
+
+ # Confirm that you are only operating in a single filter wheel configuration.
+ if self.double_wheel is False:
+
+ # Identify the Filter Number from the Filter Dictionary
+ self.wheel_position = self.filterdict[filterposition]
+
+ # Make sure you are moving it to a reasonable filter position, at a reasonable speed.
+ assert self.wheel_position in range(10)
+ assert speed in range(8)
+
+ # If previously we did not confirm that the initialization was complete, check now.
+ if not self.init_finished:
+ self.read(2)
+ self.init_finished = True
+ print('Done initializing filter wheel.')
+
+ # Filter Wheel Command Byte Encoding = wheel + (speed*16) + position = command byte
+ outputcommand = self.wheel_position + 16 * speed
+ outputcommand = outputcommand.to_bytes(1, 'little')
+
+ # Send out Command
+ self.serial.write(outputcommand)
+ if wait_until_done:
+ time.sleep(self.wait_until_done_delay)
+
+ # Read up to 2 bytes
+ self.read(2)
+
+ else:
+ raise UserWarning("Sutter Operates only in a Single Filter Wheel Configuration.")
+
+ def read(self, num_bytes):
+ for i in range(100):
+ num_waiting = self.serial.inWaiting()
+ if num_waiting == num_bytes:
+ break
+ time.sleep(0.02)
+ else:
+ raise UserWarning("The serial port to the Sutter Lambda 10-B is on, but it isn't responding as expected.")
+ return self.serial.read(num_bytes)
+
+ def close(self):
+ self.set_filter()
+ self.serial.close()
diff --git a/mesoSPIM/src/devices/stages/galil/galilcontrol.py b/mesoSPIM/src/devices/stages/galil/galilcontrol.py
index 85669da..2f0daf1 100644
--- a/mesoSPIM/src/devices/stages/galil/galilcontrol.py
+++ b/mesoSPIM/src/devices/stages/galil/galilcontrol.py
@@ -11,11 +11,12 @@
from PyQt5 import QtWidgets, QtCore, QtGui
-import gclib
+import src.devices.stages.galil.gclib as gclib
import logging
logger = logging.getLogger(__name__)
+
class StageControlGalil(QtCore.QObject):
'''
Class to control a Galil mechanical stage controller
diff --git a/mesoSPIM/src/devices/zoom/mesoSPIM_Zoom.py b/mesoSPIM/src/devices/zoom/mesoSPIM_Zoom.py
index dbf0791..be7f78b 100644
--- a/mesoSPIM/src/devices/zoom/mesoSPIM_Zoom.py
+++ b/mesoSPIM/src/devices/zoom/mesoSPIM_Zoom.py
@@ -8,7 +8,7 @@
import time
-from PyQt5 import QtWidgets, QtCore, QtGui
+from PyQt5 import QtCore
class DemoZoom(QtCore.QObject):
def __init__(self, zoomdict):
@@ -44,7 +44,7 @@ def __init__(self, zoomdict, COMport, identifier=2, baudrate=1000000):
self.goal_position_offset = 10
''' Specifies how long to sleep for the wait until done function'''
self.sleeptime = 0.05
- self.timeout = 15
+ self.timeout = 10
# the dynamixel library uses integers instead of booleans for binary information
self.torque_enable = 1
@@ -52,6 +52,12 @@ def __init__(self, zoomdict, COMport, identifier=2, baudrate=1000000):
self.port_num = dynamixel.portHandler(self.devicename)
self.dynamixel.packetHandler()
+ self._connect()
+
+ def _connect(self):
+ # open port and set baud rate
+ self.dynamixel.openPort(self.port_num)
+ self.dynamixel.setBaudRate(self.port_num, self.baudrate)
def set_zoom(self, zoom, wait_until_done=False):
"""Changes zoom after checking that the commanded value exists"""
@@ -62,9 +68,6 @@ def set_zoom(self, zoom, wait_until_done=False):
raise ValueError('Zoom designation not in the configuration')
def _move(self, position, wait_until_done=False):
- # open port and set baud rate
- self.dynamixel.openPort(self.port_num)
- self.dynamixel.setBaudRate(self.port_num, self.baudrate)
# Enable servo
self.dynamixel.write1ByteTxRx(self.port_num, 1, self.id, self.addr_mx_torque_enable, self.torque_enable)
# Write Moving Speed
@@ -92,23 +95,18 @@ def _move(self, position, wait_until_done=False):
while (cur_position < lower_limit) or (cur_position > upper_limit):
''' Timeout '''
if time.time()-start_time > self.timeout:
+ print("Dynamixel zoom servo: timeout")
break
- time.sleep(0.05)
+ time.sleep(self.sleeptime)
cur_position = self.dynamixel.read4ByteTxRx(self.port_num, 1, self.id, self.addr_mx_present_position)
# print(cur_position)
- self.dynamixel.closePort(self.port_num)
-
def read_position(self):
'''
Returns position as an int between 0 and 4096
-
- Opens & closes the port
'''
- self.dynamixel.openPort(self.port_num)
- self.dynamixel.setBaudRate(self.port_num, self.baudrate)
cur_position = self.dynamixel.read4ByteTxRx(self.port_num, 1, self.id, self.addr_mx_present_position)
+ return cur_position
+ def __del__(self):
self.dynamixel.closePort(self.port_num)
-
- return cur_position
diff --git a/mesoSPIM/src/mesoSPIM_AcquisitionManagerWindow.py b/mesoSPIM/src/mesoSPIM_AcquisitionManagerWindow.py
index 4e84e81..c221a1d 100644
--- a/mesoSPIM/src/mesoSPIM_AcquisitionManagerWindow.py
+++ b/mesoSPIM/src/mesoSPIM_AcquisitionManagerWindow.py
@@ -32,7 +32,6 @@
from .utils.widgets import MarkPositionWidget
-from .utils.acquisition_wizards import TilingWizard
from .utils.multicolor_acquisition_wizard import MulticolorTilingWizard
from .utils.filename_wizard import FilenameWizard
from .utils.focus_tracking_wizard import FocusTrackingWizard
@@ -96,6 +95,7 @@ def __init__(self, parent=None):
self.table.setDropIndicatorShown(True)
self.table.setSortingEnabled(True)
+
self.set_item_delegates()
''' Set our custom style - this draws the drop indicator across the whole row '''
@@ -127,6 +127,10 @@ def __init__(self, parent=None):
# self.SetRotationPointButton.clicked.connect(lambda bool: self.set_rotation_point() if bool is True else self.delete_rotation_point())
self.SetFoldersButton.clicked.connect(self.set_folder_names)
+ font = QtGui.QFont()
+ font.setPointSize(14)
+ self.table.horizontalHeader().setFont(font)
+ self.table.verticalHeader().setFont(font)
logger.info('Thread ID at Startup: '+str(int(QtCore.QThread.currentThreadId())))
@@ -458,16 +462,6 @@ def display_warning(self, string):
warning = QtWidgets.QMessageBox.warning(None,'mesoSPIM Warning',
string, QtWidgets.QMessageBox.Ok)
- def generate_xml(self):
- print('generating BDV XML')
-
- timestr = time.strftime("%Y%m%d-%H%M%S")
- filename = timestr + '.xml'
-
- path = self.state['acq_list'][0]['folder']+'/'+filename
-
- xml_exporter = mesoSPIM_XMLexporter(self)
- xml_exporter.generate_xml_from_acqlist(self.state['acq_list'],path)
diff --git a/mesoSPIM/src/mesoSPIM_Camera.py b/mesoSPIM/src/mesoSPIM_Camera.py
index 49c1a48..8260696 100644
--- a/mesoSPIM/src/mesoSPIM_Camera.py
+++ b/mesoSPIM/src/mesoSPIM_Camera.py
@@ -1,6 +1,7 @@
'''
mesoSPIM Camera class, intended to run in its own thread
'''
+
import os
import time
import numpy as np
@@ -18,6 +19,7 @@
logger.info('Error: Hamamatsu camera could not be imported')
'''
from .mesoSPIM_State import mesoSPIM_StateSingleton
+from .mesoSPIM_ImageWriter import mesoSPIM_ImageWriter
from .utils.acquisitions import AcquisitionList, Acquisition
class mesoSPIM_Camera(QtCore.QObject):
@@ -34,6 +36,7 @@ def __init__(self, parent = None):
self.cfg = parent.cfg
self.state = mesoSPIM_StateSingleton()
+ self.image_writer = mesoSPIM_ImageWriter(self)
self.stopflag = False
@@ -74,6 +77,8 @@ def __init__(self, parent = None):
self.camera = mesoSPIM_HamamatsuCamera(self)
elif self.cfg.camera == 'PhotometricsIris15':
self.camera = mesoSPIM_PhotometricsCamera(self)
+ elif self.cfg.camera == 'PCO':
+ self.camera = mesoSPIM_PCOCamera(self)
elif self.cfg.camera == 'DemoCamera':
self.camera = mesoSPIM_DemoCamera(self)
@@ -150,61 +155,50 @@ def set_camera_display_acquisition_subsampling(self, factor):
self.camera_display_acquisition_subsampling = factor
def set_camera_binning(self, value):
+ print('Setting camera binning: '+value)
self.camera.set_binning(value)
- @QtCore.pyqtSlot(Acquisition)
- def prepare_image_series(self, acq):
+ @QtCore.pyqtSlot(Acquisition, AcquisitionList)
+ def prepare_image_series(self, acq, acq_list):
'''
Row is a row in a AcquisitionList
'''
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.image_writer.prepare_acquisition(acq, acq_list)
- self.folder = acq['folder']
- self.filename = acq['filename']
- self.path = self.folder+'/'+self.filename
-
- 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']
self.max_frame = acq.get_image_count()
-
self.processing_options_string = acq['processing']
- self.fsize = self.x_pixels*self.y_pixels
-
- self.xy_stack = np.memmap(self.path, mode = "write", dtype = np.uint16, shape = self.fsize * self.max_frame)
-
self.camera.initialize_image_series()
self.cur_image = 0
logger.info(f'Camera: Finished Preparing Image Series')
self.start_time = time.time()
- @QtCore.pyqtSlot()
- def add_images_to_series(self):
+ @QtCore.pyqtSlot(Acquisition, AcquisitionList)
+ def add_images_to_series(self, acq, acq_list):
if self.cur_image == 0:
logger.info('Thread ID during add images: '+str(int(QtCore.QThread.currentThreadId())))
if self.stopflag is False:
if self.cur_image < self.max_frame:
- # logger.info('self.cur_image + 1: '+str(self.cur_image + 1))
images = self.camera.get_images_in_series()
for image in images:
image = np.rot90(image)
self.sig_camera_frame.emit(image[0:self.x_pixels:self.camera_display_acquisition_subsampling,0:self.y_pixels:self.camera_display_acquisition_subsampling])
- image = image.flatten()
- self.xy_stack[self.cur_image*self.fsize:(self.cur_image+1)*self.fsize] = image
+ self.image_writer.write_image(image, acq, acq_list)
self.cur_image += 1
- @QtCore.pyqtSlot()
- def end_image_series(self):
+ @QtCore.pyqtSlot(Acquisition, AcquisitionList)
+ def end_image_series(self, acq, acq_list):
if self.stopflag is False:
if self.processing_options_string != '':
if self.processing_options_string == 'MAX':
+ ''' Image processing needs to be reimplemented in an incremental fashion '''
+ pass
+
+ '''
self.sig_status_message.emit('Doing Max Projection')
logger.info('Camera: Started Max Projection of '+str(self.max_frame)+' Images')
stackview = self.xy_stack.view()
@@ -215,12 +209,14 @@ def end_image_series(self):
tifffile.imsave(path, max_proj, photometric='minisblack')
logger.info('Camera: Saved Max Projection')
self.sig_status_message.emit('Done with image processing')
+ '''
try:
self.camera.close_image_series()
- del self.xy_stack
except:
- pass
+ logger.warning('Camera: Image Series could not be closed')
+
+ self.image_writer.end_acquisition(acq, acq_list)
self.end_time = time.time()
framerate = (self.cur_image + 1)/(self.end_time - self.start_time)
@@ -231,15 +227,8 @@ def end_image_series(self):
def snap_image(self):
image = self.camera.get_image()
image = np.rot90(image)
-
- timestr = time.strftime("%Y%m%d-%H%M%S")
- filename = timestr + '.tif'
-
- path = self.state['snap_folder']+'/'+filename
-
self.sig_camera_frame.emit(image[0:self.x_pixels:self.camera_display_snap_subsampling,0:self.y_pixels:self.camera_display_snap_subsampling])
-
- tifffile.imsave(path, image, photometric='minisblack')
+ self.image_writer.write_snap_image(image)
@QtCore.pyqtSlot()
def prepare_live(self):
@@ -309,10 +298,11 @@ def set_line_interval(self, time):
pass
def set_binning(self, binning_string):
- self.x_binning = int(self.binning_string[0])
- self.y_binning = int(self.binning_string[2])
+ self.x_binning = int(binning_string[0])
+ self.y_binning = int(binning_string[2])
self.x_pixels = int(self.x_pixels / self.x_binning)
self.y_pixels = int(self.y_pixels / self.y_binning)
+ self.state['camera_binning'] = str(self.x_binning)+'x'+str(self.y_binning)
def initialize_image_series(self):
pass
@@ -338,15 +328,14 @@ def close_live_mode(self):
pass
class mesoSPIM_DemoCamera(mesoSPIM_GenericCamera):
-
def __init__(self, parent = None):
super().__init__(parent)
+ self.count = 0
+
self.line = np.linspace(0,6*np.pi,self.x_pixels)
self.line = 400*np.sin(self.line)+1200
- self.count = 0
-
def open_camera(self):
logger.info('Initialized Demo Camera')
@@ -354,12 +343,14 @@ def close_camera(self):
logger.info('Closed Demo Camera')
def set_binning(self, binning_string):
- self.x_binning = int(self.binning_string[0])
- self.y_binning = int(self.binning_string[2])
+ self.x_binning = int(binning_string[0])
+ self.y_binning = int(binning_string[2])
self.x_pixels = int(self.x_pixels / self.x_binning)
self.y_pixels = int(self.y_pixels / self.y_binning)
+ ''' Changing the number of pixels also affects the random image, so we need to update self.line '''
self.line = np.linspace(0,6*np.pi,self.x_pixels)
self.line = 400*np.sin(self.line)+1200
+ self.state['camera_binning'] = str(self.x_binning)+'x'+str(self.y_binning)
def _create_random_image(self):
data = np.array([np.roll(self.line, 4*i+self.count) for i in range(0, self.y_pixels)], dtype='uint16')
@@ -368,8 +359,6 @@ def _create_random_image(self):
self.count += 20
return data
- # return np.random.randint(low=0, high=2**16, size=(self.x_pixels,self.y_pixels), dtype='l')
-
def get_images_in_series(self):
return [self._create_random_image()]
@@ -428,10 +417,11 @@ def set_line_interval(self, time):
def set_binning(self, binningstring):
self.hcam.setPropertyValue("binning", binningstring)
- self.x_binning = int(self.binning_string[0])
- self.y_binning = int(self.binning_string[2])
+ self.x_binning = int(binning_string[0])
+ self.y_binning = int(binning_string[2])
self.x_pixels = int(self.x_pixels / self.x_binning)
self.y_pixels = int(self.y_pixels / self.y_binning)
+ self.state['camera_binning'] = str(self.x_binning)+'x'+str(self.y_binning)
def initialize_image_series(self):
self.hcam.startAcquisition()
@@ -570,11 +560,12 @@ def set_line_interval(self, time):
print('Setting line interval is not implemented, set the interval in the config file')
def set_binning(self, binningstring):
- self.x_binning = int(self.binning_string[0])
- self.y_binning = int(self.binning_string[2])
+ self.x_binning = int(binning_string[0])
+ self.y_binning = int(binning_string[2])
self.x_pixels = int(self.x_pixels / self.x_binning)
self.y_pixels = int(self.y_pixels / self.y_binning)
self.pvcam.binning = (self.x_binning, self.y_binning)
+ self.state['camera_binning'] = str(self.x_binning)+'x'+str(self.y_binning)
def get_image(self):
return self.pvcam.get_live_frame()
@@ -602,4 +593,67 @@ def get_live_image(self):
def close_live_mode(self):
self.pvcam.stop_live()
+class mesoSPIM_PCOCamera(mesoSPIM_GenericCamera):
+ def __init__(self, parent = None):
+ super().__init__(parent)
+ logger.info('Thread ID at Startup: '+str(int(QtCore.QThread.currentThreadId())))
+ logger.info('PCO Cam initialized')
+
+ def open_camera(self):
+ import pco
+ self.cam = pco.Camera() # no logging
+ # self.cam = pco.Camera(debuglevel='verbose', timestamp='on')
+
+ self.cam.sdk.set_cmos_line_timing('on', self.cfg.camera_parameters['line_interval']) # 75 us delay
+ self.cam.set_exposure_time(self.cfg.camera_parameters['exp_time'])
+ # self.cam.sdk.set_cmos_line_exposure_delay(80, 0) # 266 lines = 20 ms / 75 us
+ self.cam.configuration = {'trigger' : self.cfg.camera_parameters['trigger']}
+
+ line_time = self.cam.sdk.get_cmos_line_timing()['line time']
+ lines_exposure = self.cam.sdk.get_cmos_line_exposure_delay()['lines exposure']
+ t = self.cam.get_exposure_time()
+ #print('Exposure Time: {:9.6f} s'.format(t))
+ #print('Line Time: {:9.6f} s'.format(line_time))
+ #print('Number of Lines: {:d}'.format(lines_exposure))
+
+ self.cam.record(number_of_images=4, mode='fifo')
+
+ def close_camera(self):
+ self.cam.stop()
+ self.cam.close()
+
+ def set_exposure_time(self, time):
+ self.cam.set_exposure_time(time)
+ self.camera_exposure_time = time
+
+ def set_line_interval(self, time):
+ print('Setting line interval is not implemented, set the interval in the config file')
+
+ def set_binning(self, binningstring):
+ pass
+
+ def get_image(self):
+ image, meta = self.cam.image(image_number=-1)
+ return image
+
+ def initialize_image_series(self):
+ pass
+
+ def get_images_in_series(self):
+ image, meta = self.cam.image(image_number=-1)
+ return [image]
+
+ def close_image_series(self):
+ pass
+
+ def initialize_live_mode(self):
+ pass
+
+ def get_live_image(self):
+ image, meta = self.cam.image(image_number=-1)
+ return [image]
+
+ def close_live_mode(self):
+ pass
+
\ No newline at end of file
diff --git a/mesoSPIM/src/mesoSPIM_CameraWindow.py b/mesoSPIM/src/mesoSPIM_CameraWindow.py
index b20fff8..c1d9379 100644
--- a/mesoSPIM/src/mesoSPIM_CameraWindow.py
+++ b/mesoSPIM/src/mesoSPIM_CameraWindow.py
@@ -10,16 +10,24 @@
from PyQt5 import QtWidgets, QtCore, QtGui
from PyQt5.uic import loadUi
-
import pyqtgraph as pg
-pg.setConfigOptions(imageAxisOrder='row-major')
-pg.setConfigOptions(foreground='k')
-pg.setConfigOptions(background='w')
+from .mesoSPIM_State import mesoSPIM_StateSingleton
class mesoSPIM_CameraWindow(QtWidgets.QWidget):
def __init__(self, parent=None):
super().__init__()
+ self.parent = parent
+ self.cfg = parent.cfg
+ self.state = mesoSPIM_StateSingleton()
+
+ pg.setConfigOptions(imageAxisOrder='row-major')
+ if (hasattr(self.cfg, 'ui_options') and self.cfg.ui_options['dark_mode']) or\
+ (hasattr(self.cfg, 'dark_mode') and self.cfg.dark_mode):
+ pg.setConfigOptions(background=pg.mkColor('#19232D')) # To avoid pitch black bg for the image view
+ else:
+ pg.setConfigOptions(background="w")
+
'''Set up the UI'''
if __name__ == '__main__':
loadUi('../gui/mesoSPIM_CameraWindow.ui', self)
@@ -27,11 +35,6 @@ def __init__(self, parent=None):
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)
@@ -44,13 +47,6 @@ def __init__(self, parent=None):
''' 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']
- ''' Debugging info
-
- logger.info('x_image_width: '+str(self.x_image_width))
- logger.info('y_image_width: '+str(self.y_image_width))
- logger.info('x_image_width/2: '+str(self.x_image_width/2))
- logger.info('y_image_width/2: '+str(self.y_image_width/2))
- '''
''' Initialize crosshairs '''
self.crosspen = pg.mkPen({'color': "r", 'width': 1})
@@ -58,11 +54,52 @@ def __init__(self, parent=None):
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())
+
+ # Create overlay ROIs
+ x, y, w, h = 100, 100, 200, 200
+ self.roi_box = pg.RectROI((x, y), (w, h), sideScalers=True)
+ font = QtGui.QFont()
+ font.setPixelSize(16)
+ self.roi_box_w_text, self.roi_box_h_text = pg.TextItem(color='r'), pg.TextItem(color='r', angle=90)
+ self.roi_box_w_text.setFont(font), self.roi_box_h_text.setFont(font)
+ self.roi_box_w_text.setPos(x, y + h), self.roi_box_h_text.setPos(x, y + h)
+ self.roi_list = [self.roi_box, self.roi_box_w_text, self.roi_box_h_text]
+
+ # Set up CameraWindow signals
+ self.adjustLevelsButton.clicked.connect(self.adjust_levels)
+ self.overlayCombo.currentTextChanged.connect(self.change_overlay)
+ self.roi_box.sigRegionChangeFinished.connect(self.update_box_roi_labels)
logger.info('Thread ID at Startup: '+str(int(QtCore.QThread.currentThreadId())))
+ def adjust_levels(self, pct_low=25, pct_hi=99.99):
+ ''''Adjust histogram levels'''
+ img = self.graphicsView.getImageItem().image
+ self.graphicsView.setLevels(min=np.percentile(img, pct_low), max=np.percentile(img, pct_hi))
+
+ def px2um(self, px):
+ '''Unit converter'''
+ return px * self.cfg.pixelsize[self.state['zoom']]
+
+ @QtCore.pyqtSlot(str)
+ def change_overlay(self, overlay_name):
+ ''''Changes the image overlay'''
+ if overlay_name == 'Box roi':
+ self.update_box_roi_labels()
+ for item in self.roi_list:
+ self.graphicsView.addItem(item)
+ elif overlay_name == 'Overlay: none':
+ for item in self.roi_list:
+ self.graphicsView.removeItem(item)
+
+ @QtCore.pyqtSlot()
+ def update_box_roi_labels(self):
+ w, h = self.roi_box.size()
+ x, y = self.roi_box.pos()
+ self.roi_box_w_text.setText(f"{int(self.px2um(w)):,} \u03BCm")
+ self.roi_box_h_text.setText(f"{int(self.px2um(h)):,} \u03BCm")
+ self.roi_box_w_text.setPos(x, y + h)
+ self.roi_box_h_text.setPos(x, y + h)
@QtCore.pyqtSlot(str)
def display_status_message(self, string, time=0):
@@ -71,7 +108,6 @@ def display_status_message(self, string, time=0):
If time=0, the message will stay.
'''
-
if time == 0:
self.statusBar().showMessage(string)
else:
@@ -91,13 +127,6 @@ def set_image(self, image):
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)
- ''' Debugging info
-
- logger.info('x_image_width: '+str(self.x_image_width))
- logger.info('y_image_width: '+str(self.y_image_width))
- logger.info('x_image_width/2: '+str(self.x_image_width/2))
- logger.info('y_image_width/2: '+str(self.y_image_width/2))
- '''
else:
self.draw_crosshairs()
@@ -106,4 +135,4 @@ def set_image(self, image):
camera_window = mesoSPIM_CameraWindow()
camera_window.show()
- sys.exit(app.exec_())
\ No newline at end of file
+ sys.exit(app.exec_())
diff --git a/mesoSPIM/src/mesoSPIM_Core.py b/mesoSPIM/src/mesoSPIM_Core.py
index 290f4cf..5e7b882 100644
--- a/mesoSPIM/src/mesoSPIM_Core.py
+++ b/mesoSPIM/src/mesoSPIM_Core.py
@@ -38,7 +38,6 @@
from .utils.acquisitions import AcquisitionList, Acquisition
from .utils.utility_functions import convert_seconds_to_string
-from .utils.demo_threads import mesoSPIM_DemoThread
class mesoSPIM_Core(QtCore.QObject):
'''This class is the pacemaker of a mesoSPIM
@@ -61,10 +60,10 @@ class mesoSPIM_Core(QtCore.QObject):
sig_progress = QtCore.pyqtSignal(dict)
''' Camera-related signals '''
- sig_prepare_image_series = QtCore.pyqtSignal(Acquisition)
- sig_add_images_to_image_series = QtCore.pyqtSignal()
- sig_add_images_to_image_series_and_wait_until_done = QtCore.pyqtSignal()
- sig_end_image_series = QtCore.pyqtSignal()
+ sig_prepare_image_series = QtCore.pyqtSignal(Acquisition, AcquisitionList)
+ sig_add_images_to_image_series = QtCore.pyqtSignal(Acquisition, AcquisitionList)
+ sig_add_images_to_image_series_and_wait_until_done = QtCore.pyqtSignal(Acquisition, AcquisitionList)
+ sig_end_image_series = QtCore.pyqtSignal(Acquisition, AcquisitionList)
sig_prepare_live = QtCore.pyqtSignal()
sig_get_live_image = QtCore.pyqtSignal()
@@ -201,11 +200,9 @@ def __init__(self, config, parent):
self.state['snap_folder'] = self.cfg.startup['snap_folder']
self.start_time = 0
-
self.stopflag = False
-
logger.info('Thread ID at Startup: '+str(int(QtCore.QThread.currentThreadId())))
-
+ self.metadata_file = None
# self.acquisition_list_rotation_position = {}
def __del__(self):
@@ -576,9 +573,9 @@ def prepare_acquisition_list(self, acq_list):
def run_acquisition_list(self, acq_list):
for acq in acq_list:
if not self.stopflag:
- self.prepare_acquisition(acq)
- self.run_acquisition(acq)
- self.close_acquisition(acq)
+ self.prepare_acquisition(acq, acq_list)
+ self.run_acquisition(acq, acq_list)
+ self.close_acquisition(acq, acq_list)
def close_acquisition_list(self, acq_list):
self.sig_status_message.emit('Closing Acquisition List')
@@ -669,7 +666,7 @@ def preview_acquisition(self, z_update=True):
self.state['state'] = 'idle'
- def prepare_acquisition(self, acq):
+ def prepare_acquisition(self, acq, acq_list):
'''
Housekeeping: Prepare the acquisition
'''
@@ -715,15 +712,15 @@ def prepare_acquisition(self, acq):
self.f_step_generator = acq.get_focus_stepsize_generator()
self.sig_status_message.emit('Preparing camera: Allocating memory')
- self.sig_prepare_image_series.emit(acq)
+ self.sig_prepare_image_series.emit(acq, acq_list)
self.prepare_image_series()
# ''' HICKUP DEBUGGING: Measure z position '''
# self.z_start_measured = self.state['position']['z_pos']
- self.write_metadata(acq)
+ self.write_metadata(acq, acq_list)
- def run_acquisition(self, acq):
+ def run_acquisition(self, acq, acq_list):
steps = acq.get_image_count()
self.sig_status_message.emit('Running Acquisition')
self.open_shutters()
@@ -734,12 +731,12 @@ def run_acquisition(self, acq):
for i in range(steps):
if self.stopflag is True:
self.close_image_series()
- self.sig_end_image_series.emit()
+ self.sig_end_image_series.emit(acq, acq_list)
self.sig_finished.emit()
break
else:
self.snap_image_in_series()
- self.sig_add_images_to_image_series.emit()
+ self.sig_add_images_to_image_series.emit(acq, acq_list)
#time.sleep(0.02)
# self.sig_add_images_to_image_series_and_wait_until_done.emit()
@@ -790,7 +787,7 @@ def run_acquisition(self, acq):
self.close_shutters()
- def close_acquisition(self, acq):
+ def close_acquisition(self, acq, acq_list):
# ''' HICKUP DEBUGGING '''
# self.z_end_measured = self.state['position']['z_pos']
@@ -803,7 +800,7 @@ def close_acquisition(self, acq):
if self.stopflag is False:
# self.move_absolute(acq.get_startpoint(), wait_until_done=True)
self.close_image_series()
- self.sig_end_image_series.emit()
+ self.sig_end_image_series.emit(acq, acq_list)
self.acq_end_time = time.time()
self.acq_end_time_string = time.strftime("%Y%m%d-%H%M%S")
@@ -886,66 +883,76 @@ def write_line(self, file, key='', value=''):
else:
file.write('\n')
- def write_metadata(self, acq):
+ def write_metadata(self, acq, acq_list):
'''
Writes a metadata.txt file
Path contains the file to be written
'''
- path = acq['folder']+'/'+acq['filename']
+ path = acq['folder'] + '/' + acq['filename']
- metadata_path = os.path.dirname(path)+'/'+os.path.basename(path)+'_meta.txt'
+ metadata_path = os.path.dirname(path) + '/' + os.path.basename(path) + '_meta.txt'
# print('Metadata_path: ', metadata_path)
-
- with open(metadata_path,'w') as file:
- self.write_line(file, 'Metadata for file', path)
- self.write_line(file, 'z_stepsize', acq['z_step'])
- self.write_line(file, 'z_planes', acq['planes'])
- self.write_line(file)
- # self.write_line(file, 'COMMENTS')
- # self.write_line(file, 'Comment: ', acq(['comment']))
- # self.write_line(file)
- self.write_line(file, 'CFG')
- self.write_line(file, 'Laser', acq['laser'])
- self.write_line(file, 'Intensity (%)', acq['intensity'])
- self.write_line(file, 'Zoom', acq['zoom'])
- self.write_line(file, 'Pixelsize in um', self.state['pixelsize'])
- self.write_line(file, 'Filter', acq['filter'])
- self.write_line(file, 'Shutter', acq['shutterconfig'])
- self.write_line(file)
- self.write_line(file, 'POSITION')
- self.write_line(file, 'x_pos', acq['x_pos'])
- self.write_line(file, 'y_pos', acq['y_pos'])
- self.write_line(file, 'f_start', acq['f_start'])
- self.write_line(file, 'f_end', acq['f_end'])
- self.write_line(file, 'z_start', acq['z_start'])
- self.write_line(file, 'z_end', acq['z_end'])
- self.write_line(file, 'z_stepsize', acq['z_step'])
- self.write_line(file, 'z_planes', acq.get_image_count())
- self.write_line(file)
-
- ''' Attention: change to true ETL values ASAP '''
- self.write_line(file,'ETL PARAMETERS')
- self.write_line(file, 'ETL CFG File', self.state['ETL_cfg_file'])
- self.write_line(file,'etl_l_offset', self.state['etl_l_offset'])
- self.write_line(file,'etl_l_amplitude', self.state['etl_l_amplitude'])
- self.write_line(file,'etl_r_offset', self.state['etl_r_offset'])
- self.write_line(file,'etl_r_amplitude', self.state['etl_r_amplitude'])
- self.write_line(file)
- self.write_line(file, 'GALVO PARAMETERS')
- self.write_line(file, 'galvo_l_frequency',self.state['galvo_l_frequency'])
- self.write_line(file, 'galvo_l_amplitude',self.state['galvo_l_amplitude'])
- self.write_line(file, 'galvo_l_offset', self.state['galvo_l_offset'])
- self.write_line(file, 'galvo_r_amplitude', self.state['galvo_r_amplitude'])
- self.write_line(file, 'galvo_r_offset', self.state['galvo_r_offset'])
- self.write_line(file)
- self.write_line(file, 'CAMERA PARAMETERS')
- self.write_line(file, 'camera_type', self.cfg.camera)
- self.write_line(file, 'camera_exposure', self.state['camera_exposure_time'])
- self.write_line(file, 'camera_line_interval', self.state['camera_line_interval'])
- self.write_line(file, 'x_pixels',self.cfg.camera_parameters['x_pixels'])
- self.write_line(file, 'y_pixels',self.cfg.camera_parameters['y_pixels'])
+ if acq['filename'][-3:] == '.h5':
+ if acq == acq_list[0]:
+ self.metadata_file = open(metadata_path, 'w')
+ else:
+ self.metadata_file = open(metadata_path, 'w')
+ self.write_line(self.metadata_file, 'Metadata for file', path)
+ self.write_line(self.metadata_file, 'z_stepsize', acq['z_step'])
+ self.write_line(self.metadata_file, 'z_planes', acq['planes'])
+ self.write_line(self.metadata_file)
+ # self.write_line(file, 'COMMENTS')
+ # self.write_line(file, 'Comment: ', acq(['comment']))
+ # self.write_line(file)
+ self.write_line(self.metadata_file, 'CFG')
+ self.write_line(self.metadata_file, 'Laser', acq['laser'])
+ self.write_line(self.metadata_file, 'Intensity (%)', acq['intensity'])
+ self.write_line(self.metadata_file, 'Zoom', acq['zoom'])
+ self.write_line(self.metadata_file, 'Pixelsize in um', self.state['pixelsize'])
+ self.write_line(self.metadata_file, 'Filter', acq['filter'])
+ self.write_line(self.metadata_file, 'Shutter', acq['shutterconfig'])
+ self.write_line(self.metadata_file)
+ self.write_line(self.metadata_file, 'POSITION')
+ self.write_line(self.metadata_file, 'x_pos', acq['x_pos'])
+ self.write_line(self.metadata_file, 'y_pos', acq['y_pos'])
+ self.write_line(self.metadata_file, 'f_start', acq['f_start'])
+ self.write_line(self.metadata_file, 'f_end', acq['f_end'])
+ self.write_line(self.metadata_file, 'z_start', acq['z_start'])
+ self.write_line(self.metadata_file, 'z_end', acq['z_end'])
+ self.write_line(self.metadata_file, 'z_stepsize', acq['z_step'])
+ self.write_line(self.metadata_file, 'z_planes', acq.get_image_count())
+ self.write_line(self.metadata_file, 'rot', acq['rot'])
+ self.write_line(self.metadata_file)
+
+ ''' Attention: change to true ETL values ASAP '''
+ self.write_line(self.metadata_file, 'ETL PARAMETERS')
+ self.write_line(self.metadata_file, 'ETL CFG File', self.state['ETL_cfg_file'])
+ self.write_line(self.metadata_file, 'etl_l_offset', self.state['etl_l_offset'])
+ self.write_line(self.metadata_file, 'etl_l_amplitude', self.state['etl_l_amplitude'])
+ self.write_line(self.metadata_file, 'etl_r_offset', self.state['etl_r_offset'])
+ self.write_line(self.metadata_file, 'etl_r_amplitude', self.state['etl_r_amplitude'])
+ self.write_line(self.metadata_file)
+ self.write_line(self.metadata_file, 'GALVO PARAMETERS')
+ self.write_line(self.metadata_file, 'galvo_l_frequency', self.state['galvo_l_frequency'])
+ self.write_line(self.metadata_file, 'galvo_l_amplitude', self.state['galvo_l_amplitude'])
+ self.write_line(self.metadata_file, 'galvo_l_offset', self.state['galvo_l_offset'])
+ self.write_line(self.metadata_file, 'galvo_r_amplitude', self.state['galvo_r_amplitude'])
+ self.write_line(self.metadata_file, 'galvo_r_offset', self.state['galvo_r_offset'])
+ self.write_line(self.metadata_file)
+ self.write_line(self.metadata_file, 'CAMERA PARAMETERS')
+ self.write_line(self.metadata_file, 'camera_type', self.cfg.camera)
+ self.write_line(self.metadata_file, 'camera_exposure', self.state['camera_exposure_time'])
+ self.write_line(self.metadata_file, 'camera_line_interval', self.state['camera_line_interval'])
+ self.write_line(self.metadata_file, 'x_pixels', self.cfg.camera_parameters['x_pixels'])
+ self.write_line(self.metadata_file, 'y_pixels', self.cfg.camera_parameters['y_pixels'])
+
+ if acq['filename'][-3:] == '.h5':
+ if acq == acq_list[-1]:
+ self.metadata_file.close()
+ else:
+ self.metadata_file.close()
def execute_galil_program(self):
'''Little helper method to execute the program loaded onto the Galil stage:
diff --git a/mesoSPIM/src/mesoSPIM_ImageWriter.py b/mesoSPIM/src/mesoSPIM_ImageWriter.py
new file mode 100644
index 0000000..4127ba8
--- /dev/null
+++ b/mesoSPIM/src/mesoSPIM_ImageWriter.py
@@ -0,0 +1,141 @@
+'''
+mesoSPIM Image Writer class, intended to run in the Camera Thread and handle file I/O
+'''
+
+import os
+import time
+import numpy as np
+import tifffile
+import logging
+logger = logging.getLogger(__name__)
+import sys
+from PyQt5 import QtCore
+
+from .mesoSPIM_State import mesoSPIM_StateSingleton
+
+import npy2bdv
+
+class mesoSPIM_ImageWriter(QtCore.QObject):
+ def __init__(self, parent = None):
+ super().__init__()
+
+ self.parent = parent
+ self.cfg = parent.cfg
+
+ self.state = mesoSPIM_StateSingleton()
+
+ self.x_pixels = self.cfg.camera_parameters['x_pixels']
+ self.y_pixels = self.cfg.camera_parameters['y_pixels']
+
+ self.binning_string = self.cfg.camera_parameters['binning'] # Should return a string in the form '2x4'
+ self.x_binning = int(self.binning_string[0])
+ self.y_binning = int(self.binning_string[2])
+
+ self.x_pixels = int(self.x_pixels / self.x_binning)
+ self.y_pixels = int(self.y_pixels / self.y_binning)
+
+ self.file_extension = ''
+ self.bdv_writer = None
+
+ def prepare_acquisition(self, acq, acq_list):
+ self.folder = acq['folder']
+ self.filename = acq['filename']
+ self.path = self.folder+'/'+self.filename
+ logger.info(f'Image Writer: Save path: {self.path}')
+
+ _ , self.file_extension = os.path.splitext(self.filename)
+
+ self.binning_string = self.state['camera_binning'] # Should return a string in the form '2x4'
+ self.x_binning = int(self.binning_string[0])
+ self.y_binning = int(self.binning_string[2])
+
+ self.x_pixels = int(self.x_pixels / self.x_binning)
+ self.y_pixels = int(self.y_pixels / self.y_binning)
+
+ self.max_frame = acq.get_image_count()
+ self.processing_options_string = acq['processing']
+
+ if self.file_extension == '.h5':
+ if hasattr(self.cfg, "hdf5"):
+ subsamp = self.cfg.hdf5['subsamp']
+ compression = self.cfg.hdf5['compression']
+ flip_flags = self.cfg.hdf5['flip_xyz']
+ else:
+ subsamp = ((1, 1, 1),)
+ compression = None
+ flip_flags = (False, False, False)
+ # create writer object if the view is first in the list
+ if acq == acq_list[0]:
+ self.bdv_writer = npy2bdv.BdvWriter(self.path,
+ nilluminations=acq_list.get_n_shutter_configs(),
+ nchannels=acq_list.get_n_lasers(),
+ nangles=acq_list.get_n_angles(),
+ ntiles=acq_list.get_n_tiles(),
+ blockdim=((1, 256, 256),),
+ subsamp=subsamp,
+ compression=compression)
+ # x and y need to be exchanged to account for the image rotation
+ shape = (self.max_frame, self.y_pixels, self.x_pixels)
+ px_size_um = self.cfg.pixelsize[acq['zoom']]
+ sign_xyz = (1 - np.array(flip_flags)) * 2 - 1
+ affine_matrix = np.array(((1.0, 0.0, 0.0, sign_xyz[0] * acq['x_pos']/px_size_um),
+ (0.0, 1.0, 0.0, sign_xyz[1] * acq['y_pos']/px_size_um),
+ (0.0, 0.0, 1.0, sign_xyz[2] * acq['z_start']/acq['z_step'])))
+ self.bdv_writer.append_view(stack=None, virtual_stack_dim=shape,
+ illumination=acq_list.find_value_index(acq['shutterconfig'], 'shutterconfig'),
+ channel=acq_list.find_value_index(acq['laser'], 'laser'),
+ angle=acq_list.find_value_index(acq['rot'], 'rot'),
+ tile=acq_list.get_tile_index(acq),
+ voxel_units='um',
+ voxel_size_xyz=(px_size_um, px_size_um, acq['z_step']),
+ calibration=(1.0, 1.0, acq['z_step']/px_size_um),
+ m_affine=affine_matrix,
+ name_affine="Translation to Regular Grid"
+ )
+ else:
+ self.fsize = self.x_pixels*self.y_pixels
+ self.xy_stack = np.memmap(self.path, mode="write", dtype=np.uint16, shape=self.fsize * self.max_frame)
+
+ self.cur_image = 0
+
+ def write_image(self, image, acq, acq_list):
+ if self.file_extension == '.h5':
+ self.bdv_writer.append_plane(plane=image, z=self.cur_image,
+ illumination=acq_list.find_value_index(acq['shutterconfig'], 'shutterconfig'),
+ channel=acq_list.find_value_index(acq['laser'], 'laser'),
+ angle=acq_list.find_value_index(acq['rot'], 'rot'),
+ tile=acq_list.get_tile_index(acq)
+ )
+ else:
+ image = image.flatten()
+ self.xy_stack[self.cur_image*self.fsize:(self.cur_image+1)*self.fsize] = image
+
+ self.cur_image += 1
+
+ def end_acquisition(self, acq, acq_list):
+ if self.file_extension == '.h5':
+ if acq == acq_list[-1]:
+ try:
+ self.bdv_writer.set_attribute_labels('channel', tuple(acq_list.get_unique_attr_list('laser')))
+ self.bdv_writer.set_attribute_labels('illumination', tuple(acq_list.get_unique_attr_list('shutterconfig')))
+ self.bdv_writer.set_attribute_labels('angle', tuple(acq_list.get_unique_attr_list('rot')))
+ self.bdv_writer.write_xml()
+ except:
+ logger.error(f'HDF5 XML could not be written: {sys.exc_info()}')
+ try:
+ self.bdv_writer.close()
+ except:
+ logger.error(f'HDF5 file could not be closed: {sys.exc_info()}')
+ else:
+ try:
+ del self.xy_stack
+ except:
+ logger.warning('Raw data stack could not be deleted')
+
+ def write_snap_image(self, image):
+ timestr = time.strftime("%Y%m%d-%H%M%S")
+ filename = timestr + '.tif'
+ path = self.state['snap_folder']+'/'+filename
+ tifffile.imsave(path, image, photometric='minisblack')
+
+
diff --git a/mesoSPIM/src/mesoSPIM_MainWindow.py b/mesoSPIM/src/mesoSPIM_MainWindow.py
index 943f360..3296ea0 100644
--- a/mesoSPIM/src/mesoSPIM_MainWindow.py
+++ b/mesoSPIM/src/mesoSPIM_MainWindow.py
@@ -25,9 +25,6 @@
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
@@ -108,6 +105,7 @@ def __init__(self, config=None):
#logger.info('Core thread affinity after moveToThread? Answer:'+str(id(self.core.thread())))
''' Get buttons & connections ready '''
+ self.initialize_and_connect_menubar()
self.initialize_and_connect_widgets()
''' Widget list for blockSignals during status updates '''
@@ -170,6 +168,11 @@ def __del__(self):
except:
pass
+ def close_app(self):
+ self.camera_window.close()
+ self.acquisition_manager_window.close()
+ self.close()
+
def display_icons(self):
pass
''' Disabled taskbar button progress display due to problems with Anaconda default
@@ -293,6 +296,12 @@ def create_script_window(self):
exec(windowstring+'.sig_execute_script.connect(self.execute_script)')
self.script_window_counter += 1
+ def initialize_and_connect_menubar(self):
+ self.actionExit.triggered.connect(self.close_app)
+ self.actionOpen_Camera_Window.triggered.connect(self.camera_window.show)
+ self.actionOpen_Acquisition_Manager.triggered.connect(self.acquisition_manager_window.show)
+
+
def initialize_and_connect_widgets(self):
''' Connecting the menu actions '''
self.openScriptEditorButton.clicked.connect(self.create_script_window)
@@ -322,7 +331,42 @@ def initialize_and_connect_widgets(self):
self.rotZeroButton.clicked.connect(lambda bool: self.sig_zero_axes.emit(['theta']) if bool is True else self.sig_unzero_axes.emit(['theta']))
self.xyzLoadButton.clicked.connect(self.sig_load_sample.emit)
self.xyzUnloadButton.clicked.connect(self.sig_unload_sample.emit)
-
+
+ ''' Disabling UI buttons if necessary '''
+ if hasattr(self.cfg, 'ui_options'):
+ if self.cfg.ui_options['enable_x_buttons'] is False:
+ self.xPlusButton.setEnabled(False)
+ self.xMinusButton.setEnabled(False)
+
+ if self.cfg.ui_options['enable_y_buttons'] is False:
+ self.yPlusButton.setEnabled(False)
+ self.yMinusButton.setEnabled(False)
+
+ if self.cfg.ui_options['enable_x_buttons'] is False and self.cfg.ui_options['enable_y_buttons'] is False:
+ self.xyZeroButton.setEnabled(False)
+
+ if self.cfg.ui_options['enable_z_buttons'] is False:
+ self.zPlusButton.setEnabled(False)
+ self.zMinusButton.setEnabled(False)
+ self.zZeroButton.setEnabled(False)
+
+ if self.cfg.ui_options['enable_f_buttons'] is False:
+ self.focusPlusButton.setEnabled(False)
+ self.focusMinusButton.setEnabled(False)
+ self.focusZeroButton.setEnabled(False)
+
+ if self.cfg.ui_options['enable_rotation_buttons'] is False:
+ self.rotPlusButton.setEnabled(False)
+ self.rotMinusButton.setEnabled(False)
+ self.rotZeroButton.setEnabled(False)
+ self.goToRotationPositionButton.setEnabled(False)
+ self.markRotationPositionButton.setEnabled(False)
+
+ if self.cfg.ui_options['enable_loading_buttons'] is False:
+ self.xyzLoadButton.setEnabled(False)
+ self.xyzUnloadButton.setEnabled(False)
+
+ ''' Connecting state-changing buttons '''
self.LiveButton.clicked.connect(self.run_live)
self.SnapButton.clicked.connect(self.run_snap)
self.RunSelectedAcquisitionButton.clicked.connect(self.run_selected_acquisition)
diff --git a/mesoSPIM/src/mesoSPIM_Serial.py b/mesoSPIM/src/mesoSPIM_Serial.py
index 4ba2ad5..866ffb8 100644
--- a/mesoSPIM/src/mesoSPIM_Serial.py
+++ b/mesoSPIM/src/mesoSPIM_Serial.py
@@ -6,32 +6,25 @@
filter wheels, zoom systems etc.
'''
-import numpy as np
-import time
-
import logging
logger = logging.getLogger(__name__)
-
-'''PyQt5 Imports'''
-from PyQt5 import QtWidgets, QtCore, QtGui
+from PyQt5 import QtCore
''' Import mesoSPIM modules '''
from .mesoSPIM_State import mesoSPIM_StateSingleton
from .devices.filter_wheels.ludlcontrol import LudlFilterwheel
+from .devices.filter_wheels.sutterLambdaControl import Lambda10B
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, mesoSPIM_PI_rot_and_Galil_xyzf_Stages, mesoSPIM_PI_rotz_and_Galil_xyf_Stages, mesoSPIM_PI_rotzf_and_Galil_xy_Stages
-# from .mesoSPIM_State import mesoSPIM_State
+from .mesoSPIM_Stages import mesoSPIM_PI_1toN, mesoSPIM_PI_NtoN, 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, mesoSPIM_PI_rotzf_and_Galil_xy_Stages
+
class mesoSPIM_Serial(QtCore.QObject):
'''This class handles mesoSPIM serial connections'''
sig_finished = QtCore.pyqtSignal()
-
sig_state_request = QtCore.pyqtSignal(dict)
-
sig_position = QtCore.pyqtSignal(dict)
-
sig_zero_axes = QtCore.pyqtSignal(list)
sig_unzero_axes = QtCore.pyqtSignal(list)
sig_stop_movement = QtCore.pyqtSignal()
@@ -45,43 +38,47 @@ def __init__(self, parent):
''' Assign the parent class to a instance variable for callbacks '''
self.parent = parent
self.cfg = parent.cfg
-
self.state = mesoSPIM_StateSingleton()
''' Handling of state changing requests '''
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)
+ self.parent.sig_state_request_and_wait_until_done.connect(lambda sdict: self.state_request_handler(sdict, wait_until_done=True), type=3)
''' Attaching the filterwheel '''
if self.cfg.filterwheel_parameters['filterwheel_type'] == 'Ludl':
- self.filterwheel = LudlFilterwheel(self.cfg.filterwheel_parameters['COMport'],self.cfg.filterdict)
+ self.filterwheel = LudlFilterwheel(self.cfg.filterwheel_parameters['COMport'], self.cfg.filterdict)
elif self.cfg.filterwheel_parameters['filterwheel_type'] == 'DemoFilterWheel':
self.filterwheel = mesoSPIM_DemoFilterWheel(self.cfg.filterdict)
+ elif self.cfg.filterwheel_parameters['filterwheel_type'] == 'Sutter':
+ self.filterwheel = Lambda10B(self.cfg.filterwheel_parameters['COMport'], self.cfg.filterdict)
''' Attaching the zoom '''
if self.cfg.zoom_parameters['zoom_type'] == 'Dynamixel':
- self.zoom = DynamixelZoom(self.cfg.zoomdict,self.cfg.zoom_parameters['COMport'],self.cfg.zoom_parameters['servo_id'])
+ self.zoom = DynamixelZoom(self.cfg.zoomdict, self.cfg.zoom_parameters['COMport'],self.cfg.zoom_parameters['servo_id'])
elif self.cfg.zoom_parameters['zoom_type'] == 'DemoZoom':
self.zoom = DemoZoom(self.cfg.zoomdict)
''' Attaching the stage '''
- if self.cfg.stage_parameters['stage_type'] == 'PI':
- self.stage = mesoSPIM_PIstage(self)
+ if self.cfg.stage_parameters['stage_type'] in {'PI', 'PI_1controllerNstages'}:
+ self.stage = mesoSPIM_PI_1toN(self)
+ elif self.cfg.stage_parameters['stage_type'] == 'PI_NcontrollersNstages':
+ self.stage = mesoSPIM_PI_NtoN(self)
+ self.stage.sig_position.connect(lambda sdict: self.sig_position.emit({'position': sdict}))
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}))
+ self.stage.sig_position.connect(lambda sdict: self.sig_position.emit({'position': sdict}))
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}))
+ self.stage.sig_position.connect(lambda sdict: self.sig_position.emit({'position': sdict}))
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}))
+ self.stage.sig_position.connect(lambda sdict: self.sig_position.emit({'position': sdict}))
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}))
+ self.stage.sig_position.connect(lambda sdict: self.sig_position.emit({'position': sdict}))
elif self.cfg.stage_parameters['stage_type'] == 'PI_rotzf_and_Galil_xy':
self.stage = mesoSPIM_PI_rotzf_and_Galil_xy_Stages(self)
- self.stage.sig_position.connect(lambda dict: self.sig_position.emit({'position': dict}))
+ self.stage.sig_position.connect(lambda sdict: self.sig_position.emit({'position': sdict}))
elif self.cfg.stage_parameters['stage_type'] == 'DemoStage':
self.stage = mesoSPIM_DemoStage(self)
try:
@@ -91,10 +88,10 @@ def __init__(self, parent):
''' Wiring signals through to child objects '''
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_relative_and_wait_until_done.connect(lambda sdict: self.move_relative(sdict, wait_until_done=True), type=3)
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_move_absolute_and_wait_until_done.connect(lambda sdict: self.move_absolute(sdict, wait_until_done=True), type=3)
self.parent.sig_zero_axes.connect(self.sig_zero_axes.emit)
self.parent.sig_unzero_axes.connect(self.sig_unzero_axes.emit)
@@ -110,8 +107,8 @@ def __init__(self, parent):
@QtCore.pyqtSlot(dict)
- def state_request_handler(self, dict, wait_until_done=False):
- for key, value in zip(dict.keys(),dict.values()):
+ def state_request_handler(self, sdict, wait_until_done=False):
+ for key, value in zip(sdict.keys(), sdict.values()):
# print('Serial thread: state request: Key: ', key, ' Value: ', value)
'''
Here, the request handling is done with lots if 'ifs'
@@ -135,25 +132,25 @@ def state_request_handler(self, dict, wait_until_done=False):
logger.info('Thread ID during live: '+str(int(QtCore.QThread.currentThreadId())))
@QtCore.pyqtSlot(dict)
- def move_relative(self, dict, wait_until_done=False):
+ def move_relative(self, sdict, 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)
+ self.stage.move_relative(sdict, wait_until_done=True)
else:
- self.stage.move_relative(dict)
+ self.stage.move_relative(sdict)
@QtCore.pyqtSlot(dict)
- def move_absolute(self, dict, wait_until_done=False):
+ def move_absolute(self, sdict, wait_until_done=False):
if wait_until_done:
- self.stage.move_absolute(dict, wait_until_done=True)
+ self.stage.move_absolute(sdict, wait_until_done=True)
else:
- self.stage.move_absolute(dict)
+ self.stage.move_absolute(sdict)
@QtCore.pyqtSlot(dict)
- def report_position(self, dict):
- self.sig_position.emit({'position': dict})
+ def report_position(self, sdict):
+ self.sig_position.emit({'position': sdict})
@QtCore.pyqtSlot()
def go_to_rotation_position(self, wait_until_done=False):
@@ -163,13 +160,13 @@ def go_to_rotation_position(self, wait_until_done=False):
self.stage.go_to_rotation_position()
@QtCore.pyqtSlot(str)
- def set_filter(self, filter, wait_until_done=False):
+ def set_filter(self, sfilter, 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)
+ self.filterwheel.set_filter(sfilter, wait_until_done=True)
else:
- self.filterwheel.set_filter(filter, wait_until_done=False)
- self.state['filter'] = filter
+ self.filterwheel.set_filter(sfilter, wait_until_done=False)
+ self.state['filter'] = sfilter
@QtCore.pyqtSlot(str)
def set_zoom(self, zoom, wait_until_done=False):
diff --git a/mesoSPIM/src/mesoSPIM_Stages.py b/mesoSPIM/src/mesoSPIM_Stages.py
index b518024..6b56fdc 100644
--- a/mesoSPIM/src/mesoSPIM_Stages.py
+++ b/mesoSPIM/src/mesoSPIM_Stages.py
@@ -3,14 +3,11 @@
======================
'''
import time
-
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):
'''
@@ -31,14 +28,14 @@ class mesoSPIM_Stage(QtCore.QObject):
'''
sig_position = QtCore.pyqtSignal(dict)
- sig_status_message = QtCore.pyqtSignal(str,int)
+ sig_status_message = QtCore.pyqtSignal(str, int)
- def __init__(self, parent = None):
+ def __init__(self, parent=None):
super().__init__()
self.parent = parent
self.cfg = parent.cfg
- #self.state = mesoSPIM_StateSingleton()
+ # self.state = mesoSPIM_StateSingleton()
''' The movement signals are emitted by the mesoSPIM_Core, which in turn
instantiates the mesoSPIM_Serial thread.
@@ -119,7 +116,7 @@ def __init__(self, parent = None):
'''
# self.sig_status_message.connect(lambda string, time: print(string))
- logger.info('Thread ID at Startup: '+str(int(QtCore.QThread.currentThreadId())))
+ logger.info('Thread ID at Startup: ' + str(int(QtCore.QThread.currentThreadId())))
def create_position_dict(self):
self.position_dict = {'x_pos': self.x_pos,
@@ -160,35 +157,35 @@ def move_relative(self, dict, wait_until_done=False):
if self.x_min < self.x_pos + x_rel and self.x_max > self.x_pos + x_rel:
self.x_pos = self.x_pos + x_rel
else:
- self.sig_status_message.emit('Relative movement stopped: X Motion limit would be reached!',1000)
+ 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.y_pos = self.y_pos + y_rel
else:
- self.sig_status_message.emit('Relative movement stopped: Y Motion limit would be reached!',1000)
+ 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.z_pos = self.z_pos + z_rel
else:
- self.sig_status_message.emit('Relative movement stopped: z Motion limit would be reached!',1000)
+ 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.theta_pos = self.theta_pos + theta_rel
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']
if self.f_min < self.f_pos + f_rel and self.f_max > self.f_pos + f_rel:
self.f_pos = self.f_pos + f_rel
else:
- self.sig_status_message.emit('Relative movement stopped: f Motion limit would be reached!',1000)
+ self.sig_status_message.emit('Relative movement stopped: f Motion limit would be reached!', 1000)
if wait_until_done == True:
time.sleep(0.02)
@@ -203,7 +200,7 @@ def move_absolute(self, dict, wait_until_done=False):
if self.x_min < x_abs and self.x_max > x_abs:
self.x_pos = x_abs
else:
- self.sig_status_message.emit('Absolute movement stopped: X Motion limit would be reached!',1000)
+ self.sig_status_message.emit('Absolute movement stopped: X Motion limit would be reached!', 1000)
if 'y_abs' in dict:
y_abs = dict['y_abs']
@@ -211,7 +208,7 @@ def move_absolute(self, dict, wait_until_done=False):
if self.y_min < y_abs and self.y_max > y_abs:
self.y_pos = y_abs
else:
- self.sig_status_message.emit('Absolute movement stopped: Y Motion limit would be reached!',1000)
+ self.sig_status_message.emit('Absolute movement stopped: Y Motion limit would be reached!', 1000)
if 'z_abs' in dict:
z_abs = dict['z_abs']
@@ -219,7 +216,7 @@ def move_absolute(self, dict, wait_until_done=False):
if self.z_min < z_abs and self.z_max > z_abs:
self.z_pos = z_abs
else:
- self.sig_status_message.emit('Absolute movement stopped: Z Motion limit would be reached!',1000)
+ self.sig_status_message.emit('Absolute movement stopped: Z Motion limit would be reached!', 1000)
if 'f_abs' in dict:
f_abs = dict['f_abs']
@@ -227,7 +224,7 @@ def move_absolute(self, dict, wait_until_done=False):
if self.f_min < f_abs and self.f_max > f_abs:
self.f_pos = f_abs
else:
- self.sig_status_message.emit('Absolute movement stopped: F Motion limit would be reached!',1000)
+ 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']
@@ -235,26 +232,26 @@ def move_absolute(self, dict, wait_until_done=False):
if self.theta_min < theta_abs and self.theta_max > theta_abs:
self.theta_pos = theta_abs
else:
- self.sig_status_message.emit('Absolute movement stopped: Theta Motion limit would be reached!',1000)
+ self.sig_status_message.emit('Absolute movement stopped: Theta Motion limit would be reached!', 1000)
if wait_until_done == True:
time.sleep(3)
@QtCore.pyqtSlot()
def stop(self):
- self.sig_status_message.emit('Stopped',0)
+ self.sig_status_message.emit('Stopped', 0)
def zero_axes(self, list):
for axis in list:
try:
- exec('self.int_'+axis+'_pos_offset = -self.'+axis+'_pos')
+ exec('self.int_' + axis + '_pos_offset = -self.' + axis + '_pos')
except:
logger.info('Zeroing of axis: ', axis, 'failed')
def unzero_axes(self, list):
for axis in list:
try:
- exec('self.int_'+axis+'_pos_offset = 0')
+ exec('self.int_' + axis + '_pos_offset = 0')
except:
logger.info('Unzeroing of axis: ', axis, 'failed')
@@ -269,7 +266,8 @@ 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
- logger.info('Marking new rotation position (absolute coordinates): X: ', self.x_pos, ' Y: ', self.y_pos, ' Z: ', self.z_pos)
+ 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
@@ -279,74 +277,74 @@ def go_to_rotation_position(self, wait_until_done=False):
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):
+ def __init__(self, parent=None):
super().__init__(parent)
-class mesoSPIM_PIstage(mesoSPIM_Stage):
- '''
- It is expected that the parent class has the following signals:
- sig_move_relative = pyqtSignal(dict)
- sig_move_relative_and_wait_until_done = pyqtSignal(dict)
- sig_move_absolute = pyqtSignal(dict)
- sig_move_absolute_and_wait_until_done = pyqtSignal(dict)
- sig_zero = pyqtSignal(list)
- sig_unzero = pyqtSignal(list)
- sig_stop_movement = pyqtSignal()
- sig_mark_rotation_position = pyqtSignal()
-
- Also contains a QTimer that regularily sends position updates, e.g
- during the execution of movements.
+class mesoSPIM_PI_1toN(mesoSPIM_Stage):
+ '''
+ Configuration with 1 controller connected to N stages, (e.g. C-884, default mesoSPIM V5 setup).
+
+ Note:
+ configs as declared in mesoSPIM_config.py:
+ stage_parameters = {'stage_type' : 'PI_1controllerNstages',
+ ...
+ }
+ pi_parameters = {'controllername' : 'C-884',
+ 'stages' : ('L-509.20DG10','L-509.40DG10','L-509.20DG10','M-060.DG','M-406.4PD','NOSTAGE'),
+ 'refmode' : ('FRF',),
+ 'serialnum' : ('118075764'),
+ }
'''
- def __init__(self, parent = None):
+ def __init__(self, parent=None):
super().__init__(parent)
-
- '''
- 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.pi_stages = list(self.cfg.pi_parameters['stages'])
self.refmode = self.cfg.pi_parameters['refmode']
- # self.serialnum = ('118015439') # Wyss Geneva
- self.serialnum = self.cfg.pi_parameters['serialnum'] # UZH Irchel H45
-
+ self.serialnum = self.cfg.pi_parameters['serialnum']
self.pidevice = GCSDevice(self.controllername)
self.pidevice.ConnectUSB(serialnum=self.serialnum)
''' PI startup '''
-
''' with refmode enabled: pretty dangerous
pitools.startup(self.pidevice, stages=self.pi_stages, refmode=self.refmode)
'''
pitools.startup(self.pidevice, stages=self.pi_stages)
+ ''' Report reference status of all stages '''
+ for ii in range(1, len(self.pi_stages) + 1):
+ tStage = self.pi_stages[ii - 1]
+ if tStage == 'NOSTAGE':
+ continue
+
+ tState = self.pidevice.qFRF(ii)
+ if tState[ii]:
+ msg = 'referenced'
+ else:
+ msg = '*UNREFERENCED*'
+
+ logger.info("Axis %d (%s) reference status: %s" % (ii, tStage, msg))
+
''' Stage 5 referencing hack '''
- # print('Referencing status 3: ', self.pidevice.qFRF(3))
- # print('Referencing status 5: ', self.pidevice.qFRF(5))
self.pidevice.FRF(5)
logger.info('mesoSPIM_Stages: M-406 Emergency referencing hack: Waiting for referencing move')
self.block_till_controller_is_ready()
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))
''' Stage 5 close to good focus'''
self.startfocus = self.cfg.stage_parameters['startfocus']
- self.pidevice.MOV(5,self.startfocus/1000)
+ self.pidevice.MOV(5, self.startfocus / 1000)
def __del__(self):
try:
- '''Close the PI connection'''
self.pidevice.unload()
logger.info('Stage disconnected')
except:
@@ -354,11 +352,10 @@ def __del__(self):
def report_position(self):
positions = self.pidevice.qPOS(self.pidevice.axes)
-
- self.x_pos = round(positions['1']*1000,2)
- self.y_pos = round(positions['2']*1000,2)
- self.z_pos = round(positions['3']*1000,2)
- self.f_pos = round(positions['5']*1000,2)
+ self.x_pos = round(positions['1'] * 1000, 2)
+ self.y_pos = round(positions['2'] * 1000, 2)
+ self.z_pos = round(positions['3'] * 1000, 2)
+ self.f_pos = round(positions['5'] * 1000, 2)
self.theta_pos = positions['4']
self.create_position_dict()
@@ -370,9 +367,7 @@ def report_position(self):
self.int_theta_pos = self.theta_pos + self.int_theta_pos_offset
self.create_internal_position_dict()
-
# self.state['position'] = self.int_position_dict
-
self.sig_position.emit(self.int_position_dict)
def move_relative(self, dict, wait_until_done=False):
@@ -382,50 +377,48 @@ def move_relative(self, dict, wait_until_done=False):
'''
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:
- x_rel = x_rel/1000
- self.pidevice.MVR({1 : x_rel})
+ if self.x_min < self.x_pos + x_rel < self.x_max:
+ x_rel = x_rel / 1000
+ self.pidevice.MVR({1: x_rel})
else:
- self.sig_status_message.emit('Relative movement stopped: X Motion limit would be reached!',1000)
+ 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:
- y_rel = y_rel/1000
- self.pidevice.MVR({2 : y_rel})
+ if self.y_min < self.y_pos + y_rel < self.y_max:
+ y_rel = y_rel / 1000
+ self.pidevice.MVR({2: y_rel})
else:
- self.sig_status_message.emit('Relative movement stopped: Y Motion limit would be reached!',1000)
+ 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({3 : z_rel})
+ if self.z_min < self.z_pos + z_rel < self.z_max:
+ z_rel = z_rel / 1000
+ self.pidevice.MVR({3: z_rel})
else:
- self.sig_status_message.emit('Relative movement stopped: z Motion limit would be reached!',1000)
+ 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({4 : theta_rel})
+ if self.theta_min < self.theta_pos + theta_rel < self.theta_max:
+ self.pidevice.MVR({4: theta_rel})
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']
- if self.f_min < self.f_pos + f_rel and self.f_max > self.f_pos + f_rel:
- f_rel = f_rel/1000
- self.pidevice.MVR({5 : f_rel})
+ if self.f_min < self.f_pos + f_rel < self.f_max:
+ f_rel = f_rel / 1000
+ self.pidevice.MVR({5: f_rel})
else:
- self.sig_status_message.emit('Relative movement stopped: f Motion limit would be reached!',1000)
+ self.sig_status_message.emit('Relative movement stopped: f Motion limit would be reached!', 1000)
- if wait_until_done == True:
+ if wait_until_done:
self.pitools.waitontarget(self.pidevice)
def move_absolute(self, dict, wait_until_done=False):
'''
- PI move absolute method
-
Lots of implementation details in here, should be replaced by a facade
TODO: Also lots of repeating code.
@@ -435,74 +428,74 @@ def move_absolute(self, dict, wait_until_done=False):
if 'x_abs' in dict:
x_abs = dict['x_abs']
x_abs = x_abs - self.int_x_pos_offset
- if self.x_min < x_abs and self.x_max > x_abs:
+ if self.x_min < x_abs < self.x_max:
''' Conversion to mm and command emission'''
- x_abs= x_abs/1000
- self.pidevice.MOV({1 : x_abs})
+ x_abs = x_abs / 1000
+ self.pidevice.MOV({1: x_abs})
else:
- self.sig_status_message.emit('Absolute movement stopped: X Motion limit would be reached!',1000)
+ self.sig_status_message.emit('Absolute movement stopped: X Motion limit would be reached!', 1000)
if 'y_abs' in dict:
y_abs = dict['y_abs']
y_abs = y_abs - self.int_y_pos_offset
- if self.y_min < y_abs and self.y_max > y_abs:
+ if self.y_min < y_abs < self.y_max:
''' Conversion to mm and command emission'''
- y_abs= y_abs/1000
- self.pidevice.MOV({2 : y_abs})
+ y_abs = y_abs / 1000
+ self.pidevice.MOV({2: y_abs})
else:
- self.sig_status_message.emit('Absolute movement stopped: Y Motion limit would be reached!',1000)
+ self.sig_status_message.emit('Absolute movement stopped: Y Motion limit would be reached!', 1000)
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:
+ if self.z_min < z_abs < self.z_max:
''' Conversion to mm and command emission'''
- z_abs= z_abs/1000
- self.pidevice.MOV({3 : z_abs})
+ z_abs = z_abs / 1000
+ self.pidevice.MOV({3: z_abs})
else:
- self.sig_status_message.emit('Absolute movement stopped: Z Motion limit would be reached!',1000)
+ self.sig_status_message.emit('Absolute movement stopped: Z Motion limit would be reached!', 1000)
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:
+ if self.f_min < f_abs < self.f_max:
''' Conversion to mm and command emission'''
- f_abs= f_abs/1000
- self.pidevice.MOV({5 : f_abs})
+ f_abs = f_abs / 1000
+ self.pidevice.MOV({5: f_abs})
else:
- self.sig_status_message.emit('Absolute movement stopped: F Motion limit would be reached!',1000)
+ 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:
+ if self.theta_min < theta_abs < self.theta_max:
''' No Conversion to mm !!!! and command emission'''
- self.pidevice.MOV({4 : theta_abs})
+ self.pidevice.MOV({4: theta_abs})
else:
- self.sig_status_message.emit('Absolute movement stopped: Theta Motion limit would be reached!',1000)
+ self.sig_status_message.emit('Absolute movement stopped: Theta Motion limit would be reached!', 1000)
- if wait_until_done == True:
+ if wait_until_done:
self.pitools.waitontarget(self.pidevice)
def stop(self):
self.pidevice.STP(noraise=True)
def load_sample(self):
- y_abs = self.cfg.stage_parameters['y_load_position']/1000
- self.pidevice.MOV({2 : y_abs})
+ y_abs = self.cfg.stage_parameters['y_load_position'] / 1000
+ self.pidevice.MOV({2: y_abs})
def unload_sample(self):
- y_abs = self.cfg.stage_parameters['y_unload_position']/1000
- self.pidevice.MOV({2 : y_abs})
+ y_abs = self.cfg.stage_parameters['y_unload_position'] / 1000
+ self.pidevice.MOV({2: y_abs})
def go_to_rotation_position(self, wait_until_done=False):
- x_abs = self.x_rot_position/1000
- y_abs = self.y_rot_position/1000
- z_abs = self.z_rot_position/1000
+ x_abs = self.x_rot_position / 1000
+ y_abs = self.y_rot_position / 1000
+ z_abs = self.z_rot_position / 1000
- self.pidevice.MOV({1 : x_abs, 2 : y_abs, 3 : z_abs})
+ self.pidevice.MOV({1: x_abs, 2: y_abs, 3: z_abs})
- if wait_until_done == True:
+ if wait_until_done:
self.pitools.waitontarget(self.pidevice)
def block_till_controller_is_ready(self):
@@ -517,6 +510,205 @@ def block_till_controller_is_ready(self):
else:
time.sleep(0.1)
+
+class mesoSPIM_PI_NtoN(mesoSPIM_Stage):
+ '''
+ Expects following microscope configuration:
+ Sample XYZ movement: Physik Instrumente stage with three L-509-type stepper motor stages and individual C-663 controller.
+ F movement: Physik Instrumente C-663 controller and custom stage with stepper motor
+ Rotation: not implemented
+
+ All stage controller are of same type and the sample stages work with reference setting.
+ Focus stage has reference mode set to off.
+
+ Note:
+ configs as declared in mesoSPIM_config.py:
+ stage_parameters = {'stage_type' : 'PI_NcontrollersNstages',
+ ...
+ }
+ pi_parameters = {'axes_names': ('x', 'y', 'z', 'theta', 'f'),
+ 'stages': ('L-509.20SD00', 'L-509.40SD00', 'L-509.20SD00', None, 'MESOSPIM_FOCUS'),
+ 'controllername': ('C-663', 'C-663', 'C-663', None, 'C-663'),
+ 'serialnum': ('**********', '**********', '**********', None, '**********'),
+ 'refmode': ('FRF', 'FRF', 'FRF', None, 'RON')
+ }
+ make sure that reference points are not in conflict with general microscope setup
+ and will not hurt optics under referencing at startup
+ '''
+
+ def __init__(self, parent=None):
+ super().__init__(parent)
+ from pipython import GCSDevice, pitools
+ self.pitools = pitools
+ self.pi = self.cfg.pi_parameters
+ print("Connecting stage drive...")
+
+ # Setting up the stages with separate PI controller.
+ # Explicitly set referencing status and get position
+
+ # gather stage devices in VirtualStages class
+ class VirtualStages:
+ pass
+
+ assert len(self.pi['axes_names']) == len(self.pi['stages']) == len(self.pi['controllername']) \
+ == len(self.pi['serialnum']) == len(self.pi['refmode']), \
+ "Config file, pi_parameters dictionary: numbers of axes_names, stages, controllername, serialnum, refmode must match "
+ self.pi_stages = VirtualStages()
+ for axis_name, stage, controller, serialnum, refmode in zip(self.pi['axes_names'], self.pi['stages'],
+ self.pi['controllername'], self.pi['serialnum'],
+ self.pi['refmode']):
+ # run stage startup procedure for each axis
+ if stage:
+ print(f'starting stage {stage}')
+ pidevice_ = GCSDevice(controller)
+ pidevice_.ConnectUSB(serialnum=serialnum)
+ if refmode is None:
+ pitools.startup(pidevice_, stages=stage)
+ elif refmode == 'FRF':
+ pitools.startup(pidevice_, stages=stage, refmodes=refmode)
+ pidevice_.FRF(1)
+ elif refmode == 'RON':
+ pitools.startup(pidevice_, stages=stage)
+ pidevice_.RON({1: 0}) # set reference mode
+ # activate servo
+ pidevice_.SVO(pidevice_.axes, [True] * len(pidevice_.axes))
+ # print('servo state: {}'.format(pidevice_.qSVO()))
+ # set/get actual position as home position
+ # assumes that starting position is within reasonable distance from optimal focus
+ pidevice_.POS({1: 0.0})
+ pidevice_.DFH(1)
+ else:
+ raise ValueError(f"refmode {refmode} is not supported, PI stage {stage} initialization failed")
+ print(f'stage {stage} started')
+ print('axis {}, referencing mode: {}'.format(axis_name, pidevice_.qRON()))
+ self.wait_for_controller(pidevice_)
+ print('axis {}, stage {} ready'.format(axis_name, stage))
+ setattr(self.pi_stages, ('pidevice_' + axis_name), pidevice_)
+ else:
+ setattr(self.pi_stages, axis_name, None)
+
+ logger.info('mesoSPIM_PI_NtoN: started')
+
+
+ def wait_for_controller(self, controller):
+ # function used during stage setup
+ blockflag = True
+ while blockflag:
+ if controller.IsControllerReady():
+ blockflag = False
+ else:
+ time.sleep(0.1)
+
+
+ def __del__(self):
+ '''Close the PI connection'''
+ try:
+ [(getattr(self.pi_stages, ('pidevice_' + axis_name))).unload() for axis_name in self.pi['axes_names'] if
+ (hasattr(self.pi_stages, ('pidevice_' + axis_name)))]
+ logger.info('Stages disconnected')
+ except:
+ logger.info('Error while disconnecting the PI stage')
+
+
+ def report_position(self):
+ '''report stage position'''
+ for axis_name in self.pi['axes_names']:
+ pidevice_name = 'pidevice_' + str(axis_name)
+ if hasattr(self.pi_stages, pidevice_name):
+ try:
+ if axis_name is None:
+ pos = 0
+ elif axis_name == 'theta':
+ pos = (getattr(self.pi_stages, pidevice_name)).qPOS(1)[1]
+ else:
+ pos = round((getattr(self.pi_stages, pidevice_name)).qPOS(1)[1] * 1000, 2)
+ except:
+ print(f"Failed to report_position for axis_name {axis_name}, pidevice_name {pidevice_name}.")
+ else:
+ pos = 0
+
+ setattr(self, (axis_name + '_pos'), pos)
+ int_pos = pos + getattr(self, ('int_' + axis_name + '_pos_offset'))
+ setattr(self, ('int_' + axis_name + '_pos'), int_pos)
+
+ self.create_position_dict()
+ self.create_internal_position_dict()
+
+ self.sig_position.emit(self.int_position_dict)
+
+
+ def move_relative(self, move_dict, wait_until_done=False):
+ ''' PI move relative method '''
+ for axis_move in move_dict.keys():
+ axis_name = axis_move.split('_')[0]
+ move_value = move_dict[axis_move]
+
+ if (hasattr(self.pi_stages, ('pidevice_' + axis_name))):
+ if (getattr(self, (axis_name + '_min')) < getattr(self, (axis_name + '_pos')) + move_value) and \
+ (getattr(self, (axis_name + '_max')) > getattr(self, (axis_name + '_pos')) + move_value):
+ if not axis_name=='theta':
+ move_value = move_value/1000
+ (getattr(self.pi_stages, ('pidevice_' + axis_name))).MVR({1 : move_value})
+ else:
+ self.sig_status_message.emit('Relative movement stopped: {} Motion limit would be reached!'.format(axis_name),1000)
+ if (axis_name == 'f') or (wait_until_done == True):
+ self.pitools.waitontarget(getattr(self.pi_stages, ('pidevice_' + axis_name))) # focus may be slower than expected
+
+
+ def move_absolute(self, move_dict, wait_until_done=False):
+ ''' PI move absolute method '''
+ for axis_move in move_dict.keys():
+ axis_name = axis_move.split('_')[0]
+ move_value = move_dict[axis_move]
+ move_value = move_value - getattr(self, ('int_' + axis_name + '_pos_offset'))
+
+ if (hasattr(self.pi_stages, ('pidevice_' + axis_name))):
+ if (getattr(self, (axis_name + '_min')) < move_value) and \
+ (getattr(self, (axis_name + '_max')) > move_value):
+ if not axis_name == 'theta':
+ move_value = move_value / 1000
+ (getattr(self.pi_stages, ('pidevice_' + axis_name))).MOV({1: move_value})
+ else:
+ self.sig_status_message.emit(
+ 'Absolute movement stopped: {} Motion limit would be reached!'.format(axis_name), 1000)
+ if (axis_name == 'f') or (wait_until_done == True):
+ self.pitools.waitontarget(getattr(self.pi_stages, ('pidevice_' + axis_name))) # focus may be slower than expected
+
+
+ def stop(self):
+ '''stop stage movement'''
+ [(getattr(self.pi_stages, ('pidevice_' + axis_name))).STP(noraise=True) for axis_name in self.pi['axes_names']
+ if (hasattr(self.pi_stages, ('pidevice_' + axis_name)))]
+
+
+ def load_sample(self):
+ '''bring sample to imaging position'''
+ axis_name = 'y'
+ y_abs = self.cfg.stage_parameters['y_load_position'] / 1000
+ (getattr(self.pi_stages, ('pidevice_' + axis_name))).MOV({1: y_abs})
+
+
+ def unload_sample(self):
+ '''lift sample to sample handling position'''
+ axis_name = 'y'
+ y_abs = self.cfg.stage_parameters['y_unload_position'] / 1000
+ (getattr(self.pi_stages, ('pidevice_' + axis_name))).MOV({1: y_abs})
+
+
+ '''
+ # currently not implemented for this microscope configuration
+ def go_to_rotation_position(self, wait_until_done=False):
+ x_abs = self.x_rot_position/1000
+ y_abs = self.y_rot_position/1000
+ z_abs = self.z_rot_position/1000
+
+ self.pidevice.MOV({1 : x_abs, 2 : y_abs, 3 : z_abs})
+
+ if wait_until_done == True:
+ self.pitools.waitontarget(self.pidevice)
+ '''
+
+
class mesoSPIM_GalilStages(mesoSPIM_Stage):
'''
@@ -536,7 +728,7 @@ class mesoSPIM_GalilStages(mesoSPIM_Stage):
Todo: Rotation handling not implemented!
'''
- def __init__(self, parent = None):
+ def __init__(self, parent=None):
super().__init__(parent)
'''
@@ -550,15 +742,15 @@ def __init__(self, parent = None):
self.f_encodercounts_per_um = self.cfg.f_galil_parameters['z_encodercounts_per_um']
''' Setting up the Galil stages '''
- self.xyz_stage = StageControlGalil(COMport = self.cfg.xyz_galil_parameters['COMport'],
- x_encodercounts_per_um = self.x_encodercounts_per_um,
- y_encodercounts_per_um = self.y_encodercounts_per_um,
- z_encodercounts_per_um = self.z_encodercounts_per_um)
+ self.xyz_stage = StageControlGalil(COMport=self.cfg.xyz_galil_parameters['COMport'],
+ x_encodercounts_per_um=self.x_encodercounts_per_um,
+ y_encodercounts_per_um=self.y_encodercounts_per_um,
+ z_encodercounts_per_um=self.z_encodercounts_per_um)
- self.f_stage = StageControlGalil(COMport = self.cfg.f_galil_parameters['COMport'],
- x_encodercounts_per_um = 0,
- y_encodercounts_per_um = 0,
- z_encodercounts_per_um = self.f_encodercounts_per_um)
+ 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'))
@@ -601,42 +793,41 @@ def move_relative(self, dict, wait_until_done=False):
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))
+ self.xyz_stage.move_relative(xrel=int(x_rel))
else:
- self.sig_status_message.emit('Relative movement stopped: X Motion limit would be reached!',1000)
+ 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))
+ self.xyz_stage.move_relative(yrel=int(y_rel))
else:
- self.sig_status_message.emit('Relative movement stopped: Y Motion limit would be reached!',1000)
+ 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))
+ self.xyz_stage.move_relative(zrel=int(z_rel))
else:
- self.sig_status_message.emit('Relative movement stopped: z Motion limit would be reached!',1000)
+ 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:
- print('No rotation stage attached')
+ 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']
if self.f_min < self.f_pos + f_rel and self.f_max > self.f_pos + f_rel:
- self.f_stage.move_relative(zrel = f_rel)
+ self.f_stage.move_relative(zrel=f_rel)
else:
- self.sig_status_message.emit('Relative movement stopped: f Motion limit would be reached!',1000)
+ self.sig_status_message.emit('Relative movement stopped: f Motion limit would be reached!', 1000)
if wait_until_done == True:
pass
-
def move_absolute(self, dict, wait_until_done=False):
'''
Galil move absolute method
@@ -644,7 +835,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']
@@ -673,30 +864,18 @@ def move_absolute(self, dict, wait_until_done=False):
# y_abs = self.cfg.stage_parameters['y_unload_position']/1000
# # self.pidevice.MOV({2 : y_abs})
+
class mesoSPIM_PI_f_rot_and_Galil_xyz_Stages(mesoSPIM_Stage):
'''
-
- 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.
-
+ Deprecated?
Todo: Rotation handling not implemented!
Todo: Rotation axes are hardcoded! (M-605: #5, M-061.PD: #6)
'''
- def __init__(self, parent = None):
+ def __init__(self, parent=None):
super().__init__(parent)
- #self.state = mesoSPIM_StateSingleton()
+ # self.state = mesoSPIM_StateSingleton()
self.pos_timer = QtCore.QTimer(self)
self.pos_timer.timeout.connect(self.report_position)
@@ -711,15 +890,16 @@ 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(self.cfg.xyz_galil_parameters['port'],[self.x_encodercounts_per_um,
- self.y_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,
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'))
@@ -735,7 +915,7 @@ def __init__(self, parent = None):
self.pi = self.cfg.pi_parameters
self.controllername = self.cfg.pi_parameters['controllername']
- self.pi_stages = self.cfg.pi_parameters['stages']
+ self.pi_stages = list(self.cfg.pi_parameters['stages'])
# ('M-112K033','L-406.40DG10','M-112K033','M-116.DG','M-406.4PD','NOSTAGE')
self.refmode = self.cfg.pi_parameters['refmode']
# self.serialnum = ('118015439') # Wyss Geneva
@@ -768,7 +948,7 @@ def __init__(self, parent = None):
''' Stage 5 close to good focus'''
self.startfocus = self.cfg.stage_parameters['startfocus']
- self.pidevice.MOV(5,self.startfocus/1000)
+ self.pidevice.MOV(5, self.startfocus / 1000)
def __del__(self):
try:
@@ -787,8 +967,8 @@ def report_position(self):
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.x_pos, self.y_pos, self.z_pos = self.xyz_stage.read_position()
+ self.f_pos = round(positions['5'] * 1000, 2)
self.theta_pos = positions['6']
self.create_position_dict()
@@ -802,7 +982,7 @@ def report_position(self):
self.create_internal_position_dict()
self.sig_position.emit(self.int_position_dict)
- #print(self.int_position_dict)
+ # print(self.int_position_dict)
def move_relative(self, dict, wait_until_done=False):
''' Galil move relative method
@@ -814,47 +994,46 @@ def move_relative(self, dict, wait_until_done=False):
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)})
+ xyz_motion_dict.update({1: int(x_rel)})
else:
- self.sig_status_message.emit('Relative movement stopped: X Motion limit would be reached!',1000)
+ 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)})
+ xyz_motion_dict.update({2: int(y_rel)})
else:
- self.sig_status_message.emit('Relative movement stopped: Y Motion limit would be reached!',1000)
+ 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)})
+ xyz_motion_dict.update({3: int(z_rel)})
else:
- self.sig_status_message.emit('Relative movement stopped: z Motion limit would be reached!',1000)
-
+ 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({6 : theta_rel})
+ self.pidevice.MVR({6: theta_rel})
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']
if self.f_min < self.f_pos + f_rel and self.f_max > self.f_pos + f_rel:
- f_rel = f_rel/1000
- self.pidevice.MVR({5 : f_rel})
+ f_rel = f_rel / 1000
+ self.pidevice.MVR({5: f_rel})
else:
- self.sig_status_message.emit('Relative movement stopped: f Motion limit would be reached!',1000)
+ self.sig_status_message.emit('Relative movement stopped: f Motion limit would be reached!', 1000)
if wait_until_done == True:
self.xyz_stage.wait_until_done('XYZ')
self.pitools.waitontarget(self.pidevice)
-
def move_absolute(self, dict, wait_until_done=False):
'''
Galil move absolute method
@@ -868,42 +1047,42 @@ def move_absolute(self, dict, wait_until_done=False):
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})
+ 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})
-
+ 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})
-
+ xyz_motion_dict.update({3: z_abs})
+
if xyz_motion_dict != {}:
self.xyz_stage.move_absolute(xyz_motion_dict)
-
+
if wait_until_done == True:
self.xyz_stage.wait_until_done('XYZ')
-
+
if 'f_abs' in dict:
f_abs = dict['f_abs']
f_abs = f_abs - self.int_f_pos_offset
if self.f_min < f_abs and self.f_max > f_abs:
''' Conversion to mm and command emission'''
- f_abs= f_abs/1000
- self.pidevice.MOV({5 : f_abs})
+ f_abs = f_abs / 1000
+ self.pidevice.MOV({5: f_abs})
else:
- self.sig_status_message.emit('Absolute movement stopped: F Motion limit would be reached!',1000)
+ 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({6 : theta_abs})
+ self.pidevice.MOV({6: theta_abs})
else:
- self.sig_status_message.emit('Absolute movement stopped: Theta Motion limit would be reached!',1000)
+ 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)
@@ -913,13 +1092,15 @@ def stop(self):
self.pidevice.STP(noraise=True)
def load_sample(self):
- self.xyz_stage.move_absolute({1:self.int_x_pos, 2:self.cfg.stage_parameters['y_load_position'], 3:self.int_z_pos})
+ self.xyz_stage.move_absolute(
+ {1: self.int_x_pos, 2: self.cfg.stage_parameters['y_load_position'], 3: self.int_z_pos})
def unload_sample(self):
- self.xyz_stage.move_absolute({1:self.int_x_pos, 2:self.cfg.stage_parameters['y_unload_position'], 3:self.int_z_pos})
-
+ 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({1:self.x_rot_position, 2:self.y_rot_position, 3: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')
@@ -939,6 +1120,7 @@ def execute_program(self):
'''Executes program stored on the Galil controller'''
self.xyz_stage.execute_program()
+
class mesoSPIM_PI_rot_and_Galil_xyzf_Stages(mesoSPIM_Stage):
'''
Expects following microscope configuration:
@@ -962,10 +1144,10 @@ class mesoSPIM_PI_rot_and_Galil_xyzf_Stages(mesoSPIM_Stage):
'''
- def __init__(self, parent = None):
+ def __init__(self, parent=None):
super().__init__(parent)
- #self.state = mesoSPIM_StateSingleton()
+ # self.state = mesoSPIM_StateSingleton()
self.pos_timer = QtCore.QTimer(self)
self.pos_timer.timeout.connect(self.report_position)
@@ -981,19 +1163,21 @@ def __init__(self, parent = None):
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])
+ 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(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'))
@@ -1024,7 +1208,7 @@ def __init__(self, parent = None):
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'])
@@ -1034,11 +1218,11 @@ def __init__(self, parent = None):
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)
+ # self.pidevice.MOV(5,self.startfocus/1000)
def __del__(self):
try:
@@ -1058,15 +1242,15 @@ def report_position(self):
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()
+ 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
@@ -1076,7 +1260,7 @@ def report_position(self):
self.create_internal_position_dict()
self.sig_position.emit(self.int_position_dict)
- #print(self.int_position_dict)
+ # print(self.int_position_dict)
def move_relative(self, dict, wait_until_done=False):
''' Galil move relative method
@@ -1088,47 +1272,46 @@ def move_relative(self, dict, wait_until_done=False):
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)})
+ xyz_motion_dict.update({1: int(x_rel)})
else:
- self.sig_status_message.emit('Relative movement stopped: X Motion limit would be reached!',1000)
+ 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)})
+ xyz_motion_dict.update({2: int(y_rel)})
else:
- self.sig_status_message.emit('Relative movement stopped: Y Motion limit would be reached!',1000)
+ 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)})
+ xyz_motion_dict.update({3: int(z_rel)})
else:
- self.sig_status_message.emit('Relative movement stopped: z Motion limit would be reached!',1000)
-
+ 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})
+ self.pidevice.MVR({1: theta_rel})
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']
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)})
+ 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)
+ 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
@@ -1142,41 +1325,41 @@ def move_absolute(self, dict, wait_until_done=False):
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})
+ 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})
-
+ 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})
-
+ 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)})
+ 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)
+ 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})
+ self.pidevice.MOV({1: theta_abs})
else:
- self.sig_status_message.emit('Absolute movement stopped: Theta Motion limit would be reached!',1000)
+ 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)
@@ -1187,13 +1370,13 @@ def stop(self):
self.pidevice.STP(noraise=True)
def load_sample(self):
- self.move_absolute({'y_abs':self.cfg.stage_parameters['y_load_position']})
+ 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']})
-
+ 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})
+ 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')
@@ -1214,32 +1397,20 @@ def execute_program(self):
self.f_stage.execute_program()
self.xyz_stage.execute_program()
+
class mesoSPIM_PI_rotz_and_Galil_xyf_Stages(mesoSPIM_Stage):
'''
+ Deprecated?
Expects following microscope configuration:
-
- Sample XYF movement: Galil controller with 3 axes
- Z-Movement and Rotation: PI C-884 mercury controller
- 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.
-
+ Sample XYF movement: Galil controller with 3 axes
+ Z-Movement and Rotation: PI C-884 mercury controller
'''
- def __init__(self, parent = None):
+ def __init__(self, parent=None):
super().__init__(parent)
- #self.state = mesoSPIM_StateSingleton()
+ # self.state = mesoSPIM_StateSingleton()
self.pos_timer = QtCore.QTimer(self)
self.pos_timer.timeout.connect(self.report_position)
@@ -1254,8 +1425,9 @@ def __init__(self, parent = None):
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])
+ 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
@@ -1266,7 +1438,7 @@ def __init__(self, parent = None):
self.pi = self.cfg.pi_parameters
self.controllername = self.cfg.pi_parameters['controllername']
- self.pi_stages = self.cfg.pi_parameters['stages']
+ self.pi_stages = list(self.cfg.pi_parameters['stages'])
# ('M-112K033','L-406.40DG10','M-112K033','M-116.DG','M-406.4PD','NOSTAGE')
self.refmode = self.cfg.pi_parameters['refmode']
# self.serialnum = ('118015439') # Wyss Geneva
@@ -1281,7 +1453,7 @@ def __init__(self, parent = None):
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'])
@@ -1291,7 +1463,7 @@ def __init__(self, parent = None):
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})
@@ -1313,15 +1485,15 @@ def report_position(self):
exceptional circumstances.
'''
try:
- self.x_pos, self.y_pos, self.f_pos = self.xyf_stage.read_position()
+ 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.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
@@ -1331,7 +1503,7 @@ def report_position(self):
self.create_internal_position_dict()
self.sig_position.emit(self.int_position_dict)
- #print(self.int_position_dict)
+ # print(self.int_position_dict)
def move_relative(self, dict, wait_until_done=False):
''' Galil move relative method
@@ -1343,38 +1515,38 @@ def move_relative(self, dict, wait_until_done=False):
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)})
+ xyf_motion_dict.update({1: int(x_rel)})
else:
- self.sig_status_message.emit('Relative movement stopped: X Motion limit would be reached!',1000)
+ 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)})
+ xyf_motion_dict.update({2: int(y_rel)})
else:
- self.sig_status_message.emit('Relative movement stopped: Y Motion limit would be reached!',1000)
+ 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})
+ 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)
-
+ 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})
+ self.pidevice.MVR({1: theta_rel})
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']
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)})
+ xyf_motion_dict.update({3: int(f_rel)})
else:
- self.sig_status_message.emit('Relative movement stopped: z Motion limit would be reached!',1000)
+ 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)
@@ -1383,7 +1555,6 @@ def move_relative(self, dict, wait_until_done=False):
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
@@ -1397,42 +1568,42 @@ def move_absolute(self, dict, wait_until_done=False):
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})
+ 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})
-
+ 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})
-
+ 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})
+ 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)
+ 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})
+ self.pidevice.MOV({1: theta_abs})
else:
- self.sig_status_message.emit('Absolute movement stopped: Theta Motion limit would be reached!',1000)
+ 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')
@@ -1443,22 +1614,22 @@ def stop(self):
self.pidevice.STP(noraise=True)
def load_sample(self):
- self.xyf_stage.move_absolute({2:self.cfg.stage_parameters['y_load_position']})
-
+ 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']})
-
+ 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}
+ 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})
-
+ 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)
@@ -1475,9 +1646,12 @@ def execute_program(self):
'''Executes program stored on the Galil controller'''
self.xyf_stage.execute_program()
+
### Up for deletion --> also in mesoSPIM serial
class mesoSPIM_PI_rot_and_Galil_xyzf_Stages(mesoSPIM_Stage):
'''
+ Deprecated?
+
Expects following microscope configuration:
Sample XYZ movement: Galil controller with 3 axes
@@ -1499,10 +1673,10 @@ class mesoSPIM_PI_rot_and_Galil_xyzf_Stages(mesoSPIM_Stage):
'''
- def __init__(self, parent = None):
+ def __init__(self, parent=None):
super().__init__(parent)
- #self.state = mesoSPIM_StateSingleton()
+ # self.state = mesoSPIM_StateSingleton()
self.pos_timer = QtCore.QTimer(self)
self.pos_timer.timeout.connect(self.report_position)
@@ -1518,19 +1692,21 @@ def __init__(self, parent = None):
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])
+ 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(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'))
@@ -1546,11 +1722,10 @@ def __init__(self, parent = None):
self.pi = self.cfg.pi_parameters
self.controllername = self.cfg.pi_parameters['controllername']
- self.pi_stages = self.cfg.pi_parameters['stages']
+ self.pi_stages = list(self.cfg.pi_parameters['stages'])
# ('M-112K033','L-406.40DG10','M-112K033','M-116.DG','M-406.4PD','NOSTAGE')
self.refmode = self.cfg.pi_parameters['refmode']
- # self.serialnum = ('118015439') # Wyss Geneva
- self.serialnum = self.cfg.pi_parameters['serialnum'] # UZH Irchel H45
+ self.serialnum = self.cfg.pi_parameters['serialnum']
self.pidevice = GCSDevice(self.controllername)
self.pidevice.ConnectUSB(serialnum=self.serialnum)
@@ -1561,7 +1736,7 @@ def __init__(self, parent = None):
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'])
@@ -1571,11 +1746,11 @@ def __init__(self, parent = None):
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)
+ # self.pidevice.MOV(5,self.startfocus/1000)
def __del__(self):
try:
@@ -1595,15 +1770,15 @@ def report_position(self):
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()
+ 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
@@ -1613,7 +1788,7 @@ def report_position(self):
self.create_internal_position_dict()
self.sig_position.emit(self.int_position_dict)
- #print(self.int_position_dict)
+ # print(self.int_position_dict)
def move_relative(self, dict, wait_until_done=False):
''' Galil move relative method
@@ -1625,47 +1800,46 @@ def move_relative(self, dict, wait_until_done=False):
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)})
+ xyz_motion_dict.update({1: int(x_rel)})
else:
- self.sig_status_message.emit('Relative movement stopped: X Motion limit would be reached!',1000)
+ 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)})
+ xyz_motion_dict.update({2: int(y_rel)})
else:
- self.sig_status_message.emit('Relative movement stopped: Y Motion limit would be reached!',1000)
+ 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)})
+ xyz_motion_dict.update({3: int(z_rel)})
else:
- self.sig_status_message.emit('Relative movement stopped: z Motion limit would be reached!',1000)
-
+ 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})
+ self.pidevice.MVR({1: theta_rel})
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']
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)})
+ 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)
+ 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
@@ -1679,41 +1853,41 @@ def move_absolute(self, dict, wait_until_done=False):
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})
+ 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})
-
+ 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})
-
+ 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)})
+ 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)
+ 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})
+ self.pidevice.MOV({1: theta_abs})
else:
- self.sig_status_message.emit('Absolute movement stopped: Theta Motion limit would be reached!',1000)
+ 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)
@@ -1724,13 +1898,13 @@ def stop(self):
self.pidevice.STP(noraise=True)
def load_sample(self):
- self.move_absolute({'y_abs':self.cfg.stage_parameters['y_load_position']})
+ 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']})
-
+ 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})
+ 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')
@@ -1751,29 +1925,17 @@ def execute_program(self):
self.f_stage.execute_program()
self.xyz_stage.execute_program()
+
class mesoSPIM_PI_rotzf_and_Galil_xy_Stages(mesoSPIM_Stage):
'''
+ Deprecated?
Expects following microscope configuration:
Sample XY movement: Galil controller with 2 axes
Z-Movement, F-Movement and Rotation: PI C-884 mercury controller
-
- 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):
+ def __init__(self, parent=None):
super().__init__(parent)
self.pos_timer = QtCore.QTimer(self)
@@ -1788,8 +1950,8 @@ def __init__(self, parent = None):
self.y_encodercounts_per_um = self.cfg.xy_galil_parameters['y_encodercounts_per_um']
''' Setting up the Galil stages: XYZ '''
- self.xy_stage = StageControlGalil(self.cfg.xy_galil_parameters['port'],[self.x_encodercounts_per_um,
- self.y_encodercounts_per_um])
+ self.xy_stage = StageControlGalil(self.cfg.xy_galil_parameters['port'], [self.x_encodercounts_per_um,
+ self.y_encodercounts_per_um])
''' PI-specific code '''
from pipython import GCSDevice, pitools
@@ -1800,11 +1962,10 @@ def __init__(self, parent = None):
self.pi = self.cfg.pi_parameters
self.controllername = self.cfg.pi_parameters['controllername']
- self.pi_stages = self.cfg.pi_parameters['stages']
+ self.pi_stages = list(self.cfg.pi_parameters['stages'])
# ('M-112K033','L-406.40DG10','M-112K033','M-116.DG','M-406.4PD','NOSTAGE')
self.refmode = self.cfg.pi_parameters['refmode']
- # self.serialnum = ('118015439') # Wyss Geneva
- self.serialnum = self.cfg.pi_parameters['serialnum'] # UZH Irchel H45
+ self.serialnum = self.cfg.pi_parameters['serialnum']
self.pidevice = GCSDevice(self.controllername)
self.pidevice.ConnectUSB(serialnum=self.serialnum)
@@ -1818,7 +1979,7 @@ def __init__(self, parent = None):
''' Setting PI velocities '''
self.pidevice.VEL(self.cfg.pi_parameters['velocity'])
-
+
print('M-406 Emergency referencing hack: Waiting for referencing move')
logger.info('M-406 Emergency referencing hack: Waiting for referencing move')
self.pidevice.FRF(2)
@@ -1835,7 +1996,7 @@ def __init__(self, parent = None):
''' Stage 3 close to good focus'''
self.startfocus = self.cfg.stage_parameters['startfocus']
- self.pidevice.MOV(3,self.startfocus/1000)
+ self.pidevice.MOV(3, self.startfocus / 1000)
def __del__(self):
try:
@@ -1854,17 +2015,16 @@ def report_position(self):
exceptional circumstances.
'''
try:
- self.x_pos, self.y_pos = self.xy_stage.read_position()
+ self.x_pos, self.y_pos = self.xy_stage.read_position()
except:
logger.info('Error while unpacking Galil stage position values')
-
-
- self.f_pos = round(positions['3']*1000,2)
- self.z_pos = round(positions['2']*1000,2)
+
+ self.f_pos = round(positions['3'] * 1000, 2)
+ self.z_pos = round(positions['2'] * 1000, 2)
self.theta_pos = positions['1']
self.create_position_dict()
-
+
self.int_x_pos = self.x_pos + self.int_x_pos_offset
self.int_y_pos = self.y_pos + self.int_y_pos_offset
self.int_z_pos = self.z_pos + self.int_z_pos_offset
@@ -1874,7 +2034,7 @@ def report_position(self):
self.create_internal_position_dict()
self.sig_position.emit(self.int_position_dict)
- #print(self.int_position_dict)
+ # print(self.int_position_dict)
def move_relative(self, dict, wait_until_done=False):
''' Galil move relative method
@@ -1886,39 +2046,39 @@ def move_relative(self, dict, wait_until_done=False):
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:
- xy_motion_dict.update({1:int(x_rel)})
+ xy_motion_dict.update({1: int(x_rel)})
else:
- self.sig_status_message.emit('Relative movement stopped: X Motion limit would be reached!',1000)
+ 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:
- xy_motion_dict.update({2:int(y_rel)})
+ xy_motion_dict.update({2: int(y_rel)})
else:
- self.sig_status_message.emit('Relative movement stopped: Y Motion limit would be reached!',1000)
+ 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})
+ 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)
-
+ 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})
+ self.pidevice.MVR({1: theta_rel})
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']
if self.f_min < self.f_pos + f_rel and self.f_max > self.f_pos + f_rel:
- f_rel = f_rel/1000
- self.pidevice.MVR({3 : f_rel})
+ f_rel = f_rel / 1000
+ self.pidevice.MVR({3: f_rel})
else:
- self.sig_status_message.emit('Relative movement stopped: f Motion limit would be reached!',1000)
+ self.sig_status_message.emit('Relative movement stopped: f Motion limit would be reached!', 1000)
if xy_motion_dict != {}:
self.xy_stage.move_relative(xy_motion_dict)
@@ -1927,7 +2087,6 @@ def move_relative(self, dict, wait_until_done=False):
self.xy_stage.wait_until_done('XY')
self.pitools.waitontarget(self.pidevice)
-
def move_absolute(self, dict, wait_until_done=False):
'''
Galil move absolute method
@@ -1937,20 +2096,20 @@ def move_absolute(self, dict, wait_until_done=False):
'''
xy_motion_dict = {}
- if 'x_abs' or 'y_abs'in dict:
+ if 'x_abs' or 'y_abs' in dict:
if 'x_abs' in dict:
x_abs = dict['x_abs']
x_abs = x_abs - self.int_x_pos_offset
- xy_motion_dict.update({1:x_abs})
+ xy_motion_dict.update({1: x_abs})
if 'y_abs' in dict:
y_abs = dict['y_abs']
y_abs = y_abs - self.int_y_pos_offset
- xy_motion_dict.update({2:y_abs})
-
+ xy_motion_dict.update({2: y_abs})
+
if xy_motion_dict != {}:
self.xy_stage.move_absolute(xy_motion_dict)
-
+
if wait_until_done == True:
self.xy_stage.wait_until_done('XYZ')
@@ -1959,29 +2118,29 @@ def move_absolute(self, dict, wait_until_done=False):
f_abs = f_abs - self.int_f_pos_offset
if self.f_min < f_abs and self.f_max > f_abs:
''' Conversion to mm and command emission'''
- f_abs= f_abs/1000
- self.pidevice.MOV({3 : f_abs})
+ f_abs = f_abs / 1000
+ self.pidevice.MOV({3: f_abs})
else:
- self.sig_status_message.emit('Absolute movement stopped: F Motion limit would be reached!',1000)
-
+ self.sig_status_message.emit('Absolute movement stopped: F Motion limit would be reached!', 1000)
+
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})
+ 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)
+ 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})
+ self.pidevice.MOV({1: theta_abs})
else:
- self.sig_status_message.emit('Absolute movement stopped: Theta Motion limit would be reached!',1000)
+ self.sig_status_message.emit('Absolute movement stopped: Theta Motion limit would be reached!', 1000)
if wait_until_done == True:
self.xy_stage.wait_until_done('XY')
@@ -1992,22 +2151,22 @@ def stop(self):
self.pidevice.STP(noraise=True)
def load_sample(self):
- self.xy_stage.move_absolute({2:self.cfg.stage_parameters['y_load_position']})
-
+ self.xy_stage.move_absolute({2: self.cfg.stage_parameters['y_load_position']})
+
def unload_sample(self):
- self.xy_stage.move_absolute({2:self.cfg.stage_parameters['y_unload_position']})
-
+ self.xy_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}
+ xy_motion_dict = {1: self.x_rot_position, 2: self.y_rot_position}
self.xy_stage.move_absolute(xy_motion_dict)
- self.pidevice.MOV({2 : self.z_rot_position/1000})
-
+ self.pidevice.MOV({2: self.z_rot_position / 1000})
+
if wait_until_done == True:
self.xy_stage.wait_until_done('XY')
self.pitools.waitontarget(self.pidevice)
-
+
def block_till_controller_is_ready(self):
'''
Blocks further execution (especially during referencing moves)
@@ -2022,4 +2181,4 @@ def block_till_controller_is_ready(self):
def execute_program(self):
'''Executes program stored on the Galil controller'''
- self.xy_stage.execute_program()
\ No newline at end of file
+ self.xy_stage.execute_program()
diff --git a/mesoSPIM/src/utils/acquisition_builder.py b/mesoSPIM/src/utils/acquisition_builder.py
deleted file mode 100644
index 10652cd..0000000
--- a/mesoSPIM/src/utils/acquisition_builder.py
+++ /dev/null
@@ -1,95 +0,0 @@
-''' Classes that define acquisition builders:
-
-Take a dict with information and return an acquisition list
-'''
-from .acquisitions import Acquisition, AcquisitionList
-
-class AcquisitionListBuilder():
- '''
- Generic Acquisition List Builder as parent class?
-
- TODO: Write this class
- '''
- pass
-
-class TilingAcquisitionListBuilder():
- '''
- TODO: Filename generation
-
- self.dict['x_start']
- self.dict['x_end']
- self.dict['y_start']
- self.dict['y_end']
- self.dict['z_start']
- self.dict['z_end']
- self.dict['z_step']
- self.dict['theta_pos']
- self.dict['f_pos']
- self.dict['x_offset'] # Offset always larger than 0
- self.dict['y_offset'] # Offset always larger than 0
- self.dict['x_image_count']
- self.dict['y_image_count']
- '''
-
- def __init__(self, dict):
- self.acq_prelist = []
-
- self.dict = dict
-
- self.x_start = self.dict['x_start']
- self.y_start = self.dict['y_start']
- self.x_end = self.dict['x_end']
- self.y_end = self.dict['y_end']
-
- '''
- Reverse direction of the offset if pos_end < pos_start
- '''
- if self.x_start < self.x_end:
- self.x_offset = self.dict['x_offset']
- else:
- self.x_offset = -self.dict['x_offset']
-
- if self.y_start < self.y_end:
- self.y_offset = self.dict['y_offset']
- else:
- self.y_offset = -self.dict['y_offset']
-
- '''
- Core loop: Create an acquisition list for all x & y values
- '''
- tilecount = 0
- for i in range(0,self.dict['x_image_count']):
- self.x_pos = round(self.x_start + i * self.x_offset,2)
- for j in range(0,self.dict['y_image_count']):
- self.y_pos = round(self.y_start + j * self.y_offset,2)
-
-
- acq = Acquisition( x_pos=self.x_pos,
- y_pos=self.y_pos,
- z_start=self.dict['z_start'],
- z_end=self.dict['z_end'],
- z_step=self.dict['z_step'],
- theta_pos=self.dict['theta_pos'],
- f_start=round(self.dict['f_start'],2),
- f_end=round(self.dict['f_end'],2),
- laser=self.dict['laser'],
- intensity=self.dict['intensity'],
- filter=self.dict['filter'],
- zoom=self.dict['zoom'],
- shutterconfig=self.dict['shutterconfig'],
- folder=self.dict['folder'],
- filename='tiling_file_'+str(tilecount)+'.raw',
- etl_l_offset=self.dict['etl_l_offset'],
- etl_l_amplitude=self.dict['etl_l_amplitude'],
- etl_r_offset=self.dict['etl_r_offset'],
- etl_r_amplitude=self.dict['etl_r_amplitude'],
- )
- ''' Update number of planes as this is not done by the acquisition
- object itself '''
- acq['planes']=acq.get_image_count()
-
- self.acq_prelist.append(acq)
- tilecount += 1
-
- def get_acquisition_list(self):
- return AcquisitionList(self.acq_prelist)
diff --git a/mesoSPIM/src/utils/acquisition_wizards.py b/mesoSPIM/src/utils/acquisition_wizards.py
deleted file mode 100644
index d4c3da4..0000000
--- a/mesoSPIM/src/utils/acquisition_wizards.py
+++ /dev/null
@@ -1,538 +0,0 @@
-'''
-Contains Acquisition Wizard Classes:
-
-Widgets that take user input and create acquisition lists
-
-'''
-import numpy as np
-import pprint
-
-from PyQt5 import QtCore, QtGui, QtWidgets
-from PyQt5.QtCore import pyqtProperty
-
-# from .config import config as cfg
-from .acquisition_builder import TilingAcquisitionListBuilder
-
-from ..mesoSPIM_State import mesoSPIM_StateSingleton
-
-class TilingWizard(QtWidgets.QWizard):
- '''
- Wizard to run
-
- The parent is the Window class of the microscope
- '''
- wizard_done = QtCore.pyqtSignal()
-
- def __init__(self, parent=None):
- super().__init__(parent)
-
- ''' By an instance variable, callbacks to window signals can be handed
- through '''
- self.parent = parent
- self.cfg = parent.cfg
- self.state = mesoSPIM_StateSingleton()
-
- ''' Instance variables '''
- self.x_start = 0
- self.x_end = 0
- self.y_start = 0
- self.y_end = 0
- self.z_start = 0
- self.z_end = 0
- self.z_step = 1
- self.f_start = 0
- self.f_end = 0
- self.x_offset = 0
- self.y_offset = 0
- self.zoom = '1x'
- self.x_fov = 1
- self.y_fov = 1
- self.laser = ''
- self.intensity = 0
- self.filter = ''
- self.shutterconfig = ''
- self.theta_pos = 0
- self.f_pos = 0
- self.x_image_count = 1
- self.y_image_count = 1
- self.folder = ''
- self.delta_x = 0.0
- self.delta_y = 0.0
- self.etl_l_offset = 0.0
- self.etl_l_amplitude = 0.0
- self.etl_r_offset = 0.0
- self.etl_r_amplitude = 0.0
-
- self.acquisition_time = 0
-
- self.setWindowTitle('Tiling Wizard')
-
- self.addPage(TilingWelcomePage(self))
- self.addPage(ZeroingXYStagePage(self))
- self.addPage(DefineXYPositionPage(self))
- # self.addPage(DefineXYStartPositionPage(self))
- # self.addPage(DefineXYEndPositionPage(self))
- self.addPage(DefineZPositionPage(self))
- self.addPage(OtherAcquisitionParametersPage(self))
- self.addPage(DefineFolderPage(self))
- self.addPage(CheckTilingPage(self))
- self.addPage(FinishedTilingPage(self))
-
- self.show()
-
- def done(self, r):
- ''' Reimplementation of the done function
-
- if r == 0: canceled
- if r == 1: finished properly
- '''
- if r == 0:
- print("Wizard was canceled")
- if r == 1:
- print('Wizard was closed properly')
- # self.print_dict()
- self.update_model(self.parent.model, self.acq_list)
- ''' Update state with this new list '''
- # self.parent.update_persistent_editors()
- self.wizard_done.emit()
- else:
- print('Wizard provided return code: ', r)
-
- super().done(r)
-
- def update_model(self, model, table):
- # if self.field('appendToTable'):
- # current_acq_list = self.state['acq_list']
- # new_acq_list = current_acq_list.append(table)
- # model.setTable(new_acq_list)
- # self.state['acq_list']=new_acq_list
- # else:
- model.setTable(table)
- self.state['acq_list']=self.acq_list
-
- def update_image_counts(self):
- '''
- TODO: This needs some FOV information
- '''
- self.delta_x = abs(self.x_end - self.x_start)
- self.delta_y = abs(self.y_end - self.y_start)
-
- ''' Using the ceiling function to always create at least 1 image '''
- self.x_image_count = int(np.ceil(self.delta_x/self.x_offset))
- self.y_image_count = int(np.ceil(self.delta_y/self.y_offset))
-
- ''' The first FOV is centered on the starting location -
- therefore, add another image count to fully contain the end position
- if necessary
- '''
- if self.delta_x % self.x_offset > self.x_offset/2:
- self.x_image_count = self.x_image_count + 1
-
- if self.delta_y % self.y_offset > self.y_offset/2:
- self.y_image_count = self.y_image_count + 1
-
-
- def update_fov(self):
-
-
- pass
- # zoom = self.zoom
- # index = self.parent.cfg.zoom_options.index(zoom)
- # self.x_fov = self.parent.cfg.zoom_options[index]
- # self.y_fov = self.parent.cfg.zoom_options[index]
-
- def get_dict(self):
- return {'x_start' : self.x_start,
- 'x_end' : self.x_end,
- 'y_start' : self.y_start,
- 'y_end' : self.y_end,
- 'z_start' : self.z_start,
- 'z_end' : self.z_end,
- 'z_step' : self.z_step,
- 'theta_pos' : self.theta_pos,
- 'f_start' : self.f_start,
- 'f_end' : self.f_end,
- 'x_offset' : self.x_offset,
- 'y_offset' : self.y_offset,
- 'x_fov' : self.x_fov,
- 'y_fov' : self.y_fov,
- 'x_image_count' : self.x_image_count,
- 'y_image_count' : self.y_image_count,
- 'zoom' : self.zoom,
- 'laser' : self.laser,
- 'intensity' : self.intensity,
- 'filter' : self.filter,
- 'shutterconfig' : self.shutterconfig,
- 'folder' : self.folder,
- 'etl_l_offset' : self.etl_l_offset,
- 'etl_l_amplitude' : self.etl_l_amplitude,
- 'etl_r_offset' : self.etl_r_offset,
- 'etl_r_amplitude' : self.etl_r_amplitude,
- }
-
- def update_acquisition_list(self):
- self.update_image_counts()
- self.update_fov()
-
- ''' If the ETL amplitude is set, update the acq list accordingly'''
- if self.field('ETLCheckBox'):
- self.etl_l_offset = self.state['etl_l_offset']
- self.etl_l_amplitude = self.state['etl_l_amplitude']
- self.etl_r_offset = self.state['etl_r_offset']
- self.etl_r_amplitude = self.state['etl_r_amplitude']
-
- ''' Use the current rotation angle '''
- self.theta_pos = self.state['position']['theta_pos']
-
- dict = self.get_dict()
- self.acq_list = TilingAcquisitionListBuilder(dict).get_acquisition_list()
- self.acquisition_time = self.acq_list.get_acquisition_time()
-
- # pprint.pprint(self.acq_list)
-
- def print_dict(self):
- pprint.pprint(self.get_dict())
-
-
-class TilingWelcomePage(QtWidgets.QWizardPage):
- def __init__(self, parent=None):
- super().__init__(parent)
-
- self.setTitle("Welcome to the tiling wizard")
- self.setSubTitle("This wizard will guide you through the steps of creating a tiling acquisition.")
-
-class ZeroingXYStagePage(QtWidgets.QWizardPage):
- def __init__(self, parent=None):
- super().__init__(parent)
- self.parent = parent
-
- self.setTitle("Zero stage positions")
- self.setSubTitle("To aid in relative positioning, it is recommended to zero the XY stages.")
-
- # self.button = QtWidgets.QPushButton(self)
- # self.button.setText('Zero XY stages')
- # self.button.setCheckable(True)
-
- # self.registerField('stages_zeroed*',
- # self.button,
- # )
-
- # try:
- # '''
- # Pretty dirty approach, reaching up through the hierarchy:
-
- # The first level parent is the QWizard
- # The second level parent is the Window - which can send zeroing signals
- # The third level is the mesoSPIM MainWindow
- # '''
- # self.button.toggled.connect(lambda: self.parent.parent.parent.sig_zero_axes.emit(['x','y']))
- # except:
- # print('Zeroing connection failed')
-
-class DefineXYPositionPage(QtWidgets.QWizardPage):
- def __init__(self, parent=None):
- super().__init__(parent)
- self.parent = parent
-
- self.setTitle("Define the corners of the tiling acquisition")
- self.setSubTitle("Move XY stages to the starting corner position")
-
- self.button0 = QtWidgets.QPushButton(self)
- self.button0.setText('Set XY Start Corner')
- self.button0.setCheckable(True)
- self.button0.toggled.connect(self.get_xy_start_position)
-
- self.button1 = QtWidgets.QPushButton(self)
- self.button1.setText('Set XY End Corner')
- self.button1.setCheckable(True)
- self.button1.toggled.connect(self.get_xy_end_position)
-
- self.registerField('xy_start_position*',
- self.button0,
- )
- self.registerField('xy_end_position*',
- self.button1,
- )
-
- self.layout = QtWidgets.QGridLayout()
- self.layout.addWidget(self.button0, 0, 0)
- self.layout.addWidget(self.button1, 1, 1)
- self.setLayout(self.layout)
-
- def get_xy_start_position(self):
- self.parent.x_start = self.parent.state['position']['x_pos']
- self.parent.y_start = self.parent.state['position']['y_pos']
-
- def get_xy_end_position(self):
- self.parent.x_end = self.parent.state['position']['x_pos']
- self.parent.y_end = self.parent.state['position']['y_pos']
-
-class DefineZPositionPage(QtWidgets.QWizardPage):
- def __init__(self, parent=None):
- super().__init__(parent)
- self.parent = parent
-
- self.setTitle("Define start & end Z position")
- self.setSubTitle("Move Z stages to the start & end position")
-
- self.ZStartButton = QtWidgets.QPushButton(self)
- self.ZStartButton.setText('Set Z start')
- self.ZStartButton.setCheckable(True)
- self.ZStartButton.toggled.connect(self.update_z_start_position)
-
- self.ZEndButton = QtWidgets.QPushButton(self)
- self.ZEndButton.setText('Set Z end')
- self.ZEndButton.setCheckable(True)
- self.ZEndButton.toggled.connect(self.update_z_end_position)
-
- self.ZSpinBoxLabel = QtWidgets.QLabel('Z stepsize')
-
- self.ZStepSpinBox = QtWidgets.QSpinBox(self)
- self.ZStepSpinBox.setValue(1)
- self.ZStepSpinBox.setMinimum(1)
- self.ZStepSpinBox.setMaximum(1000)
- self.ZStepSpinBox.valueChanged.connect(self.update_z_step)
-
- self.StartFocusButton = QtWidgets.QPushButton(self)
- self.StartFocusButton.setText('Set start focus')
- self.StartFocusButton.setCheckable(True)
- self.StartFocusButton.toggled.connect(self.update_start_focus_position)
-
- self.EndFocusButton = QtWidgets.QPushButton(self)
- self.EndFocusButton.setText('Set end focus')
- self.EndFocusButton.setCheckable(True)
- self.EndFocusButton.toggled.connect(self.update_end_focus_position)
-
- self.layout = QtWidgets.QGridLayout()
- self.layout.addWidget(self.ZStartButton, 0, 0)
- self.layout.addWidget(self.ZEndButton, 0, 1)
- self.layout.addWidget(self.ZSpinBoxLabel, 2, 0)
- self.layout.addWidget(self.ZStepSpinBox, 2, 1)
- self.layout.addWidget(self.StartFocusButton, 3, 0)
- self.layout.addWidget(self.EndFocusButton, 4, 0)
- self.setLayout(self.layout)
-
- self.registerField('z_start_position*',
- self.ZStartButton,
- )
-
- self.registerField('z_end_position*',
- self.ZEndButton,
- )
-
- self.registerField('start_focus_position*',
- self.StartFocusButton,
- )
-
- self.registerField('end_focus_position*',
- self.EndFocusButton,
- )
-
- def update_z_start_position(self):
- self.parent.z_start = self.parent.state['position']['z_pos']
-
- def update_z_end_position(self):
- self.parent.z_end = self.parent.state['position']['z_pos']
-
- def update_z_step(self):
- self.parent.z_step = self.ZStepSpinBox.value()
-
- def update_start_focus_position(self):
- self.parent.f_start = self.parent.state['position']['f_pos']
-
- def update_end_focus_position(self):
- self.parent.f_end = self.parent.state['position']['f_pos']
-
-class OtherAcquisitionParametersPage(QtWidgets.QWizardPage):
- '''
-
- TODO: Needs a button: Take current parameters from Live or so
- '''
-
- def __init__(self, parent=None):
- super().__init__(parent)
- self.parent = parent
-
- self.setTitle("Define other parameters")
-
- self.zoomLabel = QtWidgets.QLabel('Zoom')
- self.zoomComboBox = QtWidgets.QComboBox(self)
- self.zoomComboBox.addItems(self.parent.cfg.zoomdict.keys())
-
- self.laserLabel = QtWidgets.QLabel('Laser')
- self.laserComboBox = QtWidgets.QComboBox(self)
- self.laserComboBox.addItems(self.parent.cfg.laserdict.keys())
-
- self.intensityLabel = QtWidgets.QLabel('Intensity')
- self.intensitySlider = QtWidgets.QSlider(QtCore.Qt.Horizontal)
- self.intensitySlider.setMinimum(0)
- self.intensitySlider.setMaximum(100)
-
- self.filterLabel = QtWidgets.QLabel('Filter')
- self.filterComboBox = QtWidgets.QComboBox(self)
- self.filterComboBox.addItems(self.parent.cfg.filterdict.keys())
-
- self.shutterLabel = QtWidgets.QLabel('Shutter')
- self.shutterComboBox = QtWidgets.QComboBox(self)
- self.shutterComboBox.addItems(self.parent.cfg.shutteroptions)
-
- self.xOffsetSpinBoxLabel = QtWidgets.QLabel('X Offset')
- self.xOffsetSpinBox = QtWidgets.QSpinBox(self)
- self.xOffsetSpinBox.setSuffix(' μm')
- self.xOffsetSpinBox.setMinimum(1)
- self.xOffsetSpinBox.setMaximum(20000)
- self.xOffsetSpinBox.setValue(500)
-
- self.yOffsetSpinBoxLabel = QtWidgets.QLabel('Y Offset')
- self.yOffsetSpinBox = QtWidgets.QSpinBox(self)
- self.yOffsetSpinBox.setSuffix(' μm')
- self.yOffsetSpinBox.setMinimum(1)
- self.yOffsetSpinBox.setMaximum(20000)
- self.yOffsetSpinBox.setValue(500)
-
- self.ETLCheckBoxLabel = QtWidgets.QLabel('ETL')
- self.ETLCheckBox = QtWidgets.QCheckBox('Copy current ETL parameters', self)
- self.ETLCheckBox.setChecked(True)
-
- self.layout = QtWidgets.QGridLayout()
- self.layout.addWidget(self.zoomLabel, 0, 0)
- self.layout.addWidget(self.zoomComboBox, 0, 1)
- self.layout.addWidget(self.laserLabel, 1, 0)
- self.layout.addWidget(self.laserComboBox, 1, 1)
- self.layout.addWidget(self.intensityLabel, 2, 0)
- self.layout.addWidget(self.intensitySlider, 2, 1)
- self.layout.addWidget(self.filterLabel, 3, 0)
- self.layout.addWidget(self.filterComboBox, 3, 1)
- self.layout.addWidget(self.shutterLabel, 4, 0)
- self.layout.addWidget(self.shutterComboBox, 4, 1)
- self.layout.addWidget(self.xOffsetSpinBoxLabel, 5, 0)
- self.layout.addWidget(self.xOffsetSpinBox, 5, 1)
- self.layout.addWidget(self.yOffsetSpinBoxLabel, 6, 0)
- self.layout.addWidget(self.yOffsetSpinBox, 6, 1)
- self.layout.addWidget(self.ETLCheckBoxLabel, 7, 0)
- self.layout.addWidget(self.ETLCheckBox, 7, 1)
-
- self.registerField('ETLCheckBox', self.ETLCheckBox)
-
- self.setLayout(self.layout)
-
- self.update_page_from_state()
-
- def validatePage(self):
- ''' The done function should update all the parent parameters '''
- self.update_other_acquisition_parameters()
- return True
-
- def update_other_acquisition_parameters(self):
- ''' Here, all the Tiling parameters are filled in the parent (TilingWizard)
-
- This method should be called when the "Next" Button is pressed
- '''
- self.parent.zoom = self.zoomComboBox.currentText()
- # self.parent.x_fov = self.parent.cfg.fov_options[self.zoomComboBox.currentIndex()]
- # self.parent.y_fov = self.parent.cfg.fov_options[self.zoomComboBox.currentIndex()]
- self.parent.x_offset = self.xOffsetSpinBox.value()
- self.parent.y_offset = self.yOffsetSpinBox.value()
- self.parent.laser = self.laserComboBox.currentText()
- self.parent.intensity = self.intensitySlider.value()
- self.parent.filter = self.filterComboBox.currentText()
- self.parent.shutterconfig = self.shutterComboBox.currentText()
-
- def initializePage(self):
- self.update_page_from_state()
-
- def update_page_from_state(self):
- self.zoomComboBox.setCurrentText(self.parent.state['zoom'])
- self.laserComboBox.setCurrentText(self.parent.state['laser'])
- self.intensitySlider.setValue(self.parent.state['intensity'])
- self.filterComboBox.setCurrentText(self.parent.state['filter'])
- self.shutterComboBox.setCurrentText(self.parent.state['shutterconfig'])
-
-class DefineFolderPage(QtWidgets.QWizardPage):
- def __init__(self, parent=None):
- super().__init__(parent)
- self.parent = parent
-
- self.setTitle("Select folder")
- self.setSubTitle("Please select the folder in which the data should be saved.")
-
- self.Button = QtWidgets.QPushButton('Select Folder')
- self.Button.setCheckable(True)
- self.Button.setChecked(False)
- self.Button.toggled.connect(self.choose_folder)
-
- self.TextEdit = QtWidgets.QLineEdit(self)
-
- self.layout = QtWidgets.QGridLayout()
- self.layout.addWidget(self.Button, 0, 0)
- self.layout.addWidget(self.TextEdit, 1, 0)
- self.setLayout(self.layout)
-
- def choose_folder(self):
- ''' File dialog for choosing the save folder '''
-
- path = QtWidgets.QFileDialog.getExistingDirectory(self.parent, 'Select Folder')
- if path:
- self.parent.folder = path
- self.TextEdit.setText(path)
-
-class CheckTilingPage(QtWidgets.QWizardPage):
- def __init__(self, parent=None):
- super().__init__(parent)
- self.parent = parent
-
- self.setTitle("Check Tiling Page")
- self.setSubTitle("Here are your parameters")
-
- # self.timeLabel = QtWidgets.QLabel('Acquisition Time:')
- # self.acqTime = QtWidgets.QLineEdit(self)
- # self.acqTime.setReadOnly(True)
-
- self.xFOVLabel = QtWidgets.QLabel('X FOVs:')
- self.xFOVs = QtWidgets.QLineEdit(self)
- self.xFOVs.setReadOnly(True)
-
- self.yFOVLabel = QtWidgets.QLabel('Y FOVs:')
- self.yFOVs = QtWidgets.QLineEdit(self)
- self.yFOVs.setReadOnly(True)
-
- self.Button = QtWidgets.QPushButton('Values are ok?')
- self.Button.setCheckable(True)
- self.Button.setChecked(False)
-
- self.layout = QtWidgets.QGridLayout()
- # self.layout.addWidget(self.timeLabel, 0, 0)
- # self.layout.addWidget(self.acqTime, 0, 1)
- self.layout.addWidget(self.xFOVLabel, 1, 0)
- self.layout.addWidget(self.xFOVs, 1, 1)
- self.layout.addWidget(self.yFOVLabel, 2, 0)
- self.layout.addWidget(self.yFOVs, 2, 1)
- self.layout.addWidget(self.Button, 3, 1)
- self.setLayout(self.layout)
-
- self.registerField('finalCheck*',self.Button)
-
- def initializePage(self):
- ''' Here, the acquisition list is created for further checking'''
- self.parent.update_acquisition_list()
- self.xFOVs.setText(str(self.parent.x_image_count))
- self.yFOVs.setText(str(self.parent.y_image_count))
-
-class FinishedTilingPage(QtWidgets.QWizardPage):
- def __init__(self, parent=None):
- super().__init__(parent)
- self.parent = parent
-
- self.setTitle("Finished!")
- self.setSubTitle("Attention: This will overwrite the Acquisition Table. Click 'Finished' to continue. To rename the files, use the filename wizard.")
-
- def validatePage(self):
- print('Update parent table')
- return True
-
-
-if __name__ == '__main__':
- import sys
- app = QtWidgets.QApplication(sys.argv)
- wizard = MyWizard()
- sys.exit(app.exec_())
diff --git a/mesoSPIM/src/utils/acquisitions.py b/mesoSPIM/src/utils/acquisitions.py
index fbca722..d5b0ff6 100644
--- a/mesoSPIM/src/utils/acquisitions.py
+++ b/mesoSPIM/src/utils/acquisitions.py
@@ -249,7 +249,7 @@ def get_capitalized_keylist(self):
def get_keylist(self):
'''
- Here, a list of capitalized keys is returnes for usage as a table header
+ Here, a list of capitalized keys is returned for usage as a table header
'''
return self[0].get_keylist()
@@ -258,7 +258,6 @@ def get_acquisition_time(self, framerate):
Returns total time in seconds of a list of acquisitions
'''
time = 0
-
for i in range(len(self)):
time += self[i].get_acquisition_time(framerate)
@@ -324,23 +323,19 @@ def check_for_existing_filenames(self):
def check_for_duplicated_filenames(self):
''' Returns a list of duplicated filenames '''
- duplicates = []
filenames = []
-
- ''' Create a list of full file paths'''
+ # Create a list of full file paths
for i in range(len(self)):
- filename = self[i]['folder']+'/'+self[i]['filename']
- filenames.append(filename)
-
+ if self[i]['filename'][-3:] != '.h5':
+ filename = self[i]['folder']+'/'+self[i]['filename']
+ filenames.append(filename)
duplicates = self.get_duplicates_in_list(filenames)
return duplicates
def check_for_nonexisting_folders(self):
''' Returns a list of nonexisting folders '''
-
nonexisting_folders = []
-
for i in range(len(self)):
folder = self[i]['folder']
if not os.path.isdir(folder):
@@ -348,11 +343,88 @@ def check_for_nonexisting_folders(self):
return nonexisting_folders
- def get_duplicates_in_list(self, list):
+ def get_duplicates_in_list(self, in_list):
duplicates = []
- unique = set(list)
+ unique = set(in_list)
for each in unique:
- count = list.count(each)
+ count = in_list.count(each)
if count > 1:
duplicates.append(each)
return duplicates
+
+ def get_n_shutter_configs(self):
+ """Get the number of unique shutter configs (1 or 2)"""
+ sconfig_list = [a['shutterconfig'] for a in self]
+ sconfig_set = set(sconfig_list)
+ return len(sconfig_set)
+
+ def get_n_angles(self):
+ """Get the number of unique angles"""
+ angle_list = [a['rot'] for a in self]
+ angle_set = set(angle_list)
+ return len(angle_set)
+
+ def get_n_lasers(self):
+ """Get the number of unique laser lines"""
+ laser_list = [a['laser'] for a in self]
+ laser_set = set(laser_list)
+ return len(laser_set)
+
+ def get_n_tiles(self):
+ """Get the number of tiles as unique (x,y,z_start,rot) combinations"""
+ tile_list = []
+ for a in self:
+ tile_str = f"{a['x_pos']}{a['y_pos']}{a['z_start']}{a['rot']}"
+ if not tile_str in tile_list:
+ tile_list.append(tile_str)
+ return len(tile_list)
+
+ def get_tile_index(self, acq):
+ """Get the the tile index for given acquisition"""
+ acq_str = f"{acq['x_pos']}{acq['y_pos']}{acq['z_start']}{acq['rot']}"
+ tile_list = []
+ for a in self:
+ tile_str = f"{a['x_pos']}{a['y_pos']}{a['z_start']}{a['rot']}"
+ if not tile_str in tile_list:
+ tile_list.append(tile_str)
+ return tile_list.index(acq_str)
+
+ def get_unique_attr_list(self, key: str = 'laser') -> list:
+ """Return ordered list of acquisition attributes.
+
+ Parameters:
+ -----------
+ key: str
+ One of ('laser', 'shutterconfig', 'rot')
+
+ Returns:
+ --------
+ List of strings, e.g. ('488', '561') for key='laser', in the order of acquisition.
+ """
+ attributes = ('laser', 'shutterconfig', 'rot')
+ assert key in attributes, f'Key {key} must be one of {attributes}.'
+ unique_list = []
+ for acq in self:
+ if acq[key] not in unique_list:
+ unique_list.append(acq[key])
+ return unique_list
+
+ def find_value_index(self, value: str = '488 nm', key: str = 'laser'):
+ """Find the attribute index in the acquisition list.
+ Example:
+ al = AcquisitionList([Acquisition(), Acquisition(), Acquisition(), Acquisition()])
+ al[0]['laser'] = '488 nm' #
+ al[1]['laser'] = '488 nm' # gets removed because non-unique
+ al[2]['laser'] = '561 nm' #
+ al[3]['laser'] = '637 nm' #
+ Output:
+ al.find_value_index('488 nm', 'laser') # -> 0
+ al.find_value_index('561 nm', 'laser') # -> 1
+ al.find_value_index('637 nm', 'laser') # -> 2
+ """
+ unique_list = self.get_unique_attr_list(key)
+ assert value in unique_list, f"Value({value}) not found in list {unique_list}"
+ return unique_list.index(value)
+
+
+
diff --git a/mesoSPIM/src/utils/bigdataviewer_xml_creator.py b/mesoSPIM/src/utils/bigdataviewer_xml_creator.py
deleted file mode 100644
index f94bd54..0000000
--- a/mesoSPIM/src/utils/bigdataviewer_xml_creator.py
+++ /dev/null
@@ -1,401 +0,0 @@
-'''
-Classes to create XMLs for Bigstitcher out of mesoSPIM-Datasets.
-'''
-import os.path
-
-from ..mesoSPIM_State import mesoSPIM_StateSingleton
-
-from lxml import etree
-
-class mesoSPIM_XMLexporter:
- '''
- Class to take a mesoSPIM acquisitionlist object and turn it into a Bigdataviewer/
- Bigstitcher XML file.
-
- TODO:
- * a single stack goes into a single view setup
- * everything has to be converted to string...
- * have the correct image loader: tif
- * 399 494 1256
- * if the coordinates match and the channels (laser or filter) are different: same tile, but different channels
- * angle can be taken directly from acqlist
- * every acq is a viewSetup
- * acqs with matching X,Y,and Z_start, Z_end and angle positions positions are the same tile
- * create a tile list?
- * assign channels by order of appearance
- * different lasers are definitely different channels
- * same lasers: check if filters are different --> if yes then channels
- * case:
- * angles not supported
- '''
-
- def __init__(self, parent=None):
- self.parent = parent
- self.state = mesoSPIM_StateSingleton()
- self.cfg = parent.cfg
-
- self.xmlwriter = mesoSPIM_BDVXMLwriter()
-
- self.xy_pixelsize = 1
- self.z_size = 1
- self.length_unit = 'micron'
-
- def generate_xml_from_acqlist(self, acqlist, path):
- channeldict = self.generate_channeldict(acqlist)
- tiledict = self.generate_tiledict(acqlist)
- illuminationdict = self.generate_illuminationdict(acqlist)
-
- num_channels = len(channeldict)
- num_tiles = len(tiledict)
- num_illuminations = len(illuminationdict)
-
- if num_channels > 1:
- layout_channels = 1
- else:
- layout_channels = 0
-
- if num_tiles > 1:
- layout_tiles = 1
- else:
- layout_tiles = 0
-
- if num_illuminations > 1:
- layout_illuminations = 1
- else:
- layout_illuminations = 0
-
- channellist = [c for c in range(num_channels)]
- illuminationlist = [i for i in range(num_illuminations)]
- tilelist = [t for t in range(num_tiles)]
- anglelist = [0]
-
- self.xmlwriter.setLayout(filepattern='tiling_file_t{x}_c{c}.raw.tif',
- timepoints=0,
- channels=layout_channels,
- illuminations=layout_illuminations,
- angles = 0,
- tiles = layout_tiles,
- imglibcontainer="ArrayImgFactory") #or CellImgFactory
-
- id = 0
- for acq in acqlist:
- channelstring = self.generate_channelstring(acq)
- illuminationstring = self.generate_illuminationstring(acq)
- tilestring = self.generate_tilestring(acq)
- calibrationstring = self.create_calibration_string(acq)
-
- self.xmlwriter.addviewsetup(id=str(id),
- name=str(acq['filename']),
- size=self.create_size_string(acq),
- vosize_unit = 'micron',
- vosize = self.create_voxelsize_string(acq),
- illumination = illuminationdict[illuminationstring],
- channel = channeldict[channelstring],
- tile = tiledict[tilestring],
- angle = self.create_angle_string(acq))
-
- self.xmlwriter.addCalibrationRegistration(tp='0', view=str(id), calibrationstring=calibrationstring)
- id += 1
-
- self.xmlwriter.addAttributes(illuminations=illuminationlist,
- channels=channellist,
- tiles=tilelist,
- angles=anglelist)
-
- self.xmlwriter.addTimepoints('')
-
- self.xmlwriter.write(path)
-
- def generate_channeldict(self, acqlist):
- '''
- Takes the acqlist and returns a dictionary of channels
- Channels are defined as:
- * different lasers in different acqs are definitely different channels
- * same lasers: check if filters are different --> if yes then channels
- '''
- channeldict = {}
- c = 0
-
- for acq in acqlist:
- channelstring = self.generate_channelstring(acq)
- if not channelstring in channeldict:
- channeldict.update({channelstring:str(c)})
- c+=1
-
- return channeldict
-
-
- def generate_tiledict(self, acqlist):
- '''
- Takes the acqlist and returns an assignment of
-
- Idea: Take an ACQ and create a hash/hashable datatype (e.g. string)
- out of:
- * X_pos
- * Y_pos
- * Z_start
- * Z_end
- * Angle
-
- Use this tilehash as keys for a dictionary
- Later on, you can use it to assign tiles
- '''
- tiledict = {}
- t=0
-
- for acq in acqlist:
- tilestring = self.generate_tilestring(acq)
- if not tilestring in tiledict:
- tiledict.update({tilestring:str(t)})
- t+=1
-
- return tiledict
-
- def generate_illuminationdict(self, acqlist):
- illuminationdict = {}
- i = 0
-
- for acq in acqlist:
- illuminationstring = self.generate_illuminationstring(acq)
- if not illuminationstring in illuminationdict:
- illuminationdict.update({illuminationstring: str(i)})
- i+=1
-
- return illuminationdict
-
- def generate_channelstring(self, acq):
- return str(acq['laser']) + ' ' + str(acq['filter'])
-
- def generate_tilestring(self, acq):
- return str(acq['x_pos'])+' '+str(acq['y_pos'])+' '+str(acq['z_start'])+' '+str(acq['rot'])
-
- def generate_illuminationstring(self, acq):
- return str(acq['shutterconfig'])
-
- def write(self, path):
- self.xmlwriter.write(path)
-
- def create_size_string(self, acq):
- ''' Creates the necessary XYZ #pixels string'''
-
- binning_string = self.cfg.camera_parameters['binning']
- x_binning = int(binning_string[0])
- y_binning = int(binning_string[2])
-
- y_pixels = int(self.cfg.camera_parameters['y_pixels'] / y_binning)
- x_pixels = int(self.cfg.camera_parameters['x_pixels'] / x_binning)
-
- z_pixels = acq['planes']
-
- ''' X and Y flipped due to image rotation '''
- return str(y_pixels) + ' ' + str(x_pixels) + ' ' + str(z_pixels)
-
- def update_pixelsizes(self, acq):
- self.xy_pixelsize = self.convert_zoom_to_pixelsize(acq['zoom'])
- self.z_pixelsize = acq['z_step']
-
- def create_voxelsize_string(self, acq):
- ''' Assumes square pixels'''
- self.update_pixelsizes(acq)
- return str(self.xy_pixelsize) + ' ' + str(self.xy_pixelsize) + ' ' + str(self.z_pixelsize)
-
- def convert_zoom_to_pixelsize(self, zoom):
- ''' Don't forget the binning!'''
- return self.cfg.pixelsize[zoom]
-
- def create_angle_string(self, acq):
- return str(int(acq['rot']))
-
- def create_calibration_string(self, acq):
- '''
- XY pixelsize: 15
- Z:pixelsize: 8
- 15/8 = 1.875
-
- Result:
- 1.875 0.0 0.0 0.0 0.0 1.875 0.0 0.0 0.0 0.0 1.0 0.0
-
- if
- XY pixelsize: 8
- Z:pixelsize: 15
- 15/8 = 1.875
-
- Result:
- 1.0 0.0 0.0 0.0 0.0 1.0 0.0 0.0 0.0 0.0 1.875 0.0
- '''
- self.update_pixelsizes(acq)
-
- if self.xy_pixelsize > self.z_pixelsize:
- factor = self.xy_pixelsize/self.z_pixelsize
- calibration_string = str(factor) + ' 0.0 0.0 0.0 0.0 ' + str(factor) + ' 0.0 0.0 0.0 0.0 1.0 0.0'
- elif self.xy_pixelsize < self.z_pixelsize:
- factor = self.z_pixelsize/self.xy_pixelsize
- calibration_string = '1.0 0.0 0.0 0.0 0.0 1.0 0.0 0.0 0.0 0.0 '+ str(factor) +' 0.0'
- else:
- calibration_string = '1.0 0.0 0.0 0.0 0.0 1.0 0.0 0.0 0.0 0.0 1.0 0.0'
-
- return calibration_string
-
-class mesoSPIM_BDVXMLwriter:
- '''
- mesoSPIM bigdataviewer-XML-writer
-
- Based on the code posted by https://github.com/Xqua here:
- https://github.com/bigdataviewer/bigdataviewer-core/issues/5
- '''
-
- def __init__(self):
-
- self.xml = etree.Element('SpimData', version="0.2")
- self.doc = etree.ElementTree(self.xml)
-
- self.BasePath = etree.SubElement(self.xml, 'BasePath', type="relative")
- self.BasePath.text = "."
-
- self.SequenceDescription = etree.SubElement(self.xml, 'SequenceDescription')
- self.ImageLoader = etree.SubElement(self.SequenceDescription, 'ImageLoader', format="spimreconstruction.stack.ij")
-
- self.ImageDirectory = etree.SubElement(self.ImageLoader, 'imagedirectory', type="relative")
-
-
-
- self.ViewSetups = etree.SubElement(self.SequenceDescription, 'ViewSetups')
- self.ViewRegistrations = etree.SubElement(self.xml, 'ViewRegistrations')
-
- etree.SubElement(self.xml, "ViewInterestPoints")
- etree.SubElement(self.xml, "BoundingBoxes")
- etree.SubElement(self.xml, "PointSpreadFunctions")
- etree.SubElement(self.xml, "StitchingResults")
- etree.SubElement(self.xml, "IntensityAdjustments")
-
- def write(self, path):
- out = str(etree.tostring(self.xml, pretty_print=True, encoding=str))
- # out = str(etree.tostring(self.xml, pretty_print=True, encoding='UTF-8'))
-
-
- with open(path, 'w') as file:
- file.write(out)
-
- def addFile(self, path):
- image = etree.SubElement(self.ImageLoader, 'hdf5', type="relative")
- image.text = path
-
- def addviewsetup(self, id, name, size, vosize_unit, vosize, illumination, channel, tile, angle):
- V = etree.SubElement(self.ViewSetups, 'ViewSetup')
-
- Id = etree.SubElement(V, 'id')
- Id.text = str(id)
- Name = etree.SubElement(V, 'name')
- Name.text = str(name)
- Size = etree.SubElement(V, 'size')
- Size.text = str(size)
-
- VoxelSize = etree.SubElement(V, 'voxelSize')
- Unit = etree.SubElement(VoxelSize, 'unit')
- Unit.text = str(vosize_unit)
- Size = etree.SubElement(VoxelSize, 'size')
- Size.text = str(vosize)
-
- Attributes = etree.SubElement(V, 'attributes')
- Ilum = etree.SubElement(Attributes, 'illumination')
- Ilum.text = str(illumination)
- Chan = etree.SubElement(Attributes, 'channel')
- Chan.text = str(channel)
- Tile = etree.SubElement(Attributes, 'tile')
- Tile.text = str(tile)
- Ang = etree.SubElement(Attributes, 'angle')
- Ang.text = str(angle)
-
- def setLayout(self, filepattern="tiling_file_{x}_c{c}.raw.tif", timepoints=0, channels=0, illuminations=0, angles=0, tiles=1,imglibcontainer="ArrayImgFactory"):
- '''
- Layout entries according to:
- https://scijava.org/javadoc.scijava.org/Fiji/spim/fiji/spimdata/imgloaders/LegacyStackImgLoader.html
-
- layoutTP - - 0 == one, 1 == one per file, 2 == all in one file
- layoutChannels - - 0 == one, 1 == one per file, 2 == all in one file
- layoutIllum - - 0 == one, 1 == one per file, 2 == all in one file
- layoutAngles - - 0 == one, 1 == one per file, 2 == all in one file
- '''
- self.FilePattern = etree.SubElement(self.ImageLoader,'filePattern')
- self.FilePattern.text = filepattern
- self.LayoutTimepoints = etree.SubElement(self.ImageLoader, 'layoutTimepoints')
- self.LayoutTimepoints.text = str(timepoints)
- self.LayoutChannels = etree.SubElement(self.ImageLoader, 'layoutChannels')
- self.LayoutChannels.text = str(channels)
- self.LayoutIlluminations = etree.SubElement(self.ImageLoader, 'layoutIlluminations')
- self.LayoutIlluminations.text = str(illuminations)
- self.LayoutAngles = etree.SubElement(self.ImageLoader, 'layoutAngles')
- self.LayoutAngles.text = str(angles)
- self.LayoutTiles = etree.SubElement(self.ImageLoader, 'layoutTiles')
- self.LayoutTiles.text = str(tiles)
-
- self.Imglib2container = etree.SubElement(self.ImageLoader, 'imglib2container')
- self.Imglib2container.text = imglibcontainer
-
- def setViewSize(self, Id, size):
- trigger = False
- for child in self.ViewSetups:
- for el in child:
- if el.tag == 'id':
- if el.text == Id:
- trigger = True
- if el.tag == 'size' and trigger:
- el.text = ' '.join(size)
- trigger = False
- return True
- return False
-
- def addAttributes(self, illuminations, channels, tiles, angles):
- illum = etree.SubElement(self.ViewSetups, 'Attributes', name="illumination")
- chan = etree.SubElement(self.ViewSetups, 'Attributes', name="channel")
- til = etree.SubElement(self.ViewSetups, 'Attributes', name="tile")
- ang = etree.SubElement(self.ViewSetups, 'Attributes', name="angle")
-
- for illumination in illuminations:
- I = etree.SubElement(illum, 'Illumination')
- Id = etree.SubElement(I, 'id')
- Id.text = str(illumination)
- Name = etree.SubElement(I, 'name')
- Name.text = str(illumination)
-
- for channel in channels:
- I = etree.SubElement(chan, 'Channel')
- Id = etree.SubElement(I, 'id')
- Id.text = str(channel)
- Name = etree.SubElement(I, 'name')
- Name.text = str(channel)
-
- for tile in tiles:
- I = etree.SubElement(til, 'Tile')
- Id = etree.SubElement(I, 'id')
- Id.text = str(tile)
- Name = etree.SubElement(I, 'name')
- Name.text = str(tile)
-
- for angle in angles:
- I = etree.SubElement(ang, 'Angle')
- Id = etree.SubElement(I, 'id')
- Id.text = str(angle)
- Name = etree.SubElement(I, 'name')
- Name.text = str(angle)
-
- def addTimepoints(self, timepoints):
- TP = etree.SubElement(self.SequenceDescription, 'Timepoints', type="pattern")
- I = etree.SubElement(TP, 'integerpattern')
- I.text = ', '.join(timepoints)
-
- def addRegistration(self, tp, view):
- V = etree.SubElement(self.ViewRegistrations, 'ViewRegistration', timepoint=tp, setup=view)
- VT = etree.SubElement(V, 'ViewTransform', type="affine")
- name = etree.SubElement(VT, 'Name')
- name.text = "calibration"
- affine = etree.SubElement(VT, 'affine')
- affine.text = '1.0 0.0 0.0 0.0 0.0 1.0 0.0 0.0 0.0 0.0 1.0 0.0'
-
- def addCalibrationRegistration(self, tp, view, calibrationstring):
- V = etree.SubElement(self.ViewRegistrations, 'ViewRegistration', timepoint=str(tp), setup=str(view))
- VT = etree.SubElement(V, 'ViewTransform', type="affine")
- name = etree.SubElement(VT, 'Name')
- name.text = "calibration"
- affine = etree.SubElement(VT, 'affine')
- affine.text = calibrationstring
\ No newline at end of file
diff --git a/mesoSPIM/src/utils/delegates.py b/mesoSPIM/src/utils/delegates.py
index 3e2daa7..2096740 100644
--- a/mesoSPIM/src/utils/delegates.py
+++ b/mesoSPIM/src/utils/delegates.py
@@ -158,9 +158,10 @@ def __init__(self, parent):
super().__init__(parent)
def createEditor(self, parent, option, index):
- spinbox = QtWidgets.QSpinBox(parent)
- spinbox.setMinimum(1)
+ spinbox = QtWidgets.QDoubleSpinBox(parent)
+ spinbox.setMinimum(0.1)
spinbox.setMaximum(1000)
+ spinbox.setDecimals(1)
spinbox.valueChanged.connect(lambda: self.commitData.emit(self.sender()))
spinbox.setAutoFillBackground(True)
return spinbox
diff --git a/mesoSPIM/src/utils/demo_threads.py b/mesoSPIM/src/utils/demo_threads.py
deleted file mode 100644
index 51d803d..0000000
--- a/mesoSPIM/src/utils/demo_threads.py
+++ /dev/null
@@ -1,17 +0,0 @@
-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
deleted file mode 100644
index 4156bc8..0000000
--- a/mesoSPIM/src/utils/demo_threads1.py
+++ /dev/null
@@ -1,18 +0,0 @@
-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/filename_wizard.py b/mesoSPIM/src/utils/filename_wizard.py
index 0e65c5e..a59f980 100644
--- a/mesoSPIM/src/utils/filename_wizard.py
+++ b/mesoSPIM/src/utils/filename_wizard.py
@@ -1,6 +1,5 @@
'''
-Contains Filename Wizard Class: autogenerates Filenames
-
+Contains Nonlinear Filename Wizard Class: autogenerates Filenames
'''
from PyQt5 import QtWidgets, QtGui, QtCore
@@ -8,19 +7,21 @@
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtCore import pyqtProperty
-# from .config import config as cfg
-# from .acquisition_builder import TilingAcquisitionListBuilder
-
from ..mesoSPIM_State import mesoSPIM_StateSingleton
+import logging
+logger = logging.getLogger(__name__)
+
class FilenameWizard(QtWidgets.QWizard):
'''
Wizard to run
-
The parent is the Window class of the microscope
'''
wizard_done = QtCore.pyqtSignal()
+ num_of_pages = 4
+ (welcome, raw, single_hdf5, finished) = range(num_of_pages)
+
def __init__(self, parent=None):
super().__init__(parent)
@@ -28,12 +29,12 @@ def __init__(self, parent=None):
through '''
self.parent = parent
self.state = mesoSPIM_StateSingleton()
-
+ self.file_format = None
self.setWindowTitle('Filename Wizard')
-
- self.addPage(FilenameWizardWelcomePage(self))
- self.addPage(FilenameWizardCheckResultsPage(self))
-
+ self.setPage(0, FilenameWizardWelcomePage(self))
+ self.setPage(1, FilenameWizardRawSelectionPage(self))
+ self.setPage(2, FilenameWizardSingleHDF5SelectionPage(self))
+ self.setPage(3, FilenameWizardCheckResultsPage(self))
self.show()
def done(self, r):
@@ -43,16 +44,12 @@ def done(self, r):
if r == 1: finished properly
'''
if r == 0:
- print("Wizard was canceled")
+ logger.info('Filename Wizard was canceled')
if r == 1:
- print('Wizard was closed properly')
- # print('Laser selected: ', self.field('Laser'))
- # print('Filter selected: ', self.field('Filter'))
- # print('Zoom selected: ', self.field('Zoom'))
- # print('Shutter selected: ', self.field('Shutterconfig'))
+ logger.info('Filename Wizard was closed properly')
self.update_filenames_in_model()
else:
- print('Wizard provided return code: ', r)
+ logger.info('Filename Wizard provided return code: ', r)
super().done(r)
@@ -62,72 +59,74 @@ def replace_spaces_with_underscores(self, string):
def replace_dots_with_underscores(self, string):
return string.replace('.','_')
- def generate_filename_list(self):
+ def generate_filename_list(self, increment_number=True):
'''
Go through the model, entry for entry and populate the filenames
'''
row_count = self.parent.model.rowCount()
- filename_column = self.parent.model.getFilenameColumn()
-
- print('Row count: ', row_count)
- print('Filename column: ', filename_column)
-
num_string = '000000'
- if self.field('StartNumber'):
- start_number = self.field('StartNumberValue')
- else:
- start_number = 0
-
+ start_number = 0
start_number_string = str(start_number)
-
self.filename_list = []
-
for row in range(0, row_count):
filename = ''
- if self.field('Description'):
- descriptionstring = self.field('Description')
+ if self.field('DescriptionRaw'):
+ descriptionstring = self.field('DescriptionRaw')
filename += self.replace_spaces_with_underscores(descriptionstring)
filename += '_'
- if self.field('xyPosition'):
- '''Round to nearest integer '''
- x_position_string = str(int(round(self.parent.model.getXPosition(row))))
- y_position_string = str(int(round(self.parent.model.getYPosition(row))))
+ if self.field('DescriptionHDF5'):
+ descriptionstring = self.field('DescriptionHDF5')
+ filename += self.replace_spaces_with_underscores(descriptionstring)
+ filename += '_'
- filename += 'X' + x_position_string + '_' + 'Y' + y_position_string + '_'
+ if self.file_format == 'raw':
+ if self.field('xyPosition'):
+ '''Round to nearest integer '''
+ x_position_string = str(int(round(self.parent.model.getXPosition(row))))
+ y_position_string = str(int(round(self.parent.model.getYPosition(row))))
- if self.field('rotationPosition'):
- rot_position_string = str(int(round(self.parent.model.getRotationPosition(row))))
- filename += 'rot_' + rot_position_string + '_'
+ filename += 'X' + x_position_string + '_' + 'Y' + y_position_string + '_'
- if self.field('Laser'):
- laserstring = self.parent.model.getLaser(row)
- filename += self.replace_spaces_with_underscores(laserstring)
- filename += '_'
-
- if self.field('Filter'):
- filterstring = self.parent.model.getFilter(row)
- filename += self.replace_spaces_with_underscores(filterstring)
- filename += '_'
-
- if self.field('Zoom'):
- zoomstring = self.parent.model.getZoom(row)
- filename += self.replace_dots_with_underscores(zoomstring)
- filename += '_'
+ if self.field('rotationPosition'):
+ rot_position_string = str(int(round(self.parent.model.getRotationPosition(row))))
+ filename += 'rot_' + rot_position_string + '_'
- if self.field('Shutterconfig'):
- shutterstring = self.parent.model.getShutterconfig(row)
- filename += shutterstring
- filename += '_'
+ if self.field('Laser'):
+ laserstring = self.parent.model.getLaser(row)
+ filename += self.replace_spaces_with_underscores(laserstring)
+ filename += '_'
- file_suffix = num_string[:-len(start_number_string)]+start_number_string + '.raw'
+ if self.field('Filter'):
+ filterstring = self.parent.model.getFilter(row)
+ filename += self.replace_spaces_with_underscores(filterstring)
+ filename += '_'
+
+ if self.field('Zoom'):
+ zoomstring = self.parent.model.getZoom(row)
+ filename += self.replace_dots_with_underscores(zoomstring)
+ filename += '_'
+
+ if self.field('Shutterconfig'):
+ shutterstring = self.parent.model.getShutterconfig(row)
+ filename += shutterstring
+ filename += '_'
+
+ file_suffix = num_string[:-len(start_number_string)] + start_number_string + '.' + self.file_format
+
+ if increment_number:
+ start_number += 1
+ start_number_string = str(start_number)
+
+ elif self.file_format == 'h5':
+ file_suffix = 'bdv.' + self.file_format
+
+ else:
+ raise ValueError(f"file suffix invalid: {self.file_format}")
- start_number += 1
- start_number_string = str(start_number)
-
filename += file_suffix
-
+
self.filename_list.append(filename)
def update_filenames_in_model(self):
@@ -135,7 +134,7 @@ def update_filenames_in_model(self):
filename_column = self.parent.model.getFilenameColumn()
for row in range(0, row_count):
- filename = self.filename_list[row]
+ filename = self.filename_list[row]
index = self.parent.model.createIndex(row, filename_column)
self.parent.model.setData(index, filename)
@@ -145,9 +144,41 @@ def __init__(self, parent=None):
self.parent = parent
self.setTitle("Autogenerate filenames")
+ self.setSubTitle("How would you like to save your data?")
+
+ self.raw_string = 'Individual Raw Files: ~.raw'
+ self.single_hdf5_string = 'Single HDF5-File: ~.h5'
+
+ self.SaveAsComboBoxLabel = QtWidgets.QLabel('Save as:')
+ self.SaveAsComboBox = QtWidgets.QComboBox()
+ self.SaveAsComboBox.addItems([self.raw_string, self.single_hdf5_string])
+ self.SaveAsComboBox.setCurrentIndex(0)
+
+ self.registerField('SaveAs', self.SaveAsComboBox, 'currentIndex')
+
+ self.layout = QtWidgets.QGridLayout()
+ self.layout.addWidget(self.SaveAsComboBoxLabel, 0, 0)
+ self.layout.addWidget(self.SaveAsComboBox, 0, 1)
+ self.setLayout(self.layout)
+
+ def nextId(self):
+ if self.SaveAsComboBox.currentText() == self.raw_string: # is .raw
+ self.parent.file_format = 'raw'
+ return self.parent.raw
+ elif self.SaveAsComboBox.currentText() == self.single_hdf5_string: # is .h5
+ self.parent.file_format = 'h5'
+ return self.parent.single_hdf5
+
+class FilenameWizardRawSelectionPage(QtWidgets.QWizardPage):
+ def __init__(self, parent=None):
+ super().__init__(parent)
+ self.parent = parent
+
+ self.setTitle("Autogenerate raw filenames")
self.setSubTitle("Which properties would you like to use?")
- self.DescriptionCheckBox = QtWidgets.QCheckBox('Description: ',self)
+ self.DescriptionCheckBox = QtWidgets.QCheckBox('Description: ', self)
+ self.DescriptionCheckBox.setChecked(True)
self.DescriptionLineEdit = QtWidgets.QLineEdit(self)
self.DescriptionCheckBox.toggled.connect(lambda boolean: self.DescriptionLineEdit.setEnabled(boolean))
@@ -156,31 +187,22 @@ def __init__(self, parent=None):
self.RotationPositionCheckBox = QtWidgets.QCheckBox('Rotation angle')
self.LaserCheckBox = QtWidgets.QCheckBox('Laser', self)
+ self.LaserCheckBox.setChecked(True)
self.FilterCheckBox = QtWidgets.QCheckBox('Filter', self)
+ # self.FilterCheckBox.setChecked(True)
self.ZoomCheckBox = QtWidgets.QCheckBox('Zoom', self)
+ self.ZoomCheckBox.setChecked(True)
self.ShutterCheckBox = QtWidgets.QCheckBox('Shutterconfig', self)
- self.StartNumberCheckBox = QtWidgets.QCheckBox('Start Number: ', self)
-
- self.StartNumberSpinBox = QtWidgets.QSpinBox(self)
- self.StartNumberSpinBox.setEnabled(False)
- self.StartNumberSpinBox.setValue(0)
- self.StartNumberSpinBox.setSingleStep(1)
- self.StartNumberSpinBox.setMinimum(0)
- self.StartNumberSpinBox.setMaximum(999999)
+ self.ShutterCheckBox.setChecked(True)
- self.StartNumberCheckBox.toggled.connect(lambda boolean: self.StartNumberSpinBox.setEnabled(boolean))
-
- self.registerField('Description', self.DescriptionLineEdit)
+ self.registerField('DescriptionRaw', self.DescriptionLineEdit)
self.registerField('xyPosition', self.xyPositionCheckBox)
self.registerField('rotationPosition', self.RotationPositionCheckBox)
- self.registerField('Laser',self.LaserCheckBox)
+ self.registerField('Laser', self.LaserCheckBox)
self.registerField('Filter', self.FilterCheckBox)
self.registerField('Zoom', self.ZoomCheckBox)
self.registerField('Shutterconfig', self.ShutterCheckBox)
- self.registerField('StartNumber', self.StartNumberCheckBox)
- self.registerField('StartNumberValue', self.StartNumberSpinBox)
-
-
+
self.layout = QtWidgets.QGridLayout()
self.layout.addWidget(self.DescriptionCheckBox, 0, 0)
self.layout.addWidget(self.DescriptionLineEdit, 0, 1)
@@ -190,14 +212,43 @@ def __init__(self, parent=None):
self.layout.addWidget(self.FilterCheckBox, 4, 0)
self.layout.addWidget(self.ZoomCheckBox, 5, 0)
self.layout.addWidget(self.ShutterCheckBox, 6, 0)
- self.layout.addWidget(self.StartNumberCheckBox, 7, 0)
- self.layout.addWidget(self.StartNumberSpinBox, 7, 1)
self.setLayout(self.layout)
def validatePage(self):
self.parent.generate_filename_list()
return super().validatePage()
+ def nextId(self):
+ return self.parent.finished
+
+
+class FilenameWizardSingleHDF5SelectionPage(QtWidgets.QWizardPage):
+ def __init__(self, parent=None):
+ super().__init__(parent)
+ self.parent = parent
+
+ self.setTitle("Autogenerate hdf5 filename")
+ self.setSubTitle("This replaces all filenames with a single hdf5 file. \n Which properties would you like to use?")
+
+ self.DescriptionCheckBox = QtWidgets.QCheckBox('Description: ', self)
+ self.DescriptionLineEdit = QtWidgets.QLineEdit(self)
+ self.DescriptionCheckBox.toggled.connect(lambda boolean: self.DescriptionLineEdit.setEnabled(boolean))
+
+ self.layout = QtWidgets.QGridLayout()
+ self.layout.addWidget(self.DescriptionCheckBox, 0, 0)
+ self.layout.addWidget(self.DescriptionLineEdit, 0, 1)
+ self.setLayout(self.layout)
+
+ self.registerField('DescriptionHDF5', self.DescriptionLineEdit)
+
+ def validatePage(self):
+ self.parent.generate_filename_list(increment_number=False)
+ return super().validatePage()
+
+ def nextId(self):
+ return self.parent.finished
+
+
class FilenameWizardCheckResultsPage(QtWidgets.QWizardPage):
def __init__(self, parent=None):
super().__init__(parent)
@@ -217,10 +268,18 @@ def __init__(self, parent=None):
self.setLayout(self.layout)
def initializePage(self):
- for i in self.parent.filename_list:
- self.mystring += str(i)
+ if self.parent.file_format == 'raw':
+ file_list = self.parent.filename_list
+ elif self.parent.file_format == 'h5':
+ file_list = [self.parent.filename_list[0]]
+ else:
+ raise ValueError(f"file_format must be in ('raw', 'h5'), received {self.parent.file_format}")
+
+ for f in file_list:
+ self.mystring += f
self.mystring += '\n'
self.TextEdit.setPlainText(self.mystring)
def cleanupPage(self):
self.mystring = ''
+
diff --git a/mesoSPIM/src/utils/multicolor_acquisition_builder.py b/mesoSPIM/src/utils/multicolor_acquisition_builder.py
index e79a721..73dcad9 100644
--- a/mesoSPIM/src/utils/multicolor_acquisition_builder.py
+++ b/mesoSPIM/src/utils/multicolor_acquisition_builder.py
@@ -58,45 +58,46 @@ def __init__(self, dict):
Core loop: Create an acquisition list for all x & y & channel values
'''
tilecount = 0
-
- for i in range(0,self.dict['x_image_count']):
- self.x_pos = round(self.x_start + i * self.x_offset,2)
-
- for j in range(0,self.dict['y_image_count']):
- self.y_pos = round(self.y_start + j * self.y_offset,2)
-
+ for i in range(0, self.dict['x_image_count']):
+ self.x_pos = round(self.x_start + i * self.x_offset, 2)
+ for j in range(0, self.dict['y_image_count']):
+ self.y_pos = round(self.y_start + j * self.y_offset, 2)
channelcount = 0
for c in range(0, len(self.dict['channels'])):
''' Get a single channeldict out of the list of dicts '''
channeldict = self.dict['channels'][c]
-
- acq = Acquisition( x_pos=self.x_pos,
- y_pos=self.y_pos,
- z_start=self.dict['z_start'],
- z_end=self.dict['z_end'],
- z_step=self.dict['z_step'],
- theta_pos=self.dict['theta_pos'],
- f_start=round(channeldict['f_start'],2),
- f_end=round(channeldict['f_end'],2),
- laser=channeldict['laser'],
- intensity=channeldict['intensity'],
- filter=channeldict['filter'],
- zoom=self.dict['zoom'],
- shutterconfig=self.dict['shutterconfig'],
- folder=self.dict['folder'],
- filename='tiling_file_t'+str(tilecount)+'_c'+str(channelcount)+'.raw',
- etl_l_offset=channeldict['etl_l_offset'],
- etl_l_amplitude=channeldict['etl_l_amplitude'],
- etl_r_offset=channeldict['etl_r_offset'],
- etl_r_amplitude=channeldict['etl_r_amplitude'],
- )
- ''' Update number of planes as this is not done by the acquisition
- object itself '''
- acq['planes']=acq.get_image_count()
-
- self.acq_prelist.append(acq)
-
- channelcount +=1
+ if self.dict['shutter_seq']:
+ n_shutter_configs = 2
+ shutter_states = ["Left", "Right"]
+ else:
+ n_shutter_configs = 1
+ shutter_states = [self.dict['shutterconfig']]
+ for i_shutter in range(n_shutter_configs):
+ acq = Acquisition( x_pos=self.x_pos,
+ y_pos=self.y_pos,
+ z_start=self.dict['z_start'],
+ z_end=self.dict['z_end'],
+ z_step=self.dict['z_step'],
+ theta_pos=self.dict['theta_pos'],
+ f_start=round(channeldict['f_start'],2),
+ f_end=round(channeldict['f_end'],2),
+ laser=channeldict['laser'],
+ intensity=channeldict['intensity'],
+ filter=channeldict['filter'],
+ zoom=self.dict['zoom'],
+ shutterconfig=shutter_states[i_shutter],
+ folder=self.dict['folder'],
+ filename='tiling_file_t'+str(tilecount)+'_c'+str(channelcount)+'.raw',
+ etl_l_offset=channeldict['etl_l_offset'],
+ etl_l_amplitude=channeldict['etl_l_amplitude'],
+ etl_r_offset=channeldict['etl_r_offset'],
+ etl_r_amplitude=channeldict['etl_r_amplitude'],
+ )
+ ''' Update number of planes as this is not done by the acquisition
+ object itself '''
+ acq['planes'] = acq.get_image_count()
+ self.acq_prelist.append(acq)
+ channelcount += 1
tilecount += 1
diff --git a/mesoSPIM/src/utils/multicolor_acquisition_wizard.py b/mesoSPIM/src/utils/multicolor_acquisition_wizard.py
index 3089077..f54b41c 100644
--- a/mesoSPIM/src/utils/multicolor_acquisition_wizard.py
+++ b/mesoSPIM/src/utils/multicolor_acquisition_wizard.py
@@ -6,6 +6,7 @@
'''
import numpy as np
import pprint
+from functools import partial
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtCore import pyqtProperty
@@ -32,7 +33,7 @@ def __init__(self, parent=None):
''' By an instance variable, callbacks to window signals can be handed
through '''
self.parent = parent
- self.cfg = parent.cfg
+ self.cfg = parent.cfg if parent else None
self.state = mesoSPIM_StateSingleton()
''' Instance variables '''
@@ -42,10 +43,12 @@ def __init__(self, parent=None):
self.y_end = 0
self.z_start = 0
self.z_end = 0
- self.z_step = 1
+ self.z_step = 10
self.x_offset = 0
self.y_offset = 0
self.zoom = '1x'
+ self.x_pixels = self.cfg.camera_parameters['x_pixels'] if self.cfg else 2048
+ self.y_pixels = self.cfg.camera_parameters['y_pixels'] if self.cfg else 2048
self.x_fov = 1
self.y_fov = 1
self.channels = []
@@ -57,6 +60,7 @@ def __init__(self, parent=None):
self.folder = ''
self.delta_x = 0.0
self.delta_y = 0.0
+ self.shutter_seq = False
self.setWindowTitle('Tiling Wizard')
@@ -70,7 +74,7 @@ def __init__(self, parent=None):
self.setPage(7, ThirdChannelPage(self))
self.setPage(8, DefineFolderPage(self))
self.setPage(9, FinishedTilingPage(self))
-
+ self.setWizardStyle(QtWidgets.QWizard.ModernStyle)
self.show()
def done(self, r):
@@ -85,7 +89,8 @@ def done(self, r):
print('Wizard was closed properly')
# self.print_dict()
self.update_acquisition_list()
- self.update_model(self.parent.model, self.acq_list)
+ if self.parent:
+ self.update_model(self.parent.model, self.acq_list)
''' Update state with this new list '''
# self.parent.update_persistent_editors()
self.wizard_done.emit()
@@ -106,32 +111,8 @@ def update_image_counts(self):
self.delta_y = abs(self.y_end - self.y_start)
''' Using the ceiling function to always create at least 1 image '''
- self.x_image_count = int(np.ceil(self.delta_x/self.x_offset))
- self.y_image_count = int(np.ceil(self.delta_y/self.y_offset))
-
- ''' Create at least 1 image even if delta_x or delta_y is 0 '''
- if self.x_image_count == 0:
- self.x_image_count = 1
- if self.y_image_count == 0:
- self.y_image_count = 1
-
- ''' The first FOV is centered on the starting location -
- therefore, add another image count to fully contain the end position
- if necessary
- '''
- if self.delta_x % self.x_offset > self.x_offset/2:
- self.x_image_count = self.x_image_count + 1
-
- if self.delta_y % self.y_offset > self.y_offset/2:
- self.y_image_count = self.y_image_count + 1
-
-
- def update_fov(self):
- pass
- # zoom = self.zoom
- # index = self.parent.cfg.zoom_options.index(zoom)
- # self.x_fov = self.parent.cfg.zoom_options[index]
- # self.y_fov = self.parent.cfg.zoom_options[index]
+ self.x_image_count = int(np.ceil(self.delta_x / self.x_offset)) + 1
+ self.y_image_count = int(np.ceil(self.delta_y / self.y_offset)) + 1
def get_dict(self):
return {'x_start' : self.x_start,
@@ -150,14 +131,14 @@ def get_dict(self):
'y_image_count' : self.y_image_count,
'zoom' : self.zoom,
'shutterconfig' : self.shutterconfig,
+ 'shutter_seq': self.shutter_seq,
'folder' : self.folder,
'channels' : self.channels,
}
def update_acquisition_list(self):
self.update_image_counts()
- self.update_fov()
-
+
''' Use the current rotation angle '''
self.theta_pos = self.state['position']['theta_pos']
@@ -189,65 +170,108 @@ def __init__(self, parent=None):
self.parent = parent
self.setTitle("Define the bounding box of the tiling acquisition")
- self.setSubTitle("Move XY stages to the starting corner position")
-
- self.button0 = QtWidgets.QPushButton(self)
- self.button0.setText('Set XY Start Corner')
- self.button0.setCheckable(True)
- self.button0.toggled.connect(self.get_xy_start_position)
-
- self.button1 = QtWidgets.QPushButton(self)
- self.button1.setText('Set XY End Corner')
- self.button1.setCheckable(True)
- self.button1.toggled.connect(self.get_xy_end_position)
+ self.setSubTitle("Define bounding box by corners OR edges. "
+ "Move XY stages to the positions before pressing the bounding box buttons.")
+
+ self.button_xy_start = QtWidgets.QPushButton(self)
+ self.button_xy_start.setText('Set XY Start Corner')
+ self.button_xy_start.setCheckable(True)
+ self.button_xy_start.clicked.connect(partial(self.get_edge_position, key='xy-start'))
+
+ self.button_x_start = QtWidgets.QPushButton(self)
+ self.button_x_start.setText('Set X start')
+ self.button_x_start.setCheckable(True)
+ self.button_x_start.clicked.connect(partial(self.get_edge_position, key='x-start'))
+
+ self.button_x_end = QtWidgets.QPushButton(self)
+ self.button_x_end.setText('Set X end')
+ self.button_x_end.setCheckable(True)
+ self.button_x_end.clicked.connect(partial(self.get_edge_position, key='x-end'))
+
+ self.button_y_start = QtWidgets.QPushButton(self)
+ self.button_y_start.setText('Set Y start')
+ self.button_y_start.setCheckable(True)
+ self.button_y_start.clicked.connect(partial(self.get_edge_position, key='y-start'))
+
+ self.button_y_end = QtWidgets.QPushButton(self)
+ self.button_y_end.setText('Set Y end')
+ self.button_y_end.setCheckable(True)
+ self.button_y_end.clicked.connect(partial(self.get_edge_position, key='y-end'))
+
+ self.button_xy_end = QtWidgets.QPushButton(self)
+ self.button_xy_end.setText('Set XY End Corner')
+ self.button_xy_end.setCheckable(True)
+ self.button_xy_end.clicked.connect(partial(self.get_edge_position, key='xy-end'))
self.ZStartButton = QtWidgets.QPushButton(self)
self.ZStartButton.setText('Set Z start')
self.ZStartButton.setCheckable(True)
- self.ZStartButton.toggled.connect(self.update_z_start_position)
+ self.ZStartButton.clicked.connect(partial(self.get_edge_position, key='z-start'))
self.ZEndButton = QtWidgets.QPushButton(self)
self.ZEndButton.setText('Set Z end')
self.ZEndButton.setCheckable(True)
- self.ZEndButton.toggled.connect(self.update_z_end_position)
+ self.ZEndButton.clicked.connect(partial(self.get_edge_position, key='z-end'))
self.ZSpinBoxLabel = QtWidgets.QLabel('Z stepsize')
-
- self.ZStepSpinBox = QtWidgets.QSpinBox(self)
- self.ZStepSpinBox.setValue(1)
- self.ZStepSpinBox.setMinimum(1)
+ self.ZStepSpinBox = QtWidgets.QDoubleSpinBox(self)
+ self.ZStepSpinBox.setValue(10)
+ self.ZStepSpinBox.setDecimals(1)
+ self.ZStepSpinBox.setMinimum(0.1)
self.ZStepSpinBox.setMaximum(1000)
self.ZStepSpinBox.valueChanged.connect(self.update_z_step)
- self.registerField('xy_start_position*',
- self.button0,
- )
- self.registerField('xy_end_position*',
- self.button1,
- )
+ self.registerField('xy_start_position*', self.button_xy_start)
+ self.registerField('xy_end_position*', self.button_xy_end)
+ self.registerField('z_end_position*', self.ZEndButton)
+ self.update_z_step()
self.layout = QtWidgets.QGridLayout()
- self.layout.addWidget(self.button0, 0, 0)
- self.layout.addWidget(self.button1, 1, 1)
- self.layout.addWidget(self.ZStartButton, 2, 0)
- self.layout.addWidget(self.ZEndButton, 2, 1)
- self.layout.addWidget(self.ZSpinBoxLabel, 3, 0)
- self.layout.addWidget(self.ZStepSpinBox, 3, 1)
+ self.layout.addWidget(self.button_xy_start, 0, 0)
+ self.layout.addWidget(self.button_y_start, 0, 1)
+ self.layout.addWidget(self.button_x_start, 1, 0)
+ self.layout.addWidget(self.button_x_end, 1, 2)
+ self.layout.addWidget(self.button_y_end, 2, 1)
+ self.layout.addWidget(self.button_xy_end, 2, 2)
+ self.layout.addWidget(self.ZStartButton, 3, 0)
+ self.layout.addWidget(self.ZEndButton, 3, 2)
+ self.layout.addWidget(self.ZSpinBoxLabel, 4, 0)
+ self.layout.addWidget(self.ZStepSpinBox, 4, 2)
self.setLayout(self.layout)
- def get_xy_start_position(self):
- self.parent.x_start = self.parent.state['position']['x_pos']
- self.parent.y_start = self.parent.state['position']['y_pos']
-
- def get_xy_end_position(self):
- self.parent.x_end = self.parent.state['position']['x_pos']
- self.parent.y_end = self.parent.state['position']['y_pos']
-
- def update_z_start_position(self):
- self.parent.z_start = self.parent.state['position']['z_pos']
-
- def update_z_end_position(self):
- self.parent.z_end = self.parent.state['position']['z_pos']
+ def get_edge_position(self, key):
+ valid_keys = ('x-start', 'x-end', 'y-start', 'y-end', 'z-start', 'z-end', 'xy-start', 'xy-end')
+ assert key in valid_keys, f"Position key {key} is invalid"
+ if key == 'x-start':
+ self.parent.x_start = self.parent.state['position']['x_pos']
+ if self.button_y_start.isChecked():
+ self.button_xy_start.setChecked(True)
+ elif key == 'x-end':
+ self.parent.x_end = self.parent.state['position']['x_pos']
+ if self.button_y_end.isChecked():
+ self.button_xy_end.setChecked(True)
+ elif key == 'y-start':
+ self.parent.y_start = self.parent.state['position']['y_pos']
+ if self.button_x_start.isChecked():
+ self.button_xy_start.setChecked(True)
+ elif key == 'y-end':
+ self.parent.y_end = self.parent.state['position']['y_pos']
+ if self.button_x_end.isChecked():
+ self.button_xy_end.setChecked(True)
+ elif key == 'z-start':
+ self.parent.z_start = self.parent.state['position']['z_pos']
+ elif key == 'z-end':
+ self.parent.z_end = self.parent.state['position']['z_pos']
+ elif key == 'xy-start':
+ self.parent.x_start = self.parent.state['position']['x_pos']
+ self.parent.y_start = self.parent.state['position']['y_pos']
+ self.button_x_start.setChecked(True)
+ self.button_y_start.setChecked(True)
+ elif key == 'xy-end':
+ self.parent.x_end = self.parent.state['position']['x_pos']
+ self.parent.y_end = self.parent.state['position']['y_pos']
+ self.button_x_end.setChecked(True)
+ self.button_y_end.setChecked(True)
def update_z_step(self):
self.parent.z_step = self.ZStepSpinBox.value()
@@ -260,9 +284,41 @@ def __init__(self, parent):
self.setTitle("Define other parameters")
+ self.channelLabel = QtWidgets.QLabel('# Channels')
+ self.channelSpinBox = QtWidgets.QSpinBox(self)
+ self.channelSpinBox.setMinimum(1)
+ self.channelSpinBox.setMaximum(3)
+
self.zoomLabel = QtWidgets.QLabel('Zoom')
self.zoomComboBox = QtWidgets.QComboBox(self)
- self.zoomComboBox.addItems(self.parent.cfg.zoomdict.keys())
+ if self.parent.cfg:
+ self.zoomComboBox.addItems(self.parent.cfg.zoomdict.keys())
+ self.zoomComboBox.currentIndexChanged.connect(self.update_fov_size)
+
+ self.shutterLabel = QtWidgets.QLabel('Shutter')
+ self.shutterComboBox = QtWidgets.QComboBox(self)
+ if self.parent.cfg:
+ self.shutterComboBox.addItems(self.parent.cfg.shutteroptions)
+
+ self.shutterSequenceLabel = QtWidgets.QLabel('Left, then Right?')
+ self.shutterSeqCheckBox = QtWidgets.QCheckBox(self)
+ self.shutterSeqCheckBox.setChecked(False)
+ self.shutterSeqCheckBox.clicked.connect(self.update_shutt_seq)
+
+ self.fovSizeLabel = QtWidgets.QLabel('FOV Size X ⨉ Y:')
+ self.fovSizeLineEdit = QtWidgets.QLineEdit(self)
+ self.fovSizeLineEdit.setReadOnly(True)
+
+ self.overlapPercentageCheckBox = QtWidgets.QCheckBox('Overlap %', self)
+ self.overlapLabel = QtWidgets.QLabel('Overlap in %')
+ self.overlapPercentageSpinBox = QtWidgets.QSpinBox(self)
+ self.overlapPercentageSpinBox.setSuffix(' %')
+ self.overlapPercentageSpinBox.setMinimum(1)
+ self.overlapPercentageSpinBox.setMaximum(50)
+ self.overlapPercentageSpinBox.setValue(10)
+ self.overlapPercentageSpinBox.valueChanged.connect(self.update_x_and_y_offset)
+
+ self.manualOverlapCheckBox = QtWidgets.QCheckBox('Set Offset Manually', self)
self.xOffsetSpinBoxLabel = QtWidgets.QLabel('X Offset')
self.xOffsetSpinBox = QtWidgets.QSpinBox(self)
@@ -270,7 +326,7 @@ def __init__(self, parent):
self.xOffsetSpinBox.setMinimum(1)
self.xOffsetSpinBox.setMaximum(30000)
self.xOffsetSpinBox.setValue(500)
-
+
self.yOffsetSpinBoxLabel = QtWidgets.QLabel('Y Offset')
self.yOffsetSpinBox = QtWidgets.QSpinBox(self)
self.yOffsetSpinBox.setSuffix(' μm')
@@ -278,26 +334,36 @@ def __init__(self, parent):
self.yOffsetSpinBox.setMaximum(30000)
self.yOffsetSpinBox.setValue(500)
- self.shutterLabel = QtWidgets.QLabel('Shutter')
- self.shutterComboBox = QtWidgets.QComboBox(self)
- self.shutterComboBox.addItems(self.parent.cfg.shutteroptions)
+ self.overlapPercentageCheckBox.clicked.connect(lambda boolean: self.overlapPercentageSpinBox.setEnabled(boolean))
+ self.overlapPercentageCheckBox.clicked.connect(self.update_x_and_y_offset)
+ self.overlapPercentageCheckBox.clicked.connect(lambda boolean: self.xOffsetSpinBox.setEnabled(not boolean))
+ self.overlapPercentageCheckBox.clicked.connect(lambda boolean: self.yOffsetSpinBox.setEnabled(not boolean))
+ self.overlapPercentageCheckBox.clicked.connect(lambda boolean: self.manualOverlapCheckBox.setChecked(not boolean))
- self.channelLabel = QtWidgets.QLabel('# Channels')
- self.channelSpinBox = QtWidgets.QSpinBox(self)
- self.channelSpinBox.setMinimum(1)
- self.channelSpinBox.setMaximum(3)
+ self.manualOverlapCheckBox.clicked.connect(lambda boolean: self.overlapPercentageSpinBox.setEnabled(not boolean))
+ self.manualOverlapCheckBox.clicked.connect(lambda boolean: self.xOffsetSpinBox.setEnabled(boolean))
+ self.manualOverlapCheckBox.clicked.connect(lambda boolean: self.yOffsetSpinBox.setEnabled(boolean))
+ self.manualOverlapCheckBox.clicked.connect(lambda boolean: self.overlapPercentageCheckBox.setChecked(not boolean))
self.layout = QtWidgets.QGridLayout()
- self.layout.addWidget(self.zoomLabel, 0, 0)
- self.layout.addWidget(self.zoomComboBox, 0, 1)
- self.layout.addWidget(self.shutterLabel, 1, 0)
- self.layout.addWidget(self.shutterComboBox, 1, 1)
- self.layout.addWidget(self.xOffsetSpinBoxLabel, 2, 0)
- self.layout.addWidget(self.xOffsetSpinBox, 2, 1)
- self.layout.addWidget(self.yOffsetSpinBoxLabel, 3, 0)
- self.layout.addWidget(self.yOffsetSpinBox, 3, 1)
- self.layout.addWidget(self.channelLabel, 4, 0)
- self.layout.addWidget(self.channelSpinBox, 4, 1)
+ self.layout.addWidget(self.channelLabel, 0, 0)
+ self.layout.addWidget(self.channelSpinBox, 0, 1)
+ self.layout.addWidget(self.zoomLabel, 1, 0)
+ self.layout.addWidget(self.zoomComboBox, 1, 1)
+ self.layout.addWidget(self.shutterLabel, 2, 0)
+ self.layout.addWidget(self.shutterComboBox, 2, 1)
+ self.layout.addWidget(self.shutterSequenceLabel, 2, 2)
+ self.layout.addWidget(self.shutterSeqCheckBox, 2, 3)
+ self.layout.addWidget(self.fovSizeLabel, 3, 0)
+ self.layout.addWidget(self.fovSizeLineEdit, 3, 1)
+ self.layout.addWidget(self.overlapPercentageCheckBox, 4, 0)
+ self.layout.addWidget(self.overlapLabel, 5, 0)
+ self.layout.addWidget(self.overlapPercentageSpinBox, 5, 1)
+ self.layout.addWidget(self.manualOverlapCheckBox, 6, 0)
+ self.layout.addWidget(self.xOffsetSpinBoxLabel, 7, 0)
+ self.layout.addWidget(self.xOffsetSpinBox, 7, 1)
+ self.layout.addWidget(self.yOffsetSpinBoxLabel, 8, 0)
+ self.layout.addWidget(self.yOffsetSpinBox, 8, 1)
self.setLayout(self.layout)
def validatePage(self):
@@ -305,9 +371,41 @@ def validatePage(self):
self.update_other_acquisition_parameters()
return True
+ @QtCore.pyqtSlot()
+ def update_fov_size(self):
+ ''' Should be invoked whenever the zoom selection is changed '''
+ new_zoom = self.zoomComboBox.currentText()
+ pixelsize_in_um = self.parent.cfg.pixelsize[new_zoom] if self.parent.cfg else 6.5
+ ''' X and Y are interchanged here to account for the camera rotation by 90°'''
+ new_x_fov_in_um = int(self.parent.y_pixels * pixelsize_in_um)
+ new_y_fov_in_um = int(self.parent.x_pixels * pixelsize_in_um)
+ self.parent.x_fov = new_x_fov_in_um
+ self.parent.y_fov = new_y_fov_in_um
+
+ self.fovSizeLineEdit.setText(str(new_x_fov_in_um)+' ⨉ '+str(new_y_fov_in_um) + ' μm²')
+
+ ''' If the zoom changes, the offset calculation should be redone'''
+ if self.overlapPercentageCheckBox.isChecked():
+ self.update_x_and_y_offset()
+
+ @QtCore.pyqtSlot()
+ def update_x_and_y_offset(self):
+ new_offset_percentage = self.overlapPercentageSpinBox.value()
+ x_offset = int(self.parent.x_fov * (1-new_offset_percentage / 100))
+ y_offset = int(self.parent.y_fov * (1-new_offset_percentage / 100))
+ self.xOffsetSpinBox.setValue(x_offset)
+ self.yOffsetSpinBox.setValue(y_offset)
+
+ @QtCore.pyqtSlot()
+ def update_shutt_seq(self):
+ self.parent.shutter_seq = self.shutterSeqCheckBox.checkState()
+ if self.shutterSeqCheckBox.checkState():
+ self.shutterComboBox.setEnabled(False)
+ else:
+ self.shutterComboBox.setEnabled(True)
+
def update_other_acquisition_parameters(self):
''' Here, all the Tiling parameters are filled in the parent (TilingWizard)
-
This method should be called when the "Next" Button is pressed
'''
self.parent.zoom = self.zoomComboBox.currentText()
@@ -318,7 +416,12 @@ def update_other_acquisition_parameters(self):
def initializePage(self):
self.update_page_from_state()
-
+ self.update_fov_size()
+ self.update_x_and_y_offset()
+ self.overlapPercentageCheckBox.setChecked(True)
+ self.xOffsetSpinBox.setEnabled(False)
+ self.yOffsetSpinBox.setEnabled(False)
+
def update_page_from_state(self):
self.zoomComboBox.setCurrentText(self.parent.state['zoom'])
self.shutterComboBox.setCurrentText(self.parent.state['shutterconfig'])
@@ -339,25 +442,49 @@ def __init__(self, parent=None):
self.yFOVs = QtWidgets.QLineEdit(self)
self.yFOVs.setReadOnly(True)
+ self.x_start_end_label = QtWidgets.QLabel('X start, end:')
+ self.x_start = QtWidgets.QLineEdit(self)
+ self.x_start.setReadOnly(True)
+ self.x_end = QtWidgets.QLineEdit(self)
+ self.x_end.setReadOnly(True)
+
+ self.y_start_end_label = QtWidgets.QLabel('Y start, end:')
+ self.y_start = QtWidgets.QLineEdit(self)
+ self.y_start.setReadOnly(True)
+ self.y_end = QtWidgets.QLineEdit(self)
+ self.y_end.setReadOnly(True)
+
self.Button = QtWidgets.QPushButton('Values are ok?')
self.Button.setCheckable(True)
self.Button.setChecked(False)
self.layout = QtWidgets.QGridLayout()
- self.layout.addWidget(self.xFOVLabel, 1, 0)
- self.layout.addWidget(self.xFOVs, 1, 1)
+ self.layout.addWidget(self.xFOVLabel, 0, 0)
+ self.layout.addWidget(self.xFOVs, 0, 1)
+ self.layout.addWidget(self.x_start_end_label, 1, 0)
+ self.layout.addWidget(self.x_start, 1, 1)
+ self.layout.addWidget(self.x_end, 1, 2)
+
self.layout.addWidget(self.yFOVLabel, 2, 0)
self.layout.addWidget(self.yFOVs, 2, 1)
- self.layout.addWidget(self.Button, 3, 1)
- self.setLayout(self.layout)
+ self.layout.addWidget(self.y_start_end_label, 3, 0)
+ self.layout.addWidget(self.y_start, 3, 1)
+ self.layout.addWidget(self.y_end, 3, 2)
- self.registerField('finalCheck*',self.Button)
+ self.layout.addWidget(self.Button, 4, 0)
+ self.setLayout(self.layout)
+ self.registerField('finalCheck*', self.Button)
def initializePage(self):
''' Here, the acquisition list is created for further checking'''
self.parent.update_image_counts()
self.xFOVs.setText(str(self.parent.x_image_count))
self.yFOVs.setText(str(self.parent.y_image_count))
+ self.x_start.setText(str(round(self.parent.x_start)))
+ self.y_start.setText(str(round(self.parent.y_start)))
+ self.x_end.setText(str(round(self.parent.x_end)))
+ self.y_end.setText(str(round(self.parent.y_end)))
+
class GenericChannelPage(QtWidgets.QWizardPage):
def __init__(self, parent=None, channel_id=0):
@@ -379,7 +506,8 @@ def __init__(self, parent=None, channel_id=0):
self.laserLabel = QtWidgets.QLabel('Laser')
self.laserComboBox = QtWidgets.QComboBox(self)
- self.laserComboBox.addItems(self.parent.cfg.laserdict.keys())
+ if self.parent.cfg:
+ self.laserComboBox.addItems(self.parent.cfg.laserdict.keys())
self.intensityLabel = QtWidgets.QLabel('Intensity')
self.intensitySlider = QtWidgets.QSlider(QtCore.Qt.Horizontal)
@@ -388,7 +516,8 @@ def __init__(self, parent=None, channel_id=0):
self.filterLabel = QtWidgets.QLabel('Filter')
self.filterComboBox = QtWidgets.QComboBox(self)
- self.filterComboBox.addItems(self.parent.cfg.filterdict.keys())
+ if self.parent.cfg:
+ self.filterComboBox.addItems(self.parent.cfg.filterdict.keys())
self.ETLCheckBoxLabel = QtWidgets.QLabel('ETL')
self.ETLCheckBox = QtWidgets.QCheckBox('Copy current ETL parameters', self)
@@ -540,7 +669,6 @@ def __init__(self, parent=None):
def choose_folder(self):
''' File dialog for choosing the save folder '''
-
path = QtWidgets.QFileDialog.getExistingDirectory(self.parent, 'Select Folder')
if path:
self.parent.folder = path
@@ -552,13 +680,15 @@ def __init__(self, parent=None):
self.parent = parent
self.setTitle("Finished!")
- self.setSubTitle("Attention: This will overwrite the Acquisition Table. Click 'Finished' to continue. To rename the files, use the filename wizard.")
+ self.setSubTitle("Attention: This will overwrite the Acquisition Table. Click 'Finished' to continue. "
+ "To rename the files, use the filename wizard.")
def validatePage(self):
return True
+
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
- wizard = MyWizard()
+ wizard = MulticolorTilingWizard()
sys.exit(app.exec_())
diff --git a/mesoSPIM/src/utils/widgets.py b/mesoSPIM/src/utils/widgets.py
index d6843d7..07ed72e 100644
--- a/mesoSPIM/src/utils/widgets.py
+++ b/mesoSPIM/src/utils/widgets.py
@@ -12,6 +12,10 @@ def __init__(self, parent=None):
self.button = QtWidgets.QPushButton()
self.button.setText("M")
+ font = QtGui.QFont()
+ font.setPointSize(14)
+ self.button.setFont(font)
+
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Preferred)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
@@ -20,6 +24,7 @@ def __init__(self, parent=None):
self.button.setMinimumSize(QtCore.QSize(25, 0))
self.lineEdit = QtWidgets.QLineEdit()
+ self.lineEdit.setFont(font)
# self.lineEdit.setReadOnly(True)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Preferred)
sizePolicy.setHorizontalStretch(0)
@@ -54,6 +59,7 @@ def __init__(self, parent=None):
self.label.setSizePolicy(sizePolicy)
self.slider = QtWidgets.QSlider()
+
self.slider.setOrientation(QtCore.Qt.Horizontal)
self.slider.setTracking(False)
self.slider.valueChanged.connect(self.setText)
@@ -65,6 +71,7 @@ def __init__(self, parent=None):
sizePolicy.setHeightForWidth(self.slider.sizePolicy().hasHeightForWidth())
self.slider.setSizePolicy(sizePolicy)
+
self.layout = QtWidgets.QHBoxLayout()
self.layout.addWidget(self.label)
self.layout.addWidget(self.slider)
@@ -73,6 +80,10 @@ def __init__(self, parent=None):
self.setLayout(self.layout)
self.setAutoFillBackground(True)
+ font = QtGui.QFont()
+ font.setPointSize(14)
+ self.slider.setFont(font)
+
def setText(self, value):
self.label.setText(str(value) + '%')
diff --git a/mesoSPIM/start_mesoSPIM.bat b/mesoSPIM/start_mesoSPIM.bat
new file mode 100644
index 0000000..9a5ce5f
--- /dev/null
+++ b/mesoSPIM/start_mesoSPIM.bat
@@ -0,0 +1,10 @@
+rem Change the Anaconda path "C:\Users\Nikita\anaconda3\Scripts\" and environments path "C:\Users\Nikita\anaconda3\envs" to your own
+rem To know your Anaconda path, run in Anaconda prompt:
+rem $ where conda
+rem
+rem In a multi-user setting, you may want to create the py36 environment in a public folder accessible for all users:
+rem $ conda create -p C:/full/public/path/to/envs/py36 python=3.6
+rem Make sure all users have right to execute python in the py36 environment (you may need admin rights for that).
+echo off
+"%windir%\System32\cmd.exe" /k ""C:\Users\Nikita\anaconda3\Scripts\activate.bat" "C:\Users\Nikita\anaconda3\envs\py36" && python "mesoSPIM_Control.py""
+pause
\ No newline at end of file
diff --git a/mesoSPIM/test/__init__.py b/mesoSPIM/test/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/mesoSPIM/test/fixtures/beams/20210430-153343.tif b/mesoSPIM/test/fixtures/beams/20210430-153343.tif
new file mode 100644
index 0000000..da9f232
Binary files /dev/null and b/mesoSPIM/test/fixtures/beams/20210430-153343.tif differ
diff --git a/mesoSPIM/test/fixtures/beams/20210430-153343.tif_meta.txt b/mesoSPIM/test/fixtures/beams/20210430-153343.tif_meta.txt
new file mode 100644
index 0000000..00a7262
--- /dev/null
+++ b/mesoSPIM/test/fixtures/beams/20210430-153343.tif_meta.txt
@@ -0,0 +1,34 @@
+[CFG]
+[Laser] 561 nm
+[Intensity (%)] 80
+[Zoom] 1.6x
+[Pixelsize in um] 4.08
+[Filter] 405/488/561/640 m
+[Shutter] Right
+
+[POSITION]
+[x_pos] 25900.01
+[y_pos] 66034.93
+[z_pos] 9300.36
+[f_pos] 61350.0
+
+[ETL PARAMETERS]
+[ETL CFG File] C:/Users/mesoSPIM/Documents/GitHub/mesoSPIM-control/mesoSPIM/config/etl_parameters/ETL_NV_Fusion_40x40x40-OptGlass-BBPEG.csv
+[etl_l_offset] 2.0980000000000008
+[etl_l_amplitude] 0.8
+[etl_r_offset] 2.4469999999999983
+[etl_r_amplitude] 0.8
+
+[GALVO PARAMETERS]
+[galvo_l_frequency] 99.9
+[galvo_l_amplitude] 0.0
+[galvo_l_offset] 0.47
+[galvo_r_amplitude] 0.0
+[galvo_r_offset] -0.45
+
+[CAMERA PARAMETERS]
+[camera_type] HamamatsuOrca
+[camera_exposure] 0.05
+[camera_line_interval] 7.5e-05
+[x_pixels] 2304
+[y_pixels] 2304
diff --git a/mesoSPIM/test/fixtures/beams/20210430-153447.tif b/mesoSPIM/test/fixtures/beams/20210430-153447.tif
new file mode 100644
index 0000000..fca6ea3
Binary files /dev/null and b/mesoSPIM/test/fixtures/beams/20210430-153447.tif differ
diff --git a/mesoSPIM/test/fixtures/beams/20210430-153447.tif_meta.txt b/mesoSPIM/test/fixtures/beams/20210430-153447.tif_meta.txt
new file mode 100644
index 0000000..a9653c3
--- /dev/null
+++ b/mesoSPIM/test/fixtures/beams/20210430-153447.tif_meta.txt
@@ -0,0 +1,34 @@
+[CFG]
+[Laser] 561 nm
+[Intensity (%)] 80
+[Zoom] 1.6x
+[Pixelsize in um] 4.08
+[Filter] 405/488/561/640 m
+[Shutter] Right
+
+[POSITION]
+[x_pos] 25900.01
+[y_pos] 66034.93
+[z_pos] 9300.36
+[f_pos] 61350.0
+
+[ETL PARAMETERS]
+[ETL CFG File] C:/Users/mesoSPIM/Documents/GitHub/mesoSPIM-control/mesoSPIM/config/etl_parameters/ETL_NV_Fusion_40x40x40-OptGlass-BBPEG.csv
+[etl_l_offset] 2.0980000000000008
+[etl_l_amplitude] 0.8
+[etl_r_offset] 2.4469999999999983
+[etl_r_amplitude] 0.0
+
+[GALVO PARAMETERS]
+[galvo_l_frequency] 99.9
+[galvo_l_amplitude] 0.0
+[galvo_l_offset] 0.47
+[galvo_r_amplitude] 0.0
+[galvo_r_offset] -0.45
+
+[CAMERA PARAMETERS]
+[camera_type] HamamatsuOrca
+[camera_exposure] 0.05
+[camera_line_interval] 7.5e-05
+[x_pixels] 2304
+[y_pixels] 2304
diff --git a/mesoSPIM/test/fixtures/beams/20210430-153514.tif b/mesoSPIM/test/fixtures/beams/20210430-153514.tif
new file mode 100644
index 0000000..2ef6e5f
Binary files /dev/null and b/mesoSPIM/test/fixtures/beams/20210430-153514.tif differ
diff --git a/mesoSPIM/test/fixtures/beams/20210430-153514.tif_meta.txt b/mesoSPIM/test/fixtures/beams/20210430-153514.tif_meta.txt
new file mode 100644
index 0000000..f670c5c
--- /dev/null
+++ b/mesoSPIM/test/fixtures/beams/20210430-153514.tif_meta.txt
@@ -0,0 +1,34 @@
+[CFG]
+[Laser] 561 nm
+[Intensity (%)] 80
+[Zoom] 1.6x
+[Pixelsize in um] 4.08
+[Filter] 405/488/561/640 m
+[Shutter] Right
+
+[POSITION]
+[x_pos] 25900.01
+[y_pos] 66034.93
+[z_pos] 9300.36
+[f_pos] 61350.0
+
+[ETL PARAMETERS]
+[ETL CFG File] C:/Users/mesoSPIM/Documents/GitHub/mesoSPIM-control/mesoSPIM/config/etl_parameters/ETL_NV_Fusion_40x40x40-OptGlass-BBPEG.csv
+[etl_l_offset] 2.0980000000000008
+[etl_l_amplitude] 0.8
+[etl_r_offset] 2.0510000000000073
+[etl_r_amplitude] 0.0
+
+[GALVO PARAMETERS]
+[galvo_l_frequency] 99.9
+[galvo_l_amplitude] 0.0
+[galvo_l_offset] 0.47
+[galvo_r_amplitude] 0.0
+[galvo_r_offset] -0.45
+
+[CAMERA PARAMETERS]
+[camera_type] HamamatsuOrca
+[camera_exposure] 0.05
+[camera_line_interval] 7.5e-05
+[x_pixels] 2304
+[y_pixels] 2304
diff --git a/mesoSPIM/test/fixtures/beams/20210430-153549.tif b/mesoSPIM/test/fixtures/beams/20210430-153549.tif
new file mode 100644
index 0000000..1414bff
Binary files /dev/null and b/mesoSPIM/test/fixtures/beams/20210430-153549.tif differ
diff --git a/mesoSPIM/test/fixtures/beams/20210430-153549.tif_meta.txt b/mesoSPIM/test/fixtures/beams/20210430-153549.tif_meta.txt
new file mode 100644
index 0000000..3da6a54
--- /dev/null
+++ b/mesoSPIM/test/fixtures/beams/20210430-153549.tif_meta.txt
@@ -0,0 +1,34 @@
+[CFG]
+[Laser] 561 nm
+[Intensity (%)] 80
+[Zoom] 1.6x
+[Pixelsize in um] 4.08
+[Filter] 405/488/561/640 m
+[Shutter] Right
+
+[POSITION]
+[x_pos] 25900.01
+[y_pos] 66034.93
+[z_pos] 9300.36
+[f_pos] 61350.0
+
+[ETL PARAMETERS]
+[ETL CFG File] C:/Users/mesoSPIM/Documents/GitHub/mesoSPIM-control/mesoSPIM/config/etl_parameters/ETL_NV_Fusion_40x40x40-OptGlass-BBPEG.csv
+[etl_l_offset] 2.0980000000000008
+[etl_l_amplitude] 0.8
+[etl_r_offset] 2.8309999999999933
+[etl_r_amplitude] 0.0
+
+[GALVO PARAMETERS]
+[galvo_l_frequency] 99.9
+[galvo_l_amplitude] 0.0
+[galvo_l_offset] 0.47
+[galvo_r_amplitude] 0.0
+[galvo_r_offset] -0.45
+
+[CAMERA PARAMETERS]
+[camera_type] HamamatsuOrca
+[camera_exposure] 0.05
+[camera_line_interval] 7.5e-05
+[x_pixels] 2304
+[y_pixels] 2304
diff --git a/mesoSPIM/test/test_serial.py b/mesoSPIM/test/test_serial.py
new file mode 100644
index 0000000..2165d9d
--- /dev/null
+++ b/mesoSPIM/test/test_serial.py
@@ -0,0 +1,80 @@
+# To run the test:
+# python -m test.test_serial
+import unittest
+import src.devices.filter_wheels.ludlcontrol as ludl
+import src.devices.zoom.mesoSPIM_Zoom as zoomlib
+
+"""
+Filterwheel settings from config file
+"""
+filterwheel_parameters = {'filterwheel_type' : 'Ludl',
+ 'COMport' : 'COM6'}
+filterdict = {'Empty-Alignment' : 0,
+ '405/50' : 1,
+ '480/40' : 2,
+ '525/50' : 3,
+ '535/30' : 4,
+ '590/50' : 5,
+ '585/40' : 6,
+ '405/488/561/640 m' : 7,
+ }
+
+'''
+Zoom configuration from config file
+'''
+zoom_parameters = {'zoom_type' : 'Dynamixel',
+ 'servo_id' : 1,
+ 'COMport' : 'COM10',
+ 'baudrate' : 1000000}
+
+zoomdict = {'0.63x' : 3423,
+ '0.8x' : 3071,
+ '1x' : 2707,
+ '1.25x' : 2389,
+ '1.6x' : 2047,
+ '2x' : 1706,
+ '2.5x' : 1354,
+ '3.2x' : 967,
+ '4x' : 637,
+ '5x' : 318,
+ '6.3x' : 0}
+
+class TestFilterWheel(unittest.TestCase):
+ def setUp(self) -> None:
+ """"This will be called for EVERY test method of the class."""
+ if filterwheel_parameters['filterwheel_type'] == 'Ludl':
+ self.fwheel = ludl.LudlFilterwheel(filterwheel_parameters['COMport'], filterdict)
+ else:
+ raise ValueError('Only Ludl filterwheel test is currently implemented')
+
+ def test_multiple_filter_pos(self):
+ n_cycles = 3
+ for i_cycle in range(n_cycles):
+ print(f"cycle {i_cycle}/{n_cycles}")
+ for filter in filterdict.keys():
+ self.fwheel.set_filter(filter, wait_until_done=True)
+ print(f"filter {filter}")
+
+ # def tearDown(self) -> None:
+ # """"Tidies up after EACH test method execution."""
+
+class TestZoomServo(unittest.TestCase):
+ def setUp(self) -> None:
+ """"This will be called for EVERY test method of the class."""
+ if zoom_parameters['zoom_type'] == 'Dynamixel':
+ self.zoom = zoomlib.DynamixelZoom(zoomdict, zoom_parameters['COMport'],
+ zoom_parameters['servo_id'], zoom_parameters['baudrate'])
+ else:
+ raise ValueError('Only Dynamixel zoom servo test is currently implemented')
+
+ def test_multiple_zoom_pos(self):
+ n_cycles = 3
+ for i_cycle in range(n_cycles):
+ print(f"cycle {i_cycle}/{n_cycles}")
+ for zoomratio in zoomdict.keys():
+ self.zoom.set_zoom(zoomratio, wait_until_done=True)
+ print(f"zoom {zoomratio}")
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/mesoSPIM/test/test_tiling.py b/mesoSPIM/test/test_tiling.py
new file mode 100644
index 0000000..1cfe002
--- /dev/null
+++ b/mesoSPIM/test/test_tiling.py
@@ -0,0 +1,33 @@
+# To run the test:
+# python -m test.test_tiling
+import unittest
+from src.utils.multicolor_acquisition_wizard import MulticolorTilingWizard
+from PyQt5 import QtWidgets
+import sys
+
+class TestTilingWizard(unittest.TestCase):
+ def setUp(self) -> None:
+ """"This will automatically call for EVERY single test method below."""
+ self.wiz = MulticolorTilingWizard()
+
+ def test_image_counts(self):
+ # assuming FOV = 1000, overlap 20%
+ xy_start_fixtures = [(0, 0), (-100, -100), (0, 0), (0, -350), (4500, 4700)]
+ xy_end_fixtures = [(200, 200), (100, 100), (10000, 7900), (0, 350), (-3000, -5800)]
+ xy_offset_fixtures = [(800, 800), (800, 800), (800, 800), (752, 752), (2948, 2948)]
+ xy_counts_correct_answers = [(2, 2), (2, 2), (14, 11), (1, 2), (4, 5)]
+ for i in range(len(xy_start_fixtures)):
+ self.wiz.x_start, self.wiz.y_start = xy_start_fixtures[i]
+ self.wiz.x_end, self.wiz.y_end = xy_end_fixtures[i]
+ self.wiz.x_offset, self.wiz.y_offset = xy_offset_fixtures[i]
+ self.wiz.update_image_counts()
+ self.assertEqual((self.wiz.x_image_count, self.wiz.y_image_count), xy_counts_correct_answers[i],
+ f"Image count [{i}] {self.wiz.x_image_count, self.wiz.y_image_count} is incorrect," \
+ f" must be {xy_counts_correct_answers[i]}")
+
+ # def tearDown(self) -> None:
+ # """"Tidies up after EACH test method execution."""
+
+if __name__ == '__main__':
+ app = QtWidgets.QApplication(sys.argv)
+ unittest.main()
diff --git a/requirements-anaconda.txt b/requirements-anaconda.txt
new file mode 100644
index 0000000..133b9e9
--- /dev/null
+++ b/requirements-anaconda.txt
@@ -0,0 +1,15 @@
+numpy==1.17.0
+scipy==1.2.1
+PyQt5==5.13.1
+PyQt5-sip==12.7.0
+nidaqmx==0.5.7
+indexed==1.2.1
+pipython==2.5.1.3
+pyserial==3.4
+pyqtgraph==0.11.1
+pywinusb==0.4.2
+tifffile==2019.7.26
+qdarkstyle==2.8.1
+npy2bdv==1.0.7
+future==0.18.2
+
diff --git a/requirements-clean-python.txt b/requirements-clean-python.txt
new file mode 100644
index 0000000..9d7687f
--- /dev/null
+++ b/requirements-clean-python.txt
@@ -0,0 +1,20 @@
+csv
+traceback
+pprint
+ctypes
+importlib
+argparse
+glob
+numpy==1.17.0
+scipy==1.2.1
+PyQt5==5.13.1
+nidaqmx==0.5.7
+indexed==1.2.1
+pipython==2.5.1.3
+pyserial==3.4
+pyqtgraph==0.11.1
+pywinusb==0.4.2
+tifffile==2019.7.26
+qdarkstyle==2.8.1
+npy2bdv==1.0.7
+future==0.18.2