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 = {}