def update_acquisition_time(self): if self._ellipsis_animator: # cancel if there is an ellipsis animator updating the status message self._ellipsis_animator.cancel() self._ellipsis_animator = None # Don't update estimated time if acquisition is running (as we are # sharing the label with the estimated time-to-completion). if self._main_data_model.is_acquiring.value: return lvl = None # icon status shown if self._main_data_model.is_preparing.value: txt = u"Optical path is being reconfigured…" self._ellipsis_animator = EllipsisAnimator(txt, self.lbl_acqestimate) self._ellipsis_animator.start() lvl = logging.INFO elif self._roa.value == UNDEFINED_ROI: # TODO: update the default text to be the same txt = u"Region of acquisition needs to be selected" lvl = logging.WARN else: streams = self._tab_data_model.acquisitionView.getStreams() acq_time = acq.estimateTime(streams) acq_time = math.ceil(acq_time) # round a bit pessimistic txt = u"Estimated time is {}." txt = txt.format(units.readable_time(acq_time)) logging.debug("Updating status message %s, with level %s", txt, lvl) self.lbl_acqestimate.SetLabel(txt) self._show_status_icons(lvl)
def update_acquisition_time(self): if self._ellipsis_animator: # cancel if there is an ellipsis animator updating the status message self._ellipsis_animator.cancel() self._ellipsis_animator = None # Don't update estimated time if acquisition is running (as we are # sharing the label with the estimated time-to-completion). if self._main_data_model.is_acquiring.value: return lvl = None # icon status shown if self._main_data_model.is_preparing.value: txt = u"Optical path is being reconfigured…" self._ellipsis_animator = EllipsisAnimator(txt, self.lbl_acqestimate) self._ellipsis_animator.start() lvl = logging.INFO elif self._roa.value == UNDEFINED_ROI: # TODO: update the default text to be the same txt = u"Region of acquisition needs to be selected" lvl = logging.WARN else: streams = self._tab_data_model.acquisitionStreams acq_time = acq.estimateTime(streams) acq_time = math.ceil(acq_time) # round a bit pessimistic txt = u"Estimated time is {}." txt = txt.format(units.readable_time(acq_time)) logging.debug("Updating status message %s, with level %s", txt, lvl) self.lbl_acqestimate.SetLabel(txt) self._show_status_icons(lvl)
class SparcAcquiController(object): """ Takes care of the acquisition button and process on the Sparc acquisition tab. """ def __init__(self, tab_data, tab_panel, streambar_controller): """ tab_data (MicroscopyGUIData): the representation of the microscope GUI tab_panel: (wx.Frame): the frame which contains the 4 viewports stream_ctrl (StreamBarController): controller to pause/resume the streams """ self._tab_data_model = tab_data self._main_data_model = tab_data.main self._tab_panel = tab_panel self._streambar_controller = streambar_controller # For file selection self.conf = conf.get_acqui_conf() # TODO: this should be the date at which the user presses the acquire # button (or when the last settings were changed)! # At least, we must ensure it's a new date after the acquisition # is done. # Filename to save the acquisition self.filename = model.StringVA( create_filename(self.conf.last_path, self.conf.fn_ptn, self.conf.last_extension, self.conf.fn_count)) self.filename.subscribe(self._onFilename, init=True) # For acquisition # a ProgressiveFuture if the acquisition is going on self.btn_acquire = self._tab_panel.btn_sparc_acquire self.btn_change_file = self._tab_panel.btn_sparc_change_file self.btn_cancel = self._tab_panel.btn_sparc_cancel self.acq_future = None self.gauge_acq = self._tab_panel.gauge_sparc_acq self.lbl_acqestimate = self._tab_panel.lbl_sparc_acq_estimate self.bmp_acq_status_warn = self._tab_panel.bmp_acq_status_warn self.bmp_acq_status_info = self._tab_panel.bmp_acq_status_info self._acq_future_connector = None # TODO: share an executor with the whole GUI. self._executor = futures.ThreadPoolExecutor(max_workers=2) # Link buttons self.btn_acquire.Bind(wx.EVT_BUTTON, self.on_acquisition) self.btn_change_file.Bind(wx.EVT_BUTTON, self.on_change_file) self.btn_cancel.Bind(wx.EVT_BUTTON, self.on_cancel) self.gauge_acq.Hide() self._tab_panel.Parent.Layout() # Animator for messages containing ellipsis character self._ellipsis_animator = None # TODO: we need to be informed if the user closes suddenly the window # self.Bind(wx.EVT_CLOSE, self.on_close) self._roa = tab_data.semStream.roi # Listen to change of streams to update the acquisition time self._prev_streams = set() # set of streams already listened to tab_data.streams.subscribe(self._onStreams, init=True) # also listen to .semStream, which is not in .streams for va in self._get_settings_vas(tab_data.semStream): va.subscribe(self._onAnyVA) # Extra options affecting the acquisitions globally tab_data.pcdActive.subscribe(self._onAnyVA) # TODO: should also listen to the VAs of the leeches on semStream tab_data.useScanStage.subscribe(self._onAnyVA) self._roa.subscribe(self._onROA, init=True) # Listen to preparation state self._main_data_model.is_preparing.subscribe(self.on_preparation) def __del__(self): self._executor.shutdown(wait=False) # black list of VAs name which are known to not affect the acquisition time VAS_NO_ACQUSITION_EFFECT = ("image", "autoBC", "intensityRange", "histogram", "is_active", "should_update", "status", "name", "tint") def _get_settings_vas(self, stream): """ Find all the VAs of a stream which can potentially affect the acquisition time return (set of VAs) """ nvas = model.getVAs(stream) # name -> va vas = set() # remove some VAs known to not affect the acquisition time for n, va in nvas.items(): if n not in self.VAS_NO_ACQUSITION_EFFECT: vas.add(va) return vas def _onFilename(self, name): """ updates the GUI when the filename is updated """ # decompose into path/file path, base = os.path.split(name) self._tab_panel.txt_destination.SetValue(unicode(path)) # show the end of the path (usually more important) self._tab_panel.txt_destination.SetInsertionPointEnd() self._tab_panel.txt_filename.SetValue(unicode(base)) def _onROA(self, roi): """ updates the acquire button according to the acquisition ROI """ self.check_acquire_button() self.update_acquisition_time() # to update the message def on_preparation(self, is_preparing): self.check_acquire_button() self.update_acquisition_time() def check_acquire_button(self): self.btn_acquire.Enable( self._roa.value != UNDEFINED_ROI and not self._main_data_model.is_preparing.value) def _onStreams(self, streams): """ Called when streams are added/deleted. Used to listen to settings change and update the acquisition time. """ streams = set(streams) # remove subscription for streams that were deleted for s in (self._prev_streams - streams): for va in self._get_settings_vas(s): va.unsubscribe(self._onAnyVA) # add subscription for new streams for s in (streams - self._prev_streams): for va in self._get_settings_vas(s): va.subscribe(self._onAnyVA) self._prev_streams = streams self.update_acquisition_time() # to update the message def _onAnyVA(self, val): """ Called whenever a VA which might affect the acquisition is modified """ self.update_acquisition_time() # to update the message def update_fn_suggestion(self): """ When the filename counter is updated in a plugin, the suggested name for the main acquisition needs to be updated """ self.filename.value = create_filename(self.conf.last_path, self.conf.fn_ptn, self.conf.last_extension, self.conf.fn_count) def on_change_file(self, evt): """ Shows a dialog to change the path, name, and format of the acquisition file. returns nothing, but updates .filename and .conf """ # Update .filename with new filename instead of input name so the right # time is used fn = create_filename(self.conf.last_path, self.conf.fn_ptn, self.conf.last_extension, self.conf.fn_count) new_name = ShowAcquisitionFileDialog(self._tab_panel, fn) if new_name is not None: self.filename.value = new_name self.conf.fn_ptn, self.conf.fn_count = guess_pattern(new_name) logging.debug("Generated filename pattern '%s'", self.conf.fn_ptn) @wxlimit_invocation(1) # max 1/s def update_acquisition_time(self): if self._ellipsis_animator: # cancel if there is an ellipsis animator updating the status message self._ellipsis_animator.cancel() self._ellipsis_animator = None # Don't update estimated time if acquisition is running (as we are # sharing the label with the estimated time-to-completion). if self._main_data_model.is_acquiring.value: return lvl = None # icon status shown if self._main_data_model.is_preparing.value: txt = u"Optical path is being reconfigured…" self._ellipsis_animator = EllipsisAnimator(txt, self.lbl_acqestimate) self._ellipsis_animator.start() lvl = logging.INFO elif self._roa.value == UNDEFINED_ROI: # TODO: update the default text to be the same txt = u"Region of acquisition needs to be selected" lvl = logging.WARN else: streams = self._tab_data_model.acquisitionStreams acq_time = acq.estimateTime(streams) acq_time = math.ceil(acq_time) # round a bit pessimistic txt = u"Estimated time is {}." txt = txt.format(units.readable_time(acq_time)) logging.debug("Updating status message %s, with level %s", txt, lvl) self.lbl_acqestimate.SetLabel(txt) self._show_status_icons(lvl) def _show_status_icons(self, lvl): # update status icon to show the logging level self.bmp_acq_status_info.Show(lvl in (logging.INFO, logging.DEBUG)) self.bmp_acq_status_warn.Show(lvl == logging.WARN) self._tab_panel.Layout() def _pause_streams(self): """ Freeze the streams settings and ensure no stream is playing """ self._streambar_controller.pauseStreams() self._streambar_controller.pause() self._streambar_controller.enable(False) def _resume_streams(self): """ Resume (unfreeze) the stream settings """ self._streambar_controller.enable(True) self._streambar_controller.resume() def _reset_acquisition_gui(self, text=None, level=None, keep_filename=False): """ Set back every GUI elements to be ready for the next acquisition text (None or str): a (error) message to display instead of the estimated acquisition time level (None or logging.*): logging level of the text, shown as an icon. If None, no icon is shown. keep_filename (bool): if True, will not update the filename """ self.btn_cancel.Hide() self.btn_acquire.Enable() self._tab_panel.Layout() self._resume_streams() if not keep_filename: self.conf.fn_count = update_counter(self.conf.fn_count) # Update filename even if keep_filename is True (but don't update counter). This # ensures that the time is always up to date. self.filename.value = create_filename(self.conf.last_path, self.conf.fn_ptn, self.conf.last_extension, self.conf.fn_count) if text is not None: self.lbl_acqestimate.SetLabel(text) self._show_status_icons(level) else: self.update_acquisition_time() def _show_acquisition(self, data, acqfile): """ Show the acquired data (saved into a file) in the analysis tab. data (list of DataFlow): all the raw data acquired acqfile (File): file object to which the data was saved """ # get the analysis tab analysis_tab = self._main_data_model.getTabByName("analysis") analysis_tab.display_new_data(acqfile.name, data) # show the new tab self._main_data_model.tab.value = analysis_tab def on_acquisition(self, evt): """ Start the acquisition (really) Similar to win.acquisition.on_acquire() """ # Time-resolved data cannot be saved in .ome.tiff format for now # OME-TIFF wants to save each time data on a separate "page", which causes too many pages. has_temporal = False for s in self._tab_data_model.streams.value: if (isinstance(s, ScannedTemporalSettingsStream) or isinstance(s, ScannedTCSettingsStream) or isinstance(s, TemporalSpectrumSettingsStream)): has_temporal = True if (self.conf.last_format == 'TIFF' or self.conf.last_format == 'Serialized TIFF') and has_temporal: raise NotImplementedError("Cannot save temporal data in %s format, data format must be HDF5." \ % self.conf.last_format) self._pause_streams() self.btn_acquire.Disable() self.btn_cancel.Enable() self._main_data_model.is_acquiring.value = True self.gauge_acq.Show() self.btn_cancel.Show() self._show_status_icons(None) self._tab_panel.Layout() # to put the gauge at the right place # start acquisition + connect events to callback self.acq_future = acq.acquire(self._tab_data_model.acquisitionStreams) self._acq_future_connector = ProgressiveFutureConnector( self.acq_future, self.gauge_acq, self.lbl_acqestimate) self.acq_future.add_done_callback(self.on_acquisition_done) def on_cancel(self, evt): """ Called during acquisition when pressing the cancel button """ if not self.acq_future: msg = "Tried to cancel acquisition while it was not started" logging.warning(msg) return self.acq_future.cancel() # self._main_data_model.is_acquiring.value = False # all the rest will be handled by on_acquisition_done() def _export_to_file(self, acq_future): """ return (list of DataArray, filename): data exported and filename """ streams = list(self._tab_data_model.acquisitionStreams) st = acq.stream.StreamTree(streams=streams) thumb = acq.computeThumbnail(st, acq_future) data, exp = acq_future.result() filename = self.filename.value if data: exporter = dataio.get_converter(self.conf.last_format) exporter.export(filename, data, thumb) logging.info(u"Acquisition saved as file '%s'.", filename) else: logging.debug("Not saving into file '%s' as there is no data", filename) return data, exp, filename @call_in_wx_main def on_acquisition_done(self, future): """ Callback called when the acquisition is finished (either successfully or cancelled) """ self.btn_cancel.Disable() self._main_data_model.is_acquiring.value = False self.acq_future = None # To avoid holding the ref in memory self._acq_future_connector = None try: data, exp = future.result() except CancelledError: # hide progress bar (+ put pack estimated time) self.gauge_acq.Hide() # don't change filename => we can reuse it self._reset_acquisition_gui(keep_filename=True) return except Exception as exp: # leave the gauge, to give a hint on what went wrong. logging.exception("Acquisition failed") self._reset_acquisition_gui("Acquisition failed (see log panel).", level=logging.WARNING, keep_filename=True) return # Handle the case acquisition failed "a bit" if exp: logging.error("Acquisition failed (after %d streams): %s", len(data), exp) # save result to file self.lbl_acqestimate.SetLabel("Saving file...") # on big acquisitions, it can take ~20s sf = self._executor.submit(self._export_to_file, future) sf.add_done_callback(self.on_file_export_done) @call_in_wx_main def on_file_export_done(self, future): """ Callback called when the acquisition is finished (either successfully or cancelled) """ # hide progress bar self.gauge_acq.Hide() try: data, exp, filename = future.result() except Exception: logging.exception("Saving acquisition failed") self._reset_acquisition_gui( "Saving acquisition file failed (see log panel).", level=logging.WARNING) return if exp is None: # Needs to be done before changing tabs as it will play again the stream # (and they will be immediately be stopped when changing tab). self._reset_acquisition_gui() # TODO: we should add the file to the list of recently-used files # cf http://pyxdg.readthedocs.org/en/latest/recentfiles.html # display in the analysis tab self._show_acquisition(data, open(filename)) else: self._reset_acquisition_gui("Acquisition failed (see log panel).", level=logging.WARNING, keep_filename=(not data))
class SparcAcquiController(object): """ Takes care of the acquisition button and process on the Sparc acquisition tab. """ def __init__(self, tab_data, tab_panel, streambar_controller): """ tab_data (MicroscopyGUIData): the representation of the microscope GUI tab_panel: (wx.Frame): the frame which contains the 4 viewports stream_ctrl (StreamBarController): controller to pause/resume the streams """ self._tab_data_model = tab_data self._main_data_model = tab_data.main self._tab_panel = tab_panel self._streambar_controller = streambar_controller # For file selection self.conf = conf.get_acqui_conf() # TODO: this should be the date at which the user presses the acquire # button (or when the last settings were changed)! # At least, we must ensure it's a new date after the acquisition # is done. # Filename to save the acquisition self.filename = model.StringVA(create_filename(self.conf.last_path, self.conf.fn_ptn, self.conf.last_extension, self.conf.fn_count)) self.filename.subscribe(self._onFilename, init=True) # For acquisition # a ProgressiveFuture if the acquisition is going on self.btn_acquire = self._tab_panel.btn_sparc_acquire self.btn_change_file = self._tab_panel.btn_sparc_change_file self.btn_cancel = self._tab_panel.btn_sparc_cancel self.acq_future = None self.gauge_acq = self._tab_panel.gauge_sparc_acq self.lbl_acqestimate = self._tab_panel.lbl_sparc_acq_estimate self.bmp_acq_status_warn = self._tab_panel.bmp_acq_status_warn self.bmp_acq_status_info = self._tab_panel.bmp_acq_status_info self._acq_future_connector = None # TODO: share an executor with the whole GUI. self._executor = futures.ThreadPoolExecutor(max_workers=2) # Link buttons self.btn_acquire.Bind(wx.EVT_BUTTON, self.on_acquisition) self.btn_change_file.Bind(wx.EVT_BUTTON, self.on_change_file) self.btn_cancel.Bind(wx.EVT_BUTTON, self.on_cancel) self.gauge_acq.Hide() self._tab_panel.Parent.Layout() # Animator for messages containing ellipsis character self._ellipsis_animator = None # TODO: we need to be informed if the user closes suddenly the window # self.Bind(wx.EVT_CLOSE, self.on_close) self._roa = tab_data.semStream.roi # Listen to change of streams to update the acquisition time self._prev_streams = set() # set of streams already listened to tab_data.streams.subscribe(self._onStreams, init=True) # also listen to .semStream, which is not in .streams for va in self._get_settings_vas(tab_data.semStream): va.subscribe(self._onAnyVA) # Extra options affecting the acquisitions globally tab_data.pcdActive.subscribe(self._onAnyVA) # TODO: should also listen to the VAs of the leeches on semStream tab_data.useScanStage.subscribe(self._onAnyVA) self._roa.subscribe(self._onROA, init=True) # Listen to preparation state self._main_data_model.is_preparing.subscribe(self.on_preparation) def __del__(self): self._executor.shutdown(wait=False) # black list of VAs name which are known to not affect the acquisition time VAS_NO_ACQUSITION_EFFECT = ("image", "autoBC", "intensityRange", "histogram", "is_active", "should_update", "status", "name", "tint") def _get_settings_vas(self, stream): """ Find all the VAs of a stream which can potentially affect the acquisition time return (set of VAs) """ nvas = model.getVAs(stream) # name -> va vas = set() # remove some VAs known to not affect the acquisition time for n, va in nvas.items(): if n not in self.VAS_NO_ACQUSITION_EFFECT: vas.add(va) return vas def _onFilename(self, name): """ updates the GUI when the filename is updated """ # decompose into path/file path, base = os.path.split(name) self._tab_panel.txt_destination.SetValue(unicode(path)) # show the end of the path (usually more important) self._tab_panel.txt_destination.SetInsertionPointEnd() self._tab_panel.txt_filename.SetValue(unicode(base)) def _onROA(self, roi): """ updates the acquire button according to the acquisition ROI """ self.check_acquire_button() self.update_acquisition_time() # to update the message def on_preparation(self, is_preparing): self.check_acquire_button() self.update_acquisition_time() def check_acquire_button(self): self.btn_acquire.Enable(self._roa.value != UNDEFINED_ROI and not self._main_data_model.is_preparing.value) def _onStreams(self, streams): """ Called when streams are added/deleted. Used to listen to settings change and update the acquisition time. """ streams = set(streams) # remove subscription for streams that were deleted for s in (self._prev_streams - streams): for va in self._get_settings_vas(s): va.unsubscribe(self._onAnyVA) # add subscription for new streams for s in (streams - self._prev_streams): for va in self._get_settings_vas(s): va.subscribe(self._onAnyVA) self._prev_streams = streams self.update_acquisition_time() # to update the message def _onAnyVA(self, val): """ Called whenever a VA which might affect the acquisition is modified """ self.update_acquisition_time() # to update the message def update_fn_suggestion(self): """ When the filename counter is updated in a plugin, the suggested name for the main acquisition needs to be updated """ self.filename.value = create_filename(self.conf.last_path, self.conf.fn_ptn, self.conf.last_extension, self.conf.fn_count) def on_change_file(self, evt): """ Shows a dialog to change the path, name, and format of the acquisition file. returns nothing, but updates .filename and .conf """ # Update .filename with new filename instead of input name so the right # time is used fn = create_filename(self.conf.last_path, self.conf.fn_ptn, self.conf.last_extension, self.conf.fn_count) new_name = ShowAcquisitionFileDialog(self._tab_panel, fn) if new_name is not None: self.filename.value = new_name self.conf.fn_ptn, self.conf.fn_count = guess_pattern(new_name) logging.debug("Generated filename pattern '%s'", self.conf.fn_ptn) @wxlimit_invocation(1) # max 1/s def update_acquisition_time(self): if self._ellipsis_animator: # cancel if there is an ellipsis animator updating the status message self._ellipsis_animator.cancel() self._ellipsis_animator = None # Don't update estimated time if acquisition is running (as we are # sharing the label with the estimated time-to-completion). if self._main_data_model.is_acquiring.value: return lvl = None # icon status shown if self._main_data_model.is_preparing.value: txt = u"Optical path is being reconfigured…" self._ellipsis_animator = EllipsisAnimator(txt, self.lbl_acqestimate) self._ellipsis_animator.start() lvl = logging.INFO elif self._roa.value == UNDEFINED_ROI: # TODO: update the default text to be the same txt = u"Region of acquisition needs to be selected" lvl = logging.WARN else: streams = self._tab_data_model.acquisitionStreams acq_time = acq.estimateTime(streams) acq_time = math.ceil(acq_time) # round a bit pessimistic txt = u"Estimated time is {}." txt = txt.format(units.readable_time(acq_time)) logging.debug("Updating status message %s, with level %s", txt, lvl) self.lbl_acqestimate.SetLabel(txt) self._show_status_icons(lvl) def _show_status_icons(self, lvl): # update status icon to show the logging level self.bmp_acq_status_info.Show(lvl in (logging.INFO, logging.DEBUG)) self.bmp_acq_status_warn.Show(lvl == logging.WARN) self._tab_panel.Layout() def _pause_streams(self): """ Freeze the streams settings and ensure no stream is playing """ self._streambar_controller.pauseStreams() self._streambar_controller.pause() self._streambar_controller.enable(False) def _resume_streams(self): """ Resume (unfreeze) the stream settings """ self._streambar_controller.enable(True) self._streambar_controller.resume() def _reset_acquisition_gui(self, text=None, level=None, keep_filename=False): """ Set back every GUI elements to be ready for the next acquisition text (None or str): a (error) message to display instead of the estimated acquisition time level (None or logging.*): logging level of the text, shown as an icon. If None, no icon is shown. keep_filename (bool): if True, will not update the filename """ self.btn_cancel.Hide() self.btn_acquire.Enable() self._tab_panel.Layout() self._resume_streams() if not keep_filename: self.conf.fn_count = update_counter(self.conf.fn_count) # Update filename even if keep_filename is True (but don't update counter). This # ensures that the time is always up to date. self.filename.value = create_filename(self.conf.last_path, self.conf.fn_ptn, self.conf.last_extension, self.conf.fn_count) if text is not None: self.lbl_acqestimate.SetLabel(text) self._show_status_icons(level) else: self.update_acquisition_time() def _show_acquisition(self, data, acqfile): """ Show the acquired data (saved into a file) in the analysis tab. data (list of DataFlow): all the raw data acquired acqfile (File): file object to which the data was saved """ # get the analysis tab analysis_tab = self._main_data_model.getTabByName("analysis") analysis_tab.display_new_data(acqfile.name, data) # show the new tab self._main_data_model.tab.value = analysis_tab def on_acquisition(self, evt): """ Start the acquisition (really) Similar to win.acquisition.on_acquire() """ # Time-resolved data cannot be saved in .ome.tiff format for now # OME-TIFF wants to save each time data on a separate "page", which causes too many pages. has_temporal = False for s in self._tab_data_model.streams.value: if (isinstance(s, ScannedTemporalSettingsStream) or isinstance(s, ScannedTCSettingsStream) or isinstance(s, TemporalSpectrumSettingsStream)): has_temporal = True if (self.conf.last_format == 'TIFF' or self.conf.last_format == 'Serialized TIFF') and has_temporal: raise NotImplementedError("Cannot save temporal data in %s format, data format must be HDF5." \ % self.conf.last_format) self._pause_streams() self.btn_acquire.Disable() self.btn_cancel.Enable() self._main_data_model.is_acquiring.value = True self.gauge_acq.Show() self.btn_cancel.Show() self._show_status_icons(None) self._tab_panel.Layout() # to put the gauge at the right place # start acquisition + connect events to callback self.acq_future = acq.acquire(self._tab_data_model.acquisitionStreams) self._acq_future_connector = ProgressiveFutureConnector(self.acq_future, self.gauge_acq, self.lbl_acqestimate) self.acq_future.add_done_callback(self.on_acquisition_done) def on_cancel(self, evt): """ Called during acquisition when pressing the cancel button """ if not self.acq_future: msg = "Tried to cancel acquisition while it was not started" logging.warning(msg) return self.acq_future.cancel() # self._main_data_model.is_acquiring.value = False # all the rest will be handled by on_acquisition_done() def _export_to_file(self, acq_future): """ return (list of DataArray, filename): data exported and filename """ streams = list(self._tab_data_model.acquisitionStreams) st = acq.stream.StreamTree(streams=streams) thumb = acq.computeThumbnail(st, acq_future) data, exp = acq_future.result() if data: filename = self.filename.value exporter = dataio.get_converter(self.conf.last_format) exporter.export(filename, data, thumb) logging.info(u"Acquisition saved as file '%s'.", filename) else: logging.debug("Not saving into file '%s' as there is no data", filename) return data, exp, filename @call_in_wx_main def on_acquisition_done(self, future): """ Callback called when the acquisition is finished (either successfully or cancelled) """ self.btn_cancel.Disable() self._main_data_model.is_acquiring.value = False self.acq_future = None # To avoid holding the ref in memory self._acq_future_connector = None try: data, exp = future.result() except CancelledError: # hide progress bar (+ put pack estimated time) self.gauge_acq.Hide() # don't change filename => we can reuse it self._reset_acquisition_gui(keep_filename=True) return except Exception as exp: # leave the gauge, to give a hint on what went wrong. logging.exception("Acquisition failed") self._reset_acquisition_gui("Acquisition failed (see log panel).", level=logging.WARNING, keep_filename=True) return # Handle the case acquisition failed "a bit" if exp: logging.error("Acquisition failed (after %d streams): %s", len(data), exp) # save result to file self.lbl_acqestimate.SetLabel("Saving file...") # on big acquisitions, it can take ~20s sf = self._executor.submit(self._export_to_file, future) sf.add_done_callback(self.on_file_export_done) @call_in_wx_main def on_file_export_done(self, future): """ Callback called when the acquisition is finished (either successfully or cancelled) """ # hide progress bar self.gauge_acq.Hide() try: data, exp, filename = future.result() except Exception: logging.exception("Saving acquisition failed") self._reset_acquisition_gui("Saving acquisition file failed (see log panel).", level=logging.WARNING) return if exp is None: # Needs to be done before changing tabs as it will play again the stream # (and they will be immediately be stopped when changing tab). self._reset_acquisition_gui() # TODO: we should add the file to the list of recently-used files # cf http://pyxdg.readthedocs.org/en/latest/recentfiles.html # display in the analysis tab self._show_acquisition(data, open(filename)) else: self._reset_acquisition_gui("Acquisition failed (see log panel).", level=logging.WARNING, keep_filename=(not data))