diff --git a/docs/requirements.txt b/docs/requirements.txt index 54633131..ca60667b 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,6 +1,6 @@ -Sphinx==2.1.0 +Sphinx nbsphinx -pygments<3,>=2.4.1 +pygments ipykernel sphinx_rtd_theme docstring-inheritance \ No newline at end of file diff --git a/docs/source/apis.rst b/docs/source/apis.rst index b87298b6..f9290472 100644 --- a/docs/source/apis.rst +++ b/docs/source/apis.rst @@ -85,10 +85,13 @@ Acquisition APIs Acquisition ============== -.. currentmodule:: pycromanager +.. currentmodule:: pycromanager.acquisition.acquisition_superclass .. autoclass:: Acquisition :members: + +.. currentmodule:: pycromanager + multi_d_acquisition_events =========================== .. autofunction:: multi_d_acquisition_events diff --git a/java/pom.xml b/java/pom.xml index 1ab6792c..fe86abac 100644 --- a/java/pom.xml +++ b/java/pom.xml @@ -2,7 +2,7 @@ 4.0.0 org.micro-manager.pycro-manager PycroManagerJava - 0.44.2 + 0.44.5 jar Pycro-Manager Java The Java components of Pycro-Manager @@ -54,12 +54,12 @@ org.micro-manager.acqengj AcqEngJ - 0.33.0 + 0.34.0 org.micro-manager.ndviewer NDViewer - 0.10.0 + 0.10.2 org.micro-manager.ndtiffstorage @@ -144,4 +144,4 @@ - + \ No newline at end of file diff --git a/java/src/main/java/org/micromanager/internal/zmq/ZMQServer.java b/java/src/main/java/org/micromanager/internal/zmq/ZMQServer.java index 42866f42..bb766e17 100644 --- a/java/src/main/java/org/micromanager/internal/zmq/ZMQServer.java +++ b/java/src/main/java/org/micromanager/internal/zmq/ZMQServer.java @@ -37,7 +37,7 @@ public class ZMQServer extends ZMQSocketWrapper { //map of objects that exist in some client of the server protected final ConcurrentHashMap externalObjects_ = new ConcurrentHashMap(); - public static final String VERSION = "5.0.0"; + public static final String VERSION = "5.1.0"; private static Function classMapper_; private static ZMQServer mainServer_; diff --git a/pycromanager/__init__.py b/pycromanager/__init__.py index 97306420..9b794221 100644 --- a/pycromanager/__init__.py +++ b/pycromanager/__init__.py @@ -8,4 +8,5 @@ from pycromanager.core import Core from pycromanager.zmq_bridge.wrappers import JavaObject, JavaClass, PullSocket, PushSocket from pycromanager.acquisition.acq_eng_py.main.acq_notification import AcqNotification +from ndtiff import Dataset from ._version import __version__, version_info diff --git a/pycromanager/_version.py b/pycromanager/_version.py index fc883cc8..42cd0936 100644 --- a/pycromanager/_version.py +++ b/pycromanager/_version.py @@ -1,2 +1,2 @@ -version_info = (0, 29, 5) +version_info = (0, 29, 9) __version__ = ".".join(map(str, version_info)) diff --git a/pycromanager/acq_future.py b/pycromanager/acq_future.py index 36d9804a..316eddb3 100644 --- a/pycromanager/acq_future.py +++ b/pycromanager/acq_future.py @@ -3,7 +3,7 @@ def _axes_to_key(axes): """ Turn axes into a hashable key """ - return frozenset(axes.items()) + return None if axes is None else frozenset(axes.items()) class AcquisitionFuture: diff --git a/pycromanager/acquisition/acq_eng_py/internal/engine.py b/pycromanager/acquisition/acq_eng_py/internal/engine.py index 875c6705..0b1c7b39 100644 --- a/pycromanager/acquisition/acq_eng_py/internal/engine.py +++ b/pycromanager/acquisition/acq_eng_py/internal/engine.py @@ -91,9 +91,18 @@ def submit_event_iterator_inner(): return self.event_generator_executor.submit(submit_event_iterator_inner) + def check_for_default_devices(self, event: AcquisitionEvent): + xy_stage = self.core.get_xy_stage_device() + z_stage = self.core.get_focus_device() + if event.get_z_position() is not None and (z_stage is None or z_stage == ""): + raise Exception("Event requires a z position, but no Core-Focus device is set") + if event.get_x_position() is not None and (xy_stage is None or xy_stage == ""): + raise Exception("Event requires an x position, but no Core-XYStage device is set") + def process_acquisition_event(self, event: AcquisitionEvent) -> Future: def process_acquisition_event_inner(): try: + self.check_for_default_devices(event) if event.acquisition_.is_debug_mode(): self.core.logMessage("Processing event: " + event.to_string()) if event.acquisition_.is_debug_mode(): diff --git a/pycromanager/acquisition/acq_eng_py/main/acq_notification.py b/pycromanager/acquisition/acq_eng_py/main/acq_notification.py index 6358cc8b..2be866e2 100644 --- a/pycromanager/acquisition/acq_eng_py/main/acq_notification.py +++ b/pycromanager/acquisition/acq_eng_py/main/acq_notification.py @@ -36,21 +36,22 @@ def to_string(): return "image" def __init__(self, type, id, phase=None): - if type == AcqNotification.Acquisition.to_string(): + if type == AcqNotification.Acquisition or type == AcqNotification.Acquisition.to_string(): self.type = AcqNotification.Acquisition self.id = id self.phase = phase - elif type == AcqNotification.Image.to_string() and phase == AcqNotification.Image.DATA_SINK_FINISHED: + elif (type == AcqNotification.Image or type == AcqNotification.Image.to_string()) and \ + phase == AcqNotification.Image.DATA_SINK_FINISHED: self.type = AcqNotification.Image self.id = id self.phase = phase elif phase in [AcqNotification.Camera.PRE_SNAP, AcqNotification.Camera.POST_EXPOSURE, AcqNotification.Camera.PRE_SEQUENCE_STARTED]: self.type = AcqNotification.Camera - self.id = json.loads(id) + self.id = json.loads(id) if isinstance(id, str) else id # convert from '{'time': 5}' to {'time': 5} elif phase in [AcqNotification.Hardware.PRE_HARDWARE, AcqNotification.Hardware.POST_HARDWARE]: self.type = AcqNotification.Hardware - self.id = json.loads(id) + self.id = json.loads(id) if isinstance(id, str) else id # convert from '{'time': 5}' to {'time': 5} elif phase == AcqNotification.Image.IMAGE_SAVED: self.type = AcqNotification.Image self.id = id diff --git a/pycromanager/acquisition/acq_eng_py/main/acquisition_event.py b/pycromanager/acquisition/acq_eng_py/main/acquisition_event.py index 58e12a67..47c25b4b 100644 --- a/pycromanager/acquisition/acq_eng_py/main/acquisition_event.py +++ b/pycromanager/acquisition/acq_eng_py/main/acquisition_event.py @@ -69,6 +69,7 @@ def copy(self): e.stageDeviceNamesToAxisNames_ = self.stageDeviceNamesToAxisNames_.copy() e.xPosition_ = self.xPosition_ e.yPosition_ = self.yPosition_ + e.zPosition_ = self.zPosition_ e.miniumumStartTime_ms_ = self.miniumumStartTime_ms_ e.slmImage_ = self.slmImage_ e.acquireImage_ = self.acquireImage_ diff --git a/pycromanager/acquisition/acquisition_superclass.py b/pycromanager/acquisition/acquisition_superclass.py index 884e2ba9..c6a0b670 100644 --- a/pycromanager/acquisition/acquisition_superclass.py +++ b/pycromanager/acquisition/acquisition_superclass.py @@ -47,6 +47,7 @@ def __init__( ---------- directory : str saving directory for this acquisition. If it is not supplied, the image data will be stored in RAM + (Java backend only) name : str Name of the acquisition. This will be used to generate the folder where the data is saved. image_process_fn : Callable @@ -76,7 +77,7 @@ def __init__( external timing device that synchronizes with other hardware. Accepts either one argument (the current acquisition event) or two arguments (current event, event_queue) notification_callback_fn : Callable - (Experimental) function that will be called whenever a notification is received from the acquisition engine. These + function that will be called whenever a notification is received from the acquisition engine. These include various stages of the control of hardware and the camera and saving of images. Notification callbacks will execute asynchronously with respect to the acquisition process. The supplied function should take a single argument, which will be an AcqNotification object. It should execute quickly, @@ -91,6 +92,17 @@ def __init__( the user debug : bool whether to print debug messages + show_display : bool + If True, show the image viewer window. If False, show no viewer. (Java backend only) + saving_queue_size : int + The number of images to queue (in memory) while waiting to write to disk. Higher values should + in theory allow sequence acquisitions to go faster, but requires the RAM to hold images while + they are waiting to save (Java backend only) + timeout : + Timeout in ms for connecting to Java side (Java backend only) + port : + Allows overriding the default port for using Java backends on a different port. Use this + after calling start_headless with the same non-default port (Java backend only) """ self._debug = debug self._dataset = None diff --git a/pycromanager/acquisition/java_backend_acquisitions.py b/pycromanager/acquisition/java_backend_acquisitions.py index 98235619..8e21ed14 100644 --- a/pycromanager/acquisition/java_backend_acquisitions.py +++ b/pycromanager/acquisition/java_backend_acquisitions.py @@ -234,25 +234,10 @@ def __init__( show_display: bool=True, napari_viewer=None, saving_queue_size: int=20, - timeout: int=2000, + timeout: int=2500, port: int=DEFAULT_PORT, debug: int=False ): - """ - Parameters - ---------- - show_display : bool - If True, show the image viewer window. If False, show no viewer. - saving_queue_size : int - The number of images to queue (in memory) while waiting to write to disk. Higher values should - in theory allow sequence acquisitions to go faster, but requires the RAM to hold images while - they are waiting to save - timeout : - Timeout in ms for connecting to Java side - port : - Allows overriding the default port for using Java backends on a different port. Use this - after calling start_headless with the same non-default port - """ # Get a dict of all named argument values (or default values when nothing provided) arg_names = [k for k in signature(JavaBackendAcquisition.__init__).parameters.keys() if k != 'self'] l = locals() @@ -286,7 +271,8 @@ def __init__( try: self._remote_notification_handler = JavaObject('org.micromanager.remote.RemoteNotificationHandler', - args=[self._acq], port=self._port, new_socket=False) + args=[self._acq], port=self._port, new_socket=False, + timeout=self._timeout) self._acq_notification_recieving_thread = self._start_receiving_notifications() self._acq_notification_dispatcher_thread = self._start_notification_dispatcher(notification_callback_fn) # TODO: can remove this after this feature has been present for a while @@ -476,7 +462,7 @@ def _initialize_image_processor(self, **kwargs): if kwargs['image_process_fn'] is not None: java_processor = JavaObject( - "org.micromanager.remote.RemoteImageProcessor", port=self._port + "org.micromanager.remote.RemoteImageProcessor", port=self._port, timeout=self._timeout ) self._acq.add_image_processor(java_processor) self._processor_thread = self._start_processor( @@ -489,14 +475,14 @@ def _initialize_hooks(self, **kwargs): self._hook_threads = [] if kwargs['event_generation_hook_fn'] is not None: hook = JavaObject( - "org.micromanager.remote.RemoteAcqHook", port=self._port, args=[self._acq] + "org.micromanager.remote.RemoteAcqHook", port=self._port, args=[self._acq], timeout=self._timeout ) self._hook_threads.append(self._start_hook(hook, kwargs['event_generation_hook_fn'], self._event_queue, process=False)) self._acq.add_hook(hook, self._acq.EVENT_GENERATION_HOOK) if kwargs['pre_hardware_hook_fn'] is not None: hook = JavaObject( - "org.micromanager.remote.RemoteAcqHook", port=self._port, args=[self._acq] + "org.micromanager.remote.RemoteAcqHook", port=self._port, args=[self._acq], timeout=self._timeout ) self._hook_threads.append(self._start_hook(hook, kwargs['pre_hardware_hook_fn'], self._event_queue, @@ -504,14 +490,14 @@ def _initialize_hooks(self, **kwargs): self._acq.add_hook(hook, self._acq.BEFORE_HARDWARE_HOOK) if kwargs['post_hardware_hook_fn'] is not None: hook = JavaObject( - "org.micromanager.remote.RemoteAcqHook", port=self._port, args=[self._acq] + "org.micromanager.remote.RemoteAcqHook", port=self._port, args=[self._acq], timeout=self._timeout ) self._hook_threads.append(self._start_hook(hook, kwargs['post_hardware_hook_fn'], self._event_queue, process=False)) self._acq.add_hook(hook, self._acq.AFTER_HARDWARE_HOOK) if kwargs['post_camera_hook_fn'] is not None: hook = JavaObject( - "org.micromanager.remote.RemoteAcqHook", port=self._port, args=[self._acq], + "org.micromanager.remote.RemoteAcqHook", port=self._port, args=[self._acq], timeout=self._timeout ) self._hook_threads.append(self._start_hook(hook, kwargs['post_camera_hook_fn'], self._event_queue, process=False)) @@ -523,7 +509,7 @@ def _create_remote_acquisition(self, **kwargs): # create a new socket for it to run on so that it can have blocking calls without interfering with # the main socket or other internal sockets new_socket=True, - port=self._port, args=[core], debug=self._debug) + port=self._port, args=[core], debug=self._debug, timeout=self._timeout) show_viewer = kwargs['show_display'] is True and kwargs['napari_viewer'] is None self._acq = acq_factory.create_acquisition(kwargs['directory'], kwargs['name'], show_viewer, kwargs['saving_queue_size'], self._debug,) @@ -665,9 +651,8 @@ def __init__( def _create_remote_acquisition(self, port, **kwargs): core = ZMQRemoteMMCoreJ(port=self._port, timeout=self._timeout) - acq_factory = JavaObject( - "org.micromanager.remote.RemoteAcquisitionFactory", port=self._port, args=[core] - ) + acq_factory = JavaObject("org.micromanager.remote.RemoteAcquisitionFactory", + port=self._port, args=[core], timeout=self._timeout) show_viewer = kwargs['show_display'] is True and\ kwargs['napari_viewer'] is None and\ @@ -711,7 +696,7 @@ def __init__( show_display: bool=True, image_saved_fn: callable=None, saving_queue_size: int=20, - timeout: int=1000, + timeout: int=2500, port: int=DEFAULT_PORT, debug: bool=False, ): diff --git a/pycromanager/acquisition/python_backend_acquisitions.py b/pycromanager/acquisition/python_backend_acquisitions.py index d34292dd..a5b4a186 100644 --- a/pycromanager/acquisition/python_backend_acquisitions.py +++ b/pycromanager/acquisition/python_backend_acquisitions.py @@ -19,7 +19,6 @@ class PythonBackendAcquisition(Acquisition, metaclass=NumpyDocstringInheritanceM def __init__( self, - directory: str=None, name: str='default_acq_name', image_process_fn: callable=None, event_generation_hook_fn: callable = None, @@ -30,6 +29,8 @@ def __init__( napari_viewer=None, image_saved_fn: callable=None, debug: int=False, + # Specificly so the directory arg can be absorbed and ignored without error, + **kwargs ): # Get a dict of all named argument values (or default values when nothing provided) arg_names = [k for k in signature(PythonBackendAcquisition.__init__).parameters.keys() if k != 'self'] @@ -37,9 +38,11 @@ def __init__( named_args = {arg_name: (l[arg_name] if arg_name in l else dict(signature(PythonBackendAcquisition.__init__).parameters.items())[arg_name].default) for arg_name in arg_names } + if 'kwargs' in named_args: + if 'directory' in named_args['kwargs'] and named_args['kwargs']['directory'] is not None: + raise Exception('The directory argument is not supported in Python backend acquisitions') + del named_args['kwargs'] super().__init__(**named_args) - if directory is not None: - raise NotImplementedError('Saving to disk is not yet implemented for the python backend. ') self._dataset = RAMDataStorage() self._finished = False self._notifications_finished = False diff --git a/pycromanager/zmq_bridge/bridge.py b/pycromanager/zmq_bridge/bridge.py index ecb835d7..229d2116 100644 --- a/pycromanager/zmq_bridge/bridge.py +++ b/pycromanager/zmq_bridge/bridge.py @@ -223,7 +223,7 @@ class _Bridge: DEFAULT_PORT = 4827 DEFAULT_TIMEOUT = 500 - _EXPECTED_ZMQ_SERVER_VERSION = "5.0.0" + _EXPECTED_ZMQ_SERVER_VERSION = "5.1.0" _bridge_creation_lock = threading.Lock() _cached_bridges_by_port_and_thread = {}