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)
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)
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()
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)
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()
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()
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)
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")
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")
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()
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)
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)
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()
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