def saveImage(self): """Dump the current frame to a file""" outfile = datetime.datetime.now().strftime( "%Y%m%d-%H%M%S.%f") + ".tiff" if self.app: module_io = self.app.get_module("io") drc = module_io.get_experiment_directory() drc.mkdir(exist_ok=True, parents=True) outfile = drc / outfile try: flatfield, h = read_tiff(module_io.get_flatfield()) frame = apply_flatfield_correction(self.frame, flatfield) except: frame = self.frame h = {} else: frame = self.frame h = {} write_tiff(outfile, frame, header=h) print(" >> Wrote file:", outfile)
def export(self, outfile: str = 'stitched.tiff') -> None: """Export the stitched image to a tiff file. Parameters ---------- outfile : str Name of the image file. """ from instamatic.formats import write_tiff write_tiff(outfile, self.stitched)
def write_image_data(self, buffer: list): """Write image data in the buffer. The image buffer is passed as a list of tuples, where each tuple contains the index (int), image data (2D numpy array), metadata/header (dict).""" if buffer: drc = self.path / "tiff_image" drc.mkdir(exist_ok=True) while len(buffer) != 0: i, img, h = buffer.pop(0) fn = drc / f"{i:05d}.tiff" write_tiff(fn, img, header=h)
def test_tiff(data, header): out = 'out.tiff' formats.write_tiff(out, data, header) assert os.path.exists(out) img, h = formats.read_image(out) assert np.allclose(img, data) assert header == h
def write_tiff(self, path: str, i: int) -> str: """Write the image+header with sequence number `i` to the directory `path` in TIFF format. Returns the path to the written image.""" img = self.data[i] h = self.headers[i] # PETS reads only 16bit unsignt integer TIFF img = np.round(img, 0).astype(np.uint16) fn = path / f"{i:05d}.tiff" write_tiff(fn, img, header=h) return fn
def save_image(controller, **kwargs): frame = kwargs.get('frame') module_io = controller.app.get_module('io') drc = module_io.get_experiment_directory() drc.mkdir(exist_ok=True, parents=True) timestamp = datetime.now().strftime( '%H-%M-%S.%f')[:-3] # cut last 3 digits for ms resolution outfile = drc / f'frame_{timestamp}.tiff' try: flatfield, h = read_tiff(module_io.get_flatfield()) frame = apply_flatfield_correction(frame, flatfield) except BaseException: frame = frame h = {} write_tiff(outfile, frame, header=h) print('Wrote file:', outfile)
def save(self, drc: str = None): """Save the data to the given directory. drc : str Path of the output directory. If `None`, it defaults to the instamatic data directory defined in the config. """ from instamatic.formats import write_tiff from instamatic.io import get_new_work_subdirectory if not drc: drc = get_new_work_subdirectory('montage') fns = [] for i, (img, h) in enumerate(self.buffer): name = f'mont_{i:04d}.tiff' write_tiff(drc / name, img, header=h) fns.append(name) n_images = i + 1 d = { 'stagecoords': self.stagecoords.tolist(), 'stagematrix': self.stagematrix.tolist(), 'gridshape': [self.nx, self.ny], 'direction': self.direction, 'zigzag': self.zigzag, 'overlap': self.overlap, 'filenames': fns, 'magnification': self.magnification, 'abs_mag_index': self.abs_mag_index, 'mode': self.mode, 'spotsize': self.spotsize, 'flip': self.flip, 'image_binning': self.binning, 'pixelsize': self.pixelsize, } import yaml yaml.dump(d, stream=open(drc / 'montage.yaml', 'w')) print(f' >> Wrote {n_images} montage images to {drc}')
def start_collection(self, target_angle: float, start_angle: float = None, manual_control: bool = False): """ manual_control : bool Control the rotation using the buttons or pedals """ angle_tolerance = 0.5 # degrees self.now = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') self.stage_positions = [] interval = 0.7 # interval at which it check for rotation to end / does tracking if self.track: start_angle, target_angle = self.prepare_tracking() if start_angle: print(f'Going to starting angle: {start_angle:.1f}') self.ctrl.stage.a = start_angle self.start_position = self.ctrl.stage.get() start_angle = self.start_position.a if self.track: # Center crystal position if self.mode == 'diff': self.ctrl.difffocus.defocus(self.defocus_offset) self.ctrl.beam.unblank() input( 'Move SAED aperture to crystal and press <ENTER> to measure! ') # cannot do this while lieview is running # img1 = self.ctrl.get_rotated_image() # write_tiff(self.path / "image_before.tiff", img1) self.ctrl.beam.blank() if self.mode == 'diff': self.ctrl.difffocus.refocus() time.sleep(3) self.ctrl.beam.unblank( delay=0.2 ) # give the beamblank some time to dissappear to avoid weak first frame # with autoincrement(False), otherwise use `get_next_empty_image_index()` # start_index is set to 1, because EMMENU always takes a single image (0) when liveview is activated start_index = 1 # start_index = self.emmenu.get_next_empty_image_index() if manual_control: start_angle = self.manual_activation() last_angle = 999 elif self.rotation_speed: self.ctrl.stage.set_a_with_speed(a=target_angle, speed=self.rotation_speed, wait=False) else: self.ctrl.stage.set(a=target_angle, wait=False) self.emmenu.start_record() # start recording t0 = time.perf_counter() t_delta = t0 n = 0 print('Acquiring data...') while True: t = time.perf_counter() if not manual_control: if abs(self.ctrl.stage.a - target_angle) < angle_tolerance: print('Target angle reached!') break if t - t_delta > interval: n += 1 x, y, z, a, _ = pos = self.ctrl.stage.get() self.stage_positions.append((t, pos)) t_delta = t # print(t, pos) if manual_control: current_angle = a if last_angle == current_angle: print( f'Manual rotation was interrupted (current: {current_angle:.2f} | last {last_angle:.2f})' ) break last_angle = current_angle print(f' >> Current angle: {a:.2f}', end=' \r') if self.track: self.track_crystal(n=n, angle=a) # Stop/interrupt and go to next crystal if msvcrt.kbhit(): key = msvcrt.getch().decode() if key == ' ': print('Stopping the stage!') self.ctrl.stage.stop() break if key == 'q': self.ctrl.stage.stop() raise InterruptedError('Data collection was interrupted!') t1 = time.perf_counter() self.emmenu.stop_liveview() if self.ctrl.beam.is_blanked: self.ctrl.beam.blank() self.end_position = self.ctrl.stage.get() end_angle = self.end_position.a end_index = self.emmenu.get_image_index() self.t_start = t0 self.t_end = t1 self.total_time = t1 - t0 self.nframes = nframes = end_index - start_index + 1 if nframes < 1: print('No frames measured??') return self.osc_angle = abs(end_angle - start_angle) / nframes # acquisition_time = total_time / nframes self.total_angle = abs(end_angle - start_angle) self.rotation_axis = config.camera.camera_rotation_vs_stage_xy self.camera_length = int(self.ctrl.magnification.get()) self.spotsize = self.ctrl.spotsize self.rotation_speed = (end_angle - start_angle) / self.total_time self.exposure_time = self.emmenu.get_exposure() self.start_angle, self.end_angle = start_angle, end_angle try: # sometimes breaks with: # AttributeError: 'NoneType' object has no attribute 'EMVector' timestamps = self.emmenu.get_timestamps(start_index, end_index) except AttributeError as e: print(e) print(f'Timestamps from {start_index} to {end_index}') timestamps = [1, 2, 3, 4, 5] # just to make it work self.timings = get_acquisition_time(timestamps, exp_time=self.exposure_time, savefig=True, drc=self.path) self.log_end_status() self.log_stage_positions() print('Writing data files...') path_data = self.path / 'tiff' path_data.mkdir(exist_ok=True, parents=True) self.emmenu.writeTiffs(start_index, end_index, path=path_data) if self.track: # Center crystal position if self.mode == 'diff': self.ctrl.difffocus.defocus(self.defocus_offset) self.ctrl.beam.unblank() img2 = self.ctrl.get_rotated_image() write_tiff(self.path / 'image_after.tiff', img2) self.ctrl.beam.blank() if self.mode == 'diff': self.ctrl.difffocus.refocus() print( f'Wrote {nframes} images (#{start_index}->#{end_index}) to {path_data}' ) if self.track: print(f'Done with this crystal (number #{self.crystal_number})!') else: print('Done with this crystal!')
def main_entry(): import argparse from instamatic.formats import write_tiff description = """Simple program to acquire image data from the camera.""" parser = argparse.ArgumentParser( description=description, formatter_class=argparse.RawDescriptionHelpFormatter) parser.add_argument( '-b', '--binsize', action='store', type=int, metavar='N', dest='binsize', help="""Binsize to use. Must be one of 1, 2, or 4 (default 1)""") parser.add_argument('-e', '--exposure', action='store', type=float, metavar='N', dest='exposure', help="""Exposure time (default 0.5)""") parser.add_argument('-o', '--out', action='store', type=str, metavar='image.png', dest='outfile', help="""Where to store image""") parser.add_argument('-d', '--display', action='store_true', dest='show_fig', help="""Show the image (default True)""") parser.add_argument( '-s', '--series', action='store_true', dest='take_series', help="""Enable mode to take a series of images (default False)""") parser.set_defaults( binsize=1, exposure=1, outfile=None, show_fig=False, test=False, take_series=False, ) options = parser.parse_args() binsize = options.binsize exposure = options.exposure outfile = options.outfile show_fig = options.show_fig take_series = options.take_series from instamatic import TEMController ctrl = TEMController.initialize() if take_series: i = 1 print('\nUsage:') print(' set b/e/i X -> set binsize/exposure/file number to X') print(' XXX -> Add comment to header') print(' exit -> exit the program') while take_series: outfile = f'image_{i:04d}' inp = input(f'\nHit enter to take an image: \n >> [{outfile}] ') if inp == 'exit': break elif inp.startswith('set'): try: key, value = inp.split()[1:3] except ValueError: print('Input not understood') continue if key == 'e': try: value = float(value) except ValueError as e: print(e) if value > 0: exposure = value elif key == 'b': try: value = int(value) except ValueError as e: print(e) if value in (1, 2, 4): binsize = value elif key == 'i': try: value = int(value) except ValueError as e: print(e) if value > 0: i = value print(f'binsize = {binsize} | exposure = {exposure} | file #{i}') else: arr, h = ctrl.get_image(binsize=binsize, exposure=exposure, comment=inp) write_tiff(outfile, arr, header=h) i += 1 else: import matplotlib.pyplot as plt arr, h = ctrl.get_image(binsize=binsize, exposure=exposure) if show_fig: plt.imshow(arr, cmap='gray', interpolation='none') plt.show() if outfile: write_tiff(outfile, arr, header=h) else: write_tiff('out', arr, header=h)
def start_collection(self, target_angle: float, start_angle: float = None, manual_control: bool = False): """ manual_control : bool Control the rotation using the buttons or pedals """ angle_tolerance = 0.5 # degrees self.now = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') self.stage_positions = [] interval = 0.7 # interval at which it check for rotation to end / does tracking if self.track: start_angle, target_angle = self.prepare_tracking() if start_angle: print(f'Going to starting angle: {start_angle:.1f}') self.ctrl.stage.a = start_angle self.start_position = self.ctrl.stage.get() start_angle = self.start_position.a if self.track: # Center crystal position if self.mode == 'diff': self.ctrl.difffocus.defocus(self.defocus_offset) self.ctrl.beam.unblank() input( 'Move SAED aperture to crystal and press <ENTER> to measure! ') # cannot do this while lieview is running # img1 = self.ctrl.get_raw_image() # write_tiff(self.path / "image_before.tiff", img1) self.ctrl.beam.blank() if self.mode == 'diff': self.ctrl.difffocus.refocus() time.sleep(3) self.ctrl.beam.unblank()( delay=0.2 ) # give the beamblank some time to dissappear to avoid weak first frame if manual_control: start_angle = self.manual_activation() last_angle = 999 elif self.rotation_speed: self.ctrl.stage.set_a_with_speed(a=target_angle, speed=self.rotation_speed, wait=False) else: self.ctrl.stage.set(a=target_angle, wait=False) self.cam.start_record() # start recording t0 = time.perf_counter() t_delta = t0 n = 0 print('Acquiring data...') while True: t = time.perf_counter() if not manual_control: if abs(self.ctrl.stage.a - target_angle) < angle_tolerance: print('Target angle reached!') break if t - t_delta > interval: n += 1 x, y, z, a, _ = pos = self.ctrl.stage.get() self.stage_positions.append((t, pos)) t_delta = t # print(t, pos) if manual_control: current_angle = a if last_angle == current_angle: print( f'Manual rotation was interrupted (current: {current_angle:.2f} | last {last_angle:.2f})' ) break last_angle = current_angle print(f' >> Current angle: {a:.2f}', end=' \r') if self.track: self.track_crystal(n=n, angle=a) # Stop/interrupt and go to next crystal if msvcrt.kbhit(): key = msvcrt.getch().decode() if key == ' ': print('Stopping the stage!') self.ctrl.stage.stop() break if key == 'q': self.ctrl.stage.stop() raise InterruptedError('Data collection was interrupted!') t1 = time.perf_counter() self.cam.stop_record() if self.ctrl.beam.is_blanked: self.ctrl.beam.blank() self.end_position = self.ctrl.stage.get() end_angle = self.end_position.a self.t_start = t0 self.t_end = t1 self.total_time = t1 - t0 print('Waiting for DM to finish...') while not self.cam.get_tag('finish_acquire'): time.sleep(0.2) self.gatan_readout = self.cam.readout() self.nframes = nframes = self.gatan_readout['nframes'] if nframes < 1: print('No frames measured??') return self.osc_angle = abs(end_angle - start_angle) / nframes # acquisition_time = total_time / nframes self.total_angle = abs(end_angle - start_angle) self.rotation_axis = config.camera.camera_rotation_vs_stage_xy self.camera_length = int(self.ctrl.magnification.get()) self.spotsize = self.ctrl.spotsize self.rotation_speed = (end_angle - start_angle) / self.total_time # self.exposure_time = self.cam.get_exposure() self.start_angle, self.end_angle = start_angle, end_angle self.log_end_status() self.log_stage_positions() print('Writing data files...') path_data = self.path / 'tiff' path_data.mkdir(exist_ok=True, parents=True) if self.track: # Center crystal position if self.mode == 'diff': self.ctrl.difffocus.defocus(self.defocus_offset) self.ctrl.beam.unblank() img2 = self.ctrl.get_rotated_image() write_tiff(self.path / 'image_after.tiff', img2) self.ctrl.beam.blank() if self.mode == 'diff': self.ctrl.difffocus.refocus() print(f'Wrote {nframes} images to {path_data}') if self.track: print(f'Done with this crystal (number #{self.crystal_number})!') else: print('Done with this crystal!')
def start_collection(self, exposure_time: float, tilt_range: float, stepsize: float): """Start or continue data collection for `tilt_range` degrees with steps given by `stepsize`, To finalize data collection and write data files, run `self.finalize`. The number of images collected is defined by `tilt_range / stepsize`. exposure_time: Exposure time for each image in seconds tilt_range: Tilt range starting from the current angle in degrees. Must be positive. stepsize: Step size for the angle in degrees, controls the direction and can be positive or negative """ self.spotsize = self.ctrl.spotsize self.now = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") self.logger.info("Data recording started at: {self.now}") self.logger.info(f"Exposure time: {exposure_time} s, Tilt range: {tilt_range}, step size: {stepsize}") ctrl = self.ctrl if stepsize < 0: tilt_range = -abs(tilt_range) else: tilt_range = abs(tilt_range) if self.current_angle is None: self.start_angle = start_angle = ctrl.stageposition.a else: start_angle = self.current_angle + stepsize tilt_positions = np.arange(start_angle, start_angle+tilt_range, stepsize) print(f"\nStart_angle: {start_angle:.3f}") # print "Angles:", tilt_positions image_mode = ctrl.mode if image_mode != "diff": fn = self.tiff_image_path / f"image_{self.offset}.tiff" img, h = self.ctrl.getImage(exposure_time / 5) write_tiff(fn, img, header=h) ctrl.mode_diffraction() time.sleep(1.0) # add some delay to account for beam lag if ctrl.cam.streamable: ctrl.cam.block() # for i, a in enumerate(tilt_positions): for i, angle in enumerate(tqdm.tqdm(tilt_positions)): ctrl.stageposition.a = angle j = i + self.offset img, h = self.ctrl.getImage(exposure_time) self.buffer.append((j, img, h)) self.offset += len(tilt_positions) self.nframes = j self.end_angle = end_angle = ctrl.stageposition.a if ctrl.cam.streamable: ctrl.cam.unblock() self.camera_length = camera_length = int(self.ctrl.magnification.get()) self.stepsize = stepsize self.exposure_time = exposure_time with open(self.path / "summary.txt", "a") as f: print(f"{self.now}: Data collected from {start_angle:.2f} degree to {end_angle:.2f} degree in {len(tilt_positions)} frames.", file=f) print(f"Data collected from {start_angle:.2f} degree to {end_angle:.2f} degree in {len(tilt_positions)} frames.") self.logger.info("Data collected from {start_angle:.2f} degree to {end_angle:.2f} degree (camera length: {camera_length} mm).") self.current_angle = angle print(f"Done, current angle = {self.current_angle:.2f} degrees") if image_mode != "diff": ctrl.mode = image_mode
def do_experiment(cam, beam_ctrl, strength, grid_x, grid_y, dwell_time, rotation, exposure, shuffle_coords=False, blocksize=1024, stream_latency=None, hardware_latency=0.0, expdir=None, plot=False, gui=True): channels = beam_ctrl.n_channels fs = beam_ctrl.fs print("starting experiment") print() print(f" Dwell time: {dwell_time}") print(f" Exposure: {exposure}") print(f" Latency: {stream_latency} + {hardware_latency}") print(f" Exposure: {exposure}") print(f" Grid x: {grid_x}") print(f" Grid y: {grid_y}") print(f" Rotation: {rotation}") print(f" Strength: {strength}") print() ft = 1 / fs if blocksize == 0: blocksize = int(dwell_time / ft) nblocks = int((dwell_time / ft) // blocksize) dwell_time = ft * blocksize * nblocks print(f"Block duration: {blocksize*ft:.3f} ms") print(f"N blocks: {nblocks}") print( f"Adjusted dwell time: {dwell_time:.3f} s @ {blocksize} frames/block") print() assert dwell_time > exposure, f"Dwell time ({dwell_time} s) must be larger than exposure ({exposure} s)" i = 0 def callback(outdata, frames, streamtime, status): assert frames == blocksize if status.output_underflow: print('Output underflow: reduce exposure?') raise sd.CallbackAbort elif status.priming_output: pass else: assert not status, str(status) try: collect, coord = next(gen_coords) data.reshape(-1)[0::channels] = coord[0] data.reshape(-1)[1::channels] = coord[1] except StopIteration: raise sd.CallbackStop if collect: # print(f"{streamtime.currentTime-start_time:.3f} - {streamtime.outputBufferDacTime-start_time:.3f}, {streamtime.outputBufferDacTime-streamtime.currentTime:.3f} ") queue.append(streamtime.outputBufferDacTime) # else: # print(f"Collect: False @ {streamtime.outputBufferDacTime:6.3f}") outdata[:] = data data = np.zeros(blocksize * channels, dtype=np.float32).reshape(-1, channels) event = threading.Event() try: stream_latency = float(stream_latency) except TypeError: if stream_latency is None: stream_latency = beam_ctrl.latency except ValueError: # must be 'low' or 'high' pass # dither: Add random noise to make signal less determininistic, I assume we do not want this stream = sd.OutputStream(samplerate=fs, blocksize=blocksize, latency=stream_latency, device=beam_ctrl.device, channels=beam_ctrl.n_channels, dtype=beam_ctrl.dtype, callback=callback, finished_callback=event.set, extra_settings=beam_ctrl.extra_settings, dither_off=True, prime_output_buffers_using_stream_callback=True) coords = get_coords(grid_x, grid_y, strength, rotation) if shuffle_coords: permutation = np.random.permutation(len(coords)) coords = coords[permutation] gen_coords = signal_generator(coords, repeat=nblocks) queue = deque() buffer = [] waiting_times = [] frame_start_times = [] frame_times = [] missed = [] empty = np.zeros((516, 516), dtype=np.uint16) i = 0 time.sleep(0.25) if gui: cam.block() with stream: start_time = stream.time window_start = start_time print(f"Reported stream latency: {stream.latency:.3f}") print(f"Stream start time: {start_time:.3f}") print() while stream.active or len(queue): previous_window_start = window_start try: next_frame_time = queue.popleft() except IndexError: # print(f" -> No frames @ {stream.time-start_time:6.3f}, continuing!") time.sleep(dwell_time / 2) continue else: i += 1 s = f"\r {i:4d} " current_time = stream.time window_start = next_frame_time + hardware_latency window_end = window_start + dwell_time s += f"[ c: {current_time - start_time:.3f} | n: {window_start - start_time:.3f} | d: {window_start - previous_window_start:.3f} ] -> " if current_time < window_start: diff = window_start - current_time s += f"Waiting: {diff:.3f} s" time.sleep(window_start - current_time) elif current_time > window_end - exposure: s += f" +{current_time - window_start:.3f} s -> Miss :( " missed.append(i) buffer.append( empty) # insert empty to maintain correct data shape print(s, end='') continue else: s += f" +{current_time - window_start:.3f} s" arr = cam.getImage(exposure).astype( np.uint16, copy=False ) # copy=False ensures that no copy is made if dtype is already satisfied buffer.append(arr) s += f" -> OK! [ Miss: {len(missed)} ] " print(s, end='') frame_time = window_start - previous_window_start frame_times.append(frame_time) frame_start_times.append(window_start) waiting_times.append(window_start - current_time) event.wait() # Wait until playback is finished if gui: cam.unblock() ntot = len(coords) nmissed = len(missed) print() print("Scanning done!") print(f"Stream latency: {1000*stream.latency:.2f} ms") print( f"Average wait: {1000*np.mean(waiting_times[1:]):.2f} +- {1000*np.std(waiting_times[1:]):.2f} ms" ) print( f"Average frame time: {1000*np.mean(frame_times[1:]):.2f} +- {1000*np.std(frame_times[1:]):.2f} ms" ) dt = max(frame_start_times) - min(frame_start_times) print(f"Time taken: {dt:.1f} s ({ntot} frames)") print(f"Frametime: {1000*(dt)/ntot:.1f} ms ({ntot/(dt):.1f} fps)") print(f"Missed frames: {nmissed} ({nmissed/ntot:.1%})") if not expdir: expdir = Path.cwd() write_output = True write_to_hdf5 = True write_to_tiff = False if shuffle_coords: idx = np.argsort(permutation) else: idx = np.arange(len(buffer)) if write_output: if write_to_tiff: for i in idx: arr = buffer[i] write_tiff(expdir / f"{i:04d}.tiff", arr) printer(f"{i} / {len(buffer)}") if write_to_hdf5: x, y = buffer[0].shape dtype = buffer[0].dtype fn = expdir / f"data.h5" f = h5.File(fn) d = f.create_dataset("STEM-diffraction", shape=(len(buffer), x, y), dtype=dtype) for i in idx: arr = buffer[i] printer(f"{i} / {len(buffer)}") d[i] = arr f.close() printer(f"Wrote buffer to {fn}\n") im = np.zeros((grid_x, grid_y)) im_view = im.ravel() for i in idx: arr = buffer[i] im_view[i] = arr.sum() h = { "scan_strength": strength, "scan_grid_x": grid_x, "scan_grid_y": grid_y, "scan_dwell_time": dwell_time, "scan_rotation": rotation, "scan_exposure": exposure, } image_fn = expdir / "image.tiff" write_tiff(expdir / image_fn, im, header=h) print(f"Wrote image to {image_fn}") with open(expdir / "scan_log.txt", "w") as f: print(time.ctime(), file=f) print(file=f) print("dwell_time:", dwell_time, file=f) print("exposure:", exposure, file=f) print("strength:", strength, file=f) print("grid_x:", grid_x, file=f) print("grid_y:", grid_y, file=f) print("rotation:", rotation, file=f) print(file=f) print(beam_ctrl.info(), file=f) print(file=f) print("Missed", file=f) print(missed, file=f) print(file=f) print("Coords", file=f) print(str(coords), file=f) print(file=f) print("Wrote info to", f.name)
def calibrate_stage_from_stageshifts(ctrl, *args, plot: bool = False, drc=None, ) -> np.array: """Run the calibration algorithm on the given X/Y ranges. An image will be taken at each position for cross correlation with the previous. An affine transformation matrix defines the relation between the pixel shift and the difference in stage position. The stagematrix takes the image binning into account. Parameters ---------- ctrl: `TEMController` TEM control object to allow stage movement to different coordinates. ranges: np.array (Nx2) Each range is a List of tuples with X/Y stage shifts (i.e. displacements from the current position). Multiple ranges can be specified to be run in sequence. plot: bool Plot the fitting result. Returns ------- stagematrix: np.ndarray (2x2) Stage matrix used to transform the camera coordinates to stage coordinates Usage ----- >>> x_shifts = [3, (10000, 0)] >>> y_shifts = [3, (0, 10000)] >>> stagematrix = calibrate_stage_from_stageshifts(ctrl, x_shifts, y_shifts) """ if drc: drc = Path(drc) stage_x, stage_y = ctrl.stage.xy stage_shifts = [] # um mag = ctrl.magnification.value mode = ctrl.mode.get() binning = ctrl.cam.getBinning() pairs = [] for i, (n_steps, step) in enumerate(args): j = 0 current_stage_pos = ctrl.stage dx, dy = step last_img, _ = ctrl.get_image() if drc: write_tiff(drc / f'{i}_{j}.tiff', last_img) for j in range(1, n_steps): new_x_pos = current_stage_pos.x + dx new_y_pos = current_stage_pos.y + dy ctrl.stage.set_xy_with_backlash_correction(x=new_x_pos, y=new_y_pos) img, _ = ctrl.get_image() if drc: write_tiff(drc / f'{i}_{j}.tiff', img) pairs.append((last_img, img)) stage_shifts.append((dx, dy)) current_stage_pos = ctrl.stage print(f'{i:02d}-{j:02d}: {current_stage_pos}') last_img = img # return to original position ctrl.stage.xy = (stage_x, stage_y) translations = cross_correlate_image_pairs(pairs) # Filter outliers sel = get_outlier_filter(translations) stage_shifts = np.array(stage_shifts)[sel] translations = np.array(translations)[sel] # Fit stagematrix fit_result = fit_affine_transformation(translations, stage_shifts, verbose=True) r = fit_result.r t = fit_result.t if drc: d = { 'n_ranges': len(args), 'stage_x': stage_x, 'stage_y': stage_y, 'mode': mode, 'magnification': mag, 'args': args, 'translations': translations, 'stage_shifts': stage_shifts, 'r': r, 't': t, 'binning': binning, } yaml.dump(d, open(drc / 'log.yaml', 'w')) if plot: r_i = np.linalg.inv(r) translations_ = np.dot(stage_shifts, r_i) plt.scatter(*translations.T, marker='<', label='Pixel translations (CC)') plt.scatter(*translations_.T, marker='>', label='Calculated pixel coordinates') plt.legend() plt.show() stagematrix = r / binning return stagematrix
def main_entry(): import argparse from instamatic.formats import write_tiff # usage = """acquire""" description = """Program to acquire image data from gatan gatan ccd camera""" parser = argparse.ArgumentParser( # usage=usage, description=description, formatter_class=argparse.RawDescriptionHelpFormatter) # parser.add_argument("args", # type=str, metavar="FILE", # help="Path to save cif") parser.add_argument( "-b", "--binsize", action="store", type=int, metavar="N", dest="binsize", help="""Binsize to use. Must be one of 1, 2, or 4 (default 1)""") parser.add_argument("-e", "--exposure", action="store", type=float, metavar="N", dest="exposure", help="""Exposure time (default 0.5)""") parser.add_argument("-o", "--out", action="store", type=str, metavar="image.png", dest="outfile", help="""Where to store image""") parser.add_argument("-d", "--display", action="store_true", dest="show_fig", help="""Show the image (default True)""") # parser.add_argument("-t", "--tem", # action="store", type=str, dest="tem", # help="""Simulate microscope connection (default False)""") parser.add_argument( "-u", "--simulate", action="store_true", dest="simulate", help="""Simulate camera/microscope connection (default False)""") parser.add_argument( "-s", "--series", action="store_true", dest="take_series", help="""Enable mode to take a series of images (default False)""") parser.set_defaults(binsize=1, exposure=1, outfile=None, show_fig=False, test=False, simulate=False, camera="simulate", take_series=False) options = parser.parse_args() binsize = options.binsize exposure = options.exposure outfile = options.outfile show_fig = options.show_fig take_series = options.take_series from instamatic import TEMController ctrl = TEMController.initialize() if take_series: i = 1 print("\nUsage:") print(" set b/e/i X -> set binsize/exposure/file number to X") print(" XXX -> Add comment to header") print(" exit -> exit the program") while take_series: outfile = f"image_{i:04d}" inp = input(f"\nHit enter to take an image: \n >> [{outfile}] ") if inp == "exit": break elif inp.startswith("set"): try: key, value = inp.split()[1:3] except ValueError: print("Input not understood") continue if key == "e": try: value = float(value) except ValueError as e: print(e) if value > 0: exposure = value elif key == "b": try: value = int(value) except ValueError as e: print(e) if value in (1, 2, 4): binsize = value elif key == "i": try: value = int(value) except ValueError as e: print(e) if value > 0: i = value print(f"binsize = {binsize} | exposure = {exposure} | file #{i}") else: arr, h = ctrl.getImage(binsize=binsize, exposure=exposure, comment=inp) write_tiff(outfile, arr, header=h) i += 1 else: import matplotlib.pyplot as plt arr, h = ctrl.getImage(binsize=binsize, exposure=exposure) if show_fig: # save_header(sys.stdout, h) plt.imshow(arr, cmap="gray", interpolation="none") plt.show() if outfile: write_tiff(outfile, arr, header=h) else: write_tiff("out", arr, header=h)
def get_image( self, exposure: float = None, binsize: int = None, comment: str = '', out: str = None, plot: bool = False, verbose: bool = False, header_keys: Tuple[str] = 'all', ) -> Tuple[np.ndarray, dict]: """Retrieve image as numpy array from camera. If the exposure and binsize are not given, the default values are read from the config file. Parameters ---------- exposure: float Exposure time in seconds binsize: int Binning to use for the image, must be 1, 2, or 4, etc comment: str Arbitrary comment to add to the header file under 'ImageComment' out: str Path or filename to which the image/header is saved (defaults to tiff) plot: bool Toggle whether to show the image using matplotlib after acquisition full_header: bool Return the full header Returns ------- image: np.ndarray, headerfile: dict Tuple of the image as numpy array and dictionary with all the tem parameters and image attributes Usage: img, h = self.get_image() """ if not self.cam: raise AttributeError( f"{self.__class__.__name__} object has no attribute 'cam' (Camera has not been initialized)" ) if not binsize: binsize = self.cam.default_binsize if not exposure: exposure = self.cam.default_exposure if not header_keys: h = {} else: h = self.to_dict(header_keys) if self.autoblank: self.beam.unblank() h['ImageGetTimeStart'] = time.perf_counter() arr = self.get_rotated_image(exposure=exposure, binsize=binsize) h['ImageGetTimeEnd'] = time.perf_counter() if self.autoblank: self.beam.blank() h['ImageGetTime'] = time.time() h['ImageExposureTime'] = exposure h['ImageBinsize'] = binsize h['ImageResolution'] = arr.shape # k['ImagePixelsize'] = config.calibration[mode]['pixelsize'][mag] * binsize # k['ImageRotation'] = config.calibration[mode]['rotation'][mag] h['ImageComment'] = comment h['ImageCameraName'] = self.cam.name h['ImageCameraDimensions'] = self.cam.getCameraDimensions() if verbose: print( f'Image acquired - shape: {arr.shape}, size: {arr.nbytes / 1024:.0f} kB' ) if out: write_tiff(out, arr, header=h) if plot: import matplotlib.pyplot as plt plt.imshow(arr) plt.show() return arr, h
def getImage(self, exposure: float=0.5, binsize: int=1, comment: str="", out: str=None, plot: bool=False, verbose: bool=False, header_keys: Tuple[str]="all") -> Tuple[np.ndarray, dict]: """Retrieve image as numpy array from camera Parameters: exposure: float, exposure time in seconds binsize: int, which binning to use for the image, must be 1, 2, or 4 comment: str, arbitrary comment to add to the header file under 'ImageComment' out: str, path or filename to which the image/header is saved (defaults to tiff) plot: bool, toggle whether to show the image using matplotlib after acquisition full_header: bool, return the full header Returns: image: np.ndarray, headerfile: dict a tuple of the image as numpy array and dictionary with all the tem parameters and image attributes Usage: img, h = self.getImage() """ if not self.cam: raise AttributeError(f"{self.__class__.__name__} object has no attribute 'cam' (Camera has not been initialized)") if not header_keys: h = {} else: h = self.to_dict(header_keys) if self.autoblank and self.beamblank: self.beamblank = False h["ImageGetTimeStart"] = time.perf_counter() arr = self.cam.getImage(exposure=exposure, binsize=binsize) h["ImageGetTimeEnd"] = time.perf_counter() if self.autoblank: self.beamblank = True h["ImageGetTime"] = time.time() h["ImageExposureTime"] = exposure h["ImageBinSize"] = binsize h["ImageResolution"] = arr.shape h["ImageComment"] = comment h["ImageCameraName"] = self.cam.name h["ImageCameraDimensions"] = self.cam.dimensions if verbose: print(f"Image acquired - shape: {arr.shape}, size: {arr.nbytes / 1024:.0f} kB") if out: write_tiff(out, arr, header=h) if plot: import matplotlib.pyplot as plt plt.imshow(arr) plt.show() return arr, h