Пример #1
0
    def test_configure_scanner_overview(self):
        """Check that for the overview mode, the correct HW settings are set on the respective scanner VAs."""

        fastem_conf.configure_scanner(self.scanner, OVERVIEW_MODE)

        self.assertFalse(self.scanner.multiBeamMode.value)
        self.assertFalse(self.scanner.external.value)
        self.assertFalse(self.scanner.blanker.value)
        self.assertFalse(self.scanner.immersion.value)
        self.assertGreater(self.scanner.horizontalFoV.value,
                           1.e-3)  # should be big FoV for overview
        self.assertEqual(self.scanner.rotation.value, math.radians(5))

        scanner_md = self.scanner.getMetadata()

        # check that the MD_POS_COR is correctly set for overview imaging.
        self.assertListEqual(scanner_md[model.MD_FIELD_FREE_POS_SHIFT],
                             scanner_md[model.MD_POS_COR])

        # check rotation set is also stored in MD as rotation correction
        self.assertEqual(scanner_md[model.MD_ROTATION_COR], math.radians(5))

        # acquire an image and check the MD is correct: ROTATION - ROTATION_COR == 0
        image = self.sed.data.get()
        self.assertAlmostEqual(
            image.metadata[model.MD_ROTATION] -
            image.metadata[model.MD_ROTATION_COR], 0)
        # merge the MD -> automatically calculates rotation - rotation_cor -> puts result on rotation in MD
        img.mergeMetadata(image.metadata)
        self.assertAlmostEqual(image.metadata[model.MD_ROTATION], 0)
Пример #2
0
    def test_configure_scanner_megafield(self):
        """Check that for megafield mode, the correct HW settings are set on the respective scanner VAs."""

        fastem_conf.configure_scanner(self.scanner, MEGAFIELD_MODE)

        self.assertTrue(self.scanner.multiBeamMode.value)
        self.assertTrue(self.scanner.external.value)
        self.assertFalse(self.scanner.blanker.value)
        self.assertTrue(self.scanner.immersion.value)
        self.assertLess(
            self.scanner.horizontalFoV.value,
            0.1e-3)  # should be smaller for megafield than for overview
        self.assertEqual(self.scanner.rotation.value, math.radians(10))

        # Changes in the rotation on the scanner (e-beam) should be reflected in the MD on the multibeam.
        # check that rotation correction is 0 or not existing for megafield imaging using the ASM/SAM
        self.assertEqual(
            self.multibeam.getMetadata().get(model.MD_ROTATION_COR, 0), 0)
        # check that rotation is the same as was specified for the scanner (e-beam) for megafield imaging
        self.assertEqual(self.multibeam.getMetadata()[model.MD_ROTATION],
                         math.radians(10))

        # check that the MD_POS_COR is set to [0, 0] for live stream imaging.
        self.assertListEqual([0, 0],
                             self.scanner.getMetadata()[model.MD_POS_COR])
Пример #3
0
    def test_configure_scanner_live(self):
        """Check that for the live mode, the correct HW settings are set on the respective scanner VAs."""

        fastem_conf.configure_scanner(self.scanner, LIVESTREAM_MODE)

        self.assertFalse(self.scanner.multiBeamMode.value)
        self.assertFalse(self.scanner.external.value)
        self.assertFalse(self.scanner.blanker.value)
        self.assertTrue(self.scanner.immersion.value)
        self.assertEqual(self.scanner.rotation.value, math.radians(5))

        scanner_md = self.scanner.getMetadata()
        # check that the MD_POS_COR is set to [0, 0] for live stream imaging.
        self.assertListEqual([0, 0], scanner_md[model.MD_POS_COR])
Пример #4
0
def _run_overview_acquisition(f, stream, stage, area, live_stream):
    """
    Runs the acquisition of one overview image (typically one scintillator).

    :param f: (ProgressiveFuture) Acquisition future object.
    :param stream: (SEMstream) The stream used for the acquisition.
    :param stage: (actuator.MultiplexActuator) The stage in the sample carrier coordinate system.
        The x and y axes are aligned with the x and y axes of the ebeam scanner.
    :param area: (float, float, float, float) xmin, ymin, xmax, ymax coordinates of the overview region.
    :param live_stream: (StaticStream or None): StaticStream to be updated with each tile acquired,
           to build up live the whole acquisition. NOT SUPPORTED YET.

    :returns: (DataArray) The complete overview image.
    """
    fastem_conf.configure_scanner(stream.emitter, fastem_conf.OVERVIEW_MODE)

    # The stage movement precision is quite good (just a few pixels). The stage's
    # position reading is much better, and we can assume it's below a pixel.
    # So as long as we are sure there is some overlap, the tiles will be positioned
    # correctly and without gap.
    overlap = STAGE_PRECISION / stream.emitter.horizontalFoV.value
    logging.debug("Overlap is %s%%", overlap * 100)  # normally < 1%

    def _pass_future_progress(sub_f, start, end):
        f.set_progress(start, end)

    # Note, for debugging, it's possible to keep the intermediary tiles with log_path="./tile.ome.tiff"
    sf = stitching.acquireTiledArea([stream],
                                    stage,
                                    area,
                                    overlap,
                                    registrar=REGISTER_IDENTITY,
                                    focusing_method=FocusingMethod.NONE)
    # Connect the progress of the underlying future to the main future
    # FIXME removed to provide better progress update in GUI
    #  When _tiledacq.py has proper time update implemented, add this line here again.
    # sf.add_update_callback(_pass_future_progress)
    das = sf.result()

    if len(das) != 1:
        logging.warning("Expected 1 DataArray, but got %d: %r", len(das), das)

    # Switch immersion mode back on, so we can focus the SEM from the TFS GUI.
    stream.emitter.immersion.value = True

    # FIXME auto blanking not working properly, so force beam blanking after image acquisition for now.
    stream.emitter.blanker.value = True

    return das[0]
Пример #5
0
def _run_overview_acquisition(f, stream, stage, area, live_stream):
    """
    :returns: (DataArray)
    """
    fastem_conf.configure_scanner(stream.emitter, fastem_conf.OVERVIEW_MODE)

    # The stage movement precision is quite good (just a few pixels). The stage's
    # position reading is much better, and we can assume it's below a pixel.
    # So as long as we are sure there is some overlap, the tiles will be positioned
    # correctly and without gap.
    overlap = STAGE_PRECISION / stream.emitter.horizontalFoV.value
    logging.debug("Overlap is %s%%", overlap * 100)  # normally < 1%

    def _pass_future_progress(sub_f, start, end):
        f.set_progress(start, end)

    # Note, for debugging, it's possible to keep the intermediary tiles with log_path="./tile.ome.tiff"
    sf = stitching.acquireTiledArea([stream],
                                    stage,
                                    area,
                                    overlap,
                                    registrar=REGISTER_IDENTITY,
                                    focusing_method=FocusingMethod.NONE)
    # Connect the progress of the underlying future to the main future
    sf.add_update_callback(_pass_future_progress)
    das = sf.result()

    if len(das) != 1:
        logging.warning("Expected 1 DataArray, but got %d: %r", len(das), das)

    # Switch immersion mode back on, so we can focus the SEM from the TFS GUI.
    stream.emitter.immersion.value = True

    # FIXME auto blanking not working properly, so force beam blanking after image acquisition for now.
    stream.emitter.blanker.value = True

    return das[0]
Пример #6
0
    def run(self):
        """
        Runs the acquisition of one ROA (megafield).
        :returns:
            megafield: (list of DataArrays) A list of the raw image data. Each data array (entire field, thumbnail,
                or zero array) represents one single field image within the roa (megafield).
            exception: (Exception or None) Exception raised during the acquisition. If some single field image data has
                already been acquired, exceptions are not raised, but returned.
        :raise:
            Exception: If it failed before any single field images were acquired or if acquisition was cancelled.
        """

        # Get the estimated time for the roa.
        total_roa_time = estimate_acquisition_time(self._roa,
                                                   self._pre_calibrations)

        # No need to set the start time of the future: it's automatically done when setting its state to running.
        self._future.set_progress(end=time.time() +
                                  total_roa_time)  # provide end time to future
        logging.info(
            "Starting acquisition of ROA %s, with expected duration of %f s and %s by %s fields.",
            self._roa.name, total_roa_time, self._roa.field_indices[-1][0] + 1,
            self._roa.field_indices[-1][1] + 1)

        # Update the position of the first tile.
        self._pos_first_tile = self.get_pos_first_tile()

        if self._pre_calibrations:
            # The pre-calibrations should run on a position that lies a full field
            # outside the ROA, therefore temporarily set the overlap to zero.
            overlap_init = self._roa.overlap
            self._roa.overlap = 0
            # Move the stage to the tile with index (-1, -1), to ensure the autofocus and image translation pre-align
            # are done to the top left of the first field, outside the region of acquisition to limit beam damage.
            self.field_idx = (-1, -1)
            self.pre_calibrate(self._pre_calibrations)
            self._roa.overlap = overlap_init  # set back the overlap to the initial value

        # Move the stage to the first tile, to ensure the correct position is
        # stored in the megafield metadata yaml file.
        self.field_idx = (0, 0)
        self.move_stage_to_next_tile()

        # set the sub-directories (<acquisition date>/<project name>) and megafield id
        self._detector.filename.value = os.path.join(self._path,
                                                     self._roa.name.value)

        exception = None
        dataflow = self._detector.data

        try:
            logging.debug("Configure hardware for acquisition.")
            # configure the HW settings
            fastem_conf.configure_scanner(self._scanner,
                                          fastem_conf.MEGAFIELD_MODE)
            fastem_conf.configure_detector(self._detector, self._rocs)

            dataflow.subscribe(self.image_received)

            # Acquire the single field images.
            self.acquire_roa(dataflow)

        except CancelledError:  # raised in acquire_roa()
            logging.debug("Acquisition was cancelled.")
            raise

        except Exception as ex:
            # Check if any field images have already been acquired; if not => just raise the exception.
            if len(self._fields_remaining) == len(self._roa.field_indices):
                raise
            # If image data was already acquired, just log a warning.
            logging.warning(
                "Exception during roa acquisition (after some data has already been acquired).",
                exc_info=True)
            exception = ex  # let the caller handle the exception

        finally:
            # Remove references to the megafield once the acquisition is finished/cancelled.
            self._fields_remaining.clear()

            # Blank the beam after the acquisition is done.
            self._scanner.blanker.value = True

            # Finish the megafield also if an exception was raised, in order to enable a new acquisition.
            logging.debug("Finish ROA acquisition.")
            dataflow.unsubscribe(self.image_received)

        return self.megafield, exception
Пример #7
0
    def run(self):
        """
        Runs the acquisition of one ROA (megafield).
        :returns:
            megafield: (list of DataArrays) A list of the raw image data. Each data array (entire field, thumbnail,
                or zero array) represents one single field image within the roa (megafield).
            exception: (Exception or None) Exception raised during the acquisition. If some single field image data has
                already been acquired, exceptions are not raised, but returned.
        :raise:
            Exception: If it failed before any single field images were acquired or if acquisition was cancelled.
        """

        # set the sub-directories (<acquisition date>/<project name>) and megafield id
        self._detector.filename.value = os.path.join(self._path,
                                                     self._roa.name.value)

        exception = None

        # Get the estimated time for the roa.
        total_roa_time = self._roa.estimate_acquisition_time()
        # No need to set the start time of the future: it's automatically done when setting its state to running.
        self._future.set_progress(end=time.time() +
                                  total_roa_time)  # provide end time to future
        logging.info(
            "Starting acquisition of mega field, with expected duration of %f s",
            total_roa_time)

        dataflow = self._detector.data

        try:
            logging.debug("Starting megafield acquisition of %s by %s fields.",
                          self._roa.field_indices[-1][0] + 1,
                          self._roa.field_indices[-1][1] + 1)
            # configure the HW settings
            fastem_conf.configure_scanner(self._scanner,
                                          fastem_conf.MEGAFIELD_MODE)

            dataflow.subscribe(self.image_received)

            # Acquire the single field images.
            self.acquire_roa(dataflow)

        except CancelledError:  # raised in acquire_roa()
            logging.debug("Acquisition was cancelled.")
            raise

        except Exception as ex:
            # Check if any field images have already been acquired; if not => just raise the exception.
            if len(self._fields_remaining) == len(self._roa.field_indices):
                raise
            # If image data was already acquired, just log a warning.
            logging.warning(
                "Exception during roa acquisition (after some data has already been acquired).",
                exc_info=True)
            exception = ex  # let the caller handle the exception

        finally:
            # Remove references to the megafield once the acquisition is finished/cancelled.
            self._fields_remaining.clear()

            # Blank the beam after the acquisition is done.
            self._scanner.blanker.value = True

            # Finish the megafield also if an exception was raised, in order to enable a new acquisition.
            logging.debug("Finish megafield acquisition.")
            dataflow.unsubscribe(self.image_received)

        return self.megafield, exception
Пример #8
0
    def pre_calibrate(self):
        """
        Run optical multiprobe autofocus and image translation pre-alignment before the ROA acquisition.
        The image translation pre-alignment adjusts the descanner.scanOffset VA such that the image of the
        multiprobe is roughly centered on the mppc detector. This function reads in the ASM configuration
        and makes sure all values, except the descanner offset, are set back after the calibrations are run.

        NOTE: Canceling is currently not supported.

        """
        if not fastem_calibrations:
            raise ModuleNotFoundError(
                "Need fastem_calibrations repository to run pre-calibrations.")

        asm_config = None

        try:
            logging.debug("Read initial Hw settings.")
            asm_config = configure_hw.get_config_asm(self._multibeam,
                                                     self._descanner,
                                                     self._detector)
            fastem_conf.configure_scanner(self._scanner,
                                          fastem_conf.MEGAFIELD_MODE)

            # Set the beamshift to zero before adjusting the descanner offset, this ensures that the maximum beamshift
            # range can be utilized when the pattern is centered on the mppc detector.
            self._beamshift.shift.value = (0, 0)

            # Autofocus multiprobe ensures the beams are focused at the start of the ROA acquisition.
            logging.debug("Running optical autofocus before ROA acquisition.")
            autofocus_multiprobe.run_autofocus(self._scanner, self._multibeam,
                                               self._descanner, self._detector,
                                               self._detector.data, self._ccd,
                                               self._stage)

            configure_hw.configure_asm(self._multibeam,
                                       self._descanner,
                                       self._detector,
                                       self._detector.data,
                                       asm_config,
                                       upload=False)
            # Image translation pre-alignment ensures that the image of the multiprobe is roughly centered on the
            # mppc detector.
            logging.debug(
                "Running image translation pre alignment before ROA acquisition."
            )
            descanner_offset = image_translation_pre_align.run_image_translation_pre_align(
                self._scanner, self._multibeam, self._descanner,
                self._detector, self._detector.data, self._ccd)

            # Set the descanner offset to the value calibrated with the image translation pre-alignment.
            asm_config["descanner"]["scanOffset"] = descanner_offset
            logging.debug(f"Descanner offset set to: {descanner_offset}")
            # Blank the beam to reduce beam damage on the sample.
            self._scanner.blanker.value = True

        except Exception as ex:
            logging.warning(
                "Exception during pre-calibration of the ROA acquisition.",
                exc_info=True)
            raise
        finally:
            # Blank the beam after the acquisition is done.
            self._scanner.blanker.value = True
            if asm_config is None:
                logging.warning(
                    "Failed to retrieve asm configuration, configure_asm cannot be executed."
                )
            else:
                # put system into state ready for next task
                # upload=False: it is enough to set calibrated values on HW during following acquisition
                configure_hw.configure_asm(self._multibeam,
                                           self._descanner,
                                           self._detector,
                                           self._detector.data,
                                           asm_config,
                                           upload=False)

            logging.debug("Finish pre-calibration.")