def MED(init_gas, other_gas, minT, maxT, num_steps, num_steady, num_trans, num_loops=2): """ 1. Start flowing the initial gas. 2. Scan the temperature from minT to maxT in `num_steps` evenly-spaced steps. 3. Hold temperature at maxT and take `num_steady` images. 4. Repeat (2) and (3) `num_loops` times. 5. Switch the gas to `other_gas` and take `num_trans` acquisitions. 6. Switch it back and take another `num_trans` acquisitions. Example ------- Set the gasses. They can be in any other, nothing to do with the order they are used in the plan. >>> gas.gas_list = ['O2', 'CO2'] Optionally, preview the plan. >>> print_summary(MED('O2', 'C02', 200, 300, 21, 20, 60)) Execute it. >>> RE(MED('O2', 'C02', 200, 300, 21, 20, 60)) """ # Step 1 yield from abs_set(gas, init_gas) # Steps 2 and 3 in a loop. for _ in range(num_loops): yield from subs_wrapper(scan([pe1, gas.current_gas], eurotherm, minT, maxT, num_steps), LiveTable([eurotherm, gas.current_gas])) yield from subs_wrapper(count([pe1], num_steady), LiveTable([])) # Step 4 yield from abs_set(gas, other_gas) yield from subs_wrapper(count([pe1], num_steady), LiveTable([])) # Step 6 yield from abs_set(gas, init_gas) yield from subs_wrapper(count([pe1], num_steady), LiveTable([]))
def factory(name, doc): # Runs may be subscribed to different sets of callbacks. Metadata from start # document may be used to identify, which run is currently being started. # In this example, the run key is explicitely added to the start document # and used to identify runs, but other data can be similarly used. cb_list = [] if doc["run_key"] == "run_1": cb_list.append(LiveTable([hw.motor1, hw.det1])) cb_list.append(LivePlot('det1', x='motor1')) elif doc["run_key"] == "run_2": cb_list.append(LiveTable([hw.motor1, hw.motor2, hw.det2])) return cb_list, []
def test_table(RE, hw): with _print_redirect() as fout: hw.det.precision = 2 hw.motor.precision = 2 hw.motor.setpoint.put(0.0) # Make dtype 'number' not 'integer'. hw.det.put(0.0) # Make dtype 'number' not 'integer'. assert hw.det.describe()['det']['precision'] == 2 assert hw.motor.describe()['motor']['precision'] == 2 assert hw.det.describe()['det']['dtype'] == 'number' assert hw.motor.describe()['motor']['dtype'] == 'number' table = LiveTable(['det', 'motor'], min_width=16, extra_pad=2) ad_scan = bp.adaptive_scan([hw.det], 'det', hw.motor, -15.0, 5., .01, 1, .05, True) # use lossless sub here because rows can get dropped token = RE.subscribe(table) RE(ad_scan) RE.unsubscribe_lossless(token) fout.seek(0) for ln, kn in zip(fout, KNOWN_TABLE.split('\n')): # this is to strip the `\n` from the print output ln = ln.rstrip() if ln[0] == '+': # test the full line on the divider lines assert ln == kn else: # skip the 'time' column on data rows # this is easier than faking up times in the scan! assert ln[:16] == kn[:16] assert ln[26:] == kn[26:]
def describe_printers(self): from bluesky.callbacks import LiveTable #markdown table table = LiveTable([self.NAMES['offset'], self.NAMES['current']], out=self.PRINT_CALLBACK) self.PRINTERS.append(table)
def test_broken_table(): start_doc, descriptor_factory, *_ = compose_run() desc, compose_event, _ = descriptor_factory( name="primary", data_keys={ "x": { "dtype": "integer", "source": "", "shape": [] }, "y": { "dtype": "number", "source": "", "shape": [] }, }, ) ev1 = compose_event(data={ "x": 1, "y": 2.0 }, timestamps={k: time.time() for k in ("x", "y")}) ev2 = compose_event(data={ "x": 1, "y": 2 }, timestamps={k: time.time() for k in ("x", "y")}) ev3 = compose_event(data={ "x": 1.0, "y": 2.0 }, timestamps={k: time.time() for k in ("x", "y")}) ev4 = compose_event( data={ "x": 1.0, "y": "aardvark" }, timestamps={k: time.time() for k in ("x", "y")}, ) sio = StringIO() LT = LiveTable(["x", "y"], out=lambda s: sio.write(s + "\n")) LT("start", start_doc) LT("descriptor", desc) LT("event", ev1) LT("event", ev2) LT("event", ev3) LT("event", ev4) sio.seek(0) lines = sio.readlines() assert len(lines) == 7 for ln in lines[-2:]: assert ln.strip() == "failed to format row"
def test_table(RE, hw): with _print_redirect() as fout: hw.det.precision = 2 hw.motor.precision = 2 hw.motor.setpoint.put(0.0) # Make dtype 'number' not 'integer'. hw.det.trigger() assert hw.det.describe()['det']['precision'] == 2 assert hw.motor.describe()['motor']['precision'] == 2 assert hw.det.describe()['det']['dtype'] == 'number' assert hw.motor.describe()['motor']['dtype'] == 'number' table = LiveTable(['det', 'motor'], min_width=16, extra_pad=2, separator_lines=False) ad_scan = bp.adaptive_scan([hw.det], 'det', hw.motor, -15.0, 5., .01, 1, .05, True) # use lossless sub here because rows can get dropped token = RE.subscribe(table) RE(ad_scan) RE.unsubscribe_lossless(token) fout.seek(0) _compare_tables(fout, KNOWN_TABLE)
def describe_printers(self): from bluesky.callbacks import LiveTable #markdown table table = LiveTable([self.NAMES['inj_kicker'], self.NAMES['booster_current1'], self.NAMES['booster_current2']], out=self.PRINT_CALLBACK) self.PRINTERS.append(table)
def test_table(fresh_RE): RE = fresh_RE with _print_redirect() as fout: det.precision = 2 motor.precision = 2 table = LiveTable(['det', 'motor'], min_width=16, extra_pad=2) ad_scan = AdaptiveAbsScanPlan([det], 'det', motor, -15, 5, .01, 1, .05, True) # use lossless sub here because rows can get dropped token = RE.subscribe_lossless('all', table) RE(ad_scan) RE.unsubscribe_lossless(token) fout.seek(0) for ln, kn in zip(fout, KNOWN_TABLE.split('\n')): # this is to strip the `\n` from the print output ln = ln.rstrip() if ln[0] == '+': # test the full line on the divider lines assert ln == kn else: # skip the 'time' column on data rows # this is easier than faking up times in the scan! assert ln[:16] == kn[:16] assert ln[26:] == kn[26:]
def escan(start, stop, num, md=None): """ Scan the mono_energy while reading the scaler. Parameters ---------- start : number stop : number num : integer number of data points (i.e. number of strides + 1) md : dictionary, optional """ dets = [sclr] motor = mono.energy cols = ['I0', 'fbratio', 'It', 'If_tot'] x = 'mono_energy' fig, axes = plt.subplots(2, sharex=True) plan = bp.scan(dets, motor, start, stop, num, md=md) plan2 = bpp.subs_wrapper(plan, [ LiveTable(cols), LivePlot('If_tot', x, ax=axes[0]), LivePlot('I0', x, ax=axes[1]) ]) yield from plan2
def run_publisher(in_port, data_path, quiet=False): """ Acquire data in an infinite loop and publish it. """ publisher = Publisher(f"localhost:{in_port}") RE = RunEngine(loop=asyncio.new_event_loop()) sd = SupplementalData() RE.preprocessors.append(sd) sd.baseline.extend([motor1, motor2]) RE.subscribe(publisher) def factory(name, doc): serializer = Serializer(data_path / "abc", flush=True) return [serializer], [] rr = RunRouter([factory]) RE.subscribe(rr) if not quiet: RE.subscribe(LiveTable(["motor", "det"])) motor.delay = 0.2 det.kind = "hinted" def infinite_plan(): while True: for i in range(1, 5): yield from sleep(2) yield from scan([det], motor, -1, 1, 5 * i) # Just as a convenience, avoid collission with scan_ids of runs in Catalog. RE.md["scan_id"] = 100 try: RE(infinite_plan()) finally: RE.halt()
def simple_ct(dets, exposure, *, md=None): """A minimal wrapper around count that adjusts exposure time.""" md = md or {} # setting up area_detector (ad, ) = (d for d in dets if hasattr(d, "cam")) (num_frame, acq_time, computed_exposure) = yield from configure_area_det(ad, exposure) sp = { "time_per_frame": acq_time, "num_frames": num_frame, "requested_exposure": exposure, "computed_exposure": computed_exposure, "type": "ct", "uid": str(uuid.uuid4()), "plan_name": "ct", } # update md _md = {"sp": sp, **{f"sp_{k}": v for k, v in sp.items()}} _md.update(md) plan = bp.count(dets, md=_md) plan = bpp.subs_wrapper(plan, LiveTable([])) return (yield from plan)
def __call__(self, time=None, subs=None, **kwargs): if subs is None: table = LiveTable(gs.TABLE_COLS) subs = [table] original_times = _set_acquire_time(time) result = super().__call__(subs=subs, **kwargs) _unset_acquire_time(original_times) return result
def ct(sample, exposure): """ Capture how many exposures are needed to get a total exposure of the given value, and sum those into one file before saving. """ pe1c.images_per_set.put(1) pe1c.number_of_sets.put(1) plan = subs_wrapper(count([pe1c], num=1), LiveTable([])) # plan = robot_wrapper(plan, sample) yield from plan
def count_dets(_dets, _full_md): _count_plan = bp.count(_dets, md=_full_md) _count_plan = bpp.subs_wrapper(_count_plan, LiveTable(_dets)) _count_plan = bpp.finalize_wrapper( _count_plan, bps.abs_set(xpd_configuration['shutter'], XPD_SHUTTER_CONF['close'], wait=True)) yield from bps.abs_set(xpd_configuration['shutter'], XPD_SHUTTER_CONF['open'], wait=True) yield from _count_plan
def main(): act = MimicActuator(name='act') bk_dev = Bookkeeping(name='bk') dets = [bk_dev] RE = RunEngine({}) RE.log.setLevel(logging.DEBUG) lt = LiveTable([ bk_dev.status.name, bk_dev.target.name, act.setpoint.name, act.readback.name ]) RE(bp.scan(dets, act, 1, 5, 5, per_step=solve_stub), lt)
def tseries(sample, exposure, num): """ Capture how ever many exposures are needed to get a total exposure of the given value, and divide those into files of 'num' exposures each, summed. """ if pe1c.cam.acquire_time.get() != 0.1: raise RuntimeError("We expect pe1c.cam.acquire_time to be 0.1") pe1c.images_per_set.put(num) pe1c.number_of_sets.put(exposure // num) plan = subs_wrapper(count([pe1c], num=1), LiveTable([])) i # plan = robot_wrapper(plan, sample) yield from plan
def tseries(dets, exposure, delay, num, *, md=None): """ time series scan with area detector. Parameters ---------- detectors : list list of 'readable' objects exposure : float exposure time at each reading from area detector in seconds delay : float delay between two adjustant reading from area detector in seconds num : int total number of readings md : dict, optional metadata Note ---- area detector that is triggered will always be the one configured in global state. Please refer to http://xpdacq.github.io for more information """ pe1c, = dets if md is None: md = {} # setting up area_detector (num_frame, acq_time, computed_exposure) = _configure_pe1c(exposure) real_delay = max(0, delay - computed_exposure) period = max(computed_exposure, real_delay + computed_exposure) print('INFO: requested delay = {}s -> computed delay = {}s'.format( delay, real_delay)) print('INFO: nominal period (neglecting readout overheads) of {} s'.format( period)) # update md _md = ChainMap( md, { 'sp_time_per_frame': acq_time, 'sp_num_frames': num_frame, 'sp_requested_exposure': exposure, 'sp_computed_exposure': computed_exposure, 'sp_type': 'tseries', # need a name that shows all parameters values # 'sp_name': 'tseries_<exposure_time>', 'sp_uid': str(uuid.uuid4()), 'sp_plan_name': 'tseries' }) plan = bp.count([glbl.area_det], num, delay, md=_md) plan = bp.subs_wrapper(plan, LiveTable([glbl.area_det])) yield from plan
def count_with_calib(detectors: list, num: int = 1, delay: float = None, *, calibration_md: dict = None, md: dict = None) -> typing.Generator: """ Take one or more readings from detectors with shutter control and calibration metadata injection. Parameters ---------- detectors : list list of 'readable' objects num : integer, optional number of readings to take; default is 1 If None, capture data until canceled delay : iterable or scalar, optional Time delay in seconds between successive readings; default is 0. calibration_md : The calibration data in a dictionary. If not applied, the function is a normal `bluesky.plans.count`. md : dict, optional metadata Notes ----- If ``delay`` is an iterable, it must have at least ``num - 1`` entries or the plan will raise a ``ValueError`` during iteration. """ if md is None: md = dict() if calibration_md is not None: md["calibration_md"] = calibration_md def _per_shot(_detectors): yield from open_shutter_stub() yield from bps.one_shot(_detectors) yield from close_shutter_stub() return prev_state = glbl["auto_load_calib"] glbl["auto_load_calib"] = False try: plan = bp.count(detectors, num, delay, md=md, per_shot=_per_shot) bpp.subs_wrapper(plan, LiveTable(detectors)) sts = yield from plan except Exception as error: glbl["auto_load_calib"] = prev_state raise error glbl["auto_load_calib"] = prev_state return sts
def main(): logger = logging.getLogger('bact2') cart_pole = CartPole(name='cp') RE = RunEngine({}) # RE.log.setLevel('DEBUG') cart_pole.log = RE.log live_plots = True if live_plots: fig = plt.figure() ax = fig.add_subplot(211) ax2 = ax.twinx() ax3 = fig.add_subplot(212) ax4 = ax3.twinx() cbs = [ LiveTable([ 'cp_rl_mode', 'cp_action', 'cp_x', 'cp_x_dot', 'cp_theta', 'cp_theta_dot' ]) ] if live_plots: cbs = [ LivePlotTest('cp_action', color='r', linestyle='-', ax=ax), LivePlotTest('cp_x', color='b', linestyle='-', ax=ax2), LivePlotTest('cp_theta', color='g', linestyle='-', ax=ax3), LivePlotTest('cp_x_dot', color='b', linestyle='--', ax=ax4), LivePlotTest('cp_theta_dot', color='g', linestyle='--', ax=ax4), ] stm = [cart_pole.x, cart_pole.x_dot, cart_pole.theta, cart_pole.theta_dot] cpst = CartPoleEnv(detectors=[cart_pole], motors=[cart_pole], state_motors=stm, log=RE.log, user_kwargs={'mode_var': cart_pole.rl_mode}) server = EnvironmentProxyForServer(receiver=cpst) # partial = setup_xml_server(cpst) partial = server RE.log.info('Handling execution to bluesky') RE(run_environment(cpst, partial, log=RE.log, n_loops=-1) #, cbs ) RE.log.info('Bluesky operation finished')
def test_table_warns(): table = LiveTable(['field']) table('start', {}) with pytest.warns(UserWarning): table( 'descriptor', { 'uid': 'asdf', 'name': 'primary', 'data_keys': { 'field': { 'dtype': 'array' } } })
def Gas_Plan(gas_in='He', liveplot_key=None, totExpTime=5, num_exp=1, delay=1): """ Execute it ---------- >> %run -i /home/xf28id2/Documents/Sanjit/Scripts/GasXrun_Plan.py >> change all the parameters inside Gas_Plan as required >>> gas_plan = Gas_Plan(gas_in = 'He', liveplot_key= 'rga_mass1', totExpTime = 5, num_exp = 3, delay = 1) >> to run the xrun, save metadata & save_tiff run the following >>> run_and_save(sample_num = 0) Example ------- Set the gasses. They can be in any other, nothing to do with the order they are used in the plan. But, should match the connections on switch. >>> gas.gas_list = ['He', 'N2', 'CO2'] >>> RGA mass is set to different value, base shows 1^-13 with He 5 cc flow shows max 2^-8 >>> RGA mass setup in mID mode: 4,18,28,31,44,79,94,32,81 Parameters ---------- gas_in : string e.g., 'He', default is 'He' These gas must be in `gas.gas_list` but they may be in any order. liveplot_key : str, optional e. g., liveplot_key = rga_mass1 data key for LivePlot. default is None, which means no LivePlot totExpTime : float total exposure time per frame in seconds. Dafault value is 5 sec num_exp : int number of images/exposure, default is 1 delay: float delay time between exposures in sec """ ## switch gas yield from abs_set(gas, gas_in) ## configure the exposure time first _configure_area_det(totExpTime) # 5 secs exposuretime ## ScanPlan you need plan = bp.count([pe1c, gas.current_gas, rga], num=num_exp, delay=delay) #plan = bpp.subs_wrapper(plan, LiveTable([xpd_configuration['area_det'], rga])) # give you LiveTable plan = bpp.subs_wrapper( plan, LiveTable([xpd_configuration['area_det'], gas.current_gas, rga])) if liveplot_key and isinstance(liveplot_key, str): plan = bpp.subs_wrapper(plan, LivePlot(liveplot_key)) yield from plan
def escan(): """ Scan the mono_energy while reading the scaler. Parameters ---------- start : number stop : number num : integer number of data points (i.e. number of strides + 1) md : dictionary, optional """ """ dets = [xs] motor = mono.energy cols = ['I0', 'fbratio', 'It', 'If_tot'] x = 'mono_energy' fig, axes = plt.subplots(2, sharex=True) plan = bp.scan(dets, motor, start, stop, num, md=md) plan2 = bpp.subs_wrapper(plan, [LiveTable(cols), LivePlot('If_tot', x, ax=axes[0]), LivePlot('I0', x, ax=axes[1])]) yield from plan2 """ ept = numpy.array([]) det = [sclr, xs] last_time_pt = time.time() ringbuf = collections.deque(maxlen=10) # c2pitch_kill=EpicsSignal("XF:05IDA-OP:1{Mono:HDCM-Ax:P2}Cmd:Kill-Cmd") xs.external_trig.put(False) # @bpp.stage_decorator([xs]) yield from abs_set(xs.settings.acquire_time, 0.1) yield from abs_set(xs.total_points, 100) roi_name = "roi{:02}".format(roinum[0]) roi_key = [] roi_key.append(getattr(xs.channel1.rois, roi_name).value.name) livetableitem.append(roi_key[0]) livecallbacks.append(LiveTable(livetableitem)) liveploty = roi_key[0] liveplotx = energy.energy.name liveplotfig = plt.figure("raw xanes") livecallbacks.append(LivePlot(liveploty, x=liveplotx, fig=liveplotfig)) myscan = count
def grid_in_grid(samples): """ Scan a grid around the neighborhood of each sample. Parameters ---------- sample : dict mapping each sample's name to its (x, y) position """ # In this example we hard-code the hardware and other parameters. For more # flexibility, they could instead be parameters to the function. detector = det4 x = motor1 y = motor2 x_range = y_range = 0.2 x_num = y_num = 5 @subs_decorator( [LiveTable([detector, x, y]), LivePlot('motor2', 'motor1')]) def plan(): for name, position in samples.items(): # Prepare metadata. md = {'sample': name} # Move to the cetner of the sample position. x_pos, y_pos = position yield from abs_set(x, x_pos) yield from abs_set(y, y_pos) yield from wait() # Scan a grid around that position. yield from relative_outer_product_scan([detector], x, -x_range, x_range, x_num, y, -y_range, y_range, y_num, True, md=md) yield from plan()
def ct(dets, exposure): """ Take one reading from area detector with given exposure time Parameters ---------- dets : list list of 'readable' objects. default to area detector linked to xpdAcq. exposure : float total time of exposrue in seconds Notes ----- area detector being triggered will always be the one configured in global state. To find out which these are, please using following commands: >>> xpd_configuration['area_det'] to see which device is being linked """ pe1c, = dets md = {} # setting up area_detector (num_frame, acq_time, computed_exposure) = yield from _configure_area_det( exposure ) area_det = xpd_configuration["area_det"] # update md _md = ChainMap( md, { "sp_time_per_frame": acq_time, "sp_num_frames": num_frame, "sp_requested_exposure": exposure, "sp_computed_exposure": computed_exposure, "sp_type": "ct", "sp_uid": str(uuid.uuid4()), "sp_plan_name": "ct", }, ) plan = bp.count([area_det], md=_md) plan = bpp.subs_wrapper(plan, LiveTable([])) yield from plan
def plan(self): from bluesky.plans import subs_decorator, scan from bluesky.callbacks import LiveTable, LivePlot from bluesky.utils import first_key_heuristic lp = LivePlot(first_key_heuristic(self.dets[0]), first_key_heuristic(self.motor), fig=self.fig) @subs_decorator([LiveTable([self.motor] + self.dets), lp]) def scan_gui_plan(): yield from scan(self.dets, self.motor, self.start.value(), self.stop.value(), self.steps.value(), md={'created_by': 'GUI'}) return scan_gui_plan()
def config_det_and_count(motors: List[object], sample_md: dict, exposure: float): """ Take one reading from area detector with given exposure time and motors. Save the motor reading results in the start document. Parameters ---------- motors : List[float] A list of readable motors. sample_md The metadata of the sample. exposure The exposure time in seconds. Yields ------- Message to configure the detector and run the scan. """ # setting up area_detector _md = {} num_frame, acq_time, computed_exposure = yield from _configure_area_det(exposure) area_det = xpd_configuration["area_det"] # update md _md.update(**sample_md) plan_md = { "sp_time_per_frame": acq_time, "sp_num_frames": num_frame, "sp_requested_exposure": exposure, "sp_computed_exposure": computed_exposure, "sp_type": "cryostat", "sp_uid": str(uuid.uuid4()), "sp_plan_name": "cryostat" } _md.update(**plan_md) motor_md = {motor.name: dict(motor.read()) for motor in motors} _md.update(**motor_md) # yield plan dets = [area_det] + motors plan = count(dets, md=_md) plan = subs_wrapper(plan, LiveTable([])) yield from plan
def ct(dets, exposure, *, md=None): """ Take one reading from area detectors with given exposure time Parameters ---------- detectors : list list of 'readable' objects exposure : float total time of exposrue in seconds md : dict, optional extra metadata Note ---- area detector that is triggered will always be the one configured in global state. Please refer to http://xpdacq.github.io for more information """ pe1c, = dets if md is None: md = {} # setting up area_detector (num_frame, acq_time, computed_exposure) = _configure_pe1c(exposure) # update md _md = ChainMap( md, { 'sp_time_per_frame': acq_time, 'sp_num_frames': num_frame, 'sp_requested_exposure': exposure, 'sp_computed_exposure': computed_exposure, 'sp_type': 'ct', # need a name that shows all parameters values # 'sp_name': 'ct_<exposure_time>', 'sp_uid': str(uuid.uuid4()), 'sp_plan_name': 'ct' }) plan = bp.count([glbl.area_det], md=_md) plan = bp.subs_wrapper(plan, LiveTable([glbl.area_det])) yield from plan
def test_table(): with _print_redirect() as fout: table = LiveTable(['det', 'motor']) ad_scan = AdaptiveAbsScan([det], 'det', motor, -15, 5, .01, 1, .05, True) RE(ad_scan, subs={'all': [table]}) fout.seek(0) for ln, kn in zip(fout, KNOWN_TABLE.split('\n')): # this is to strip the `\n` from the print output ln = ln.rstrip() if ln[0] == '+': # test the full line on the divider lines assert_equal(ln, kn) else: # skip the 'time' column on data rows # this is easier than faking up times in the scan! assert_equal(ln[:16], kn[:16]) assert_equal(ln[26:], kn[26:])
def test_evil_table_names(RE): from ophyd import Signal sigs = [ Signal(value=0, name="a:b"), Signal(value=0, name="a,b"), Signal(value=0, name="a'b"), Signal(value=0, name="🐍"), ] table = LiveTable([s.name for s in sigs], min_width=5, extra_pad=2, separator_lines=False) with _print_redirect() as fout: print() # get a blank line in camptured output RE(bpp.subs_wrapper(bp.count(sigs, num=2), table)) reference = """ +------------+--------------+--------+--------+--------+--------+ | seq_num | time | a:b | a,b | a'b | 🐍 | +------------+--------------+--------+--------+--------+--------+ | 1 | 12:47:09.7 | 0 | 0 | 0 | 0 | | 2 | 12:47:09.7 | 0 | 0 | 0 | 0 | +------------+--------------+--------+--------+--------+--------+""" _compare_tables(fout, reference)
from bluesky.plans import rel_scan from bluesky.callbacks import LiveTable, LivePlot subs = [LiveTable(['diff_xh', 'xray_eye3_stats1_total', 'xray_eye3_stats2_total']), LivePlot('xray_eye3_stats1_total', 'diff_xh')] print ( 'Motor is diff.xh, camera is xray_eye3 with saving images') RE(rel_scan([xray_eye3_writing], diff.xh, -.1, .1, 3), subs) img = get_images(db[-1], 'xray_eye3_image') print('show the first image') plt.imshow( img[0] )