From 520b154eb51bd96c0d6f6bf7f6a2d854dd0d4f4a Mon Sep 17 00:00:00 2001 From: Justin Eskesen Date: Fri, 29 Aug 2025 14:52:34 -0700 Subject: [PATCH 1/3] use ome-writer with acquire-zarr backend instead of acquire-zarr directly --- mantis/acquisition/acq_engine.py | 110 ++++++++++++++++--------------- 1 file changed, 56 insertions(+), 54 deletions(-) diff --git a/mantis/acquisition/acq_engine.py b/mantis/acquisition/acq_engine.py index c02e9ab7..749fe32c 100644 --- a/mantis/acquisition/acq_engine.py +++ b/mantis/acquisition/acq_engine.py @@ -11,7 +11,7 @@ from threading import Thread from typing import Iterable, Union -import acquire_zarr as aqz +from ome_writers import create_stream, Dimension import copylot import nidaqmx import numpy as np @@ -220,7 +220,7 @@ def autoexposure_settings(self, settings: AutoexposureSettings): ) self._autoexposure_settings = settings - def setup(self, output_path: Union[str, os.PathLike] = None): + def setup(self, output_path: Union[str, os.PathLike] = None, position_settings=None, time_settings=None): """ Apply acquisition settings as specified by the class properties """ @@ -285,57 +285,51 @@ def setup(self, output_path: Union[str, os.PathLike] = None): y_size = self.mmc.getImageHeight() if output_path: - zarr_settings = aqz.StreamSettings( - store_path=output_path, - dtype=aqz.DataType.UINT16, # FIXME: hardcoded for now, should be set from acquisition settings - dimensions=[ - aqz.Dimension( - name='t', - array_size_px=0, - chunk_size_px=1, - shard_size_chunks=1, - kind=aqz.DimensionType.TIME, - ), # zero denotes the append dimension in acquire - aqz.Dimension( - name='z', - array_size_px=self.slice_settings.num_slices, - chunk_size_px=int(self.slice_settings.num_slices / z_n_chunks), - shard_size_chunks=1, - kind=aqz.DimensionType.SPACE, - ), - aqz.Dimension( - name='y', - array_size_px=y_size, - chunk_size_px=int(y_size / xy_n_chunks), - shard_size_chunks=1, - kind=aqz.DimensionType.SPACE, - ), - aqz.Dimension( - name='x', - array_size_px=x_size, - chunk_size_px=int(x_size / xy_n_chunks), - shard_size_chunks=1, - kind=aqz.DimensionType.SPACE, - ), - ], - muiltscale=False, - version=aqz.ZarrVersion.V3, - max_threads=0, + # Create dimensions for ome_writers with position and time dimensions + num_positions = position_settings.num_positions if position_settings else 1 + num_timepoints = time_settings.num_timepoints if time_settings else 1 + + dimensions = [ + Dimension( + label='p', # position dimension + size=num_positions, + chunk_size=1 + ), + Dimension( + label='t', # time dimension + size=num_timepoints, + chunk_size=1 + ), + Dimension( + label='c', + size=self.channel_settings.num_channels, + chunk_size=self.channel_settings.num_channels + ), + Dimension( + label='z', + size=self.slice_settings.num_slices, + chunk_size=int(max(1, self.slice_settings.num_slices / z_n_chunks)) + ), + Dimension( + label='y', + size=y_size, + chunk_size=int(max(1, y_size / xy_n_chunks)) + ), + Dimension( + label='x', + size=x_size, + chunk_size=int(max(1, x_size / xy_n_chunks)) + ), + ] + + self._ome_writer = create_stream( + output_path, + dtype='uint16', # FIXME: hardcoded for now, should be set from acquisition settings + dimensions=dimensions, + backend="acquire-zarr", + overwrite=True ) - if self.channel_settings.num_channels > 1: - zarr_settings.dimensions.insert( - 1, - aqz.Dimension( - name='c', - array_size_px=self.channel_settings.num_channels, - chunk_size_px=int(self.channel_settings.num_channels), - shard_size_chunks=1, - kind=aqz.DimensionType.CHANNEL, - ), - ) - self._zarr_writer = aqz.ZarrStream(zarr_settings) - self.mmc.mda.events.frameReady.connect(self.write_data) def reset(self): @@ -388,7 +382,7 @@ def write_data(self, data: np.ndarray, event: useq.MDAEvent) -> None: event : useq.Event The event containing metadata about the acquisition. """ - self._zarr_writer.append(data) + self._ome_writer.append(data) class MantisAcquisition(object): @@ -1195,10 +1189,18 @@ def setup(self): logger.info('Setting up acquisition') logger.debug('Setting up label-free acquisition') - self.lf_acq.setup(output_path=f'{self._acq_dir}/{self._acq_name}_{LF_ACQ_LABEL}') + self.lf_acq.setup( + output_path=f'{self._acq_dir}/{self._acq_name}_{LF_ACQ_LABEL}', + position_settings=self.position_settings, + time_settings=self.time_settings + ) logger.debug('Setting up light-sheet acquisition') - self.ls_acq.setup(output_path=f'{self._acq_dir}/{self._acq_name}_{LS_ACQ_LABEL}') + self.ls_acq.setup( + output_path=f'{self._acq_dir}/{self._acq_name}_{LS_ACQ_LABEL}', + position_settings=self.position_settings, + time_settings=self.time_settings + ) logger.debug('Setting up DAQ') self.setup_daq() From 93c83132267f9ab85daac7d2d982bdb6dc8906fc Mon Sep 17 00:00:00 2001 From: Justin Eskesen Date: Tue, 2 Sep 2025 11:25:19 -0700 Subject: [PATCH 2/3] since mm-app-path & ls-config-path are optional, don't fail if the file's don't exist --- mantis/cli/run_acquisition.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mantis/cli/run_acquisition.py b/mantis/cli/run_acquisition.py index 5ac43099..fd2ed3f5 100644 --- a/mantis/cli/run_acquisition.py +++ b/mantis/cli/run_acquisition.py @@ -26,7 +26,7 @@ def load_settings(raw_settings: dict, settings_key: str, settings_class): @click.option( "--mm-app-path", default=default_mm_app_path, - type=click.Path(exists=True, file_okay=False, dir_okay=True), + type=click.Path(file_okay=False, dir_okay=True), show_default=True, help='''Path to Micro-manager installation directory which will run the light-sheet acquisition''', @@ -34,7 +34,7 @@ def load_settings(raw_settings: dict, settings_key: str, settings_class): @click.option( "--ls-config-filepath", default=default_ls_config_filepath, - type=click.Path(exists=True, file_okay=True, dir_okay=False), + type=click.Path(file_okay=True, dir_okay=False), show_default=True, help='''Path to Micro-manager config file which will run the light-sheet acquisition''', From 3e591f3d6f7d9f5ddb9f297c6cd332a49a142969 Mon Sep 17 00:00:00 2001 From: Justin Eskesen Date: Tue, 2 Sep 2025 11:32:05 -0700 Subject: [PATCH 3/3] style --- mantis/acquisition/acq_engine.py | 67 +++++++++++++++----------------- 1 file changed, 31 insertions(+), 36 deletions(-) diff --git a/mantis/acquisition/acq_engine.py b/mantis/acquisition/acq_engine.py index 749fe32c..034a9771 100644 --- a/mantis/acquisition/acq_engine.py +++ b/mantis/acquisition/acq_engine.py @@ -220,7 +220,12 @@ def autoexposure_settings(self, settings: AutoexposureSettings): ) self._autoexposure_settings = settings - def setup(self, output_path: Union[str, os.PathLike] = None, position_settings=None, time_settings=None): + def setup( + self, + output_path: Union[str, os.PathLike] = None, + position_settings=None, + time_settings=None, + ): """ Apply acquisition settings as specified by the class properties """ @@ -288,37 +293,27 @@ def setup(self, output_path: Union[str, os.PathLike] = None, position_settings=N # Create dimensions for ome_writers with position and time dimensions num_positions = position_settings.num_positions if position_settings else 1 num_timepoints = time_settings.num_timepoints if time_settings else 1 - + dimensions = [ Dimension( - label='p', # position dimension - size=num_positions, - chunk_size=1 - ), - Dimension( - label='t', # time dimension - size=num_timepoints, - chunk_size=1 + label='p', size=num_positions, chunk_size=1 # position dimension ), + Dimension(label='t', size=num_timepoints, chunk_size=1), # time dimension Dimension( - label='c', - size=self.channel_settings.num_channels, - chunk_size=self.channel_settings.num_channels + label='c', + size=self.channel_settings.num_channels, + chunk_size=self.channel_settings.num_channels, ), Dimension( label='z', size=self.slice_settings.num_slices, - chunk_size=int(max(1, self.slice_settings.num_slices / z_n_chunks)) + chunk_size=int(max(1, self.slice_settings.num_slices / z_n_chunks)), ), Dimension( - label='y', - size=y_size, - chunk_size=int(max(1, y_size / xy_n_chunks)) + label='y', size=y_size, chunk_size=int(max(1, y_size / xy_n_chunks)) ), Dimension( - label='x', - size=x_size, - chunk_size=int(max(1, x_size / xy_n_chunks)) + label='x', size=x_size, chunk_size=int(max(1, x_size / xy_n_chunks)) ), ] @@ -327,7 +322,7 @@ def setup(self, output_path: Union[str, os.PathLike] = None, position_settings=N dtype='uint16', # FIXME: hardcoded for now, should be set from acquisition settings dimensions=dimensions, backend="acquire-zarr", - overwrite=True + overwrite=True, ) self.mmc.mda.events.frameReady.connect(self.write_data) @@ -830,19 +825,19 @@ def setup_autoexposure(self): ) if ts2_ttl_state == 32: # State 32 corresponds to illumination with 488 laser - self.ls_acq.channel_settings.light_sources[ - channel_idx - ] = microscope_operations.setup_vortran_laser(VORTRAN_488_COM_PORT) + self.ls_acq.channel_settings.light_sources[channel_idx] = ( + microscope_operations.setup_vortran_laser(VORTRAN_488_COM_PORT) + ) elif ts2_ttl_state == 64: # State 64 corresponds to illumination with 561 laser - self.ls_acq.channel_settings.light_sources[ - channel_idx - ] = microscope_operations.setup_vortran_laser(VORTRAN_561_COM_PORT) + self.ls_acq.channel_settings.light_sources[channel_idx] = ( + microscope_operations.setup_vortran_laser(VORTRAN_561_COM_PORT) + ) elif ts2_ttl_state == 128: # State 128 corresponds to illumination with 639 laser - self.ls_acq.channel_settings.light_sources[ - channel_idx - ] = microscope_operations.setup_vortran_laser(VORTRAN_639_COM_PORT) + self.ls_acq.channel_settings.light_sources[channel_idx] = ( + microscope_operations.setup_vortran_laser(VORTRAN_639_COM_PORT) + ) else: logger.error( 'Unknown TTL state {} for channel {} in config group {}'.format( @@ -1192,14 +1187,14 @@ def setup(self): self.lf_acq.setup( output_path=f'{self._acq_dir}/{self._acq_name}_{LF_ACQ_LABEL}', position_settings=self.position_settings, - time_settings=self.time_settings + time_settings=self.time_settings, ) logger.debug('Setting up light-sheet acquisition') self.ls_acq.setup( output_path=f'{self._acq_dir}/{self._acq_name}_{LS_ACQ_LABEL}', position_settings=self.position_settings, - time_settings=self.time_settings + time_settings=self.time_settings, ) logger.debug('Setting up DAQ') @@ -1301,10 +1296,10 @@ def acquire(self): ) continue else: - self.position_settings.xyz_positions[p_idx][ - 2 - ] = self.lf_acq.mmc.getPosition( - self.lf_acq.microscope_settings.autofocus_stage + self.position_settings.xyz_positions[p_idx][2] = ( + self.lf_acq.mmc.getPosition( + self.lf_acq.microscope_settings.autofocus_stage + ) ) logger.debug( f'Autofocus successful. Z position updated to {self.position_settings.xyz_positions[p_idx][2]} at position {p_label}'