From 1e13f6eb1b7e6cc5f4b2183cab711beb52a52789 Mon Sep 17 00:00:00 2001 From: Henry Pinkard <7969470+henrypinkard@users.noreply.github.com> Date: Tue, 9 Jul 2024 18:21:07 +0200 Subject: [PATCH] revert accidental changes --- .github/ISSUE_TEMPLATE/bug_report.md | 4 +- .github/ISSUE_TEMPLATE/enhancement.md | 2 +- .github/workflows/build_and_deploy.yml | 18 +- .github/workflows/build_and_test.yml | 2 +- build_automation/update_PycroManagerJava.py | 2 +- .../application_notebooks/PSF_viewer.py | 6 +- misc/PropertyMap.py | 6 +- misc/examples/positionTransformation.py | 8 +- misc/positions.py | 8 +- pycromanager/acq_future.py | 6 +- .../acquisition/acq_eng_py/internal/engine.py | 434 +++++++-- .../acq_eng_py/main/AcqEngPy_Acquisition.py | 267 ++++++ .../acq_eng_py/main/acq_eng_metadata.py | 4 +- .../acq_eng_py/main/acquisition_event.py | 902 +++++++++--------- .../acquisition/acquisition_superclass.py | 125 ++- .../python_backend_acquisitions.py | 211 +--- scripts/bridge_tests.py | 2 +- scripts/camera_triggering/genIexamples.py | 4 +- scripts/custom_axis_acq.py | 2 +- scripts/external_camera_trigger.py | 2 +- scripts/generate_ndtiff_test.py | 2 +- scripts/headless_demo.py | 2 +- scripts/image_processor.py | 2 +- scripts/image_processor_divert.py | 2 +- scripts/image_processor_multiple.py | 2 +- scripts/lightsheet_deskew.py | 6 +- scripts/magellan_surfaces.py | 2 +- scripts/multi_d_acq.py | 2 +- scripts/napari_frontend.py | 8 +- scripts/speed_test.py | 2 +- scripts/string_axes.py | 4 +- 31 files changed, 1199 insertions(+), 850 deletions(-) create mode 100644 pycromanager/acquisition/acq_eng_py/main/AcqEngPy_Acquisition.py diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index bca952d9..8e9756b7 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -19,7 +19,7 @@ labels: bug configuration, it is likely unrelated to pycro-manager --> - ### Problem diff --git a/.github/workflows/build_and_deploy.yml b/.github/workflows/build_and_deploy.yml index 5f07edd5..38197d4b 100644 --- a/.github/workflows/build_and_deploy.yml +++ b/.github/workflows/build_and_deploy.yml @@ -1,9 +1,9 @@ # If changes to java versions -# Deploy execution_engine version of pycromanager java to maven, +# Deploy new version of pycromanager java to maven, # and then update micro-manager ivy file and make PR # If changes to python version -# await for PR to merge into execution_engine MM version -# then publish execution_engine version to pypi +# await for PR to merge into new MM version +# then publish new version to pypi name: Build and deploy Java and Python components of Pycro-Manager @@ -20,7 +20,7 @@ concurrency: PM_version_update jobs: - # Use a filter to determine whether to deploy execution_engine java version + # Use a filter to determine whether to deploy new java version check-java-version: if: ${{ github.repository == 'micro-manager/pycro-manager' }} runs-on: ubuntu-latest @@ -123,7 +123,7 @@ jobs: repository: micro-manager/pycro-manager ref: main - - name: Wait for execution_engine version to be available and update ivy.xml + - name: Wait for new version to be available and update ivy.xml run: | cd pycro-manager git config --global user.email "41898282+github-actions[bot]@users.noreply.github.com" @@ -194,7 +194,7 @@ jobs: git push origin --delete dependency_update_from_pycromanager - # After java deps have updated in MM, time to check if a execution_engine python version is needed + # After java deps have updated in MM, time to check if a new python version is needed check-python-version: if: ${{ github.repository == 'micro-manager/pycro-manager' }} runs-on: ubuntu-latest @@ -213,12 +213,12 @@ jobs: pypi-deploy: - # Once any changes to java have gone into micro-manager, a execution_engine version of PM can be deployed to PyPi + # Once any changes to java have gone into micro-manager, a new version of PM can be deployed to PyPi needs: [check-java-version, mm-update, maven-deploy, check-python-version] - name: Deploy execution_engine version to PyPi if needed + name: Deploy new version to PyPi if needed # Run if - # java update is complete without errors and execution_engine version is merged into MM main (or no java update) + # java update is complete without errors and new version is merged into MM main (or no java update) # and python version changed # weird syntax needed, see: https://github.com/actions/runner/issues/491#issuecomment-850884422 if: ${{ github.repository == 'micro-manager/pycro-manager' && always() && needs.check-python-version.outputs.changed == 'true' && !contains(needs.*.result, 'failure') && !contains(needs.*.result, 'cancelled')}} diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml index d5905918..8bc18164 100644 --- a/.github/workflows/build_and_test.yml +++ b/.github/workflows/build_and_test.yml @@ -1,4 +1,4 @@ -name: Build and integration_tests +name: Build and test on: pull_request: diff --git a/build_automation/update_PycroManagerJava.py b/build_automation/update_PycroManagerJava.py index 34e253ca..aa8efaab 100644 --- a/build_automation/update_PycroManagerJava.py +++ b/build_automation/update_PycroManagerJava.py @@ -39,7 +39,7 @@ def read_versions(root): for lib_name in main_branch_versions.keys(): old_version = main_branch_versions[lib_name] new_version = updated_versions[lib_name] - print('\t', lib_name, '\t\told: ', old_version, '\texecution_engine: ', new_version) + print('\t', lib_name, '\t\told: ', old_version, '\tnew: ', new_version) if new_version > old_version: if new_version.minor > old_version.minor: minor_version_increased = True diff --git a/docs/source/application_notebooks/PSF_viewer.py b/docs/source/application_notebooks/PSF_viewer.py index cfe70607..84d4dac0 100644 --- a/docs/source/application_notebooks/PSF_viewer.py +++ b/docs/source/application_notebooks/PSF_viewer.py @@ -163,8 +163,8 @@ def grab_image(image, metadata): def acquire_data(z_range): - """ micro-manager data acquisition. Creates acquisition event_implementations for z-stack. - This example: use custom event_implementations, not multi_d_acquisition because the + """ micro-manager data acquisition. Creates acquisition events for z-stack. + This example: use custom events, not multi_d_acquisition because the z-stage is not run from micro-manager but controlled via external DAQ.""" with JavaBackendAcquisition(directory=None, name=None, show_display=True, @@ -177,7 +177,7 @@ def acquire_data(z_range): def acquire_multid(z_range): - """ micro-manager data acquisition. Creates acquisition event_implementations for z-stack. + """ micro-manager data acquisition. Creates acquisition events for z-stack. This example: use multi_d_acquisition because the z-stage is run from micro-manager. Unless hardware triggering is set up in micro-manager, this will be fairly slow: diff --git a/misc/PropertyMap.py b/misc/PropertyMap.py index 8444dc14..cda27221 100644 --- a/misc/PropertyMap.py +++ b/misc/PropertyMap.py @@ -82,7 +82,7 @@ def encode(self) -> dict: @staticmethod def hook(d: dict): - """Check if a dictionary represents an instance of this class and return a execution_engine instance. If this dict does not match + """Check if a dictionary represents an instance of this class and return a new instance. If this dict does not match the correct pattern then just return the original dict.""" if "type" in d and d["type"] in Property.pTypes.values(): if "scalar" in d: @@ -101,7 +101,7 @@ def encode(self) -> dict: @staticmethod def hook(d: dict): - """Check if a dictionary represents an instance of this class and return a execution_engine instance. If this dict does not match + """Check if a dictionary represents an instance of this class and return a new instance. If this dict does not match the correct pattern then just return the original dict.""" if "type" in d and d["type"] in Property.pTypes.values(): if "array" in d: @@ -239,7 +239,7 @@ def __getitem__(self, idx: typing.Union[slice, int]) -> PropertyMap: if __name__ == "__main__": - """Test that opens a position list file, saves it to a execution_engine file and then checks that both versions + """Test that opens a position list file, saves it to a new file and then checks that both versions are still identical""" path1 = r"PositionList.pos" path2 = r"PositionListOut.pos" diff --git a/misc/examples/positionTransformation.py b/misc/examples/positionTransformation.py index cd588b01..66ab8c31 100644 --- a/misc/examples/positionTransformation.py +++ b/misc/examples/positionTransformation.py @@ -1,8 +1,8 @@ from misc.positions import PositionList -"""This example demonstrates how to generate execution_engine imaging positions from a set of positions after the sample has been picked up and likely shifted or rotated. +"""This example demonstrates how to generate new imaging positions from a set of positions after the sample has been picked up and likely shifted or rotated. This method relies on measuring a set of reference positions (at least 3) before and after moving the dish. You can then use these positions to generate an -affine transform. This affine transform can then be applied to your original cell positions in order to generate a execution_engine set of positions for the same cells. +affine transform. This affine transform can then be applied to your original cell positions in order to generate a new set of positions for the same cells. In the case of a standard cell culture dish it is best to use the corners of the glass coverslip as your reference locations. """ preTreatRefPositions = PositionList.load( @@ -19,10 +19,10 @@ ) # Load the positions of the cells we are measuring before the dish was removed. postTreatCellPositions = preTreatCellPositions.applyAffineTransform( transformMatrix -) # Transform the cell positions to the execution_engine expected locations. +) # Transform the cell positions to the new expected locations. postTreatCellPositions.save( r"experimentPath\transformedPositions.pos" -) # Save the execution_engine positions to a file that can be loaded by Micro-Manager. +) # Save the new positions to a file that can be loaded by Micro-Manager. preTreatRefPositions.plot() postTreatRefPositions.plot() diff --git a/misc/positions.py b/misc/positions.py index f3ccaebe..cc7b947a 100644 --- a/misc/positions.py +++ b/misc/positions.py @@ -221,7 +221,7 @@ def renameXYStage(self, label: str): """Change the name of the xy stage. Args: - label: The execution_engine name for the xy Stage + label: The new name for the xy Stage """ self.defaultXYStage = label self.getXYPosition().renameStage(label) @@ -230,7 +230,7 @@ def copy(self) -> MultiStagePosition: """Creates a copy fo the object Returns: - A execution_engine `MultiStagePosition` object. + A new `MultiStagePosition` object. """ return copy.deepcopy(self) @@ -340,7 +340,7 @@ def renameStage(self, label) -> PositionList: """Change the name of the xy stage. Args: - label: The execution_engine name for the xy Stage + label: The new name for the xy Stage Returns: A reference to this object @@ -523,7 +523,7 @@ def hover(event): def generateList(data: np.ndarray) -> PositionList: - """Example function to create a brand execution_engine position list in python. + """Example function to create a brand new position list in python. Args: data: An Nx2 array of xy coordinates. These coordinates will be converted to a PositionList which can be diff --git a/pycromanager/acq_future.py b/pycromanager/acq_future.py index 382ed7f9..94ff746d 100644 --- a/pycromanager/acq_future.py +++ b/pycromanager/acq_future.py @@ -41,7 +41,7 @@ def _add_notifications(self, axes_or_axes_list): def _notify(self, notification): """ - Called by the kernel notification dispatcher in order so that it can check off that the notification was + Called by the internal notification dispatcher in order so that it can check off that the notification was received. Want to store this, rather than just waiting around for it, in case the await methods are called after the notification has already been sent. """ @@ -66,10 +66,10 @@ def _notify(self, notification): def _monitor_axes(self, axes_or_axes_list): """ - In the case where the acquisition future is constructed for a Generator, the event_implementations to be monitored + In the case where the acquisition future is constructed for a Generator, the events to be monitored are not known until the generator is run. If user code awaits for an event and that event has already passed, the future must be able to check if the event has already passed and return immediately. - So this function is called by the generator as event_implementations are created to add them to the list of event_implementations to + So this function is called by the generator as events are created to add them to the list of events to keep track of. :param axes_or_axes_list: the axes of the event diff --git a/pycromanager/acquisition/acq_eng_py/internal/engine.py b/pycromanager/acquisition/acq_eng_py/internal/engine.py index 6c45a293..6018d783 100644 --- a/pycromanager/acquisition/acq_eng_py/internal/engine.py +++ b/pycromanager/acquisition/acq_eng_py/internal/engine.py @@ -1,16 +1,14 @@ import traceback +from concurrent.futures import Future from concurrent.futures import ThreadPoolExecutor import time import datetime -# from pycromanager.acquisition.execution_engine.acq_events import AcquisitionEvent -# TODO -AcquisitionEvent = None - +from pycromanager.acquisition.acq_eng_py.main.acquisition_event import AcquisitionEvent +from pycromanager.acquisition.acq_eng_py.main.acq_eng_metadata import AcqEngMetadata from pycromanager.acquisition.acq_eng_py.internal.hardware_sequences import HardwareSequences import pymmcore from pycromanager.acquisition.acq_eng_py.main.acq_notification import AcqNotification -# from pycromanager.acquisition.python_backend_acquisitions import PythonBackendAcquisition HARDWARE_ERROR_RETRIES = 6 DELAY_BETWEEN_RETRIES_MS = 5 @@ -35,7 +33,7 @@ def shutdown(self): @staticmethod def get_core(): - return Engine.singleton._core + return Engine.singleton.core @staticmethod def get_instance(): @@ -43,22 +41,54 @@ def get_instance(): def finish_acquisition(self, acq): def finish_acquisition_inner(): + if acq.is_debug_mode(): + Engine.get_core().logMessage("recieved acquisition finished signal") self.sequenced_events.clear() - self.execute_acquisition_event(acq, None) + if acq.is_debug_mode(): + Engine.get_core().logMessage("creating acquisition finished event") + self.execute_acquisition_event(AcquisitionEvent.create_acquisition_finished_event(acq)) acq.block_until_events_finished() return self.event_generator_executor.submit(finish_acquisition_inner) - def submit_event_iterator(self, acquisition, event_generator): + def submit_event_iterator(self, event_iterator): + def submit_event_iterator_inner(): + acq = None + while True: + try: + event = next(event_iterator, None) + except StopIteration: + traceback.print_exc() + break + if event is None: + break # iterator exhausted + acq = event.acquisition_ + if acq.is_debug_mode(): + Engine.get_core().logMessage("got event: " + event.to_string()) + for h in event.acquisition_.get_event_generation_hooks(): + event = h.run(event) + if event is None: + return + while event.acquisition_.is_paused(): + time.sleep(0.005) + try: + if acq.is_abort_requested(): + if acq.is_debug_mode(): + Engine.get_core().logMessage("acquisition aborted") + return + image_acquired_future = self.process_acquisition_event(event) + image_acquired_future.result() + + except Exception as ex: + traceback.print_exc() + acq.abort(ex) + raise ex - for event in event_generator: - image_acquired_future = self.acq_executor.submit(lambda: self.execute_acquisition_event(acquisition, event)) + last_image_future = self.process_acquisition_event(AcquisitionEvent.create_acquisition_sequence_end_event(acq)) + last_image_future.result() - # TODO: before, this used to use the event generator thread to do any transpiling (i.e. checking for sequenceing) - # in order to (theoretically) improve speed. Now we're just returning the image acquired future directly. - # Probably doesn't matter becuase this is suppoed to be async anyway - # return self.event_generator_executor.submit(submit_event_iterator_inner) + return self.event_generator_executor.submit(submit_event_iterator_inner) def check_for_default_devices(self, event: AcquisitionEvent): @@ -69,65 +99,134 @@ def check_for_default_devices(self, event: AcquisitionEvent): 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, acquisition: PythonBackendAcquisition, - # event: AcquisitionEvent) -> Future: - - # TODO - # def process_acquisition_event_inner(): - # try: - # self.check_for_default_devices(event) - # if event.acquisition_.is_debug_mode(): - # self.core.logMessage("Processing event: " + str(event)) - # self.core.logMessage("checking for sequencing") - # if not self.sequenced_events and not event.is_acquisition_sequence_end_event(): - # self.sequenced_events.append(event) - # elif self.is_sequencable(self.sequenced_events, event, len(self.sequenced_events) + 1): - # # merge event into the sequence - # self.sequenced_events.append(event) - # else: - # # all event_implementations - # sequence_event = self.merge_sequence_event(self.sequenced_events) - # self.sequenced_events.clear() - # # Add in the start of the execution_engine sequence - # if not event.is_acquisition_sequence_end_event(): - # self.sequenced_events.append(event) - # if event.acquisition_.is_debug_mode(): - # self.core.logMessage("executing acquisition event") - # try: - # self.execute_acquisition_event(sequence_event) - # except HardwareControlException as e: - # raise e - # except Exception as e: - # traceback.print_exc() - # if self.core.is_sequence_running(): - # self.core.stop_sequence_acquisition() - # raise e - # - # - # return self.acq_executor.submit(process_acquisition_event_inner) - - def execute_acquisition_event(self, acquisition,event: AcquisitionEvent): + 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: " + str(event)) + self.core.logMessage("checking for sequencing") + if not self.sequenced_events and not event.is_acquisition_sequence_end_event(): + self.sequenced_events.append(event) + elif self.is_sequencable(self.sequenced_events, event, len(self.sequenced_events) + 1): + # merge event into the sequence + self.sequenced_events.append(event) + else: + # all events + sequence_event = self.merge_sequence_event(self.sequenced_events) + self.sequenced_events.clear() + # Add in the start of the new sequence + if not event.is_acquisition_sequence_end_event(): + self.sequenced_events.append(event) + if event.acquisition_.is_debug_mode(): + self.core.logMessage("executing acquisition event") + try: + self.execute_acquisition_event(sequence_event) + except HardwareControlException as e: + raise e + except Exception as e: + traceback.print_exc() + if self.core.is_sequence_running(): + self.core.stop_sequence_acquisition() + raise e + + + return self.acq_executor.submit(process_acquisition_event_inner) + + def execute_acquisition_event(self, event: AcquisitionEvent): # check if we should pause until the minimum start time of the event has occured - # while event.get_minimum_start_time_absolute() is not None and \ - # time.time() * 1000 < event.get_minimum_start_time_absolute(): - # wait_time = event.get_minimum_start_time_absolute() - time.time() * 1000 - # event.acquisition_.block_unless_aborted(wait_time) - - if event is not None: - # execute the event - for instruction in event.device_instructions: - instruction.execute() - else: + while event.get_minimum_start_time_absolute() is not None and \ + time.time() * 1000 < event.get_minimum_start_time_absolute(): + wait_time = event.get_minimum_start_time_absolute() - time.time() * 1000 + event.acquisition_.block_unless_aborted(wait_time) + + if event.is_acquisition_finished_event(): # signal to finish saving thread and mark acquisition as finished - if acquisition._are_events_finished(): + if event.acquisition_.are_events_finished(): return # Duplicate finishing event, possibly from x-ing out viewer - acquisition._add_to_output(None) - acquisition._post_notification(AcqNotification.create_acq_events_finished_notification()) + # send message acquisition finished message so things shut down properly + for h in event.acquisition_.get_event_generation_hooks(): + h.run(event) + h.close() + for h in event.acquisition_.get_before_hardware_hooks(): + h.run(event) + h.close() + for h in event.acquisition_.get_after_hardware_hooks(): + h.run(event) + h.close() + for h in event.acquisition_.get_after_camera_hooks(): + h.run(event) + h.close() + for h in event.acquisition_.get_after_exposure_hooks(): + h.run(event) + h.close() + event.acquisition_.add_to_output(self.core.TaggedImage(None, None)) + event.acquisition_.post_notification(AcqNotification.create_acq_events_finished_notification()) + + else: + event.acquisition_.post_notification(AcqNotification( + AcqNotification.Hardware, event.axisPositions_, AcqNotification.Hardware.PRE_HARDWARE)) + for h in event.acquisition_.get_before_hardware_hooks(): + event = h.run(event) + if event is None: + return # The hook cancelled this event + self.abort_if_requested(event, None) + hardware_sequences_in_progress = HardwareSequences() + try: + self.prepare_hardware(event, hardware_sequences_in_progress) + except HardwareControlException as e: + self.stop_hardware_sequences(hardware_sequences_in_progress) + raise e + + event.acquisition_.post_notification(AcqNotification( + AcqNotification.Hardware, event.axisPositions_, AcqNotification.Hardware.PRE_Z_DRIVE)) + for h in event.acquisition_.get_before_z_hooks(): + event = h.run(event) + if event is None: + return # The hook cancelled this event + self.abort_if_requested(event, None) + + try: + self.start_z_drive(event, hardware_sequences_in_progress) + except HardwareControlException as e: + self.stop_hardware_sequences(hardware_sequences_in_progress) + raise e + + event.acquisition_.post_notification(AcqNotification( + AcqNotification.Hardware, event.axisPositions_, AcqNotification.Hardware.POST_HARDWARE)) + for h in event.acquisition_.get_after_hardware_hooks(): + event = h.run(event) + if event is None: + return # The hook cancelled this event + self.abort_if_requested(event, hardware_sequences_in_progress) + # Hardware hook may have modified wait time, so check again if we should + # pause until the minimum start time of the event has occurred. + while event.get_minimum_start_time_absolute() is not None and \ + time.time() * 1000 < event.get_minimum_start_time_absolute(): + try: + self.abort_if_requested(event, hardware_sequences_in_progress) + wait_time = event.get_minimum_start_time_absolute() - time.time() * 1000 + event.acquisition_.block_unless_aborted(wait_time) + except Exception: + # Abort while waiting for next time point + return + + if event.should_acquire_image(): + if event.acquisition_.is_debug_mode(): + self.core.logMessage("acquiring image(s)") + try: + self.acquire_images(event, hardware_sequences_in_progress) + except TimeoutError: + # Don't abort on a timeout + # TODO: this could probably be an option added to the acquisition in the future + print("Timeout while acquiring images") + + # if the acquisition was aborted, make sure everything shuts down properly + self.abort_if_requested(event, hardware_sequences_in_progress) - def acquire_images(self, acquisition , - event: AcquisitionEvent, hardware_sequences_in_progress: HardwareSequences) -> None: + def acquire_images(self, event: AcquisitionEvent, hardware_sequences_in_progress: HardwareSequences) -> None: """ Acquire 1 or more images in a sequence, add some metadata, then put them into an output queue. @@ -135,25 +234,176 @@ def acquire_images(self, acquisition , If the event is a sequence and a sequence acquisition is started in the core, It should be completed by the time this method returns. """ - - acquisition.post_notification(AcqNotification( - AcqNotification.Camera, event.axisPositions_, AcqNotification.Camera.PRE_SEQUENCE_STARTED)) - - # add standard metadata - # TODO - # AcqEngMetadata.add_image_metadata(self.core, ti.tags, corresponding_event, - # current_time_ms - corresponding_event.acquisition_.get_start_time_ms(), - # exposure) - # add user metadata specified in the event - # acquisition.add_tags_to_tagged_image(ti.tags, corresponding_event.get_tags()) - - - - # acquisition._add_to_output(ti) - - # TODO stop sequences - # TODO: exceptiopn handling - # TODO: shutdown + camera_image_counts = event.get_camera_image_counts(self.core.get_camera_device()) + if event.get_sequence() is not None and len(event.get_sequence()) > 1: + # start sequences on one or more cameras + for camera_device_name, image_count in camera_image_counts.items(): + event.acquisition_.post_notification(AcqNotification( + AcqNotification.Camera, event.axisPositions_, AcqNotification.Camera.PRE_SEQUENCE_STARTED)) + self.core.start_sequence_acquisition( + camera_device_name, camera_image_counts[camera_device_name], 0, True) + else: + # snap one image with no sequencing + event.acquisition_.post_notification(AcqNotification( + AcqNotification.Camera, event.axisPositions_, AcqNotification.Camera.PRE_SNAP)) + if event.get_camera_device_name() is not None: + current_camera = self.core.get_camera_device() + width = self.core.get_image_width() + height = self.core.get_image_height() + self.core.set_camera_device(event.get_camera_device_name()) + self.core.snap_image() + self.core.set_camera_device(current_camera) + else: + # Unlike MMCoreJ, pymmcore does not automatically add this metadata when snapping, so need to do it manually + width = self.core.get_image_width() + height = self.core.get_image_height() + self.core.snap_image() + event.acquisition_.post_notification(AcqNotification( + AcqNotification.Camera, event.axisPositions_, AcqNotification.Camera.POST_SNAP)) + for h in event.acquisition_.get_after_exposure_hooks(): + h.run(event) + + # get elapsed time + current_time_ms = time.time() * 1000 + if event.acquisition_.get_start_time_ms() == -1: + # first image, initialize + event.acquisition_.set_start_time_ms(current_time_ms) + + # need to assign events to images as they come out, assuming they might be in arbitrary order, + # but that each camera itself is ordered + multi_cam_adapter_camera_event_lists = None + if event.get_sequence() is not None: + multi_cam_adapter_camera_event_lists = {} + for cam_index in range(self.core.get_number_of_camera_channels()): + multi_cam_adapter_camera_event_lists[cam_index] = [] + for e in event.get_sequence(): + multi_cam_adapter_camera_event_lists[cam_index].append(e) + + # Run a hook after the camera sequence acquisition has started. This can be used for + # external triggering of the camera (when it is in sequence mode). + # note: SnapImage will block until exposure finishes. + # If it is desired that AfterCameraHooks trigger cameras + # in Snap mode, one possibility is that those hooks (or SnapImage) should run + # in a separate thread, started after snapImage is started. But there is no + # guarantee that the camera will be ready to accept a trigger at that point. + for h in event.acquisition_.get_after_camera_hooks(): + h.run(event) + + if event.acquisition_.is_debug_mode(): + self.core.log_message("images acquired, copying from core") + start_copy_time = time.time() + # Loop through and collect all acquired images. There will be + # (# of images in sequence) x (# of camera channels) of them + timeout = False + for i in range(0, 1 if event.get_sequence() is None else len(event.get_sequence())): + if timeout: + # Cancel the rest of the sequence + self.stop_hardware_sequences(hardware_sequences_in_progress) + break + try: + exposure = self.core.get_exposure() if event.get_exposure() is None else event.get_exposure() + except Exception as ex: + raise Exception("Couldnt get exposure form core") + num_cam_channels = self.core.get_number_of_camera_channels() + + need_to_run_after_exposure_hooks = len(event.acquisition_.get_after_exposure_hooks()) > 0 + for cam_index in range(num_cam_channels): + ti = None + camera_name = None + while ti is None: + if event.acquisition_.is_abort_requested(): + return + try: + if event.get_sequence() is not None and len(event.get_sequence()) > 1: + if self.core.is_buffer_overflowed(): + raise Exception("Sequence buffer overflow") + try: + ti = self.core.pop_next_tagged_image() + camera_name = ti.tags["Camera"] + except Exception as e: + # continue waiting + if not self.core.is_sequence_running() and self.core.get_remaining_image_count() == 0: + raise Exception("Expected images did not arrive in circular buffer") + # check if timeout has been exceeded. This is used in the case of a + # camera waiting for a trigger that never comes. + if event.get_sequence()[i].get_timeout_ms() is not None: + if time.time() - start_copy_time > event.get_sequence()[i].get_timeout_ms(): + timeout = True + self.core.stop_sequence_acquisition() + while self.core.is_sequence_running(): + time.sleep(0.001) + break + else: + try: + # TODO: probably there should be a timeout here too, but I'm + # not sure the snap_image system supports it (as opposed to sequences) + # This is a little different from the java version due to differences in metadata + # handling in the SWIG wrapper + camera_name = self.core.get_camera_device() + ti = self.core.get_tagged_image(cam_index, camera_name, height, width) + except Exception as e: + # continue waiting + pass + except Exception as ex: + # Sequence buffer overflow + e = HardwareControlException(str(ex)) + event.acquisition_.abort(e) + raise e + if need_to_run_after_exposure_hooks: + for camera_device_name in camera_image_counts.keys(): + if self.core.is_sequence_running(camera_device_name): + # all of the sequences are not yet done, so this will need to be handled + # on another iteration of the loop + break + event.acquisition_.post_notification(AcqNotification( + AcqNotification.Camera, event.axisPositions_, AcqNotification.Camera.POST_EXPOSURE)) + for h in event.acquisition_.get_after_exposure_hooks(): + h.run(event) + need_to_run_after_exposure_hooks = False + + if timeout: + break + # Doesn't seem to be a version in the API in which you don't have to do this + actual_cam_index = cam_index + if "Multi Camera-CameraChannelIndex" in ti.tags.keys() : + actual_cam_index = ti.tags["Multi Camera-CameraChannelIndex"] + if num_cam_channels == 1: + # probably a mistake in the core.... + actual_cam_index = 0 # Override index because not using multi cam mode right now + + corresponding_event = event + if event.get_sequence() is not None: + # Find the event that corresponds to the camera that captured this image. + # This assumes that the images from a single camera are in order + # in the sequence, though different camera images may be interleaved + if event.get_sequence()[0].get_camera_device_name() is not None: + # camera is specified in the acquisition event. Find the first event that matches + # this camera name. + the_camera_name = camera_name + corresponding_event = next(filter(lambda + e: e.get_camera_device_name() is not None and e.get_camera_device_name() == the_camera_name, + multi_cam_adapter_camera_event_lists.get(actual_cam_index))) + multi_cam_adapter_camera_event_lists.get(actual_cam_index).remove(corresponding_event) + else: + # multi camera adapter or just using the default camera + corresponding_event = multi_cam_adapter_camera_event_lists.get(actual_cam_index).pop(0) + # add standard metadata + AcqEngMetadata.add_image_metadata(self.core, ti.tags, corresponding_event, + current_time_ms - corresponding_event.acquisition_.get_start_time_ms(), + exposure) + # add user metadata specified in the event + corresponding_event.acquisition_.add_tags_to_tagged_image(ti.tags, corresponding_event.get_tags()) + corresponding_event.acquisition_.add_to_image_metadata(ti.tags) + corresponding_event.acquisition_.add_to_output(ti) + + self.stop_hardware_sequences(hardware_sequences_in_progress) + + if event.get_sequence() is not None: + event.acquisition_.post_notification(AcqNotification( + AcqNotification.Camera, event.axisPositions_, AcqNotification.Camera.POST_SEQUENCE_STOPPED)) + + if timeout: + raise TimeoutError("Timeout waiting for images to arrive in circular buffer") def abort_if_requested(self, event: AcquisitionEvent, hardware_sequences_in_progress: HardwareSequences) -> None: if event.acquisition_.is_abort_requested(): @@ -190,7 +440,7 @@ def move_xy_stage(event): if event.is_xy_sequenced(): self.core.start_xy_stage_sequence(xy_stage) else: - # Could be sequenced over other device_implementations.py, in that case get xy position from first in sequence + # Could be sequenced over other devices, in that case get xy position from first in sequence prev_x_position = None if self.last_event is None else None if self.last_event.get_sequence() is None else \ self.last_event.get_sequence()[0].get_x_position() x_position = event.get_sequence()[ @@ -377,11 +627,11 @@ def change_additional_properties(event): # Compare to last event to see what needs to change if self.last_event is not None and self.last_event.acquisition_ != event.acquisition_: - self.last_event = None # Update all hardware if switching to a execution_engine acquisition + self.last_event = None # Update all hardware if switching to a new acquisition - # Other stage device_implementations.py - loop_hardware_command_retries(lambda: move_other_stage_devices(event), "Moving other stage device_implementations.py") + # Other stage devices + loop_hardware_command_retries(lambda: move_other_stage_devices(event), "Moving other stage devices") # XY Stage loop_hardware_command_retries(lambda: move_xy_stage(event), "Moving XY stage") # Channels @@ -496,7 +746,7 @@ def is_sequencable(self, previous_events, next_event, new_seq_length): return False # arbitrary z stages - # TODO implement sequences along arbitrary other stage device_implementations.py + # TODO implement sequences along arbitrary other stage devices for stage_device in previous_event.get_stage_device_names(): return False @@ -522,7 +772,7 @@ def is_sequencable(self, previous_events, next_event, new_seq_length): new_seq_length > self.core.get_exposure_sequence_max_length(self.core.get_camera_device()): return False - # If there is a nonzero delay between event_implementations, then its not sequencable + # If there is a nonzero delay between events, then its not sequencable if previous_event.get_t_index() is not None and next_event.get_t_index() is not None and \ previous_event.get_t_index() != next_event.get_t_index(): if previous_event.get_minimum_start_time_absolute() is not None and \ diff --git a/pycromanager/acquisition/acq_eng_py/main/AcqEngPy_Acquisition.py b/pycromanager/acquisition/acq_eng_py/main/AcqEngPy_Acquisition.py new file mode 100644 index 00000000..6bc6fa90 --- /dev/null +++ b/pycromanager/acquisition/acq_eng_py/main/AcqEngPy_Acquisition.py @@ -0,0 +1,267 @@ +import json +import queue +import traceback +import threading + +from pycromanager.acquisition.acq_eng_py.main.acq_eng_metadata import AcqEngMetadata +from pycromanager.acquisition.acq_eng_py.internal.engine import Engine +from pycromanager.acquisition.acq_eng_py.main.acq_notification import AcqNotification +from pycromanager.acquisition.acq_eng_py.internal.notification_handler import NotificationHandler + + +class Acquisition(): + + EVENT_GENERATION_HOOK = 0 + # This hook runs before changes to the hardware (corresponding to the instructions in the + # event) are made + BEFORE_HARDWARE_HOOK = 1 + # This hook runs after all changes to the hardware except dor setting th Z drive have been + # made. This is useful for things such as autofocus. + BEFORE_Z_DRIVE = 2 + # This hook runs after changes to the hardware took place, but before camera exposure + # (either a snap or a sequence) is started + AFTER_HARDWARE_HOOK = 3 + # Hook runs after the camera sequence acquisition has started. This can be used for + # external triggering of the camera + AFTER_CAMERA_HOOK = 4 + # Hook runs after the camera exposure ended (when possible, before readout of the camera + # and availability of the images in memory). + AFTER_EXPOSURE_HOOK = 5 + + IMAGE_QUEUE_SIZE = 30 + + def __init__(self, sink, summary_metadata_processor=None, initialize=True): + self.xy_stage_ = None + self.events_finished_ = threading.Event() + self.abort_requested_ = threading.Event() + self.start_time_ms_ = -1 + self.paused_ = False + self.event_generation_hooks_ = [] + self.before_hardware_hooks_ = [] + self.before_z_hooks_ = [] + self.after_hardware_hooks_ = [] + self.after_camera_hooks_ = [] + self.after_exposure_hooks_ = [] + self.image_processors_ = [] + self.first_dequeue_ = queue.Queue(maxsize=self.IMAGE_QUEUE_SIZE) + self.processor_output_queues_ = {} + self.debug_mode_ = False + self.abort_exception_ = None + self.image_metadata_processor_ = None + self.notification_handler_ = NotificationHandler() + self.started_ = False + self.core_ = Engine.get_core() + self.summary_metadata_processor_ = summary_metadata_processor + self.data_sink_ = sink + if initialize: + self.initialize() + + def post_notification(self, notification): + self.notification_handler_.post_notification(notification) + + def add_acq_notification_listener(self, post_notification_fn): + self.notification_handler_.add_listener(post_notification_fn) + + def get_data_sink(self): + return self.data_sink_ + + def set_debug_mode(self, debug): + self.debug_mode_ = debug + + def is_debug_mode(self): + return self.debug_mode_ + + def is_abort_requested(self): + return self.abort_requested_.is_set() + + def abort(self, e=None): + if e: + self.abort_exception_ = e + if self.abort_requested_.is_set(): + return + self.abort_requested_.set() + if self.is_paused(): + self.set_paused(False) + Engine.get_instance().finish_acquisition(self) + + def check_for_exceptions(self): + if self.abort_exception_: + raise self.abort_exception_ + + def add_to_summary_metadata(self, summary_metadata): + if self.summary_metadata_processor_: + self.summary_metadata_processor_(summary_metadata) + + def add_to_image_metadata(self, tags): + if self.image_metadata_processor_: + self.image_metadata_processor_(tags) + + def add_tags_to_tagged_image(self, tags, more_tags): + if not more_tags: + return + more_tags_object = json.loads(json.dumps(more_tags)) + tags['AcqEngMetadata.TAGS'] = more_tags_object + + def submit_event_iterator(self, evt): + if not self.started_: + self.start() + return Engine.get_instance().submit_event_iterator(evt) + + def start_saving_thread(self): + def saving_thread(acq): + try: + while True: + if acq.debug_mode_: + acq.core_.log_message(f"Image queue size: {len(acq.first_dequeue_)}") + if not acq.image_processors_: + if acq.debug_mode_: + acq.core_.log_message("waiting for image to save") + img = acq.first_dequeue_.get() + if acq.debug_mode_: + acq.core_.log_message("got image to save") + acq.save_image(img) + if img.tags is None and img.pix is None: + break + else: + img = acq.processor_output_queues_[acq.image_processors_[-1]].get() + if acq.data_sink_: + if acq.debug_mode_: + acq.core_.log_message("Saving image") + if img.tags is None and img.pix is None: + break + acq.save_image(img) + if acq.debug_mode_: + acq.core_.log_message("Finished saving image") + except Exception as ex: + traceback.print_exc() + acq.abort(ex) + finally: + acq.save_image(acq.core_.TaggedImage(None, None)) + + threading.Thread(target=saving_thread, args=(self,)).start() + + def add_image_processor(self, p): + if self.started_: + raise RuntimeError("Cannot add processor after acquisition started") + self.image_processors_.append(p) + self.processor_output_queues_[p] = queue.Queue(maxsize=self.IMAGE_QUEUE_SIZE) + if len(self.image_processors_) == 1: + p.set_acq_and_queues(self, self.first_dequeue_, self.processor_output_queues_[p]) + else: + p.set_acq_and_queues(self, self.processor_output_queues_[self.image_processors_[-2]], + self.processor_output_queues_[self.image_processors_[-1]]) + + def add_hook(self, h, type_): + if self.started_: + raise RuntimeError("Cannot add hook after acquisition started") + if type_ == self.EVENT_GENERATION_HOOK: + self.event_generation_hooks_.append(h) + elif type_ == self.BEFORE_HARDWARE_HOOK: + self.before_hardware_hooks_.append(h) + elif type_ == self.BEFORE_Z_HOOK: + self.before_z_hooks_.append(h) + elif type_ == self.AFTER_HARDWARE_HOOK: + self.after_hardware_hooks_.append(h) + elif type_ == self.AFTER_CAMERA_HOOK: + self.after_camera_hooks_.append(h) + elif type_ == self.AFTER_EXPOSURE_HOOK: + self.after_exposure_hooks_.append(h) + + def initialize(self): + summary_metadata = AcqEngMetadata.make_summary_metadata(self.core_, self) + self.add_to_summary_metadata(summary_metadata) + if self.data_sink_: + self.data_sink_.initialize(summary_metadata) + + def start(self): + if self.data_sink_: + self.start_saving_thread() + self.post_notification(AcqNotification.create_acq_started_notification()) + self.started_ = True + + def save_image(self, image): + if image.tags is None and image.pix is None: + self.data_sink_.finish() + self.post_notification(AcqNotification.create_data_sink_finished_notification()) + else: + pixels, metadata = image.pix, image.tags + axes = AcqEngMetadata.get_axes(metadata) + self.data_sink_.put_image(axes, pixels, metadata) + self.post_notification(AcqNotification.create_image_saved_notification(axes)) + + def get_start_time_ms(self): + return self.start_time_ms_ + + def set_start_time_ms(self, time): + self.start_time_ms_ = time + + def is_paused(self): + return self.paused_ + + def is_started(self): + return self.started_ + + def set_paused(self, pause): + self.paused_ = pause + + def get_summary_metadata(self): + return self.summary_metadata_ + + # perhaps not needed in python like it is in java + # def anything_acquired(self): + # return not self.data_sink_ or self.data_sink_.anything_acquired() + + def add_image_metadata_processor(self, processor): + if not self.image_metadata_processor_: + self.image_metadata_processor_ = processor + else: + raise RuntimeError("Multiple metadata processors not supported") + + def get_event_generation_hooks(self): + return self.event_generation_hooks_ + + def get_before_hardware_hooks(self): + return self.before_hardware_hooks_ + + def get_before_z_hooks(self): + return self.before_z_hooks_ + + def get_after_hardware_hooks(self): + return self.after_hardware_hooks_ + + def get_after_camera_hooks(self): + return self.after_camera_hooks_ + + def get_after_exposure_hooks(self): + return self.after_exposure_hooks_ + + def add_to_output(self, ti): + try: + if ti.tags is None and ti.pix is None: + self.events_finished_.set() + self.first_dequeue_.put(ti) + except Exception as ex: + raise RuntimeError(ex) + + def finish(self): + Engine.get_instance().finish_acquisition(self) + + def are_events_finished(self): + return self.events_finished_.is_set() + + def block_until_events_finished(self, timeout=None): + """Blocks until all events have been processed.""" + self.events_finished_.wait(timeout) + + def block_unless_aborted(self, timeout_ms=None): + """Blocks until acquisition is aborted.""" + self.abort_requested_.wait(timeout_ms / 1000) + + + def get_image_transfer_queue_size(self): + return self.IMAGE_QUEUE_SIZE + + def get_image_transfer_queue_count(self): + return len(self.first_dequeue_) + + diff --git a/pycromanager/acquisition/acq_eng_py/main/acq_eng_metadata.py b/pycromanager/acquisition/acq_eng_py/main/acq_eng_metadata.py index 99243972..aef8807e 100644 --- a/pycromanager/acquisition/acq_eng_py/main/acq_eng_metadata.py +++ b/pycromanager/acquisition/acq_eng_py/main/acq_eng_metadata.py @@ -114,7 +114,7 @@ def make_summary_metadata(core, acq): AcqEngMetadata.set_pixel_type_from_byte_depth(summary, byte_depth) AcqEngMetadata.set_pixel_size_um(summary, core.get_pixel_size_um()) - # Info about core device_implementations.py + # Info about core devices try: AcqEngMetadata.set_core_xy(summary, core.get_xy_stage_device()) AcqEngMetadata.set_core_focus(summary, core.get_focus_device()) @@ -125,7 +125,7 @@ def make_summary_metadata(core, acq): AcqEngMetadata.set_core_slm(summary, core.get_slm_device()) AcqEngMetadata.set_core_shutter(summary, core.get_shutter_device()) except Exception as e: - raise RuntimeError("couldn't get info from core about device_implementations.py") + raise RuntimeError("couldn't get info from core about devices") # TODO restore # # Affine transform diff --git a/pycromanager/acquisition/acq_eng_py/main/acquisition_event.py b/pycromanager/acquisition/acq_eng_py/main/acquisition_event.py index 73660b12..22d7601d 100644 --- a/pycromanager/acquisition/acq_eng_py/main/acquisition_event.py +++ b/pycromanager/acquisition/acq_eng_py/main/acquisition_event.py @@ -1,451 +1,451 @@ -# from collections import namedtuple -# import json -# from pycromanager.acquisition.acq_eng_py.main.acq_eng_metadata import AcqEngMetadata -# -# class AcquisitionEvent: -# class SpecialFlag: -# ACQUISITION_FINISHED = "AcqusitionFinished" -# ACQUISITION_SEQUENCE_END = "AcqusitionSequenceEnd" -# -# def __init__(self, acq, sequence=None): -# self.acquisition_ = acq -# self.axisPositions_ = {} -# self.camera_ = None -# self.timeout_ms_ = None -# self.configGroup_ = None -# self.configPreset_ = None -# self.exposure_ = None -# self.miniumumStartTime_ms_ = None -# self.zPosition_ = None -# self.xPosition_ = None -# self.yPosition_ = None -# self.stageCoordinates_ = {} -# self.stageDeviceNamesToAxisNames_ = {} -# self.tags_ = {} -# self.acquireImage_ = None -# self.slmImage_ = None -# self.properties_ = set() -# self.sequence_ = None -# self.xySequenced_ = False -# self.zSequenced_ = False -# self.exposureSequenced_ = False -# self.configGroupSequenced_ = False -# self.specialFlag_ = None -# -# if sequence: -# self.acquisition_ = sequence[0].acquisition_ -# self.miniumumStartTime_ms_ = sequence[0].miniumumStartTime_ms_ -# self.sequence_ = list(sequence) -# zPosSet = set() -# xPosSet = set() -# yPosSet = set() -# exposureSet = set() -# configSet = set() -# for event in self.sequence_: -# if event.zPosition_ is not None: -# zPosSet.add(event.get_z_position()) -# if event.xPosition_ is not None: -# xPosSet.add(event.get_x_position()) -# if event.yPosition_ is not None: -# yPosSet.add(event.get_y_position()) -# if event.exposure_ is not None: -# exposureSet.add(event.get_exposure()) -# if event.configPreset_ is not None: -# configSet.add(event.get_config_preset()) -# self.exposureSequenced_ = len(exposureSet) > 1 -# self.configGroupSequenced_ = len(configSet) > 1 -# self.xySequenced_ = len(xPosSet) > 1 and len(yPosSet) > 1 -# self.zSequenced_ = len(zPosSet) > 1 -# if sequence[0].exposure_ and not self.exposureSequenced_: -# self.exposure_ = sequence[0].exposure_ -# -# -# def copy(self): -# e = AcquisitionEvent(self.acquisition_) -# e.axisPositions_ = self.axisPositions_.copy() -# e.configPreset_ = self.configPreset_ -# e.configGroup_ = self.configGroup_ -# e.stageCoordinates_ = self.stageCoordinates_.copy() -# 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_ -# e.properties_ = set(self.properties_) -# e.camera_ = self.camera_ -# e.timeout_ms_ = self.timeout_ms_ -# e.setTags(self.tags_) # Assuming setTags is a method in the class -# return e -# -# @staticmethod -# def event_to_json(e): -# data = {} -# -# if e.is_acquisition_finished_event(): -# data["special"] = "acquisition-end" -# return data -# elif e.is_acquisition_sequence_end_event(): -# data["special"] = "sequence-end" -# return data -# -# if e.miniumumStartTime_ms_: -# data["min_start_time"] = e.miniumumStartTime_ms_ / 1000 -# -# if e.has_config_group(): -# data["config_group"] = [e.configGroup_, e.configPreset_] -# -# if e.exposure_ is not None: -# data["exposure"] = e.exposure_ -# -# if e.slmImage_: -# data["slm_pattern"] = e.slmImage_ -# -# if e.timeout_ms_ is not None: -# data["timeout_ms"] = e.timeout_ms_ -# -# axes = {axis: e.axisPositions_[axis] for axis in e.axisPositions_} -# if axes: -# data["axes"] = axes -# -# stage_positions = [[stageDevice, e.get_stage_single_axis_stage_position(stageDevice)] -# for stageDevice in e.get_stage_device_names()] -# if stage_positions: -# data["stage_positions"] = stage_positions -# -# if e.zPosition_ is not None: -# data["z"] = e.zPosition_ -# -# if e.xPosition_ is not None: -# data["x"] = e.xPosition_ -# -# if e.yPosition_ is not None: -# data["y"] = e.yPosition_ -# -# if e.camera_: -# data["camera"] = e.camera_ -# -# if e.get_tags() and e.get_tags(): # Assuming getTags is a method in the class -# data["tags"] = {key: value for key, value in e.getTags().items()} -# -# props = [[t.dev, t.prop, t.val] for t in e.properties_] -# if props: -# data["properties"] = props -# -# return data -# -# @staticmethod -# def event_from_json(data, acq): -# if "special" in data: -# if data["special"] == "acquisition-end": -# return AcquisitionEvent.create_acquisition_finished_event(acq) -# elif data["special"] == "sequence-end": -# return AcquisitionEvent.create_acquisition_sequence_end_event(acq) -# -# event = AcquisitionEvent(acq) -# -# if "axes" in data: -# for axisLabel, value in data["axes"].items(): -# event.axisPositions_[axisLabel] = value -# -# if "min_start_time" in data: -# event.miniumumStartTime_ms_ = int(data["min_start_time"] * 1000) -# -# if "timeout_ms" in data: -# event.timeout_ms_ = float(data["timeout_ms"]) -# -# if "config_group" in data: -# event.configGroup_ = data["config_group"][0] -# event.configPreset_ = data["config_group"][1] -# -# if "exposure" in data: -# event.exposure_ = float(data["exposure"]) -# -# # if "timeout_ms" in data: -# # event.slmImage_ = float(data["timeout_ms"]) -# -# if "stage_positions" in data: -# for stagePos in data["stage_positions"]: -# event.set_stage_coordinate(stagePos[0], stagePos[1]) -# -# if "z" in data: -# event.zPosition_ = float(data["z"]) -# -# if "stage" in data: -# deviceName = data["stage"]["device_name"] -# position = data["stage"]["position"] -# event.axisPositions_[deviceName] = float(position) -# if "axis_name" in data["stage"]: -# axisName = data["stage"]["axis_name"] -# event.stageDeviceNamesToAxisNames_[deviceName] = axisName -# -# # # Assuming XYTiledAcquisition is a class and AcqEngMetadata is a class or module with constants -# # if isinstance(event.acquisition_, XYTiledAcquisition): -# # posIndex = event.acquisition_.getPixelStageTranslator().getPositionIndices( -# # [int(event.axisPositions_[AcqEngMetadata.AXES_GRID_ROW])], -# # [int(event.axisPositions_[AcqEngMetadata.AXES_GRID_COL])])[0] -# # xyPos = event.acquisition_.getPixelStageTranslator().getXYPosition(posIndex).getCenter() -# # event.xPosition_ = xyPos.x -# # event.yPosition_ = xyPos.y -# -# if "x" in data: -# event.xPosition_ = float(data["x"]) -# -# if "y" in data: -# event.yPosition_ = float(data["y"]) -# -# if "slm_pattern" in data: -# event.slmImage_ = data["slm_pattern"] -# -# if "camera" in data: -# event.camera_ = data["camera"] -# -# if "tags" in data: -# tags = {key: value for key, value in data["tags"].items()} -# event.setTags(tags) -# -# if "properties" in data: -# for trip in data["properties"]: -# t = ThreeTuple(trip[0], trip[1], trip[2]) -# event.properties_.add(t) -# -# return event -# -# def to_json(self): -# if self.sequence_: -# event_implementations = [self.event_to_json(e) for e in self.sequence_] -# return event_implementations -# else: -# return self.event_to_json(self) -# -# @staticmethod -# def from_json(data, acq): -# if not isinstance(data, list): -# return AcquisitionEvent.event_from_json(data, acq) -# else: -# sequence = [AcquisitionEvent.event_from_json(event, acq) for event in data] -# return AcquisitionEvent(acq, sequence=sequence) -# -# def get_camera_device_name(self): -# return self.camera_ -# -# def set_camera_device_name(self, camera): -# self.camera_ = camera -# -# def get_additional_properties(self): -# return [(t.dev, t.prop, t.val) for t in self.properties_] -# -# def should_acquire_image(self): -# if self.sequence_: -# return True -# return self.configPreset_ is not None or self.axisPositions_ is not None -# -# def has_config_group(self): -# return self.configPreset_ is not None and self.configGroup_ is not None -# -# def get_config_preset(self): -# return self.configPreset_ -# -# def get_config_group(self): -# return self.configGroup_ -# -# def set_config_preset(self, config): -# self.configPreset_ = config -# -# def set_config_group(self, group): -# self.configGroup_ = group -# -# def get_exposure(self): -# return self.exposure_ -# -# def set_exposure(self, exposure): -# self.exposure_ = exposure -# -# def set_property(self, device, property, value): -# self.properties_.add(ThreeTuple(device, property, value)) -# -# def set_minimum_start_time(self, l): -# self.miniumumStartTime_ms_ = l -# -# def get_defined_axes(self): -# return set(self.axisPositions_.keys()) -# -# def set_axis_position(self, label, position): -# if position is None: -# raise Exception("Cannot set axis position to null") -# self.axisPositions_[label] = position -# -# def set_stage_coordinate(self, deviceName, v, axisName=None): -# self.stageCoordinates_[deviceName] = v -# self.stageDeviceNamesToAxisNames_[deviceName] = deviceName if axisName is None else axisName -# -# def get_stage_single_axis_stage_position(self, deviceName): -# return self.stageCoordinates_.get(deviceName) -# -# def get_axis_positions(self): -# return self.axisPositions_ -# -# def get_axis_position(self, label): -# return self.axisPositions_.get(label) -# -# def get_timeout_ms(self): -# return self.timeout_ms_ -# -# def set_time_index(self, index): -# self.set_axis_position(AcqEngMetadata.TIME_AXIS, index) -# -# def set_channel_name(self, name): -# self.set_axis_position(AcqEngMetadata.CHANNEL_AXIS, name) -# -# def get_slm_image(self): -# return self.slmImage_ -# -# def set_z(self, index, position): -# if index is not None: -# self.set_axis_position(AcqEngMetadata.Z_AXIS, index) -# self.zPosition_ = position -# -# def get_t_index(self): -# return self.get_axis_position(AcqEngMetadata.TIME_AXIS) -# -# def get_z_index(self): -# return self.get_axis_position(AcqEngMetadata.Z_AXIS) -# -# def get_device_axis_name(self, deviceName): -# if deviceName not in self.stageDeviceNamesToAxisNames_: -# raise Exception(f"No axis name for device {deviceName}. call setStageCoordinate first") -# return self.stageDeviceNamesToAxisNames_[deviceName] -# -# def get_stage_device_names(self): -# return set(self.stageDeviceNamesToAxisNames_.keys()) -# -# @staticmethod -# def create_acquisition_finished_event(acq): -# evt = AcquisitionEvent(acq) -# evt.specialFlag_ = AcquisitionEvent.SpecialFlag.ACQUISITION_FINISHED -# return evt -# -# def is_acquisition_finished_event(self): -# return self.specialFlag_ == AcquisitionEvent.SpecialFlag.ACQUISITION_FINISHED -# -# @staticmethod -# def create_acquisition_sequence_end_event(acq): -# evt = AcquisitionEvent(acq) -# evt.specialFlag_ = AcquisitionEvent.SpecialFlag.ACQUISITION_SEQUENCE_END -# return evt -# -# def is_acquisition_sequence_end_event(self): -# return self.specialFlag_ == AcquisitionEvent.SpecialFlag.ACQUISITION_SEQUENCE_END -# -# def get_z_position(self): -# return self.zPosition_ -# -# def get_minimum_start_time_absolute(self): -# if self.miniumumStartTime_ms_ is None: -# return None -# return self.acquisition_.get_start_time_ms() + self.miniumumStartTime_ms_ -# -# def get_sequence(self): -# return self.sequence_ -# -# def is_exposure_sequenced(self): -# return self.exposureSequenced_ -# -# def is_config_group_sequenced(self): -# return self.configGroupSequenced_ -# -# def is_xy_sequenced(self): -# return self.xySequenced_ -# -# def is_z_sequenced(self): -# return self.zSequenced_ -# -# def get_x_position(self): -# return self.xPosition_ -# -# def get_camera_image_counts(self, default_camera_device_name): -# """ -# Get the number of images to be acquired on each camera in a sequence event. -# For a non-sequence event, the number of images is 1, and the camera is the core camera. -# This is passed in as an argument in order to avoid this class talking to the core directly. -# -# Args: -# default_camera_device_name (str): Default camera device name. -# -# Returns: -# defaultdict: Dictionary containing the camera device names as keys and image counts as values. -# """ -# # Figure out how many images on each camera and start sequence with appropriate number on each -# camera_image_counts = {} -# camera_device_names = set() -# if self.get_sequence() is None: -# camera_image_counts[default_camera_device_name] = 1 -# return camera_image_counts -# -# for event in self.get_sequence(): -# camera_device_names.add(event.get_camera_device_name() if event.get_camera_device_name() is not None else -# default_camera_device_name) -# if None in camera_device_names: -# camera_device_names.remove(None) -# camera_device_names.add(default_camera_device_name) -# -# for camera_device_name in camera_device_names: -# camera_image_counts[camera_device_name] = sum(1 for event in self.get_sequence() -# if event.get_camera_device_name() == camera_device_name) -# -# if len(camera_device_names) == 1 and camera_device_name == default_camera_device_name: -# camera_image_counts[camera_device_name] = len(self.get_sequence()) -# -# return camera_image_counts -# -# def get_y_position(self): -# return self.yPosition_ -# -# def get_position_name(self): -# axisPosition_ = self.get_axis_position(AcqEngMetadata.POSITION_AXIS) -# if isinstance(axisPosition_, str): -# return axisPosition_ -# return None -# -# def set_x(self, x): -# self.xPosition_ = x -# -# def set_y(self, y): -# self.yPosition_ = y -# -# def set_tags(self, tags): -# self.tags_.clear() -# if tags: -# self.tags_.update(tags) -# -# def get_tags(self): -# return dict(self.tags_) -# -# def __str__(self): -# if self.specialFlag_ == AcquisitionEvent.SpecialFlag.ACQUISITION_FINISHED: -# return "Acq finished event" -# elif self.specialFlag_ == AcquisitionEvent.SpecialFlag.ACQUISITION_SEQUENCE_END: -# return "Acq sequence end event" -# -# builder = [] -# for deviceName in self.stageDeviceNamesToAxisNames_.keys(): -# builder.append(f"\t{deviceName}: {self.get_stage_single_axis_stage_position(deviceName)}") -# -# if self.zPosition_ is not None: -# builder.append(f"z {self.zPosition_}") -# if self.xPosition_ is not None: -# builder.append(f"x {self.xPosition_}") -# if self.yPosition_ is not None: -# builder.append(f"y {self.yPosition_}") -# -# for axis in self.axisPositions_.keys(): -# builder.append(f"\t{axis}: {self.axisPositions_[axis]}") -# -# if self.camera_ is not None: -# builder.append(f"\t{self.camera_}: {self.camera_}") -# -# return ' '.join(builder) -# -# -# ThreeTuple = namedtuple('ThreeTuple', ['dev', 'prop', 'val']) +from collections import namedtuple +import json +from pycromanager.acquisition.acq_eng_py.main.acq_eng_metadata import AcqEngMetadata + +class AcquisitionEvent: + class SpecialFlag: + ACQUISITION_FINISHED = "AcqusitionFinished" + ACQUISITION_SEQUENCE_END = "AcqusitionSequenceEnd" + + def __init__(self, acq, sequence=None): + self.acquisition_ = acq + self.axisPositions_ = {} + self.camera_ = None + self.timeout_ms_ = None + self.configGroup_ = None + self.configPreset_ = None + self.exposure_ = None + self.miniumumStartTime_ms_ = None + self.zPosition_ = None + self.xPosition_ = None + self.yPosition_ = None + self.stageCoordinates_ = {} + self.stageDeviceNamesToAxisNames_ = {} + self.tags_ = {} + self.acquireImage_ = None + self.slmImage_ = None + self.properties_ = set() + self.sequence_ = None + self.xySequenced_ = False + self.zSequenced_ = False + self.exposureSequenced_ = False + self.configGroupSequenced_ = False + self.specialFlag_ = None + + if sequence: + self.acquisition_ = sequence[0].acquisition_ + self.miniumumStartTime_ms_ = sequence[0].miniumumStartTime_ms_ + self.sequence_ = list(sequence) + zPosSet = set() + xPosSet = set() + yPosSet = set() + exposureSet = set() + configSet = set() + for event in self.sequence_: + if event.zPosition_ is not None: + zPosSet.add(event.get_z_position()) + if event.xPosition_ is not None: + xPosSet.add(event.get_x_position()) + if event.yPosition_ is not None: + yPosSet.add(event.get_y_position()) + if event.exposure_ is not None: + exposureSet.add(event.get_exposure()) + if event.configPreset_ is not None: + configSet.add(event.get_config_preset()) + self.exposureSequenced_ = len(exposureSet) > 1 + self.configGroupSequenced_ = len(configSet) > 1 + self.xySequenced_ = len(xPosSet) > 1 and len(yPosSet) > 1 + self.zSequenced_ = len(zPosSet) > 1 + if sequence[0].exposure_ and not self.exposureSequenced_: + self.exposure_ = sequence[0].exposure_ + + + def copy(self): + e = AcquisitionEvent(self.acquisition_) + e.axisPositions_ = self.axisPositions_.copy() + e.configPreset_ = self.configPreset_ + e.configGroup_ = self.configGroup_ + e.stageCoordinates_ = self.stageCoordinates_.copy() + 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_ + e.properties_ = set(self.properties_) + e.camera_ = self.camera_ + e.timeout_ms_ = self.timeout_ms_ + e.setTags(self.tags_) # Assuming setTags is a method in the class + return e + + @staticmethod + def event_to_json(e): + data = {} + + if e.is_acquisition_finished_event(): + data["special"] = "acquisition-end" + return data + elif e.is_acquisition_sequence_end_event(): + data["special"] = "sequence-end" + return data + + if e.miniumumStartTime_ms_: + data["min_start_time"] = e.miniumumStartTime_ms_ / 1000 + + if e.has_config_group(): + data["config_group"] = [e.configGroup_, e.configPreset_] + + if e.exposure_ is not None: + data["exposure"] = e.exposure_ + + if e.slmImage_: + data["slm_pattern"] = e.slmImage_ + + if e.timeout_ms_ is not None: + data["timeout_ms"] = e.timeout_ms_ + + axes = {axis: e.axisPositions_[axis] for axis in e.axisPositions_} + if axes: + data["axes"] = axes + + stage_positions = [[stageDevice, e.get_stage_single_axis_stage_position(stageDevice)] + for stageDevice in e.get_stage_device_names()] + if stage_positions: + data["stage_positions"] = stage_positions + + if e.zPosition_ is not None: + data["z"] = e.zPosition_ + + if e.xPosition_ is not None: + data["x"] = e.xPosition_ + + if e.yPosition_ is not None: + data["y"] = e.yPosition_ + + if e.camera_: + data["camera"] = e.camera_ + + if e.get_tags() and e.get_tags(): # Assuming getTags is a method in the class + data["tags"] = {key: value for key, value in e.getTags().items()} + + props = [[t.dev, t.prop, t.val] for t in e.properties_] + if props: + data["properties"] = props + + return data + + @staticmethod + def event_from_json(data, acq): + if "special" in data: + if data["special"] == "acquisition-end": + return AcquisitionEvent.create_acquisition_finished_event(acq) + elif data["special"] == "sequence-end": + return AcquisitionEvent.create_acquisition_sequence_end_event(acq) + + event = AcquisitionEvent(acq) + + if "axes" in data: + for axisLabel, value in data["axes"].items(): + event.axisPositions_[axisLabel] = value + + if "min_start_time" in data: + event.miniumumStartTime_ms_ = int(data["min_start_time"] * 1000) + + if "timeout_ms" in data: + event.timeout_ms_ = float(data["timeout_ms"]) + + if "config_group" in data: + event.configGroup_ = data["config_group"][0] + event.configPreset_ = data["config_group"][1] + + if "exposure" in data: + event.exposure_ = float(data["exposure"]) + + # if "timeout_ms" in data: + # event.slmImage_ = float(data["timeout_ms"]) + + if "stage_positions" in data: + for stagePos in data["stage_positions"]: + event.set_stage_coordinate(stagePos[0], stagePos[1]) + + if "z" in data: + event.zPosition_ = float(data["z"]) + + if "stage" in data: + deviceName = data["stage"]["device_name"] + position = data["stage"]["position"] + event.axisPositions_[deviceName] = float(position) + if "axis_name" in data["stage"]: + axisName = data["stage"]["axis_name"] + event.stageDeviceNamesToAxisNames_[deviceName] = axisName + + # # Assuming XYTiledAcquisition is a class and AcqEngMetadata is a class or module with constants + # if isinstance(event.acquisition_, XYTiledAcquisition): + # posIndex = event.acquisition_.getPixelStageTranslator().getPositionIndices( + # [int(event.axisPositions_[AcqEngMetadata.AXES_GRID_ROW])], + # [int(event.axisPositions_[AcqEngMetadata.AXES_GRID_COL])])[0] + # xyPos = event.acquisition_.getPixelStageTranslator().getXYPosition(posIndex).getCenter() + # event.xPosition_ = xyPos.x + # event.yPosition_ = xyPos.y + + if "x" in data: + event.xPosition_ = float(data["x"]) + + if "y" in data: + event.yPosition_ = float(data["y"]) + + if "slm_pattern" in data: + event.slmImage_ = data["slm_pattern"] + + if "camera" in data: + event.camera_ = data["camera"] + + if "tags" in data: + tags = {key: value for key, value in data["tags"].items()} + event.setTags(tags) + + if "properties" in data: + for trip in data["properties"]: + t = ThreeTuple(trip[0], trip[1], trip[2]) + event.properties_.add(t) + + return event + + def to_json(self): + if self.sequence_: + events = [self.event_to_json(e) for e in self.sequence_] + return events + else: + return self.event_to_json(self) + + @staticmethod + def from_json(data, acq): + if not isinstance(data, list): + return AcquisitionEvent.event_from_json(data, acq) + else: + sequence = [AcquisitionEvent.event_from_json(event, acq) for event in data] + return AcquisitionEvent(acq, sequence=sequence) + + def get_camera_device_name(self): + return self.camera_ + + def set_camera_device_name(self, camera): + self.camera_ = camera + + def get_additional_properties(self): + return [(t.dev, t.prop, t.val) for t in self.properties_] + + def should_acquire_image(self): + if self.sequence_: + return True + return self.configPreset_ is not None or self.axisPositions_ is not None + + def has_config_group(self): + return self.configPreset_ is not None and self.configGroup_ is not None + + def get_config_preset(self): + return self.configPreset_ + + def get_config_group(self): + return self.configGroup_ + + def set_config_preset(self, config): + self.configPreset_ = config + + def set_config_group(self, group): + self.configGroup_ = group + + def get_exposure(self): + return self.exposure_ + + def set_exposure(self, exposure): + self.exposure_ = exposure + + def set_property(self, device, property, value): + self.properties_.add(ThreeTuple(device, property, value)) + + def set_minimum_start_time(self, l): + self.miniumumStartTime_ms_ = l + + def get_defined_axes(self): + return set(self.axisPositions_.keys()) + + def set_axis_position(self, label, position): + if position is None: + raise Exception("Cannot set axis position to null") + self.axisPositions_[label] = position + + def set_stage_coordinate(self, deviceName, v, axisName=None): + self.stageCoordinates_[deviceName] = v + self.stageDeviceNamesToAxisNames_[deviceName] = deviceName if axisName is None else axisName + + def get_stage_single_axis_stage_position(self, deviceName): + return self.stageCoordinates_.get(deviceName) + + def get_axis_positions(self): + return self.axisPositions_ + + def get_axis_position(self, label): + return self.axisPositions_.get(label) + + def get_timeout_ms(self): + return self.timeout_ms_ + + def set_time_index(self, index): + self.set_axis_position(AcqEngMetadata.TIME_AXIS, index) + + def set_channel_name(self, name): + self.set_axis_position(AcqEngMetadata.CHANNEL_AXIS, name) + + def get_slm_image(self): + return self.slmImage_ + + def set_z(self, index, position): + if index is not None: + self.set_axis_position(AcqEngMetadata.Z_AXIS, index) + self.zPosition_ = position + + def get_t_index(self): + return self.get_axis_position(AcqEngMetadata.TIME_AXIS) + + def get_z_index(self): + return self.get_axis_position(AcqEngMetadata.Z_AXIS) + + def get_device_axis_name(self, deviceName): + if deviceName not in self.stageDeviceNamesToAxisNames_: + raise Exception(f"No axis name for device {deviceName}. call setStageCoordinate first") + return self.stageDeviceNamesToAxisNames_[deviceName] + + def get_stage_device_names(self): + return set(self.stageDeviceNamesToAxisNames_.keys()) + + @staticmethod + def create_acquisition_finished_event(acq): + evt = AcquisitionEvent(acq) + evt.specialFlag_ = AcquisitionEvent.SpecialFlag.ACQUISITION_FINISHED + return evt + + def is_acquisition_finished_event(self): + return self.specialFlag_ == AcquisitionEvent.SpecialFlag.ACQUISITION_FINISHED + + @staticmethod + def create_acquisition_sequence_end_event(acq): + evt = AcquisitionEvent(acq) + evt.specialFlag_ = AcquisitionEvent.SpecialFlag.ACQUISITION_SEQUENCE_END + return evt + + def is_acquisition_sequence_end_event(self): + return self.specialFlag_ == AcquisitionEvent.SpecialFlag.ACQUISITION_SEQUENCE_END + + def get_z_position(self): + return self.zPosition_ + + def get_minimum_start_time_absolute(self): + if self.miniumumStartTime_ms_ is None: + return None + return self.acquisition_.get_start_time_ms() + self.miniumumStartTime_ms_ + + def get_sequence(self): + return self.sequence_ + + def is_exposure_sequenced(self): + return self.exposureSequenced_ + + def is_config_group_sequenced(self): + return self.configGroupSequenced_ + + def is_xy_sequenced(self): + return self.xySequenced_ + + def is_z_sequenced(self): + return self.zSequenced_ + + def get_x_position(self): + return self.xPosition_ + + def get_camera_image_counts(self, default_camera_device_name): + """ + Get the number of images to be acquired on each camera in a sequence event. + For a non-sequence event, the number of images is 1, and the camera is the core camera. + This is passed in as an argument in order to avoid this class talking to the core directly. + + Args: + default_camera_device_name (str): Default camera device name. + + Returns: + defaultdict: Dictionary containing the camera device names as keys and image counts as values. + """ + # Figure out how many images on each camera and start sequence with appropriate number on each + camera_image_counts = {} + camera_device_names = set() + if self.get_sequence() is None: + camera_image_counts[default_camera_device_name] = 1 + return camera_image_counts + + for event in self.get_sequence(): + camera_device_names.add(event.get_camera_device_name() if event.get_camera_device_name() is not None else + default_camera_device_name) + if None in camera_device_names: + camera_device_names.remove(None) + camera_device_names.add(default_camera_device_name) + + for camera_device_name in camera_device_names: + camera_image_counts[camera_device_name] = sum(1 for event in self.get_sequence() + if event.get_camera_device_name() == camera_device_name) + + if len(camera_device_names) == 1 and camera_device_name == default_camera_device_name: + camera_image_counts[camera_device_name] = len(self.get_sequence()) + + return camera_image_counts + + def get_y_position(self): + return self.yPosition_ + + def get_position_name(self): + axisPosition_ = self.get_axis_position(AcqEngMetadata.POSITION_AXIS) + if isinstance(axisPosition_, str): + return axisPosition_ + return None + + def set_x(self, x): + self.xPosition_ = x + + def set_y(self, y): + self.yPosition_ = y + + def set_tags(self, tags): + self.tags_.clear() + if tags: + self.tags_.update(tags) + + def get_tags(self): + return dict(self.tags_) + + def __str__(self): + if self.specialFlag_ == AcquisitionEvent.SpecialFlag.ACQUISITION_FINISHED: + return "Acq finished event" + elif self.specialFlag_ == AcquisitionEvent.SpecialFlag.ACQUISITION_SEQUENCE_END: + return "Acq sequence end event" + + builder = [] + for deviceName in self.stageDeviceNamesToAxisNames_.keys(): + builder.append(f"\t{deviceName}: {self.get_stage_single_axis_stage_position(deviceName)}") + + if self.zPosition_ is not None: + builder.append(f"z {self.zPosition_}") + if self.xPosition_ is not None: + builder.append(f"x {self.xPosition_}") + if self.yPosition_ is not None: + builder.append(f"y {self.yPosition_}") + + for axis in self.axisPositions_.keys(): + builder.append(f"\t{axis}: {self.axisPositions_[axis]}") + + if self.camera_ is not None: + builder.append(f"\t{self.camera_}: {self.camera_}") + + return ' '.join(builder) + + +ThreeTuple = namedtuple('ThreeTuple', ['dev', 'prop', 'val']) diff --git a/pycromanager/acquisition/acquisition_superclass.py b/pycromanager/acquisition/acquisition_superclass.py index e1962fc8..ccf00db9 100644 --- a/pycromanager/acquisition/acquisition_superclass.py +++ b/pycromanager/acquisition/acquisition_superclass.py @@ -5,38 +5,38 @@ import copy import types import numpy as np -from typing import List, Iterable +from typing import Union, List, Iterable import warnings from abc import ABCMeta, abstractmethod from docstring_inheritance import NumpyDocstringInheritanceMeta import queue import weakref from pycromanager.acq_future import AcqNotification, AcquisitionFuture +import os import threading from inspect import signature +from typing import Generator from types import GeneratorType +import time from queue import Queue from typing import Generator, Dict, Union -# from pycromanager.acquisition.execution_engine.acq_events import AcquisitionEvent -AcquisitionEvent = None class EventQueue(Queue): """ - A queue that can hold both event_implementations/lists of event_implementations and generators of event_implementations/lists of event_implementations. When a generator is + A queue that can hold both events/lists of events and generators of events/lists of events. When a generator is retrieved from the queue, it will be automatically expanded and its elements will be the output of queue.get """ def __init__(self, maxsize=0): super().__init__(maxsize) - self.current_generator: Union[Generator[AcquisitionEvent, None, None], None] = None + self.current_generator: Union[Generator[Dict, None, None], None] = None def clear(self): self.queue.clear() self.current_generator = None - def put(self, item: Union[AcquisitionEvent, List[AcquisitionEvent], - Generator[AcquisitionEvent, None, None], None], block=True, timeout=None): + def put(self, item: Union[Dict, Generator[Dict, None, None]], block=True, timeout=None): if isinstance(item, dict): super().put(item, block, timeout) elif isinstance(item, list): @@ -50,7 +50,7 @@ def put(self, item: Union[AcquisitionEvent, List[AcquisitionEvent], else: raise TypeError("Event must be a dictionary, list or generator") - def get(self, block=True, timeout=None) -> AcquisitionEvent: + def get(self, block=True, timeout=None) -> Dict: while True: if self.current_generator is None: item = super().get(block, timeout) @@ -101,21 +101,21 @@ def __init__( image processing function that will be called on each image that gets acquired. Can either take two arguments (image, metadata) where image is a numpy array and metadata is a dict containing the corresponding image metadata. Or a three argument version is accepted, which accepts (image, - metadata, queue), where queue is a Queue object that holds upcoming acquisition event_implementations. The function + metadata, queue), where queue is a Queue object that holds upcoming acquisition events. The function should return either an (image, metadata) tuple or a list of such tuples event_generation_hook_fn : Callable - hook function that will as soon as acquisition event_implementations are generated (before hardware sequencing optimization - in the acquisition engine. This is useful if one wants to modify acquisition event_implementations that they didn't generate + hook function that will as soon as acquisition events are generated (before hardware sequencing optimization + in the acquisition engine. This is useful if one wants to modify acquisition events that they didn't generate (e.g. those generated by a GUI application). Accepts either one argument (the current acquisition event) or two arguments (current event, event_queue) pre_hardware_hook_fn : Callable hook function that will be run just before the hardware is updated before acquiring - a execution_engine image. In the case of hardware sequencing, it will be run just before a sequence of instructions are + a new image. In the case of hardware sequencing, it will be run just before a sequence of instructions are dispatched to the hardware. Accepts either one argument (the current acquisition event) or two arguments (current event, event_queue) post_hardware_hook_fn : Callable hook function that will be run just before the hardware is updated before acquiring - a execution_engine image. In the case of hardware sequencing, it will be run just after a sequence of instructions are + a new image. In the case of hardware sequencing, it will be run just after a sequence of instructions are dispatched to the hardware, but before the camera sequence has been started. Accepts either one argument (the current acquisition event) or two arguments (current event, event_queue) post_camera_hook_fn : Callable @@ -131,7 +131,7 @@ def __init__( so as to not back up the processing of other notifications. image_saved_fn : Callable function that takes two arguments (the Axes of the image that just finished saving, and the Dataset) - or three arguments (Axes, Dataset and the event_queue) and gets called whenever a execution_engine image is written to + or three arguments (Axes, Dataset and the event_queue) and gets called whenever a new image is written to disk napari_viewer : napari.Viewer Provide a napari viewer to display acquired data in napari (https://napari.org/) rather than the built-in @@ -221,35 +221,35 @@ def get_dataset(self): def mark_finished(self): """ - Signal to acquisition that no more event_implementations will be added and it is time to initiate shutdown. + Signal to acquisition that no more events will be added and it is time to initiate shutdown. This is only needed if the context manager (i.e. "with Acquisition...") is not used. """ - # Some acquisition types (e.g. ExploreAcquisitions) generate their own event_implementations - # and don't send event_implementations over a port + # Some acquisition types (e.g. ExploreAcquisitions) generate their own events + # and don't send events over a port if self._event_queue is not None: - # this should shut down storage_implementations and viewer as appropriate + # this should shut down storage and viewer as appropriate self._event_queue.put(None) def acquire(self, event_or_events: dict or list or Generator) -> AcquisitionFuture: """ - Submit an event or a list of event_implementations for acquisition. A single event is a python dictionary - with a specific structure. The acquisition engine will determine if multiple event_implementations can + Submit an event or a list of events for acquisition. A single event is a python dictionary + with a specific structure. The acquisition engine will determine if multiple events can be merged into a hardware sequence and executed at once without computer-hardware communication in - between. This sequencing will only take place for event_implementations that are within a single call to acquire, + between. This sequencing will only take place for events that are within a single call to acquire, so if you want to ensure this doesn't happen, call acquire multiple times with each event in a list individually. Parameters ---------- event_or_events : list, dict, Generator - A single acquistion event (a dict), a list of acquisition event_implementations, or a generator that yields - acquisition event_implementations. + A single acquistion event (a dict), a list of acquisition events, or a generator that yields + acquisition events. """ try: - if self._are_events_finished(): + if self._acq.are_events_finished(): raise AcqAlreadyCompleteException( - 'Cannot submit more event_implementations because this acquisition is already finished') + 'Cannot submit more events because this acquisition is already finished') if event_or_events is None: # manual shutdown @@ -260,8 +260,8 @@ def acquire(self, event_or_events: dict or list or Generator) -> AcquisitionFutu acq_future = AcquisitionFuture(self) def notifying_generator(original_generator): - # store in a weakref so that if user code doesn't hang on to AcqFuture - # it doesn't needlessly track event_implementations + # store in a weakref so that if user code doesn't hange on to AcqFuture + # it doesn't needlessly track events acq_future_weakref = weakref.ref(acq_future) for event in original_generator: future = acq_future_weakref() @@ -269,7 +269,6 @@ def notifying_generator(original_generator): acq_future._monitor_axes(event['axes']) _validate_acq_events(event) yield event - event_or_events = notifying_generator(event_or_events) else: _validate_acq_events(event_or_events) @@ -288,7 +287,7 @@ def notifying_generator(original_generator): def abort(self, exception=None): """ - Cancel any pending event_implementations and shut down immediately + Cancel any pending events and shut down immediately Parameters ---------- @@ -299,23 +298,17 @@ def abort(self, exception=None): if exception is not None: self._exception = exception - # Clear any pending event_implementations on the python side, if applicable + # Clear any pending events on the python side, if applicable if self._event_queue is not None: self._event_queue.clear() - # Don't send any more event_implementations. The event sending thread should know shut itself down by + # Don't send any more events. The event sending thread should know shut itself down by # checking the status of the acquisition - self.abort() + self._acq.abort() - @abstractmethod - def _are_events_finished(self): - """ - Check if all event_implementations have been processed and executed - """ - pass def _add_storage_monitor_fn(self, image_saved_fn=None): """ - Add a callback function that gets called whenever a execution_engine image is writtern to disk (for acquisitions in + Add a callback function that gets called whenever a new image is writtern to disk (for acquisitions in progress only) Parameters @@ -350,7 +343,7 @@ def _storage_monitor_fn(): return t def _create_event_queue(self): - """Create thread safe queue for event_implementations so they can be passed from multiple processes""" + """Create thread safe queue for events so they can be passed from multiple processes""" self._event_queue = EventQueue() def _call_image_process_fn(self, image, metadata): @@ -383,8 +376,8 @@ def __exit__(self, exc_type, exc_val, exc_tb): def _validate_acq_events(events: dict or list): """ - Validate if supplied event_implementations are a dictionary or a list of dictionaries - that contain valid event_implementations. Throw an exception if not + Validate if supplied events are a dictionary or a list of dictionaries + that contain valid events. Throw an exception if not Parameters ---------- @@ -395,14 +388,14 @@ def _validate_acq_events(events: dict or list): _validate_acq_dict(events) elif isinstance(events, list): if len(events) == 0: - raise Exception('event_implementations list cannot be empty') + raise Exception('events list cannot be empty') for event in events: if isinstance(event, dict): _validate_acq_dict(event) else: - raise Exception('event_implementations must be a dictionary or a list of dictionaries') + raise Exception('events must be a dictionary or a list of dictionaries') else: - raise Exception('event_implementations must be a dictionary or a list of dictionaries') + raise Exception('events must be a dictionary or a list of dictionaries') def _validate_acq_dict(event: dict): """ @@ -429,20 +422,20 @@ def _validate_acq_dict(event: dict): def multi_d_acquisition_events( - num_time_points: int = None, - time_interval_s: Union[float, List[float]] = 0, - z_start: float = None, - z_end: float = None, - z_step: float = None, - channel_group: str = None, - channels: list = None, - channel_exposures_ms: list = None, - xy_positions: Iterable = None, - xyz_positions: Iterable = None, - position_labels: List[str] = None, - order: str = "tpcz", + num_time_points: int=None, + time_interval_s: Union[float, List[float]]=0, + z_start: float=None, + z_end: float=None, + z_step: float=None, + channel_group: str=None, + channels: list=None, + channel_exposures_ms: list=None, + xy_positions: Iterable=None, + xyz_positions: Iterable=None, + position_labels: List[str]=None, + order: str="tpcz", ): - """Convenience function for generating the event_implementations of a typical multi-dimensional acquisition (i.e. an + """Convenience function for generating the events of a typical multi-dimensional acquisition (i.e. an acquisition with some combination of multiple timepoints, channels, z-slices, or xy positions) Parameters @@ -450,8 +443,8 @@ def multi_d_acquisition_events( num_time_points : int How many time points if it is a timelapse (Default value = None) time_interval_s : float or list of floats - the minimum interval between consecutive time points in seconds. If set to 0, the - acquisition will go as fast as possible. If a list is provided, its length should + the minimum interval between consecutive time points in seconds. If set to 0, the + acquisition will go as fast as possible. If a list is provided, its length should be equal to 'num_time_points'. Elements in the list are assumed to be the intervals between consecutive timepoints in the timelapse. First element in the list indicates delay before capturing the first image (Default value = 0) @@ -486,7 +479,7 @@ def multi_d_acquisition_events( Returns ------- - event_implementations : dict + events : dict """ if xy_positions is not None and xyz_positions is not None: raise ValueError( @@ -507,7 +500,7 @@ def multi_d_acquisition_events( raise ValueError("xy_positions and position_labels must be of equal length") if xyz_positions is not None and len(xyz_positions) != len(position_labels): raise ValueError("xyz_positions and position_labels must be of equal length") - + # If any of z_start, z_step, z_end are provided, then they should all be provided # Here we can't use `all` as some of the values of z_start, z_step, z_end # may be zero and all((0,)) = False @@ -583,7 +576,7 @@ def generate_events(event, order): elif order[0] == "c" and channel_group is not None and channels is not None: for i in range(len(channels)): new_event = copy.deepcopy(event) - new_event["config_group"] = [channel_group, channels[i]] + new_event["config_group"] = [channel_group, channels[i]] new_event["axes"]["channel"] = channels[i] if channel_exposures_ms is not None: new_event["exposure"] = channel_exposures_ms[i] @@ -592,7 +585,7 @@ def generate_events(event, order): # this axis appears to be missing yield generate_events(event, order[1:]) - # collect all event_implementations into a single list + # collect all events into a single list base_event = {"axes": {}} events = [] @@ -615,4 +608,6 @@ def appender(next): events.append(next) appender(generate_events(base_event, order)) - return events \ No newline at end of file + return events + + diff --git a/pycromanager/acquisition/python_backend_acquisitions.py b/pycromanager/acquisition/python_backend_acquisitions.py index 09400a7d..070adc93 100644 --- a/pycromanager/acquisition/python_backend_acquisitions.py +++ b/pycromanager/acquisition/python_backend_acquisitions.py @@ -1,34 +1,25 @@ import warnings from docstring_inheritance import NumpyDocstringInheritanceMeta +from pycromanager.acquisition.acq_eng_py.main.AcqEngPy_Acquisition import Acquisition as pymmcore_Acquisition from pycromanager.acquisition.acquisition_superclass import _validate_acq_events, Acquisition -# from pycromanager.acquisition.execution_engine.acq_events import AcquisitionEvent -#TODO: -AcquisitionEvent = None -from pycromanager.acquisition.acq_eng_py.main.acq_eng_metadata import AcqEngMetadata -from pycromanager.acquisition.acq_eng_py.main.acq_notification import AcqNotification -from pycromanager.acquisition.acq_eng_py.internal.notification_handler import NotificationHandler -from pycromanager.acquisition.acq_eng_py.internal.engine import Engine +from pycromanager.acquisition.acq_eng_py.main.acquisition_event import AcquisitionEvent +from pycromanager.acq_future import AcqNotification import threading from inspect import signature import traceback -import queue from ndstorage.ndram_dataset import NDRAMDataset from ndstorage.ndtiff_dataset import NDTiffDataset -from pycromanager.acquisition.acq_eng_py.internal.hooks import EVENT_GENERATION_HOOK, \ - BEFORE_HARDWARE_HOOK, BEFORE_Z_DRIVE_HOOK, AFTER_HARDWARE_HOOK, AFTER_CAMERA_HOOK, AFTER_EXPOSURE_HOOK - - -IMAGE_QUEUE_SIZE = 30 - - class PythonBackendAcquisition(Acquisition, metaclass=NumpyDocstringInheritanceMeta): """ - Pycro-Manager acquisition that uses a Python runtime backend. + Pycro-Manager acquisition that uses a Python runtime backend. Unlike the Java backend, + Python-backed acquisitions currently do not automatically write data to disk. Instead, by default, + they store data in RAM which can be queried with the Dataset class. If instead you want to + implement your own data storage, you can pass an image_process_fn which diverts the data to + a custom endpoint. """ - def __init__( self, directory: str=None, @@ -51,9 +42,6 @@ def __init__( dict(signature(PythonBackendAcquisition.__init__).parameters.items())[arg_name].default) for arg_name in arg_names } super().__init__(**named_args) - - self._engine = Engine.get_instance() - self._dataset = NDRAMDataset() if not directory else NDTiffDataset(directory, name=name, writable=True) self._finished = False self._notifications_finished = False @@ -63,52 +51,25 @@ def __init__( self._image_processor = ImageProcessor(self) if image_process_fn is not None else None - # create a thread that submits event_implementations - # event_implementations can be added to the queue through image processors, hooks, or the acquire method + # create a thread that submits events + # events can be added to the queue through image processors, hooks, or the acquire method def submit_events(): while True: event_or_events = self._event_queue.get() if event_or_events is None: - self._finish() - self._events_finished.wait() + self._acq.finish() + self._acq.block_until_events_finished() break _validate_acq_events(event_or_events) if isinstance(event_or_events, dict): event_or_events = [event_or_events] # convert to objects - event_or_events = [AcquisitionEvent.from_json(event, self) for event in event_or_events] - Engine.get_instance().submit_event_iterator(iter(event_or_events)) - + event_or_events = [AcquisitionEvent.from_json(event, self._acq) for event in event_or_events] + self._acq.submit_event_iterator(iter(event_or_events)) self._event_thread = threading.Thread(target=submit_events) self._event_thread.start() - self._events_finished = threading.Event() - self.abort_requested_ = threading.Event() - self.start_time_ms_ = -1 - self.paused_ = False - - self.event_generation_hooks_ = [] - self.before_hardware_hooks_ = [] - self.before_z_hooks_ = [] - self.after_hardware_hooks_ = [] - self.after_camera_hooks_ = [] - self.after_exposure_hooks_ = [] - self.image_processors_ = [] - - self.first_dequeue_ = queue.Queue(maxsize=IMAGE_QUEUE_SIZE) - self.processor_output_queues_ = {} - self.debug_mode_ = False - self.abort_exception_ = None - self.image_metadata_processor_ = None - self.notification_handler_ = NotificationHandler() - self.started_ = False - self.core_ = Engine.get_core() - self.data_sink_ = self._dataset - - summary_metadata = AcqEngMetadata.make_summary_metadata(self.core_, self) - - if self.data_sink_: - self.data_sink_.initialize(summary_metadata) + self._acq = pymmcore_Acquisition(self._dataset) # receive notifications from the acquisition engine. Unlike the java_backend analog # of this, the python backend does not have a separate thread for notifications because @@ -122,7 +83,7 @@ def post_notification(notification): if self._image_notification_queue.qsize() > self._image_notification_queue.maxsize * 0.9: warnings.warn(f"Acquisition image notification queue size: {self._image_notification_queue.qsize()}") - self._add_acq_notification_listener(NotificationListener(post_notification)) + self._acq.add_acq_notification_listener(NotificationListener(post_notification)) self._notification_dispatch_thread = self._start_notification_dispatcher(notification_callback_fn) @@ -153,10 +114,7 @@ def post_notification(notification): assert isinstance(napari_viewer, napari.Viewer), 'napari_viewer must be an instance of napari.Viewer' self._napari_viewer = napari_viewer start_napari_signalling(self._napari_viewer, self.get_dataset()) - - self._start_saving_thread() - self._post_notification(AcqNotification.create_acq_started_notification()) - self.started_ = True + self._acq.start() ######## Public API ########### @@ -164,13 +122,12 @@ def post_notification(notification): def await_completion(self): """Wait for acquisition to finish and resources to be cleaned up""" try: - while not self._are_events_finished() or ( - self._dataset is not None and not self._dataset.is_finished()): + while not self._acq.are_events_finished() or ( + self._acq.get_data_sink() is not None and not self._acq.get_data_sink().is_finished()): self._check_for_exceptions() - self._events_finished.wait(0.05) - if self._dataset is not None: - self._dataset.block_until_finished(0.05) - # time.sleep(0.05) # does this prevent things from getting stuck? + self._acq.block_until_events_finished(0.05) + if self._acq.get_data_sink() is not None: + self._acq.get_data_sink().block_until_finished(0.05) self._check_for_exceptions() finally: self._event_thread.join() @@ -208,130 +165,10 @@ def _check_for_exceptions(self): def _are_acquisition_notifications_finished(self): """ - Called by the storage_implementations to check if all notifications have been processed + Called by the storage to check if all notifications have been processed """ return self._notifications_finished - - def _post_notification(self, notification): - self.notification_handler_.post_notification(notification) - - def _add_acq_notification_listener(self, post_notification_fn): - self.notification_handler_.add_listener(post_notification_fn) - - def _save_image(self, image): - if image is None: - self.data_sink_.finish() - self._post_notification(AcqNotification.create_data_sink_finished_notification()) - else: - pixels, metadata = image.pix, image.tags - axes = AcqEngMetadata.get_axes(metadata) - self.data_sink_.put_image(axes, pixels, metadata) - self._post_notification(AcqNotification.create_image_saved_notification(axes)) - - def _start_saving_thread(self): - def saving_thread(acq): - try: - while True: - if acq.debug_mode_: - acq.core_.log_message(f"Image queue size: {len(acq.first_dequeue_)}") - if not acq.image_processors_: - if acq.debug_mode_: - acq.core_.log_message("waiting for image to save") - img = acq.first_dequeue_.get() - if acq.debug_mode_: - acq.core_.log_message("got image to save") - acq._save_image(img) - if img is None: - break - else: - img = acq.processor_output_queues_[acq.image_processors_[-1]].get() - if acq.data_sink_: - if acq.debug_mode_: - acq.core_.log_message("Saving image") - if img.tags is None and img.pix is None: - break - acq._save_image(img) - if acq.debug_mode_: - acq.core_.log_message("Finished saving image") - except Exception as ex: - traceback.print_exc() - acq.abort(ex) - finally: - acq._save_image(None) - - threading.Thread(target=saving_thread, args=(self,)).start() - - - def _add_to_output(self, ti): - try: - if ti is None: - self._events_finished.set() - self.first_dequeue_.put(ti) - except Exception as ex: - raise RuntimeError(ex) - - def _finish(self): - Engine.get_instance().finish_acquisition(self) - - def _abort(self, ex): - if ex: - self.abort_exception_ = ex - if self.abort_requested_.is_set(): - return - self.abort_requested_.set() - if self.is_paused(): - self.set_paused(False) - Engine.get_instance().finish_acquisition(self) - - def _check_for_exceptions(self): - if self.abort_exception_: - raise self.abort_exception_ - - def _add_image_processor(self, p): - if self.started_: - raise RuntimeError("Cannot add processor after acquisition started") - self.image_processors_.append(p) - self.processor_output_queues_[p] = queue.Queue(maxsize=self.IMAGE_QUEUE_SIZE) - if len(self.image_processors_) == 1: - p.set_acq_and_queues(self, self.first_dequeue_, self.processor_output_queues_[p]) - else: - p.set_acq_and_queues(self, self.processor_output_queues_[self.image_processors_[-2]], - self.processor_output_queues_[self.image_processors_[-1]]) - - def _add_hook(self, h, type_): - if self.started_: - raise RuntimeError("Cannot add hook after acquisition started") - if type_ == EVENT_GENERATION_HOOK: - self.event_generation_hooks_.append(h) - elif type_ == BEFORE_HARDWARE_HOOK: - self.before_hardware_hooks_.append(h) - elif type_ == BEFORE_Z_DRIVE_HOOK: - self.before_z_hooks_.append(h) - elif type_ == AFTER_HARDWARE_HOOK: - self.after_hardware_hooks_.append(h) - elif type_ == AFTER_CAMERA_HOOK: - self.after_camera_hooks_.append(h) - elif type_ == AFTER_EXPOSURE_HOOK: - self.after_exposure_hooks_.append(h) - - def _get_hooks(self, type): - if type == EVENT_GENERATION_HOOK: - return self.event_generation_hooks_ - elif type == BEFORE_HARDWARE_HOOK: - return self.before_hardware_hooks_ - elif type == BEFORE_Z_DRIVE_HOOK: - return self.before_z_hooks_ - elif type == AFTER_HARDWARE_HOOK: - return self.after_hardware_hooks_ - elif type == AFTER_CAMERA_HOOK: - return self.after_camera_hooks_ - elif type == AFTER_EXPOSURE_HOOK: - return self.after_exposure_hooks_ - - def _are_events_finished(self): - return self._events_finished.is_set() - class ImageProcessor: """ This is the equivalent of RemoteImageProcessor in the Java version. @@ -354,7 +191,7 @@ def _process(self): while True: # wait for an image to arrive tagged_image = self.input_queue.get() - if tagged_image is None: + if tagged_image.tags is None and tagged_image.pix is None: # this is a signal to stop self.output_queue.put(tagged_image) break diff --git a/scripts/bridge_tests.py b/scripts/bridge_tests.py index 70ccf89b..3ec328d7 100644 --- a/scripts/bridge_tests.py +++ b/scripts/bridge_tests.py @@ -10,7 +10,7 @@ def other_thread(core): core = None -### Create an object and a child object on a execution_engine socket +### Create an object and a child object on a new socket core = ZMQRemoteMMCoreJ(debug=False) core.get_system_state_cache(new) diff --git a/scripts/camera_triggering/genIexamples.py b/scripts/camera_triggering/genIexamples.py index 93cf2b41..82556ac4 100644 --- a/scripts/camera_triggering/genIexamples.py +++ b/scripts/camera_triggering/genIexamples.py @@ -130,7 +130,7 @@ def live_mode_hardware_trigger(): core.set_exposure(camera_name, 500) core.arm_acquisition(camera_name) core.start_acquisition(camera_name) - # TODO: event_implementations + # TODO: events # Register(Camera.EventExposureEnd, CallbackDataObject, CallbackFunctionPtr) # EventSelector = ExposureEnd; # EventNotification = On; @@ -178,7 +178,7 @@ def multiple_bursts_hardware_trigger(): # TriggerMode = On; # TriggerActivation = RisingEdge; # TriggerSource = Line1; - # TODO event_implementations + # TODO events # Register(Camera.EventFrameBurstEnd,CallbackDataObject,CallbackFunctionPtr) # EventSelector = FrameBurstEnd; # EventNotification = On; diff --git a/scripts/custom_axis_acq.py b/scripts/custom_axis_acq.py index 2c1c3e6e..1b423aa5 100644 --- a/scripts/custom_axis_acq.py +++ b/scripts/custom_axis_acq.py @@ -8,7 +8,7 @@ for time in range(5): for index, z_um in enumerate(np.arange(start=0, stop=10, step=0.5)): evt = { - #'axes' is required. It is used by the image viewer and data storage_implementations to + #'axes' is required. It is used by the image viewer and data storage to # identify the acquired image "axes": {"l": index, "time": time}, # the 'z' field provides the z position in µm diff --git a/scripts/external_camera_trigger.py b/scripts/external_camera_trigger.py index 3d809539..36afb497 100644 --- a/scripts/external_camera_trigger.py +++ b/scripts/external_camera_trigger.py @@ -13,7 +13,7 @@ def external_trigger_fn(event): name="tcz_acq", post_camera_hook_fn=external_trigger_fn, ) as acq: - # Generate the event_implementations for a single z-stack + # Generate the events for a single z-stack events = multi_d_acquisition_events( num_time_points=10, time_interval_s=0, diff --git a/scripts/generate_ndtiff_test.py b/scripts/generate_ndtiff_test.py index cebe0bdc..69e2d7cf 100644 --- a/scripts/generate_ndtiff_test.py +++ b/scripts/generate_ndtiff_test.py @@ -26,7 +26,7 @@ with JavaBackendAcquisition(directory=save_dir, name="ndtiffv3.0_test", show_display=True, ) as acq: - # Generate the event_implementations for a single z-stack + # Generate the events for a single z-stack events = multi_d_acquisition_events( num_time_points=5, time_interval_s=0, diff --git a/scripts/headless_demo.py b/scripts/headless_demo.py index 1507274f..a727f552 100644 --- a/scripts/headless_demo.py +++ b/scripts/headless_demo.py @@ -28,7 +28,7 @@ def image_saved_fn(axes, dataset): with JavaBackendAcquisition(directory=save_dir, name="tcz_acq", show_display=True, image_saved_fn=image_saved_fn ) as acq: - # Generate the event_implementations for a single z-stack + # Generate the events for a single z-stack events = multi_d_acquisition_events( num_time_points=5, time_interval_s=0, diff --git a/scripts/image_processor.py b/scripts/image_processor.py index b10663f6..e0b112d9 100644 --- a/scripts/image_processor.py +++ b/scripts/image_processor.py @@ -10,7 +10,7 @@ def img_process_fn(image, metadata): with JavaBackendAcquisition( directory=r"C:\Users\henry\Desktop\datadump", name="tcz_acq", image_process_fn=img_process_fn ) as acq: - # Generate the event_implementations for a single z-stack + # Generate the events for a single z-stack events = multi_d_acquisition_events( num_time_points=10, time_interval_s=0, diff --git a/scripts/image_processor_divert.py b/scripts/image_processor_divert.py index 9347a0ff..f08dc556 100644 --- a/scripts/image_processor_divert.py +++ b/scripts/image_processor_divert.py @@ -7,7 +7,7 @@ def img_process_fn(image, metadata): pass # send them somewhere else, not default saving and display with JavaBackendAcquisition(image_process_fn=img_process_fn) as acq: - # Generate the event_implementations for a single z-stack + # Generate the events for a single z-stack events = multi_d_acquisition_events( num_time_points=10, time_interval_s=0, diff --git a/scripts/image_processor_multiple.py b/scripts/image_processor_multiple.py index 30ceb41d..115ded99 100644 --- a/scripts/image_processor_multiple.py +++ b/scripts/image_processor_multiple.py @@ -26,7 +26,7 @@ def img_process_fn(image, metadata): with JavaBackendAcquisition( directory="/Users/henrypinkard/megllandump", name="tcz_acq", image_process_fn=img_process_fn ) as acq: - # Generate the event_implementations for a single z-stack + # Generate the events for a single z-stack events = multi_d_acquisition_events( num_time_points=10, time_interval_s=0, diff --git a/scripts/lightsheet_deskew.py b/scripts/lightsheet_deskew.py index a4b81e15..5fc769bc 100644 --- a/scripts/lightsheet_deskew.py +++ b/scripts/lightsheet_deskew.py @@ -104,7 +104,7 @@ def precompute_recon_weightings(self, do_orthogonal_views=True, do_volume=True): for z_index_camera in np.arange(self.camera_shape[0]): for y_index_camera in np.arange(self.camera_shape[1]): - # where does each line of x pixels belong in the execution_engine image? + # where does each line of x pixels belong in the new image? if (z_index_camera, y_index_camera) not in self.recon_coord_LUT: print('ignoring: ', z_index_camera, y_index_camera) continue @@ -145,7 +145,7 @@ def make_projections(self, data, do_orthogonal_views=True, do_volume=True): # do the projection/reconstruction # iterate through each z slice of the image - # at each z slice, iterate through each x pixel and copy a line of y pixels to the execution_engine image + # at each z slice, iterate through each x pixel and copy a line of y pixels to the new image for z_index_camera in np.arange(0, self.camera_shape[0], 1): image_on_camera = data[z_index_camera] for y_index_camera in range(self.camera_shape[1]): @@ -153,7 +153,7 @@ def make_projections(self, data, do_orthogonal_views=True, do_volume=True): continue source_line_of_x_pixels = image_on_camera[y_index_camera] - # where does each line of x pixels belong in the execution_engine image? + # where does each line of x pixels belong in the new image? dest_coords = self.recon_coord_LUT[(z_index_camera, y_index_camera)] for dest_coord in dest_coords: recon_z, recon_y = dest_coord diff --git a/scripts/magellan_surfaces.py b/scripts/magellan_surfaces.py index 482e71d2..ecb41542 100644 --- a/scripts/magellan_surfaces.py +++ b/scripts/magellan_surfaces.py @@ -27,7 +27,7 @@ ### Part 3a run autofocus ##### - # TODO: maybe run an initial focus integration_tests to see how off the surface is + # TODO: maybe run an initial focus test to see how off the surface is # this function will run after the hardware has been updated (i.e. xy stage moved) but before each image is acquired diff --git a/scripts/multi_d_acq.py b/scripts/multi_d_acq.py index 39e82525..d5411af5 100644 --- a/scripts/multi_d_acq.py +++ b/scripts/multi_d_acq.py @@ -2,7 +2,7 @@ with JavaBackendAcquisition(directory=r"/Users/henrypinkard/tmp", name="tcz_acq", debug=False) as acq: - # Generate the event_implementations for a single z-stack + # Generate the events for a single z-stack events = multi_d_acquisition_events( num_time_points=8, time_interval_s=0, diff --git a/scripts/napari_frontend.py b/scripts/napari_frontend.py index 7e8e9fe6..b6618f41 100644 --- a/scripts/napari_frontend.py +++ b/scripts/napari_frontend.py @@ -22,7 +22,7 @@ def image_saved_callback(axes, d): """ - Callback function that will be used to signal to napari that a execution_engine image is ready + Callback function that will be used to signal to napari that a new image is ready """ global dataset global update_ready @@ -48,7 +48,7 @@ def run_acq(): def update_layer(image): """ - update the napari layer with the execution_engine image + update the napari layer with the new image """ if len(viewer.layers) == 0: viewer.add_image(image) @@ -61,14 +61,14 @@ def update_layer(image): @thread_worker(connect={'yielded': update_layer}) def update_images(): """ - Monitor for signals that Acqusition has a execution_engine image ready, and when that happens + Monitor for signals that Acqusition has a new image ready, and when that happens update napari appropriately """ global update_ready while True: if update_ready: update_ready = False - # A execution_engine image has arrived, but we only need to regenerate the dask array + # A new image has arrived, but we only need to regenerate the dask array # if its shape has changed shape = np.array([len(dataset.axes[name]) for name in dataset.axes.keys()]) if not hasattr(update_images, 'old_shape') or \ diff --git a/scripts/speed_test.py b/scripts/speed_test.py index 2942711f..65291e77 100644 --- a/scripts/speed_test.py +++ b/scripts/speed_test.py @@ -1,7 +1,7 @@ from pycromanager import JavaClass, ZMQRemoteMMCoreJ -tester = JavaClass('org.micromanager.acquisition.kernel.acqengjcompat.speedtest.SpeedTest') +tester = JavaClass('org.micromanager.acquisition.internal.acqengjcompat.speedtest.SpeedTest') pass dir = r'C:\Users\henry\Desktop\data' diff --git a/scripts/string_axes.py b/scripts/string_axes.py index 061a95fe..6cbe26b0 100644 --- a/scripts/string_axes.py +++ b/scripts/string_axes.py @@ -1,5 +1,5 @@ """ -integration_tests the ability to acquisitions to have String axes instead of int ones +test the ability to acquisitions to have String axes instead of int ones """ @@ -8,7 +8,7 @@ with JavaBackendAcquisition(directory="/Users/henrypinkard/tmp", name="NDTiff3.2_monochrome", debug=False) as acq: - # Generate the event_implementations for a single z-stack + # Generate the events for a single z-stack events = multi_d_acquisition_events_new( num_time_points=8, time_interval_s=0,