def _onNewImage(self, _): # update overview whenever the streams change, limited to a frequency of 1 Hz if self.curr_s and self.curr_s.image.value is not None: s = self.curr_s img = s.image.value logging.debug("Updating overview using image at %s", getBoundingBox(img)) if isinstance(s, acqstream.OpticalStream): insert_tile_to_image(img, self.im_opt) elif isinstance(s, acqstream.EMStream): insert_tile_to_image(img, self.im_sem) else: logging.info("%s not added to overview image as it's not optical nor EM", s) self._update_ovv()
def test_overview_acquisition(self): s = stream.SEMStream( "Single beam", self.sed, self.sed.data, self.ebeam, focuser=self. efocuser, # Not used during acquisition, but done by the GUI hwemtvas={"scale", "dwellTime", "horizontalFoV"}) # This should be used by the acquisition s.dwellTime.value = 1e-6 # s # Use random settings and check they are overridden by the overview acquisition s.scale.value = (2, 2) s.horizontalFoV.value = 20e-6 # m # Known position of the center scintillator scintillator5_area = (-0.007, -0.007, 0.007, 0.007) # l, b, r, t # Small area for DEBUG (3x3) # scintillator5_area = (-0.002, -0.002, 0.002, 0.002) # l, b, r, t est_time = fastem.estimateTiledAcquisitionTime(s, self.stage, scintillator5_area) # don't use for DEBUG example self.assertGreater(est_time, 10) # It should take more than 10s! (expect ~5 min) before_start_t = time.time() f = fastem.acquireTiledArea(s, self.stage, scintillator5_area) time.sleep(1) start_t, end_t = f.get_progress() self.assertGreater(start_t, before_start_t) # don't use for DEBUG example self.assertGreater(end_t, time.time() + 10) # Should report still more than 10s overview_da = f.result() self.assertGreater(overview_da.shape[0], 2000) self.assertGreater(overview_da.shape[1], 2000) # Check the final area fits the requested area, with possibly a little bit of margin bbox = img.getBoundingBox(overview_da) fov = bbox[2] - bbox[0], bbox[3] - bbox[1] logging.debug("Got image of size %s, with FoV %s = %s", overview_da.shape, fov, bbox) self.assertLessEqual(bbox[0], scintillator5_area[0]) # Left self.assertLessEqual(bbox[1], scintillator5_area[1]) # Bottom self.assertGreaterEqual(bbox[2], scintillator5_area[2]) # Right self.assertGreaterEqual(bbox[3], scintillator5_area[3]) # Top
def _getFov(self, sd): """ sd (Stream or DataArray): If it's a stream, it must be a live stream, and the FoV will be estimated based on the settings. return (float, float): width, height in m """ if isinstance(sd, model.DataArray): # The actual FoV, as the data recorded it im_bbox = img.getBoundingBox(sd) logging.debug("Bounding box of stream data: %s", im_bbox) return im_bbox[2] - im_bbox[0], im_bbox[3] - im_bbox[1] elif isinstance(sd, Stream): # Ask the stream, which estimates based on the emitter/detector settings try: return sd.guessFoV() except (NotImplementedError, AttributeError): raise TypeError( "Unsupported Stream %s, it doesn't have a .guessFoV()" % (sd, )) else: raise TypeError("Unsupported object")
def test_area(self): """ Test the acquired area matches the requested area """ fm_fov = compute_camera_fov(self.ccd) # Using "songbird-sim-ccd.h5" in simcam with tile max_res: (260, 348) area = (0, 0, fm_fov[0] * 2, fm_fov[1] * 3) # left, bottom, right, top overlap = 0.2 # No focuser, to make it faster, and it doesn't affect the FoV fs = stream.FluoStream("fluo1", self.ccd, self.ccd.data, self.light, self.light_filter) future = acquireTiledArea([fs], self.stage, area=area, overlap=overlap, registrar=REGISTER_IDENTITY, weaver=WEAVER_MEAN) data = future.result() self.assertIsInstance(data[0], model.DataArray) self.assertEqual(len(data[0].shape), 2) # The center should be almost precisely at the center of the request RoA, # modulo the stage precision (which is very good on the simulator). # The size can be a little bit bigger (but never smaller), as it's # rounded up to a tile. bbox = img.getBoundingBox(data[0]) data_center = data[0].metadata[model.MD_POS] area_center = (area[0] + area[2]) / 2, (area[1] + area[3]) / 2 logging.debug("Expected area: %s %s, actual area: %s %s", area, area_center, bbox, data_center) self.assertAlmostEqual(data_center[0], area_center[0], delta=1e-6) # +- 1µm self.assertAlmostEqual(data_center[1], area_center[1], delta=1e-6) # +- 1µm self.assertTrue(area[0] - fm_fov[0] / 2 <= bbox[0] <= area[0]) self.assertTrue(area[1] - fm_fov[1] / 2 <= bbox[1] <= area[1]) self.assertTrue(area[2] <= bbox[2] <= area[2] + fm_fov[0] / 2) self.assertTrue(area[3] <= bbox[3] <= area[3] + fm_fov[1] / 2)
def _on_overview_acquire(self, evt): # Disable direct image update, as it would duplicate the overview image, but less pretty. self.main_data.stage.position.unsubscribe(self.on_stage_pos_change) self._on_current_stream([]) try: das = self._acquisition_controller.open_acquisition_dialog() finally: self.main_data.stage.position.subscribe(self.on_stage_pos_change) self._on_current_stream(self._data_model.streams.value) if not das: return for da in das: logging.debug("Acquired overview image %s FoV: %s", da.metadata.get(MD_DESCRIPTION, ""), getBoundingBox(da)) # Store the data somewhere, so that it's possible to open it full size later self._save_overview(das) # Convert each DataArray to a Stream + Projection, so that we can display it streams = udataio.data_to_static_streams(das) # Only reset the channels which have a new data opt = [s for s in streams if isinstance(s, stream.OpticalStream)] if opt: self._bkg_opt[:] = 0 em = [s for s in streams if isinstance(s, stream.EMStream)] if em: self._bkg_sem[:] = 0 # Compute the projection, this is done asynchronously (and for now, # all at the same time, which might be clever... or not, if the # data is really large and the memory is limited) projs = [stream.RGBSpatialProjection(s) for s in opt + em] logging.debug("Adding %s streams to the overview", len(projs)) for p in projs: def add_bkg_ovv(im, proj=p): """ Receive the projected image (RGB) and add it to the overview """ # To handle cases where the projection was faster than subscribing, # we get called at subscription. If we receive None, we just need # to be a little bit more patient. if im is None: return if isinstance(proj.stream, stream.OpticalStream): bkg = self._bkg_opt else: bkg = self._bkg_sem insert_tile_to_image(im, bkg) logging.debug("Added overview projection %s", proj.name.value) # Normally not necessary as the image will not change, and the # projection + stream will go out of scope, which will cause # the VA to be unsubscribed automatically. But it feels cleaner. proj.image.unsubscribe(add_bkg_ovv) del self._bkg_ovv_subs[proj] # We could only do it when _bkg_ovv_subs is empty, as a sign it's # the last one... but it could delay quite a bit, and could easily # break if for some reason projection fails. self._update_ovv() # Keep a reference self._bkg_ovv_subs[p] = add_bkg_ovv p.image.subscribe(add_bkg_ovv, init=True)
def __init__(self, main_data, tab, overview_canvas, m_view, stream_bar): self.main_data = main_data self._tab = tab self._data_model = tab.tab_data_model self.canvas = overview_canvas self.m_view = m_view self._stream_bar = stream_bar self.conf = get_acqui_conf() self.curr_s = None # Timer to detect when the stage ends moving self._timer_pos = wx.PyTimer(self.add_pos_to_history) if hasattr(m_view, "merge_ratio"): m_view.merge_ratio.subscribe(self._on_merge_ratio_change) # Global overview image (Delphi) if main_data.overview_ccd: # Overview camera can be RGB => in that case len(shape) == 4 if len(main_data.overview_ccd.shape) == 4: overview_stream = acqstream.RGBCameraStream( "Overview", main_data.overview_ccd, main_data.overview_ccd.data, None, acq_type=MD_AT_OVV_FULL) else: overview_stream = acqstream.BrightfieldStream( "Overview", main_data.overview_ccd, main_data.overview_ccd.data, None, acq_type=MD_AT_OVV_FULL) self.m_view.addStream(overview_stream) # TODO: add it to self.tab_data_model.streams? else: # black image to display history overlay separately from built-up ovv image # controlled by merge slider da, _ = self._initialize_ovv_im(OVV_SHAPE) history_stream = acqstream.RGBUpdatableStream( "History Stream", da, acq_type=MD_AT_HISTORY) self.m_view.addStream(history_stream) # Built-up overview image self.ovv_im, self.m_view.mpp.value = self._initialize_ovv_im(OVV_SHAPE) logging.debug("Overview image FoV: %s", getBoundingBox(self.ovv_im)) # Initialize individual ovv images for optical and sem stream self.im_opt = copy.deepcopy(self.ovv_im) self.im_sem = copy.deepcopy(self.ovv_im) # Extra images to be used for complete overviews, shown behind the build-up images self._bkg_opt = copy.deepcopy(self.ovv_im) self._bkg_sem = copy.deepcopy(self.ovv_im) # Add stream to view self.upd_stream = acqstream.RGBUpdatableStream( "Overview Stream", self.ovv_im, acq_type=MD_AT_OVV_TILES) self.m_view.addStream(self.upd_stream) self._data_model.focussedView.subscribe(self._on_focused_view) if main_data.stage: # Update the image when the stage move main_data.stage.position.subscribe(self.on_stage_pos_change, init=True) main_data.chamberState.subscribe(self._on_chamber_state) self._data_model.streams.subscribe(self._on_current_stream) # Add a "acquire overview" button. self._stream_bar.btn_add_overview.Bind(wx.EVT_BUTTON, self._on_overview_acquire) self._acquisition_controller = acqcont.OverviewStreamAcquiController( self._data_model, tab) self._bkg_ovv_subs = { } # Just used temporarily when background overview is projected