Exemple #1
0
    def export_viewport(self, filepath, export_format, export_type):
        """ Export the image from the focused view to the filesystem.

        :param filepath: (str) full path to the destination file
        :param export_format: (str) the format name
        :param export_type: (str) spatial, AR, spectrum or spectrum-line
        """
        try:
            exporter = get_converter(export_format)
            raw = export_format in EXPORTERS[export_type][1]
            self._conf.export_raw = raw
            self._conf.last_export_path = os.path.dirname(filepath)

            exported_data = self.export(export_type, raw)
            # record everything to a file
            exporter.export(filepath, exported_data)

            popup.show_message(self._main_frame,
                               "Exported in %s" % (filepath,),
                               timeout=3
                               )

            logging.info("Exported a %s view into file '%s'.", export_type, filepath)
        except LookupError as ex:
            logging.info("Export of a %s view as %s seems to contain no data.",
                         export_type, export_format, exc_info=True)
            dlg = wx.MessageDialog(self._main_frame,
                                   "Failed to export: %s\n"
                                   "Please make sure that at least one stream is visible in the current view." % (ex,),
                                   "No data to export",
                                   wx.OK | wx.ICON_WARNING)
            dlg.ShowModal()
            dlg.Destroy()
        except Exception:
            logging.exception("Failed to export a %s view as %s", export_type, export_format)
Exemple #2
0
    def export_viewport(self, filepath, export_format, export_type):
        """ Export the image from the focused view to the filesystem.

        :param filepath: (str) full path to the destination file
        :param export_format: (str) the format name
        :param export_type: (str) spatial, AR or spectrum
        """
        try:
            exporter = get_converter(export_format)
            raw = export_format in EXPORTERS[export_type][1]
            exported_data = self.export(export_type, raw)
            # record everything to a file
            exporter.export(filepath, exported_data)

            popup.show_message(self._main_frame,
                               "Exported in %s" % (filepath,),
                               timeout=3
                               )

            logging.info("Exported a %s view into file '%s'.", export_type, filepath)
        except LookupError as ex:
            logging.info("Export of a %s view as %s seems to contain no data.",
                         export_type, export_format, exc_info=True)
            dlg = wx.MessageDialog(self._main_frame,
                                   "Failed to export: %s\n"
                                   "Please make sure that at least one stream is visible in the current view." % (ex,),
                                   "No data to export",
                                   wx.OK | wx.ICON_WARNING)
            dlg.ShowModal()
            dlg.Destroy()
        except Exception:
            logging.exception("Failed to export a %s view as %s", export_type, export_format)
Exemple #3
0
 def on_stop_axes(self, evt):
     if self._main_data:
         popup.show_message(self._main_frame,
                            "Stopping motion on every axes",
                            timeout=1)
         self._main_data.stopMotion()
     else:
         evt.Skip()
Exemple #4
0
 def _save_overview(self, das):
     """
     Save a set of DataArrays into a single TIFF file
     das (list of DataArrays)
     """
     fn = create_filename(self.conf.last_path, "{datelng}-{timelng}-overview",
                          ".ome.tiff")
     # We could use find_fittest_converter(), but as we always use tiff, it's not needed
     tiff.export(fn, das, pyramid=True)
     popup.show_message(self._tab.main_frame, "Overview saved", "Stored in %s" % (fn,),
                        timeout=3)
Exemple #5
0
    def _on_fa_done(self, future):
        logging.debug("End of overlay procedure")
        main_data = self._main_data_model
        self._acq_future = None  # To avoid holding the ref in memory
        self._faf_connector = None

        try:
            trans_val, cor_md = future.result()
            opt_md, sem_md = cor_md

            # Save the optical correction metadata straight into the CCD
            main_data.ccd.updateMetadata(opt_md)

            # The SEM correction metadata goes to the ebeam
            main_data.ebeam.updateMetadata(sem_md)
        except CancelledError:
            self._tab_panel.lbl_fine_align.Label = "Cancelled"
        except Exception:
            self._tab_panel.lbl_fine_align.Label = "Failed"
        else:
            self._tab_panel.lbl_fine_align.Label = "Successful"
            self._main_frame.menu_item_reset_finealign.Enable(True)
            # Temporary info until the GUI can actually rotate the images
            rot = math.degrees(opt_md.get(model.MD_ROTATION_COR, 0))
            shear = sem_md.get(model.MD_SHEAR_COR, 0)
            scaling_xy = sem_md.get(model.MD_PIXEL_SIZE_COR, (1, 1))
            # the worse is the rotation, the longer it's displayed
            timeout = max(2, min(abs(rot), 10))
            popup.show_message(
                self._tab_panel,
                u"Rotation applied: %s\nShear applied: %s\nX/Y Scaling applied: %s"
                % (units.readable_str(
                    rot, unit="°", sig=3), units.readable_str(
                        shear, sig=3), units.readable_str(scaling_xy, sig=3)),
                timeout=timeout)
            logging.warning(
                "Fine alignment computed rotation needed of %f°, "
                "shear needed of %s, and X/Y scaling needed of %s.", rot,
                shear, scaling_xy)

        # As the CCD image might have different pixel size, force to fit
        self._tab_panel.vp_align_ccd.canvas.fit_view_to_next_image = True

        main_data.is_acquiring.value = False
        self._tab_panel.btn_fine_align.Bind(wx.EVT_BUTTON, self._on_fine_align)
        self._tab_panel.btn_fine_align.Label = self._fa_btn_label
        self._resume()

        self._tab_panel.lbl_fine_align.Show()
        self._tab_panel.gauge_fine_align.Hide()
        self._sizer.Layout()
Exemple #6
0
    def _on_fa_done(self, future):
        logging.debug("End of overlay procedure")
        main_data = self._main_data_model
        self._acq_future = None  # To avoid holding the ref in memory
        self._faf_connector = None

        try:
            trans_val, cor_md = future.result()
            opt_md, sem_md = cor_md

            # Save the optical correction metadata straight into the CCD
            main_data.ccd.updateMetadata(opt_md)

            # The SEM correction metadata goes to the ebeam
            main_data.ebeam.updateMetadata(sem_md)
        except CancelledError:
            self._tab_panel.lbl_fine_align.Label = "Cancelled"
        except Exception:
            self._tab_panel.lbl_fine_align.Label = "Failed"
        else:
            self._tab_panel.lbl_fine_align.Label = "Successful"
            self._main_frame.menu_item_reset_finealign.Enable(True)
            # Temporary info until the GUI can actually rotate the images
            rot = math.degrees(opt_md.get(model.MD_ROTATION_COR, 0))
            shear = sem_md.get(model.MD_SHEAR_COR, 0)
            scaling_xy = sem_md.get(model.MD_PIXEL_SIZE_COR, (1, 1))
            # the worse is the rotation, the longer it's displayed
            timeout = max(2, min(abs(rot), 10))
            popup.show_message(
                self._tab_panel,
                u"Rotation applied: %s\nShear applied: %s\nX/Y Scaling applied: %s"
                % (units.readable_str(rot, unit="°", sig=3),
                   units.readable_str(shear, sig=3),
                   units.readable_str(scaling_xy, sig=3)),
                timeout=timeout
            )
            logging.warning("Fine alignment computed rotation needed of %f°, "
                            "shear needed of %s, and X/Y scaling needed of %s.",
                            rot, shear, scaling_xy)

        # As the CCD image might have different pixel size, force to fit
        self._tab_panel.vp_align_ccd.canvas.fit_view_to_next_image = True

        main_data.is_acquiring.value = False
        self._tab_panel.btn_fine_align.Bind(wx.EVT_BUTTON, self._on_fine_align)
        self._tab_panel.btn_fine_align.Label = self._fa_btn_label
        self._resume()

        self._tab_panel.lbl_fine_align.Show()
        self._tab_panel.gauge_fine_align.Hide()
        self._sizer.Layout()
Exemple #7
0
        def state_change_pop_up(component_state_value,
                                component_name=component.name):
            if component_state_value == ST_RUNNING:
                show_message(wx.GetApp().main_frame,
                             'Recovered ' + component_name,
                             'Functionality of the "' + component_name +
                             '" is recovered successfully.',
                             timeout=3.0,
                             level=logging.INFO)

            elif isinstance(component_state_value, HwError):
                show_message(wx.GetApp().main_frame,
                             'Error in ' + component_name,
                             str(component_state_value),
                             timeout=5.0,
                             level=logging.WARNING)
Exemple #8
0
    def snapshot_viewport(self, tab, filepath, exporter, anim):
        """ Save a snapshot of the raw image from the focused view to the
        filesystem.

        :param tab: (Tab) the current tab to save the snapshot from
        :param filepath: (str) full path to the destination file
        :param exporter: (func) exporter to use for writing the file
        :param anim: (bool) if True will show an animation

        When no dialog is shown, the name of the file will follow the scheme
        `date`-`time`.tiff (e.g., 20120808-154812.tiff) and it will be saved
        in the user's picture directory.

        """

        try:
            tab_data_model = tab.tab_data_model

            # Take all the streams available
            streams = tab_data_model.streams.value
            if not streams:
                logging.info("Failed to take snapshot, no stream in tab %s",
                                tab.name)
                return

            if anim:
                self.start_snapshot_animation()

            # get currently focused view
            view = tab_data_model.focussedView.value
            if not view:
                try:
                    view = tab_data_model.views.value[0]
                except IndexError:
                    view = None

            # let's try to get a thumbnail
            if not view or view.thumbnail.value is None:
                thumbnail = None
            else:
                # need to convert from wx.Image to ndimage
                thumbnail = img.wxImage2NDImage(view.thumbnail.value,
                                                keep_alpha=False)
                # add some basic info to the image
                mpp = view.mpp.value
                metadata = {model.MD_POS: view.view_pos.value,
                            model.MD_PIXEL_SIZE: (mpp, mpp),
                            model.MD_DESCRIPTION: "Composited image preview"}
                thumbnail = model.DataArray(thumbnail, metadata=metadata)

            # for each stream seen in the viewport
            raw_images = []
            for s in streams:
                data = s.raw # list of raw images for this stream (with metadata)
                for d in data:
                    # add the stream name to the image
                    if not hasattr(d, "metadata"):
                        # Not a DataArray => let's try to convert it
                        try:
                            d = model.DataArray(d)
                        except Exception:
                            logging.warning("Raw data of stream %s doesn't seem to be DataArray", s.name.value)
                            continue

                    if model.MD_DESCRIPTION not in d.metadata:
                        d.metadata[model.MD_DESCRIPTION] = s.name.value

                    raw_images.append(d)

            popup.show_message(self._main_frame,
                                 "Snapshot saved in %s" % (filepath,),
                                 timeout=3
                                 )
            # record everything to a file
            exporter.export(filepath, raw_images, thumbnail)

            logging.info("Snapshot saved as file '%s'.", filepath)
        except Exception:
            logging.exception("Failed to save snapshot")
Exemple #9
0
    def snapshot_viewport(self, tab, filepath, exporter, anim):
        """ Save a snapshot of the raw image from the focused view to the
        filesystem.

        :param tab: (Tab) the current tab to save the snapshot from
        :param filepath: (str) full path to the destination file
        :param exporter: (func) exporter to use for writing the file
        :param anim: (bool) if True will show an animation

        When no dialog is shown, the name of the file will follow the scheme
        `date`-`time`.tiff (e.g., 20120808-154812.tiff) and it will be saved
        in the user's picture directory.

        """

        try:
            tab_data_model = tab.tab_data_model

            # Take all the streams available
            streams = tab_data_model.streams.value
            if not streams:
                logging.info("Failed to take snapshot, no stream in tab %s",
                             tab.name)
                return

            if anim:
                self.start_snapshot_animation()

            # get currently focused view
            view = tab_data_model.focussedView.value
            if not view:
                try:
                    view = tab_data_model.views.value[0]
                except IndexError:
                    view = None

            # let's try to get a thumbnail
            if not view or view.thumbnail.value is None:
                thumbnail = None
            else:
                # need to convert from wx.Image to ndimage
                thumbnail = img.wxImage2NDImage(view.thumbnail.value,
                                                keep_alpha=False)
                # add some basic info to the image
                mpp = view.mpp.value
                metadata = {
                    model.MD_POS: view.view_pos.value,
                    model.MD_PIXEL_SIZE: (mpp, mpp),
                    model.MD_DESCRIPTION: "Composited image preview"
                }
                thumbnail = model.DataArray(thumbnail, metadata=metadata)

            # for each stream seen in the viewport
            raw_images = []
            for s in streams:
                data = s.raw
                if isinstance(data, tuple):  # 2D tuple = tiles
                    data = [mergeTiles(data)]

                for d in data:
                    # add the stream name to the image
                    if not hasattr(d, "metadata"):
                        # Not a DataArray => let's try to convert it
                        try:
                            d = model.DataArray(d)
                        except Exception:
                            logging.warning(
                                "Raw data of stream %s doesn't seem to be DataArray",
                                s.name.value)
                            continue

                    if model.MD_DESCRIPTION not in d.metadata:
                        d.metadata[model.MD_DESCRIPTION] = s.name.value

                    raw_images.append(d)

            popup.show_message(self._main_frame,
                               "Snapshot saved in %s" % (filepath, ),
                               timeout=3)
            # record everything to a file
            exporter.export(filepath, raw_images, thumbnail)

            logging.info("Snapshot saved as file '%s'.", filepath)
        except Exception:
            logging.exception("Failed to save snapshot")
Exemple #10
0
    def _on_fa_done(self, future):
        logging.debug("End of overlay procedure")
        main_data = self._main_data_model
        self._acq_future = None  # To avoid holding the ref in memory
        self._faf_connector = None

        try:
            # DEBUG
            try:
                trans_val, cor_md = future.result()
            except Exception:
                cor_md = {}, {}
            opt_md, sem_md = cor_md

            # Save the optical correction metadata straight into the CCD
            main_data.ccd.updateMetadata(opt_md)

            # The SEM correction metadata goes to the ebeam
            main_data.ebeam.updateMetadata(sem_md)
        except CancelledError:
            self._tab_panel.lbl_fine_align.Label = "Cancelled"
        except Exception as ex:
            logging.warning("Failure during overlay: %s", ex)
            self._tab_panel.lbl_fine_align.Label = "Failed"
        else:
            self._main_frame.menu_item_reset_finealign.Enable(True)

            # Check whether the values make sense. If not, we still accept them,
            # but hopefully make it clear enough to the user that the calibration
            # should not be trusted.
            rot = opt_md.get(model.MD_ROTATION_COR, 0)
            rot0 = (rot + math.pi) % (2 *
                                      math.pi) - math.pi  # between -pi and pi
            rot_deg = math.degrees(rot0)
            opt_scale = opt_md.get(model.MD_PIXEL_SIZE_COR, (1, 1))[0]
            shear = sem_md.get(model.MD_SHEAR_COR, 0)
            scaling_xy = sem_md.get(model.MD_PIXEL_SIZE_COR, (1, 1))
            if (not abs(rot_deg) < 10 or  # Rotation < 10°
                    not 0.9 < opt_scale < 1.1 or  # Optical mag < 10%
                    not abs(shear) < 0.3 or  # Shear < 30%
                    any(not 0.9 < v < 1.1
                        for v in scaling_xy)  # SEM ratio diff < 10%
                ):
                # Special warning in case of wrong magnification
                if not 0.9 < opt_scale < 1.1 and model.hasVA(
                        main_data.lens, "magnification"):
                    lens_mag = main_data.lens.magnification.value
                    measured_mag = lens_mag / opt_scale
                    logging.warning(
                        "The measured optical magnification is %fx, instead of expected %fx. "
                        "Check that the lens magnification and the SEM magnification are correctly set.",
                        measured_mag, lens_mag)
                else:  # Generic warning
                    logging.warning(
                        u"The fine alignment values are very large, try on a different place on the sample. "
                        u"mag correction: %f, rotation: %f°, shear: %f, X/Y scale: %f",
                        opt_scale, rot_deg, shear, scaling_xy)

                self._tab_panel.lbl_fine_align.Label = "Probably incorrect"
            else:
                self._tab_panel.lbl_fine_align.Label = "Successful"

            # Rotation is compensated in software on the FM image, but the user
            # can also change the SEM scan rotation, and re-run the alignment,
            # so show it clearly, for the user to take action.
            # The worse the rotation, the longer it's displayed.
            timeout = max(2, min(abs(rot_deg), 10))
            popup.show_message(
                self._tab_panel,
                u"Rotation applied: %s\nShear applied: %s\nX/Y Scaling applied: %s"
                % (units.readable_str(
                    rot_deg, unit=u"°", sig=3), units.readable_str(
                        shear, sig=3), units.readable_str(scaling_xy, sig=3)),
                timeout=timeout)
            logging.info(
                u"Fine alignment computed mag correction of %f, rotation of %f°, "
                u"shear needed of %s, and X/Y scaling needed of %s.",
                opt_scale, rot, shear, scaling_xy)

        # As the CCD image might have different pixel size, force to fit
        self._tab_panel.vp_align_ccd.canvas.fit_view_to_next_image = True

        main_data.is_acquiring.value = False
        self._tab_panel.btn_fine_align.Bind(wx.EVT_BUTTON, self._on_fine_align)
        self._tab_panel.btn_fine_align.Label = self._fa_btn_label
        self._resume()

        self._tab_panel.lbl_fine_align.Show()
        self._tab_panel.gauge_fine_align.Hide()
        self._sizer.Layout()
Exemple #11
0
 def on_stop_axes(self, evt):
     if self._main_data:
         popup.show_message(self._main_frame, "Stopping motion on every axes", timeout=1)
         self._main_data.stopMotion()
     else:
         evt.Skip()
Exemple #12
0
    def export_viewport(self, filepath, export_format, export_type):
        """ Export the image from the focused view to the filesystem.

        :param filepath: (str) full path to the destination file
        :param export_format: (str) the format name
        :param export_type: (str) spatial, AR, spectrum or spectrum-line
        """
        try:
            exporter = get_converter(export_format)
            raw = export_format in EXPORTERS[export_type][1]
            self._conf.export_raw = raw
            self._conf.last_export_path = os.path.dirname(filepath)

            exported_data = self.export(export_type, raw)

            # batch export
            # TODO: for now we do not create a new folder where all files are saved

            if isinstance(exported_data, dict):
                # get the file names
                filename_dict = {}
                n_exist = 0
                dir_name, base = os.path.split(filepath)
                base_name, file_extension = splitext(base)
                for key in exported_data.keys():
                    # use the filename defined by the user and add the MD_POL_MODE to the filename
                    filename = os.path.join(
                        dir_name, base_name + "_" + key + file_extension)

                    # detect we'd overwrite an existing file => show our own warning
                    if os.path.exists(filename):
                        n_exist += 1

                    filename_dict[key] = filename

                if n_exist:
                    dlg = wx.MessageDialog(
                        self._main_frame,
                        "Some files (%d/%d) already exists.\n"
                        "Do you want to replace them?" %
                        (n_exist, len(filename_dict)), "Files already exist",
                        wx.YES_NO | wx.NO_DEFAULT | wx.ICON_QUESTION)
                    ret = dlg.ShowModal()
                    dlg.Destroy()
                    if ret == wx.ID_NO:
                        return

                # record everything to a file
                for key, data in exported_data.items():
                    exporter.export(filename_dict[key], data)

                # TODO need to be adapted for batch export as now redundant
                popup.show_message(
                    self._main_frame,
                    "Images exported",
                    "Stored as %s" % (os.path.join(
                        dir_name, base_name + "_" + "xxx" + file_extension), ),
                    timeout=3)

                logging.info(
                    "Exported a %s view into files with name of type '%s'.",
                    export_type,
                    os.path.join(dir_name,
                                 base_name + "_" + "mode" + file_extension))

            else:  # single file export
                exporter.export(filepath, exported_data)

                popup.show_message(self._main_frame,
                                   "Image exported",
                                   "Stored in %s" % (filepath, ),
                                   timeout=3)

                logging.info("Exported a %s view into file '%s'.", export_type,
                             filepath)
        except LookupError as ex:
            logging.info("Export of a %s view as %s seems to contain no data.",
                         export_type,
                         export_format,
                         exc_info=True)
            dlg = wx.MessageDialog(
                self._main_frame, "Failed to export: %s\n"
                "Please make sure that at least one stream is visible in the current view."
                % (ex, ), "No data to export", wx.OK | wx.ICON_WARNING)
            dlg.ShowModal()
            dlg.Destroy()
        except Exception:
            logging.exception("Failed to export a %s view as %s", export_type,
                              export_format)
Exemple #13
0
    def acquire(self, dlg):
        main_data = self.main_app.main_data
        str_ctrl = self._tab.streambar_controller
        str_ctrl.pauseStreams()
        dlg.pauseSettings()
        self._unsubscribe_vas()

        orig_pos = main_data.stage.position.value
        trep = (self.nx.value, self.ny.value)
        nb = trep[0] * trep[1]
        # It's not a big deal if it was a bad guess as we'll use the actual data
        # before the first move
        sfov = self._guess_smallest_fov()
        fn = self.filename.value
        exporter = dataio.find_fittest_converter(fn)
        fn_bs, fn_ext = udataio.splitext(fn)

        ss, stitch_ss = self._get_acq_streams()
        end = self.estimate_time() + time.time()

        ft = model.ProgressiveFuture(end=end)
        self.ft = ft  # allows future to be canceled in show_dlg after closing window
        ft.running_subf = model.InstantaneousFuture()
        ft._task_state = RUNNING
        ft._task_lock = threading.Lock()
        ft.task_canceller = self._cancel_acquisition  # To allow cancelling while it's running
        ft.set_running_or_notify_cancel()  # Indicate the work is starting now
        dlg.showProgress(ft)

        # For stitching only
        da_list = []  # for each position, a list of DataArrays
        i = 0
        prev_idx = [0, 0]
        try:
            for ix, iy in self._generate_scanning_indices(trep):
                logging.debug("Acquiring tile %dx%d", ix, iy)
                self._move_to_tile((ix, iy), orig_pos, sfov, prev_idx)
                prev_idx = ix, iy
                # Update the progress bar
                ft.set_progress(end=self.estimate_time(nb - i) + time.time())

                ft.running_subf = acqmng.acquire(
                    ss, self.main_app.main_data.settings_obs)
                das, e = ft.running_subf.result(
                )  # blocks until all the acquisitions are finished
                if e:
                    logging.warning(
                        "Acquisition for tile %dx%d partially failed: %s", ix,
                        iy, e)

                if ft._task_state == CANCELLED:
                    raise CancelledError()

                # TODO: do in a separate thread
                fn_tile = "%s-%.5dx%.5d%s" % (fn_bs, ix, iy, fn_ext)
                logging.debug("Will save data of tile %dx%d to %s", ix, iy,
                              fn_tile)
                exporter.export(fn_tile, das)

                if ft._task_state == CANCELLED:
                    raise CancelledError()

                if self.stitch.value:
                    # Sort tiles (largest sem on first position)
                    da_list.append(self.sort_das(das, stitch_ss))

                # Check the FoV is correct using the data, and if not update
                if i == 0:
                    sfov = self._check_fov(das, sfov)
                i += 1

            # Move stage to original position
            main_data.stage.moveAbs(orig_pos)

            # Stitch SEM and CL streams
            st_data = []
            if self.stitch.value and (not da_list or not da_list[0]):
                # if only AR or Spectrum are acquired
                logging.warning(
                    "No stream acquired that can be used for stitching.")
            elif self.stitch.value:
                logging.info("Acquisition completed, now stitching...")
                ft.set_progress(end=self.estimate_time(0) + time.time())

                logging.info("Computing big image out of %d images",
                             len(da_list))
                das_registered = stitching.register(da_list)

                # Select weaving method
                # On a Sparc system the mean weaver gives the best result since it
                # smoothes the transitions between tiles. However, using this weaver on the
                # Secom/Delphi generates an image with dark stripes in the overlap regions which are
                # the result of carbon decomposition effects that typically occur in samples imaged
                # by these systems. To mediate this, we use the
                # collage_reverse weaver that only shows the overlap region of the tile that
                # was imaged first.
                if self.microscope.role in ("secom", "delphi"):
                    weaving_method = WEAVER_COLLAGE_REVERSE
                    logging.info(
                        "Using weaving method WEAVER_COLLAGE_REVERSE.")
                else:
                    weaving_method = WEAVER_MEAN
                    logging.info("Using weaving method WEAVER_MEAN.")

                # Weave every stream
                if isinstance(das_registered[0], tuple):
                    for s in range(len(das_registered[0])):
                        streams = []
                        for da in das_registered:
                            streams.append(da[s])
                        da = stitching.weave(streams, weaving_method)
                        da.metadata[
                            model.MD_DIMS] = "YX"  # TODO: do it in the weaver
                        st_data.append(da)
                else:
                    da = stitching.weave(das_registered, weaving_method)
                    st_data.append(da)

                # Save
                exporter = dataio.find_fittest_converter(fn)
                if exporter.CAN_SAVE_PYRAMID:
                    exporter.export(fn, st_data, pyramid=True)
                else:
                    logging.warning(
                        "File format doesn't support saving image in pyramidal form"
                    )
                    exporter.export(fn, st_data)

            ft.set_result(None)  # Indicate it's over

            # End of the (completed) acquisition
            if ft._task_state == CANCELLED:
                raise CancelledError()
            dlg.Close()

            # Open analysis tab
            if st_data:
                popup.show_message(self.main_app.main_frame,
                                   "Tiled acquisition complete",
                                   "Will display stitched image")
                self.showAcquisition(fn)
            else:
                popup.show_message(self.main_app.main_frame,
                                   "Tiled acquisition complete",
                                   "Will display last tile")
                # It's easier to know the last filename, and it's also the most
                # interesting for the user, as if something went wrong (eg, focus)
                # it's the tile the most likely to show it.
                self.showAcquisition(fn_tile)

            # TODO: also export a full image (based on reported position, or based
            # on alignment detection)
        except CancelledError:
            logging.debug("Acquisition cancelled")
            dlg.resumeSettings()
        except Exception as ex:
            logging.exception("Acquisition failed.")
            ft.running_subf.cancel()
            ft.set_result(None)
            # Show also in the window. It will be hidden next time a setting is changed.
            self._dlg.setAcquisitionInfo("Acquisition failed: %s" % (ex, ),
                                         lvl=logging.ERROR)
        finally:
            logging.info("Tiled acquisition ended")
            main_data.stage.moveAbs(orig_pos)
Exemple #14
0
    def _on_fa_done(self, future):
        logging.debug("End of overlay procedure")
        main_data = self._main_data_model
        self._acq_future = None  # To avoid holding the ref in memory
        self._faf_connector = None

        try:
            # DEBUG
            try:
                trans_val, cor_md = future.result()
            except Exception:
                cor_md = {}, {}
            opt_md, sem_md = cor_md

            # Save the optical correction metadata straight into the CCD
            main_data.ccd.updateMetadata(opt_md)

            # The SEM correction metadata goes to the ebeam
            main_data.ebeam.updateMetadata(sem_md)
        except CancelledError:
            self._tab_panel.lbl_fine_align.Label = "Cancelled"
        except Exception as ex:
            logging.warning("Failure during overlay: %s", ex)
            self._tab_panel.lbl_fine_align.Label = "Failed"
        else:
            self._main_frame.menu_item_reset_finealign.Enable(True)

            # Check whether the values make sense. If not, we still accept them,
            # but hopefully make it clear enough to the user that the calibration
            # should not be trusted.
            rot = opt_md.get(model.MD_ROTATION_COR, 0)
            rot0 = (rot + math.pi) % (2 * math.pi) - math.pi  # between -pi and pi
            rot_deg = math.degrees(rot0)
            opt_scale = opt_md.get(model.MD_PIXEL_SIZE_COR, (1, 1))[0]
            shear = sem_md.get(model.MD_SHEAR_COR, 0)
            scaling_xy = sem_md.get(model.MD_PIXEL_SIZE_COR, (1, 1))
            if (not abs(rot_deg) < 10 or  # Rotation < 10°
                not 0.9 < opt_scale < 1.1 or  # Optical mag < 10%
                not abs(shear) < 0.3 or  # Shear < 30%
                any(not 0.9 < v < 1.1 for v in scaling_xy) # SEM ratio diff < 10%
               ):
                # Special warning in case of wrong magnification
                if not 0.9 < opt_scale < 1.1 and model.hasVA(main_data.lens, "magnification"):
                    lens_mag = main_data.lens.magnification.value
                    measured_mag = lens_mag / opt_scale
                    logging.warning("The measured optical magnification is %fx, instead of expected %fx. "
                                    "Check that the lens magnification and the SEM magnification are correctly set.",
                                    measured_mag, lens_mag)
                else:  # Generic warning
                    logging.warning(u"The fine alignment values are very large, try on a different place on the sample. "
                                    u"mag correction: %f, rotation: %f°, shear: %f, X/Y scale: %f",
                                    opt_scale, rot_deg, shear, scaling_xy)

                self._tab_panel.lbl_fine_align.Label = "Probably incorrect"
            else:
                self._tab_panel.lbl_fine_align.Label = "Successful"

            # Rotation is compensated in software on the FM image, but the user
            # can also change the SEM scan rotation, and re-run the alignment,
            # so show it clearly, for the user to take action.
            # The worse the rotation, the longer it's displayed.
            timeout = max(2, min(abs(rot_deg), 10))
            popup.show_message(
                self._tab_panel,
                u"Rotation applied: %s\nShear applied: %s\nX/Y Scaling applied: %s"
                % (units.readable_str(rot_deg, unit=u"°", sig=3),
                   units.readable_str(shear, sig=3),
                   units.readable_str(scaling_xy, sig=3)),
                timeout=timeout
            )
            logging.info(u"Fine alignment computed mag correction of %f, rotation of %f°, "
                         u"shear needed of %s, and X/Y scaling needed of %s.",
                         opt_scale, rot, shear, scaling_xy)

        # As the CCD image might have different pixel size, force to fit
        self._tab_panel.vp_align_ccd.canvas.fit_view_to_next_image = True

        main_data.is_acquiring.value = False
        self._tab_panel.btn_fine_align.Bind(wx.EVT_BUTTON, self._on_fine_align)
        self._tab_panel.btn_fine_align.Label = self._fa_btn_label
        self._resume()

        self._tab_panel.lbl_fine_align.Show()
        self._tab_panel.gauge_fine_align.Hide()
        self._sizer.Layout()
Exemple #15
0
    def _updateTemperature(self, temperature):
        """
        Subscriber to the temperature VA
        """
        # add the new temperature to the history with a timestamp
        self.temperature_history.append((time.time(), temperature))
        wx.CallAfter(self.temperature_label.SetLabel, u"Sample: {temp:.1f}°C".format(temp=temperature))
        
        # clear out values of the history that are too old i.e. older than the max_duration
        while len(self.temperature_history) > 0:
            t, _ = self.temperature_history[0]
            if t < time.time() - self.max_duration:
                self.temperature_history.pop(0)
            else:
                break

        # Get boundaries
        safe_range = self.thermostat.getMetadata().get(model.MD_SAFE_REL_RANGE, None)
        safe_speed = self.thermostat.getMetadata().get(model.MD_SAFE_SPEED_RANGE, None)

        # Calculate a linear regression of the temperature history to determine the speed
        l = list(zip(*self.temperature_history))  # convert [(time, temp), ...] to [[time, ...], [temp...]]
        speed = 0
        # calculate speed (only possible if there is data in the history
        if len(self.temperature_history) > 1:
            try:
                speed, _ = numpy.polyfit(l[0], l[1], 1)
            except:
                logging.exception("Could not compute the rate of change of temperature.")

        # determine setting the warning
        target_temperature = self.thermostat.targetTemperature.value
        if (safe_range is not None and
            not target_temperature + safe_range[0] <= temperature <= target_temperature + safe_range[1]
            ):
            # temperature is out of range
            # change colour to red
            set_label_colour(self.temperature_label, gui.FG_COLOUR_ERROR, weight=wx.BOLD)
            text = u"Temperature {temp:.2f}°C is outside of the target range of {lo:.2f}°C to {hi:.2f}°C!".format(
                    temp=temperature,
                    lo=target_temperature + safe_range[0],
                    hi=target_temperature + safe_range[1])
            logging.warning(text)
            # display messagebox warning, but only once per minute
            if time.time() - self._time_last_warning > MIN_PERIOD_WARNING:
                popup.show_message(self.main_frame, "Temperature Warning", message=text, level=logging.WARNING)
                self._time_last_warning = time.time()

        elif safe_speed is not None and not safe_speed[0] <= speed <= safe_speed[1]:
            # temperature is changing at an unsafe speed
            # change colour to yellow
            set_label_colour(self.temperature_label, gui.FG_COLOUR_ERROR, weight=wx.BOLD)
            text = u"Temperature {temp:.2f}°C is changing at an unsafe rate of {speed:.2f}°C/s!".format(temp=temperature, speed=speed)
            logging.warning(text)
            # display messagebox warning, but only once per minute
            if time.time() - self._time_last_speed_warning > MIN_PERIOD_WARNING:
                popup.show_message(self.main_frame, "Temperature Warning", message=text, level=logging.WARNING)
                self._time_last_speed_warning = time.time()

        else:
            # temperature is normal. Set the colour back to normal
            set_label_colour(self.temperature_label, self._fg_color, weight=wx.NORMAL)
            # reset warnings so they can be invoked again.
            self._time_last_speed_warning = 0
            self._time_last_warning = 0