diff --git a/.github/workflows/pytests.yml b/.github/workflows/pytests.yml index e2c2a8b8..deb6faa6 100644 --- a/.github/workflows/pytests.yml +++ b/.github/workflows/pytests.yml @@ -11,7 +11,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ['3.7', '3.8', '3.9', '3.10'] + python-version: ['3.8', '3.9', '3.10'] steps: - name: Checkout repo diff --git a/.github/workflows/test_and_deploy.yml b/.github/workflows/test_and_deploy.yml index c87be625..be163849 100644 --- a/.github/workflows/test_and_deploy.yml +++ b/.github/workflows/test_and_deploy.yml @@ -21,7 +21,7 @@ jobs: strategy: matrix: platform: [ubuntu-latest, windows-latest, macos-latest] - python-version: [3.7, 3.8, 3.9] + python-version: ['3.8', '3.9', '3.10'] steps: - uses: actions/checkout@v2 diff --git a/README.md b/README.md index d172e0cc..48a50693 100644 --- a/README.md +++ b/README.md @@ -30,9 +30,9 @@ The acquisition, calibration, background correction, reconstruction, and applica conda create -y -n recOrder python=3.9 conda activate recOrder ``` -Install `napari` and `recOrder-napari`: +Install `recOrder-napari`: ``` -pip install "napari[all]" recOrder-napari +pip install recOrder-napari ``` Open `napari` with `recOrder-napari`: ``` diff --git a/docs/microscope-installation-guide.md b/docs/microscope-installation-guide.md index 604b592f..6eef255e 100644 --- a/docs/microscope-installation-guide.md +++ b/docs/microscope-installation-guide.md @@ -15,9 +15,9 @@ conda create -y -n recOrder python=3.9 conda activate recOrder ``` -Install `napari` and `recOrder`: +Install `recOrder`: ``` -pip install "napari[all]" recOrder-napari +pip install recOrder-napari ``` Check your installation: ``` diff --git a/docs/software-installation-guide.md b/docs/software-installation-guide.md index 20965236..def19def 100644 --- a/docs/software-installation-guide.md +++ b/docs/software-installation-guide.md @@ -7,9 +7,9 @@ conda create -y -n recOrder python=3.9 conda activate recOrder ``` -Install `napari` and `recOrder-napari`: +Install `recOrder-napari`: ``` -pip install "napari[all]" recOrder-napari +pip install recOrder-napari ``` Open `napari` with `recOrder-napari`: ``` diff --git a/recOrder/acq/acq_functions.py b/recOrder/acq/acq_functions.py index 3f0cbad0..cc60e730 100644 --- a/recOrder/acq/acq_functions.py +++ b/recOrder/acq/acq_functions.py @@ -7,7 +7,7 @@ import glob def generate_acq_settings(mm, channel_group, channels=None, zstart=None, zend=None, zstep=None, - save_dir=None, prefix=None, keep_shutter_open = False): + save_dir=None, prefix=None, keep_shutter_open_channels=False, keep_shutter_open_slices=False): """ This function generates a json file specific to the micromanager SequenceSettings. It has default parameters for a multi-channels z-stack acquisition but does not yet @@ -74,7 +74,8 @@ def generate_acq_settings(mm, channel_group, channels=None, zstart=None, zend=No original_json['relativeZSlice'] = True original_json['slicesFirst'] = True original_json['timeFirst'] = False - original_json['keepShutterOpenSlices'] = keep_shutter_open + original_json['keepShutterOpenSlices'] = keep_shutter_open_slices + original_json['keepShutterOpenChannels'] = keep_shutter_open_channels original_json['useAutofocus'] = False original_json['saveMode'] = 'MULTIPAGE_TIFF' original_json['save'] = True if save_dir else False diff --git a/recOrder/calib/Calibration.py b/recOrder/calib/Calibration.py index 3d910dea..8ebbbe0c 100644 --- a/recOrder/calib/Calibration.py +++ b/recOrder/calib/Calibration.py @@ -7,6 +7,7 @@ set_lc_state, snap_and_average, snap_and_get_image, get_lc, define_config_state from recOrder.calib.Optimization import BrentOptimizer, MinScalarOptimizer from mpl_toolkits.axes_grid1.axes_divider import make_axes_locatable +from napari.utils.notifications import show_warning from scipy.interpolate import interp1d from scipy.stats import linregress from scipy.optimize import least_squares @@ -145,6 +146,11 @@ def __init__(self, mmc, mm, group='Channel', lc_control_mode='MM-Retardance', in self.directory = None self.inst_mat = None + # Shutter + self.shutter_device = self.mmc.getShutterDevice() + self._auto_shutter_state = None + self._shutter_state = None + def set_dacs(self, lca_dac, lcb_dac): self.PROPERTIES['LCA-DAC'] = (f'TS_{lca_dac}', 'Volts') self.PROPERTIES['LCB-DAC'] = (f'TS_{lcb_dac}', 'Volts') @@ -639,13 +645,46 @@ def opt_I135(self, lca_bound, lcb_bound): logging.info("--------done--------") logging.debug("--------done--------") - def calc_blacklevel(self): + def open_shutter(self): + if self.shutter_device == '': # no shutter + input('Please manually open the shutter and press ') + else: + self.mmc.setShutterOpen(True) + + def reset_shutter(self): + """ + Return autoshutter to its original state before closing - auto_shutter = self.mmc.getAutoShutter() - shutter = self.mmc.getShutterOpen() + Returns + ------- - self.mmc.setAutoShutter(False) - self.mmc.setShutterOpen(False) + """ + if self.shutter_device == '': # no shutter + input('Please reset the shutter to its original state and press ') + logging.info("This is the end of the command-line instructions. You can return to the napari window.") + else: + self.mmc.setAutoShutter(self._auto_shutter_state) + self.mmc.setShutterOpen(self._shutter_state) + + def close_shutter_and_calc_blacklevel(self): + self._auto_shutter_state = self.mmc.getAutoShutter() + self._shutter_state = self.mmc.getShutterOpen() + + if self.shutter_device == '': # no shutter + show_warning('No shutter found. Please follow the command-line instructions...') + shutter_warning_msg = """ + recOrder could not find an automatic shutter configured through Micro-Manager. + >>> If you would like manually enter the black level, enter an integer or float and press + >>> If you would like to estimate the black level, please close the shutter and press + """ + + in_string = input(shutter_warning_msg) + if in_string.isdigit(): # True if positive integer + self.I_Black = float(in_string) + return + else: + self.mmc.setAutoShutter(False) + self.mmc.setShutterOpen(False) n_avg = 20 avgs = [] @@ -655,16 +694,8 @@ def calc_blacklevel(self): avgs.append(mean) blacklevel = np.mean(avgs) - - self.mmc.setAutoShutter(auto_shutter) - - if not auto_shutter: - self.mmc.setShutterOpen(shutter) - self.I_Black = blacklevel - return blacklevel - def get_full_roi(self): # Get Image Parameters self.mmc.snapImage() @@ -718,131 +749,6 @@ def display_and_check_ROI(self, rect): else: raise ValueError('Did not understand your answer, please check spelling') - def run_5state_calibration(self, param): - """ - Param is a list or tuple of: - (swing, wavelength, lc_bounds, black level) - """ - self.swing = param[0] - self.wavelength = param[1] - self.meta_file = param[2] - use_full_FOV = param[3] - - # Get Image Parameters - self.mmc.snapImage() - self.mmc.getImage() - self.height, self.width = self.mmc.getImageHeight(), self.mmc.getImageWidth() - self.ROI = (0, 0, self.width, self.height) - - # Check if change of ROI is needed - if use_full_FOV is False: - rect = self.check_and_get_roi() - cont = self.display_and_check_ROI(rect) - - if not cont: - print('\n---------Stopping Calibration---------\n') - return - else: - self.mmc.setROI(rect.x, rect.y, rect.width, rect.height) - self.ROI = (rect.x, rect.y, rect.width, rect.height) - - # Calculate Blacklevel - logging.debug('Calculating Blacklevel ...') - self.I_Black = self.calc_blacklevel() - logging.debug(f'Blacklevel: {self.I_Black}\n') - - # Set LC Wavelength: - if self.mode == 'MM-Retardance': - self.mmc.setProperty(LC_DEVICE_NAME, 'Wavelength', self.wavelength) - - self.opt_Iext() - self.opt_I0() - self.opt_I45(0.05, 0.05) - self.opt_I90(0.05, 0.05) - self.opt_I135(0.05, 0.05) - - # Calculate Extinction - self.extinction_ratio = self.calculate_extinction() - - # Write Metadata - self.write_metadata() - - # Return ROI to full FOV - if use_full_FOV is False: - self.mmc.clearROI() - - logging.info("\n=======Finished Calibration=======\n") - logging.info(f"EXTINCTION = {self.extinction_ratio}") - logging.debug("\n=======Finished Calibration=======\n") - logging.debug(f"EXTINCTION = {self.extinction_ratio}") - - def run_4state_calibration(self, param): - """ - Param is a list or tuple of: - (swing, wavelength, lc_bounds, black level, ) - where is one of 'full','coarse','fine' - """ - self.swing = param[0] - self.wavelength = param[1] - self.meta_file = param[2] - use_full_FOV = param[3] - - # Get Image Parameters - self.mmc.snapImage() - self.mmc.getImage() - self.height, self.width = self.mmc.getImageHeight(), self.mmc.getImageWidth() - self.ROI = (0, 0, self.width, self.height) - - # Check if change of ROI is needed - if use_full_FOV is False: - rect = self.check_and_get_roi() - cont = self.display_and_check_ROI(rect) - - if not cont: - print('\n---------Stopping Calibration---------\n') - return - else: - self.mmc.setROI(rect.x, rect.y, rect.width, rect.height) - self.ROI = (rect.x, rect.y, rect.width, rect.height) - - # Calculate Blacklevel - print('Calculating Blacklevel ...') - self.I_Black = self.calc_blacklevel() - print(f'Blacklevel: {self.I_Black}\n') - - # Set LC Wavelength: - if self.mode == 'MM-Retardance': - self.mmc.setProperty(LC_DEVICE_NAME, 'Wavelength', self.wavelength) - - self.opt_Iext() - self.opt_I0() - self.opt_I60(0.05, 0.05) - self.opt_I120(0.05, 0.05) - - # Calculate Extinction - self.extinction_ratio = self.calculate_extinction() - - # Write Metadata - self.write_metadata() - - # Return ROI to full FOV - if use_full_FOV is False: - self.mmc.clearROI() - - print("\n=======Finished Calibration=======\n") - print(f"EXTINCTION = {self.extinction_ratio}") - - def run_calibration(self, scheme, options): - - if scheme == '5-State': - self.run_5state_calibration(options) - - elif scheme == '4-State Extinction': - self.run_4state_calibration(options) - - else: - raise ValueError('Please define the calibration scheme') - def calculate_extinction(self, swing, black_level, intensity_extinction, intensity_elliptical): return np.round((1 / np.sin(np.pi * swing) ** 2) * \ (intensity_elliptical - black_level) / (intensity_extinction - black_level), 2) diff --git a/recOrder/plugin/widget/main_widget.py b/recOrder/plugin/widget/main_widget.py index 70248f3b..446467f7 100644 --- a/recOrder/plugin/widget/main_widget.py +++ b/recOrder/plugin/widget/main_widget.py @@ -2417,7 +2417,8 @@ def load_calibration(self): self.swing = metadata.Swing # Initialize calibration class - self.calib = QLIPP_Calibration(self.mmc, self.mm, group=self.config_group) + self.calib = QLIPP_Calibration(self.mmc, self.mm, group=self.config_group, lc_control_mode=self.calib_mode, + interp_method=self.interp_method, wavelength=self.wavelength) self.calib.swing = self.swing self.ui.le_swing.setText(str(self.swing)) self.calib.wavelength = self.wavelength diff --git a/recOrder/plugin/workers/acquisition_workers.py b/recOrder/plugin/workers/acquisition_workers.py index 8f16b63d..4869e899 100644 --- a/recOrder/plugin/workers/acquisition_workers.py +++ b/recOrder/plugin/workers/acquisition_workers.py @@ -855,7 +855,8 @@ def work(self): channel_group=self.channel_group, channels=channels, save_dir=self.snap_dir, - prefix=self.prefix) + prefix=self.prefix, + keep_shutter_open_channels=True) self._check_abort() # acquire images stack = self._acquire() @@ -872,9 +873,16 @@ def work(self): zend=self.calib_window.z_end, zstep=self.calib_window.z_step, save_dir=self.snap_dir, - prefix=self.prefix) + prefix=self.prefix, + keep_shutter_open_channels=True, + keep_shutter_open_slices=True) self._check_abort() + + # set acquisition order to channel-first + self.settings['slicesFirst'] = False + self.settings['acqOrderMode'] = 0 # TIME_POS_SLICE_CHANNEL + # acquire images stack = self._acquire() diff --git a/recOrder/plugin/workers/calibration_workers.py b/recOrder/plugin/workers/calibration_workers.py index 8d0530ad..7d65e076 100644 --- a/recOrder/plugin/workers/calibration_workers.py +++ b/recOrder/plugin/workers/calibration_workers.py @@ -70,16 +70,20 @@ def work(self): self._check_abort() - # Calculate Blacklevel logging.info('Calculating Black Level ...') logging.debug('Calculating Black Level ...') - self.calib.calc_blacklevel() + self.calib.close_shutter_and_calc_blacklevel() + + # Calculate Blacklevel logging.info(f'Black Level: {self.calib.I_Black:.0f}\n') logging.debug(f'Black Level: {self.calib.I_Black:.0f}\n') self._check_abort() self.progress_update.emit((10, 'Calibrating Extinction State...')) + # Open shutter + self.calib.open_shutter() + # Set LC Wavelength: self.calib.set_wavelength(int(self.calib_window.wavelength)) if self.calib_window.calib_mode == 'MM-Retardance': @@ -90,6 +94,9 @@ def work(self): # Optimize States self._calibrate_4state() if self.calib_window.calib_scheme == '4-State' else self._calibrate_5state() + # Reset shutter autoshutter + self.calib.reset_shutter() + # Return ROI to full FOV if self.calib_window.use_cropped_roi: self.calib_window.mmc.clearROI() @@ -356,11 +363,13 @@ def load_calibration(calib, metadata: MetadataReader): # Calculate black level after loading these properties calib.intensity_emitter = MockEmitter() - calib.calc_blacklevel() + calib.close_shutter_and_calc_blacklevel() + calib.open_shutter() set_lc_state(calib.mmc, calib.group, 'State0') calib.I_Ext = snap_and_average(calib.snap_manager) set_lc_state(calib.mmc, calib.group, 'State1') calib.I_Elliptical = snap_and_average(calib.snap_manager) + calib.reset_shutter() yield str(calib.calculate_extinction(calib.swing, calib.I_Black, calib.I_Ext, calib.I_Elliptical)) diff --git a/setup.cfg b/setup.cfg index 591a4e70..1a8d669d 100644 --- a/setup.cfg +++ b/setup.cfg @@ -11,7 +11,6 @@ classifiers = License :: OSI Approved :: BSD License Programming Language :: Python Programming Language :: Python :: 3 :: Only - Programming Language :: Python :: 3.7 Programming Language :: Python :: 3.8 Programming Language :: Python :: 3.9 Programming Language :: Python :: 3.10 @@ -34,7 +33,7 @@ project_urls = [options] packages = find: include_package_data = True -python_requires = >=3.7 +python_requires = >=3.8 setup_requires = setuptools_scm # add your package requirements here install_requires = @@ -55,6 +54,7 @@ install_requires = superqt>=0.2.4 napari-ome-zarr>=0.3.2 qtpy + napari[all] importlib-metadata [options.extras_require] @@ -67,7 +67,6 @@ dev = requests>=2.22.0 wget>=3.2 tox - napari pyqt5 pre-commit black diff --git a/tox.ini b/tox.ini index 112d8b2b..8b3cec91 100644 --- a/tox.ini +++ b/tox.ini @@ -1,14 +1,14 @@ # Modified from from cookiecutter-napari-plugin # For more information about tox, see https://tox.readthedocs.io/en/latest/ [tox] -envlist = py{37, 38,39}-{linux,macos,windows} +envlist = py{38,39,310}-{linux,macos,windows} isolated_build=true [gh-actions] python = - 3.7: py37 3.8: py38 3.9: py39 + 3.10: py310 [gh-actions:env] PLATFORM =