def test_filename_is_unique(self): fns = { 'test-123': 'test-124', 'test 0800 great': 'test 0801 great', 'test-%s-1' % time.strftime('%Y%m%d'): 'test-%s-2' % time.strftime('%Y%m%d'), 'booo': 'booo-001', } for fn, new_fn in fns.items(): ext = '.0.ome.tiff' # Create file open('./%s%s' % (fn, ext), "w+").close() ptn, cnt = guess_pattern(fn) new_fullfn = os.path.join('.', new_fn) + ext next_fullfn = create_filename('.', ptn, ext, cnt) self.assertEqual(next_fullfn, new_fullfn) os.remove('./%s%s' % (fn, ext)) # Check what happens is next proposed file is also already in directory open('./test-123.tiff', "w+").close() open('./test-124.tiff', "w+").close() ptn, cnt = guess_pattern('./test-123') new_fullfn = os.path.join('.', 'test-125.tiff') self.assertEqual(create_filename('.', ptn, '.tiff', cnt), new_fullfn) os.remove('./test-123.tiff') os.remove('./test-124.tiff')
def test_create_filename(self): # Test some time related patterns later, so that the right time is used fn_ptns = { 'test-123': ('test-{cnt}', '123'), '%stest-123' % date: ('{datelng}test-{cnt}', '123'), '123-test-%s' % date: ('{cnt}-test-{datelng}', '123'), 'test-0000': ('test-{cnt}', '0000'), 'test2-45': ('test2-{cnt}', '45') } for fn, ptn in fn_ptns.items(): for ext in EXTS: fullfn = os.path.join(PATH, fn) + ext self.assertEqual(create_filename(PATH, ptn[0], ext, ptn[1]), fullfn) # Assertion takes ~ 1e-4 seconds, so it's safe to assume that the time hasn't changed self.assertEqual( create_filename(PATH, 'test-{cnt}-{timeshrt}', '.0.ome.tiff', '123'), os.path.join(PATH, 'test-123-%s.0.ome.tiff' % time.strftime('%H%M'))) self.assertEqual( create_filename(PATH, 'test-{cnt}-{timeshrt_colon}', '.0.ome.tiff', '123'), os.path.join(PATH, 'test-123-%s.0.ome.tiff' % time.strftime('%H:%M'))) self.assertEqual( create_filename(PATH, '{datelng}{timelng}-acquisition', '.tiff', '001'), os.path.join(PATH, '%s-acquisition.tiff' % time.strftime('%Y%m%d%H%M%S'))) self.assertEqual( create_filename(PATH, '{daterev}{timelng}', '.tiff', '001'), os.path.join(PATH, '%s.tiff' % time.strftime('%d%m%Y%H%M%S')))
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 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 _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 _update_filename(self): """ Set filename from pattern in conf file. """ fn = create_filename(self.conf.last_path, self.conf.fn_ptn, '.tiff', self.conf.fn_count) self.conf.fn_count = update_counter(self.conf.fn_count) # Update the widget, without updating the pattern and counter again self.filename.unsubscribe(self._on_filename) self.filename.value = fn self.filename.subscribe(self._on_filename)
def _update_filename(self): """ Set filename from pattern in conf file """ fn = create_filename(self.conf.last_path, self.conf.fn_ptn, '.png', self.conf.fn_count) self.conf.fn_count = update_counter(self.conf.fn_count) # Update the widget, without updating the pattern and counter again self.filename.unsubscribe(self._on_filename) self.filename.value = fn self.filename.subscribe(self._on_filename)
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)
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 """ fn = create_filename(self.conf.last_path, self.conf.fn_ptn, self.conf.last_extension, self.conf.fn_count) new_name = ShowAcquisitionFileDialog(self, 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) # It means the user wants to do a new acquisition self.btn_secom_acquire.SetLabel("START") self.last_saved_file = None
def __init__(self, parent, orig_tab_data): xrcfr_acq.__init__(self, parent) self.conf = get_acqui_conf() for n in presets: self.cmb_presets.Append(n) # TODO: record and reuse the preset used? self.cmb_presets.Select(0) 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) # The name of the last file that got written to disk (used for auto viewing on close) self.last_saved_file = None # True when acquisition occurs self.acquiring = False # a ProgressiveFuture if the acquisition is going on self.acq_future = None self._acq_future_connector = None self._main_data_model = orig_tab_data.main # duplicate the interface, but with only one view self._tab_data_model = self.duplicate_tab_data_model(orig_tab_data) # Create a new settings controller for the acquisition dialog self._settings_controller = SecomSettingsController( self, self._tab_data_model, highlight_change=True # also adds a "Reset" context menu ) orig_view = orig_tab_data.focussedView.value self._view = self._tab_data_model.focussedView.value self._hidden_view = StreamView("Plugin View Hidden") self.streambar_controller = StreamBarController(self._tab_data_model, self.pnl_secom_streams, static=True, ignore_view=True) # The streams currently displayed are the one visible self.add_all_streams() # The list of streams ready for acquisition (just used as a cache) self._acq_streams = {} # FIXME: pass the fold_panels # Compute the preset values for each preset self._preset_values = {} # dict string -> dict (SettingEntries -> value) self._orig_entries = get_global_settings_entries(self._settings_controller) for sc in self.streambar_controller.stream_controllers: self._orig_entries += get_local_settings_entries(sc) self._orig_settings = preset_as_is(self._orig_entries) # to detect changes for n, preset in presets.items(): self._preset_values[n] = preset(self._orig_entries) # Presets which have been confirmed on the hardware self._presets_confirmed = set() # (string) self.start_listening_to_va() # If it could be possible to do fine alignment, allow the user to choose if self._can_fine_align(self._tab_data_model.streams.value): self.chkbox_fine_align.Show() # Set to True to make it the default, but will be automatically # disabled later if the current visible streams don't allow it. self.chkbox_fine_align.Value = True for s in self._tab_data_model.streams.value: if isinstance(s, EMStream): em_det = s.detector em_emt = s.emitter elif isinstance(s, OpticalStream) and not isinstance(s, ScannedFluoStream): opt_det = s.detector self._ovrl_stream = stream.OverlayStream("Fine alignment", opt_det, em_emt, em_det, opm=self._main_data_model.opm) self._ovrl_stream.dwellTime.value = self._main_data_model.fineAlignDwellTime.value else: self.chkbox_fine_align.Show(False) self.chkbox_fine_align.Value = False self._prev_fine_align = self.chkbox_fine_align.Value # make sure the view displays the same thing as the one we are # duplicating self._view.view_pos.value = orig_view.view_pos.value self._view.mpp.value = orig_view.mpp.value self._view.merge_ratio.value = orig_view.merge_ratio.value # attach the view to the viewport self.pnl_view_acq.canvas.fit_view_to_next_image = False self.pnl_view_acq.setView(self._view, self._tab_data_model) # The TOOL_ROA is not present because we don't allow the user to change # the ROA), so we need to explicitly request the canvas to show the ROA. if hasattr(self._tab_data_model, "roa") and self._tab_data_model.roa is not None: cnvs = self.pnl_view_acq.canvas self.roa_overlay = RepetitionSelectOverlay(cnvs, self._tab_data_model.roa, self._tab_data_model.fovComp) cnvs.add_world_overlay(self.roa_overlay) self.Bind(wx.EVT_CHAR_HOOK, self.on_key) self.btn_cancel.Bind(wx.EVT_BUTTON, self.on_close) self.btn_change_file.Bind(wx.EVT_BUTTON, self.on_change_file) self.btn_secom_acquire.Bind(wx.EVT_BUTTON, self.on_acquire) self.cmb_presets.Bind(wx.EVT_COMBOBOX, self.on_preset) self.Bind(wx.EVT_CLOSE, self.on_close) # on_streams_changed is compatible because it doesn't use the args self.chkbox_fine_align.Bind(wx.EVT_CHECKBOX, self.on_streams_changed) self.on_preset(None) # will force setting the current preset # To update the estimated time when streams are removed/added self._view.stream_tree.flat.subscribe(self.on_streams_changed) self._hidden_view.stream_tree.flat.subscribe(self.on_streams_changed)
def __init__(self, parent, orig_tab_data): xrcfr_overview_acq.__init__(self, parent) self.conf = get_acqui_conf() # True when acquisition occurs self.acquiring = False self.data = None # a ProgressiveFuture if the acquisition is going on self.acq_future = None self._acq_future_connector = None self._main_data_model = orig_tab_data.main # duplicate the interface, but with only one view self._tab_data_model = self.duplicate_tab_data_model(orig_tab_data) # The pattern to use for storing each tile file individually # None disables storing them self.filename_tiles = create_filename(self.conf.last_path, "{datelng}-{timelng}-overview", ".ome.tiff") # Create a new settings controller for the acquisition dialog self._settings_controller = LocalizationSettingsController( self, self._tab_data_model, highlight_change=True # also adds a "Reset" context menu ) self.zsteps = model.IntContinuous(1, range=(1, 51)) self._zsteps_vac = VigilantAttributeConnector(self.zsteps, self.zstack_steps, events=wx.EVT_SLIDER) orig_view = orig_tab_data.focussedView.value self._view = self._tab_data_model.focussedView.value self.streambar_controller = StreamBarController(self._tab_data_model, self.pnl_secom_streams, static=True, ignore_view=True) # The streams currently displayed are the one visible self.add_streams() # The list of streams ready for acquisition (just used as a cache) self._acq_streams = {} # Compute the preset values for each preset self._orig_entries = get_global_settings_entries(self._settings_controller) self._orig_settings = preset_as_is(self._orig_entries) for sc in self.streambar_controller.stream_controllers: self._orig_entries += get_local_settings_entries(sc) self.start_listening_to_va() # make sure the view displays the same thing as the one we are # duplicating self._view.view_pos.value = orig_view.view_pos.value self._view.mpp.value = orig_view.mpp.value self._view.merge_ratio.value = orig_view.merge_ratio.value # attach the view to the viewport self.pnl_view_acq.canvas.fit_view_to_next_image = False self.pnl_view_acq.setView(self._view, self._tab_data_model) self.Bind(wx.EVT_CHAR_HOOK, self.on_key) self.btn_cancel.Bind(wx.EVT_BUTTON, self.on_close) self.btn_secom_acquire.Bind(wx.EVT_BUTTON, self.on_acquire) self.Bind(wx.EVT_CLOSE, self.on_close) # on_streams_changed is compatible because it doesn't use the args # To update the estimated time when streams are removed/added self._view.stream_tree.flat.subscribe(self.on_streams_changed) # Set parameters for tiled acq self.overlap = 0.2 try: # Use the stage range, which can be overridden by the MD_POS_ACTIVE_RANGE, # which can be overridden by MD_OVERVIEW_RANGE. # Note: this last one might be temporary, until we have a RoA tool provided in the GUI. stage_rng = { "x": self._main_data_model.stage.axes["x"].range, "y": self._main_data_model.stage.axes["y"].range } stage_md = self._main_data_model.stage.getMetadata() if model.MD_POS_ACTIVE_RANGE in stage_md: stage_rng.update(stage_md[model.MD_POS_ACTIVE_RANGE]) if model.MD_OVERVIEW_RANGE in stage_md: stage_rng.update(stage_md[model.MD_OVERVIEW_RANGE]) # left, bottom, right, top self.area = (stage_rng["x"][0], stage_rng["y"][0], stage_rng["x"][1], stage_rng["y"][1]) except (KeyError, IndexError): raise ValueError("Failed to find stage.MD_POS_ACTIVE_RANGE with x and y range") # Note: It should never be possible to reach here with no streams streams = self.get_acq_streams() for s in streams: self._view.addStream(s) self.update_acquisition_time()
def __init__(self, parent, orig_tab_data): xrcfr_overview_acq.__init__(self, parent) self.conf = get_acqui_conf() # True when acquisition occurs self.acquiring = False self.data = None # a ProgressiveFuture if the acquisition is going on self.acq_future = None self._acq_future_connector = None self._main_data_model = orig_tab_data.main # duplicate the interface, but with only one view self._tab_data_model = self.duplicate_tab_data_model(orig_tab_data) # Store the final image as {datelng}-{timelng}-overview # The pattern to store them in a sub folder, with the name xxxx-overview-tiles/xxx-overview-NxM.ome.tiff # The pattern to use for storing each tile file individually # None disables storing them save_dir = self.conf.last_path if isinstance(orig_tab_data, guimodel.CryoGUIData): save_dir = self.conf.pj_last_path self.filename = create_filename(save_dir, "{datelng}-{timelng}-overview", ".ome.tiff") assert self.filename.endswith(".ome.tiff") dirname, basename = os.path.split(self.filename) tiles_dir = os.path.join(dirname, basename[:-len(".ome.tiff")] + "-tiles") self.filename_tiles = os.path.join(tiles_dir, basename) # Create a new settings controller for the acquisition dialog self._settings_controller = LocalizationSettingsController( self, self._tab_data_model, ) self.zsteps = model.IntContinuous(1, range=(1, 51)) # The depth of field is an indication of how far the focus needs to move # to see the current in-focus position out-of-focus. So it's a good default # value for the zstep size. We use 2x to "really" see something else. # Typically, it's about 1 µm. dof = self._main_data_model.ccd.depthOfField.value self.zstep_size = model.FloatContinuous(2 * dof, range=(1e-9, 100e-6), unit="m") self._zstep_size_vac = VigilantAttributeConnector( self.zstep_size, self.zstep_size_ctrl, events=wx.EVT_COMMAND_ENTER) self.tiles_nx = model.IntContinuous(5, range=(1, 1000)) self.tiles_ny = model.IntContinuous(5, range=(1, 1000)) self._zsteps_vac = VigilantAttributeConnector(self.zsteps, self.zstack_steps, events=wx.EVT_SLIDER) self._tiles_n_vacx = VigilantAttributeConnector( self.tiles_nx, self.tiles_number_x, events=wx.EVT_COMMAND_ENTER) self._tiles_n_vacy = VigilantAttributeConnector( self.tiles_ny, self.tiles_number_y, events=wx.EVT_COMMAND_ENTER) self.area = None # None or 4 floats: left, top, right, bottom positions of the acquisition area (in m) orig_view = orig_tab_data.focussedView.value self._view = self._tab_data_model.focussedView.value self.streambar_controller = StreamBarController(self._tab_data_model, self.pnl_secom_streams, static=True, ignore_view=True) # The streams currently displayed are the one visible self.add_streams() # The list of streams ready for acquisition (just used as a cache) self._acq_streams = {} # Find every setting, and listen to it self._orig_entries = get_global_settings_entries( self._settings_controller) for sc in self.streambar_controller.stream_controllers: self._orig_entries += get_local_settings_entries(sc) self.start_listening_to_va() # make sure the view displays the same thing as the one we are # duplicating self._view.view_pos.value = orig_view.view_pos.value self._view.mpp.value = orig_view.mpp.value self._view.merge_ratio.value = orig_view.merge_ratio.value # attach the view to the viewport self.pnl_view_acq.canvas.fit_view_to_next_image = False self.pnl_view_acq.setView(self._view, self._tab_data_model) self.Bind(wx.EVT_CHAR_HOOK, self.on_key) self.btn_cancel.Bind(wx.EVT_BUTTON, self.on_close) self.btn_secom_acquire.Bind(wx.EVT_BUTTON, self.on_acquire) self.Bind(wx.EVT_CLOSE, self.on_close) # Set parameters for tiled acq self.overlap = 0.2 try: # Use the stage range, which can be overridden by the MD_POS_ACTIVE_RANGE. # Note: this last one might be temporary, until we have a RoA tool provided in the GUI. self._tiling_rng = { "x": self._main_data_model.stage.axes["x"].range, "y": self._main_data_model.stage.axes["y"].range } stage_md = self._main_data_model.stage.getMetadata() if model.MD_POS_ACTIVE_RANGE in stage_md: self._tiling_rng.update(stage_md[model.MD_POS_ACTIVE_RANGE]) except (KeyError, IndexError): raise ValueError( "Failed to find stage.MD_POS_ACTIVE_RANGE with x and y range") # Note: It should never be possible to reach here with no streams streams = self.get_acq_streams() for s in streams: self._view.addStream(s) # To update the estimated time & area when streams are removed/added self._view.stream_tree.flat.subscribe(self.on_streams_changed, init=True)
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 __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)