예제 #1
0
class Acquisition(object):
    """ """
    def __init__(
        self,
        directory=None,
        name=None,
        image_process_fn=None,
        event_generation_hook_fn=None,
        pre_hardware_hook_fn=None,
        post_hardware_hook_fn=None,
        post_camera_hook_fn=None,
        show_display=True,
        tile_overlap=None,
        max_multi_res_index=None,
        magellan_acq_index=None,
        magellan_explore=False,
        process=False,
        debug=False,
    ):
        """
        Parameters
        ----------
        directory : str
            saving directory for this acquisition. Required unless an image process function will be
            implemented that diverts images from saving
        name : str
            Saving name for the acquisition. Required unless an image process function will be
            implemented that diverts images from saving
        image_process_fn : Callable
            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 iamge metadata. Or a 4 argument version is accepted, which accepts (image,
            metadata, bridge, queue), where bridge and queue are an instance of the pycromanager.acquire.Bridge
            object for the purposes of interacting with arbitrary code on the Java side (such as the micro-manager
            core), and queue is a Queue objects that holds upcomning acquisition events. Both version must either
            return
        event_generation_hook_fn : Callable
            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 three arguments (current event, bridge, event Queue)
        pre_hardware_hook_fn : Callable
            hook function that will be run just before the hardware is updated before acquiring
            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 three arguments
            (current event, bridge, event Queue)
        post_hardware_hook_fn : Callable
            hook function that will be run just before the hardware is updated before acquiring
            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 three arguments (current event, bridge, event Queue)
        post_camera_hook_fn : Callable
            hook function that will be run just after the camera has been triggered to snapImage or
            startSequence. A common use case for this hook is when one want to send TTL triggers to the camera from an
            external timing device that synchronizes with other hardware. Accepts either one argument (the current
            acquisition event) or three arguments (current event, bridge, event Queue)
        tile_overlap : int or tuple of int
            If given, XY tiles will be laid out in a grid and multi-resolution saving will be
            actived. Argument can be a two element tuple describing the pixel overlaps between adjacent
            tiles. i.e. (pixel_overlap_x, pixel_overlap_y), or an integer to use the same overlap for both.
            For these features to work, the current hardware configuration must have a valid affine transform
            between camera coordinates and XY stage coordinates
        max_multi_res_index : int
            Maximum index to downsample to in multi-res pyramid mode (which is only active if a value for
            "tile_overlap" is passed in, or if running a Micro-Magellan acquisition). 0 is no downsampling,
            1 is downsampled up to 2x, 2 is downsampled up to 4x, etc. If not provided, it will be dynamically
            calculated and updated from data
        show_display : bool
            show the image viewer window
        magellan_acq_index : int
            run this acquisition using the settings specified at this position in the main
            GUI of micro-magellan (micro-manager plugin). This index starts at 0
        magellan_explore : bool
            Run a Micro-magellan explore acquisition
        process : bool
            Use multiprocessing instead of multithreading for acquisition hooks and image
            processors. This can be used to speed up CPU-bounded processing by eliminating bottlenecks
            caused by Python's Global Interpreter Lock, but also creates complications on Windows-based
            systems
        debug : bool
            whether to print debug messages
        """
        self.bridge = Bridge(debug=debug)
        self._debug = debug
        self._dataset = None

        if directory is not None:
            # Expend ~ in path
            directory = os.path.expanduser(directory)
            # If path is relative, retain knowledge of the current working directory
            directory = os.path.abspath(directory)

        if magellan_acq_index is not None:
            magellan_api = self.bridge.get_magellan()
            self._remote_acq = magellan_api.create_acquisition(
                magellan_acq_index)
            self._event_queue = None
        elif magellan_explore:
            magellan_api = self.bridge.get_magellan()
            self._remote_acq = magellan_api.create_explore_acquisition()
            self._event_queue = None
        else:
            # Create thread safe queue for events so they can be passed from multiple processes
            self._event_queue = multiprocessing.Queue(
            ) if process else queue.Queue()
            core = self.bridge.get_core()
            acq_factory = self.bridge.construct_java_object(
                "org.micromanager.remote.RemoteAcquisitionFactory",
                args=[core])

            show_viewer = show_display and (directory is not None
                                            and name is not None)
            if tile_overlap is None:
                # argument placeholders, these wont actually be used
                x_overlap = 0
                y_overlap = 0
            else:
                if type(tile_overlap) is tuple:
                    x_overlap, y_overlap = tile_overlap
                else:
                    x_overlap = tile_overlap
                    y_overlap = tile_overlap

            self._remote_acq = acq_factory.create_acquisition(
                directory,
                name,
                show_viewer,
                tile_overlap is not None,
                x_overlap,
                y_overlap,
                max_multi_res_index if max_multi_res_index is not None else -1,
            )
        storage = self._remote_acq.get_data_sink()
        if storage is not None:
            self.disk_location = storage.get_disk_location()

        if image_process_fn is not None:
            processor = self.bridge.construct_java_object(
                "org.micromanager.remote.RemoteImageProcessor")
            self._remote_acq.add_image_processor(processor)
            self._start_processor(processor,
                                  image_process_fn,
                                  self._event_queue,
                                  process=process)

        if event_generation_hook_fn is not None:
            hook = self.bridge.construct_java_object(
                "org.micromanager.remote.RemoteAcqHook",
                args=[self._remote_acq])
            self._start_hook(hook,
                             event_generation_hook_fn,
                             self._event_queue,
                             process=process)
            self._remote_acq.add_hook(hook,
                                      self._remote_acq.EVENT_GENERATION_HOOK)
        if pre_hardware_hook_fn is not None:
            hook = self.bridge.construct_java_object(
                "org.micromanager.remote.RemoteAcqHook",
                args=[self._remote_acq])
            self._start_hook(hook,
                             pre_hardware_hook_fn,
                             self._event_queue,
                             process=process)
            self._remote_acq.add_hook(hook,
                                      self._remote_acq.BEFORE_HARDWARE_HOOK)
        if post_hardware_hook_fn is not None:
            hook = self.bridge.construct_java_object(
                "org.micromanager.remote.RemoteAcqHook",
                args=[self._remote_acq])
            self._start_hook(hook,
                             post_hardware_hook_fn,
                             self._event_queue,
                             process=process)
            self._remote_acq.add_hook(hook,
                                      self._remote_acq.AFTER_HARDWARE_HOOK)
        if post_camera_hook_fn is not None:
            hook = self.bridge.construct_java_object(
                "org.micromanager.remote.RemoteAcqHook",
                args=[self._remote_acq])
            self._start_hook(hook,
                             post_camera_hook_fn,
                             self._event_queue,
                             process=process)
            self._remote_acq.add_hook(hook, self._remote_acq.AFTER_CAMERA_HOOK)

        self._remote_acq.start()

        if magellan_acq_index is None and not magellan_explore:
            self.event_port = self._remote_acq.get_event_port()

            self.event_process = threading.Thread(
                target=_event_sending_fn,
                args=(self.event_port, self._event_queue, self._debug),
                name="Event sending",
            )
            self.event_process.start()

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        if self._event_queue is not None:  # magellan acquisitions dont have this
            # this should shut down storage and viewer as apporpriate
            self._event_queue.put(None)
        # now wait on it to finish
        self.await_completion()

    def get_disk_location(self):
        """
        Return the path where the dataset is on disk
        """
        return self._remote_acq.get_storage().get_disk_location()

    def get_dataset(self):
        """ """
        if self._dataset is None:
            self._dataset = Dataset(
                remote_storage=self._remote_acq.get_storage())
        return self._dataset

    def await_completion(self):
        """Wait for acquisition to finish and resources to be cleaned up"""
        while not self._remote_acq.is_finished():
            time.sleep(0.1)

    def acquire(self, events, keep_shutter_open=False):
        """Submit an event or a list of events for acquisition. Optimizations (i.e. taking advantage of
        hardware synchronization, where available), will take place across this list of events, but not
        over multiple calls of this method. A single event is a python dictionary with a specific structure

        Parameters
        ----------
        events

        keep_shutter_open :
             (Default value = False)

        Returns
        -------


        """
        if keep_shutter_open and isinstance(events, list):
            for e in events:
                e["keep_shutter_open"] = True
            events.append({"keep_shutter_open": False
                           })  # return to autoshutter, dont acquire an image
        elif keep_shutter_open and isinstance(events, dict):
            events["keep_shutter_open"] = True
            events = [
                events,
                {
                    "keep_shutter_open": False
                },
            ]  # return to autoshutter, dont acquire an image
        self._event_queue.put(events)

    def _start_hook(self, remote_hook, remote_hook_fn, event_queue, process):
        """

        Parameters
        ----------
        remote_hook :

        remote_hook_fn :

        event_queue :

        process :


        Returns
        -------

        """
        hook_connected_evt = multiprocessing.Event(
        ) if process else threading.Event()

        pull_port = remote_hook.get_pull_port()
        push_port = remote_hook.get_push_port()

        hook_thread = (multiprocessing.Process
                       if process else threading.Thread)(
                           target=_acq_hook_startup_fn,
                           name="AcquisitionHook",
                           args=(
                               pull_port,
                               push_port,
                               hook_connected_evt,
                               event_queue,
                               remote_hook_fn,
                               self._debug,
                           ),
                       )
        # if process else threading.Thread(target=_acq_hook_fn, args=(), name='AcquisitionHook')
        hook_thread.start()

        hook_connected_evt.wait()  # wait for push/pull sockets to connect

    def _start_processor(self, processor, process_fn, event_queue, process):
        """

        Parameters
        ----------
        processor :

        process_fn :

        event_queue :

        process :


        Returns
        -------

        """
        # this must start first
        processor.start_pull()

        sockets_connected_evt = multiprocessing.Event(
        ) if process else threading.Event()

        pull_port = processor.get_pull_port()
        push_port = processor.get_push_port()

        self.processor_thread = (multiprocessing.Process
                                 if process else threading.Thread)(
                                     target=_processor_startup_fn,
                                     args=(
                                         pull_port,
                                         push_port,
                                         sockets_connected_evt,
                                         process_fn,
                                         event_queue,
                                         self._debug,
                                     ),
                                     name="ImageProcessor",
                                 )
        self.processor_thread.start()

        sockets_connected_evt.wait()  # wait for push/pull sockets to connect
        processor.start_push()
예제 #2
0
class Acquisition(object):
    def __init__(self,
                 directory=None,
                 name=None,
                 image_process_fn=None,
                 pre_hardware_hook_fn=None,
                 post_hardware_hook_fn=None,
                 tile_overlap=None,
                 magellan_acq_index=None,
                 process=True,
                 debug=False):
        """
        :param directory: saving directory for this acquisition. Required unless an image process function will be
            implemented that diverts images from saving
        :type directory: str
        :param name: Saving name for the acquisition. Required unless an image process function will be
            implemented that diverts images from saving
        :type name: str
        :param image_process_fn: 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 iamge metadata. Or a 4 argument version is accepted, which accepts (image,
            metadata, bridge, queue), where bridge and queue are an instance of the pycromanager.acquire.Bridge
            object for the purposes of interacting with arbitrary code on the Java side (such as the micro-manager
            core), and queue is a Queue objects that holds upcomning acquisition events. Both version must either
            return
        :param pre_hardware_hook_fn: hook function that will be run just before the hardware is updated before acquiring
            a new image. Accepts either one argument (the current acquisition event) or three arguments (current event,
            bridge, event Queue)
        :param post_hardware_hook_fn: hook function that will be run just before the hardware is updated before acquiring
            a new image. Accepts either one argument (the current acquisition event) or three arguments (current event,
            bridge, event Queue)
        :param tile_overlap: If given, XY tiles will be laid out in a grid and multi-resolution saving will be
            actived. Argument can be a two element tuple describing the pixel overlaps between adjacent
            tiles. i.e. (pixel_overlap_x, pixel_overlap_y), or an integer to use the same overlap for both.
            For these features to work, the current hardware configuration must have a valid affine transform
            between camera coordinates and XY stage coordinates
        :type tile_overlap: tuple, int
        :param magellan_acq_index: run this acquisition using the settings specified at this position in the main
            GUI of micro-magellan (micro-manager plugin). This index starts at 0
        :type magellan_acq_index: int
        :param process: (Experimental) use multiprocessing instead of multithreading for acquisition hooks and image
            processors
        :type process: boolean
        :param debug: print debugging stuff
        :type debug: boolean
        """
        self.bridge = Bridge(debug=debug)
        self._debug = debug
        self._dataset = None

        if directory is not None:
            # Expend ~ in path
            directory = os.path.expanduser(directory)
            # If path is relative, retain knowledge of the current working directory
            directory = os.path.abspath(directory)

        if magellan_acq_index is not None:
            magellan_api = self.bridge.get_magellan()
            self._remote_acq = magellan_api.create_acquisition(
                magellan_acq_index)
            self._event_queue = None
        else:
            # Create thread safe queue for events so they can be passed from multiple processes
            self._event_queue = multiprocessing.Queue()
            core = self.bridge.get_core()
            acq_factory = self.bridge.construct_java_object(
                'org.micromanager.remote.RemoteAcquisitionFactory',
                args=[core])

            #TODO: could add hiding viewer as an option
            show_viewer = directory is not None and name is not None
            if tile_overlap is None:
                #argument placeholders, these wont actually be used
                x_overlap = 0
                y_overlap = 0
            else:
                if type(tile_overlap) is tuple:
                    x_overlap, y_overlap = tile_overlap
                else:
                    x_overlap = tile_overlap
                    y_overlap = tile_overlap

            self._remote_acq = acq_factory.create_acquisition(
                directory, name, show_viewer, tile_overlap is not None,
                x_overlap, y_overlap)

        if image_process_fn is not None:
            processor = self.bridge.construct_java_object(
                'org.micromanager.remote.RemoteImageProcessor')
            self._remote_acq.add_image_processor(processor)
            self._start_processor(processor,
                                  image_process_fn,
                                  self._event_queue,
                                  process=process)

        if pre_hardware_hook_fn is not None:
            hook = self.bridge.construct_java_object(
                'org.micromanager.remote.RemoteAcqHook')
            self._start_hook(hook,
                             pre_hardware_hook_fn,
                             self._event_queue,
                             process=process)
            self._remote_acq.add_hook(hook,
                                      self._remote_acq.BEFORE_HARDWARE_HOOK,
                                      args=[self._remote_acq])
        if post_hardware_hook_fn is not None:
            hook = self.bridge.construct_java_object(
                'org.micromanager.remote.RemoteAcqHook',
                args=[self._remote_acq])
            self._start_hook(hook,
                             post_hardware_hook_fn,
                             self._event_queue,
                             process=process)
            self._remote_acq.add_hook(hook,
                                      self._remote_acq.AFTER_HARDWARE_HOOK)

        self._remote_acq.start()

        if magellan_acq_index is None:
            self.event_port = self._remote_acq.get_event_port()

            self.event_process = multiprocessing.Process(
                target=_event_sending_fn,
                args=(self.event_port, self._event_queue, self._debug),
                name='Event sending')
            # if multiprocessing else threading.Thread(target=event_sending_fn, args=(), name='Event sending')
            self.event_process.start()

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        if self._event_queue is not None:  #magellan acquisitions dont have this
            # this should shut down storage and viewer as apporpriate
            self._event_queue.put(None)
        #now wait on it to finish
        self.await_completion()

    def get_dataset(self):
        """
        Return a :class:`~pycromanager.data.Dataset` object that has access to the underlying pixels

        :return: :class:`~pycromanager.data.Dataset` corresponding to this acquisition
        """
        if self._dataset is None:
            self._dataset = Dataset(
                remote_storage=self._remote_acq.get_storage())
        return self._dataset

    def await_completion(self):
        """
        Wait for acquisition to finish and resources to be cleaned up
        """
        while (not self._remote_acq.is_finished()):
            time.sleep(0.1)

    def acquire(self, events):
        """
        Submit an event or a list of events for acquisition. Optimizations (i.e. taking advantage of
        hardware synchronization, where available), will take place across this list of events, but not
        over multiple calls of this method. A single event is a python dictionary with a specific structure

        :param events: single event (i.e. a dictionary) or a list of events
        """
        self._event_queue.put(events)

    def _start_hook(self, remote_hook, remote_hook_fn, event_queue, process):
        hook_connected_evt = multiprocessing.Event(
        ) if process else threading.Event()

        pull_port = remote_hook.get_pull_port()
        push_port = remote_hook.get_push_port()

        hook_thread = multiprocessing.Process(
            target=_acq_hook_startup_fn,
            name='AcquisitionHook',
            args=(pull_port, push_port, hook_connected_evt, event_queue,
                  remote_hook_fn, self._debug))
        # if process else threading.Thread(target=_acq_hook_fn, args=(), name='AcquisitionHook')
        hook_thread.start()

        hook_connected_evt.wait()  # wait for push/pull sockets to connect

    def _start_processor(self, processor, process_fn, event_queue, process):
        # this must start first
        processor.start_pull()

        sockets_connected_evt = multiprocessing.Event(
        ) if process else threading.Event()

        pull_port = processor.get_pull_port()
        push_port = processor.get_push_port()

        self.processor_thread = multiprocessing.Process(
            target=_processor_startup_fn,
            args=(pull_port, push_port, sockets_connected_evt, process_fn,
                  event_queue, self._debug),
            name='ImageProcessor')
        # if multiprocessing else threading.Thread(target=other_thread_fn, args=(),  name='ImageProcessor')
        self.processor_thread.start()

        sockets_connected_evt.wait()  # wait for push/pull sockets to connect
        processor.start_push()
예제 #3
0
def start_headless(mm_app_path,
                   config_file,
                   java_loc=None,
                   core_log_path=None,
                   buffer_size_mb=1024):
    """
    Start a Java process that contains the neccessary libraries for pycro-manager to run,
    so that it can be run independently of the Micro-Manager GUI/application. This call
    will create and initialize MMCore with the configuration file provided.

    On windows plaforms, the Java Runtime Environment will be grabbed automatically
    as it is installed along with the Micro-Manager application.

    On non-windows platforms, it may need to be installed/specified manually in order to ensure compatibility.
    This can be checked by looking at the "maven.compiler.source" entry which the java parts of
    pycro-manager were compiled with. See here: https://github.com/micro-manager/pycro-manager/blob/29b584bfd71f0d05750f5d39600318902186a06a/java/pom.xml#L8

    Parameters
        ----------
        mm_app_path : str
            Path to top level folder of Micro-Manager installation (made with graphical installer)
        config_file : str
            Path to micro-manager config file, with which core will be initialized
        java_loc: str
            Path to the java version that it should be run with
        core_log_path : str
            Path to where core log files should be created
        buffer_size_mb : int
            Size of circular buffer in MB in MMCore
    """

    classpath = '"' + mm_app_path + '/plugins/Micro-Manager/*"'
    if java_loc is None:
        if platform.system() == "Windows":
            # windows comes with its own JRE
            java_loc = mm_app_path + "/jre/bin/javaw.exe"
        else:
            java_loc = "java"
    # This starts Java process and instantiates essential objects (core,
    # acquisition engine, ZMQServer)
    p = subprocess.Popen([
        java_loc,
        "-classpath",
        classpath,
        "-Dsun.java2d.dpiaware=false",
        "-Xmx2000m",
        # This is used by MM desktop app but breaks things on MacOS...Don't think its neccessary
        # "-XX:MaxDirectMemorySize=1000",
        "org.micromanager.remote.HeadlessLauncher",
    ])
    # make sure Java process cleans up when Python process exits
    atexit.register(lambda: p.terminate())

    # Initialize core
    bridge = Bridge()
    core = bridge.get_core()

    core.wait_for_system()
    core.load_system_configuration(config_file)

    core.set_circular_buffer_memory_footprint(buffer_size_mb)

    if core_log_path is not None:
        core.enable_stderr_log(True)
        core.enable_debug_log(True)
        core.set_primary_log_file(core_log_path)