class PatternExecutor(Patternable): """ a pattern is only good for one execution. self.pattern needs to be reset after stop or finish using load_pattern(name_or_pickle) """ controller = Any laser_manager = Any show_patterning = Bool(False) _alive = Bool(False) def __init__(self, *args, **kw): super(PatternExecutor, self).__init__(*args, **kw) self._next_point = None self.pattern = None self._xy_thread = None self._power_thread = None self._z_thread = None def start(self, show=False): self._alive = True if show: self.show_pattern() if self.pattern: self.pattern.clear_graph() def finish(self): self._alive = False self.close_pattern() self.pattern = None def set_stage_values(self, sm): if self.pattern: self.pattern.set_stage_values(sm) def set_current_position(self, x, y, z): if self.isPatterning(): graph = self.pattern.graph graph.set_data([x], series=1, axis=0) graph.set_data([y], series=1, axis=1) graph.add_datum((x, y), series=2) graph.redraw() def load_pattern(self, name_or_pickle): """ look for name_or_pickle in local pattern dir if not found try interpreting name_or_pickle is a pickled name_or_pickle """ if name_or_pickle is None: path = self.open_file_dialog() if path is None: return else: path = self.is_local_pattern(name_or_pickle) if path: wfile = open(path, 'rb') else: # convert name_or_pickle into a file like obj wfile = StringIO(name_or_pickle) # self._load_pattern sets self.pattern pattern = self._load_pattern(wfile, path) self.on_trait_change(self.stop, 'canceled') return pattern def is_local_pattern(self, name): def test_name(ni): path = os.path.join(paths.pattern_dir, ni) if os.path.isfile(path): return path for ni in (name, name + '.lp'): p = test_name(ni) if p: return p def stop(self): self._alive = False if self.controller: self.info('User requested stop') self.controller.stop() if self.pattern is not None: if self.controller: self.controller.linear_move(self.pattern.cx, self.pattern.cy, source='pattern stop') # self.pattern.close_ui() self.info('Pattern {} stopped'.format(self.pattern_name)) # prevent future stops (AbortJogs from massspec) from executing self.pattern = None def isPatterning(self): return self._alive def close_pattern(self): pass def show_pattern(self): self.pattern.window_x = 50 self.pattern.window_y = 50 open_view(self.pattern, view='graph_view') def execute(self, block=False, duration=None, thread_safe=True): """ if block is true wait for patterning to finish before returning """ if not self.pattern: return self.start(show=self.show_patterning) evt = None # if current_thread().name != 'MainThread': if thread_safe: evt = Event() invoke_in_main_thread(self._pre_execute, evt) while not evt.is_set(): time.sleep(0.05) else: self._pre_execute(evt) self.debug('execute xy pattern') xyp = self.pattern.xy_pattern_enabled if duration: self.pattern.external_duration = float(duration) if xyp: self._xy_thread = Thread(target=self._execute_xy_pattern) self._xy_thread.start() pp = self.pattern.power_pattern if pp: self.debug('execute power pattern') self._power_thread = Thread(target=self._execute_power_pattern) self._power_thread.start() zp = self.pattern.z_pattern if zp: self.debug('execute z pattern') self._z_thread = Thread(target=self._execute_z_pattern) self._z_thread.start() if block: if self._xy_thread: self._xy_thread.join() if self._z_thread: self._z_thread.join() if self._power_thread: self._power_thread.join() self.finish() def _pre_execute(self, evt): self.debug('pre execute') pattern = self.pattern kind = pattern.kind if kind in ('SeekPattern', 'DragonFlyPeakPattern'): self._info = open_view(pattern, view='execution_graph_view') if evt is not None: evt.set() self.debug('pre execute finished') def _execute_power_pattern(self): pat = self.pattern self.info('starting power pattern {}'.format(pat.name)) def func(v): self.laser_manager.set_laser_power(v) self._execute_(func, pat.power_values(), pat.power_sample, 'power pattern setpoint={value}') def _execute_z_pattern(self): pat = self.pattern self.info('starting power pattern {}'.format(pat.name)) def func(v): self.controller.set_z(v) self._execute_(func, pat.z_values(), pat.z_sample, 'z pattern z={value}') def _execute_(self, func, vs, period, msg): for v in vs: st = time.time() self.debug(msg.format(value=v)) func(v) et = st - time.time() p = period - et time.sleep(p) def _execute_xy_pattern(self): pat = self.pattern self.info('starting pattern {}'.format(pat.name)) st = time.time() self.controller.update_position() time.sleep(1) pat.cx, pat.cy = self.controller.x, self.controller.y try: for ni in range(pat.niterations): if not self.isPatterning(): break self.info('doing pattern iteration {}'.format(ni)) self._execute_iteration() self.controller.linear_move(pat.cx, pat.cy, block=True, source='execute_xy_pattern') if pat.disable_at_end: self.laser_manager.disable_device() self.finish() self.info('finished pattern: transit time={:0.1f}s'.format(time.time() - st)) except (TargetPositionError, PositionError) as e: self.finish() self.controller.stop() self.laser_manager.emergency_shutoff(str(e)) def _execute_iteration(self): controller = self.controller pattern = self.pattern if controller is not None: kind = pattern.kind if kind == 'ArcPattern': self._execute_arc(controller, pattern) elif kind == 'CircularContourPattern': self._execute_contour(controller, pattern) elif kind in ('SeekPattern', 'DragonFlyPeakPattern'): self._execute_seek(controller, pattern) else: self._execute_points(controller, pattern, multipoint=False) def _execute_points(self, controller, pattern, multipoint=False): pts = pattern.points_factory() if multipoint: controller.multiple_point_move(pts, velocity=pattern.velocity) else: for x, y in pts: if not self.isPatterning(): break controller.linear_move(x, y, block=True, velocity=pattern.velocity) def _execute_contour(self, controller, pattern): for ni in range(pattern.nsteps): if not self.isPatterning(): break r = pattern.radius * (1 + ni * pattern.percent_change) self.info('doing circular contour {} {}'.format(ni + 1, r)) controller.single_axis_move('x', pattern.cx + r, block=True) controller.arc_move(pattern.cx, pattern.cy, 360, block=True) time.sleep(0.1) def _execute_arc(self, controller, pattern): controller.single_axis_move('x', pattern.radius, block=True) controller.arc_move(pattern.cx, pattern.cy, pattern.degrees, block=True) def _execute_seek(self, controller, pattern): duration = pattern.duration total_duration = pattern.total_duration lm = self.laser_manager sm = lm.stage_manager ld = sm.lumen_detector ld.mask_kind = pattern.mask_kind ld.custom_mask = pattern.custom_mask_radius osdp = sm.canvas.show_desired_position sm.canvas.show_desired_position = False st = time.time() self.debug('Pre seek delay {}'.format(pattern.pre_seek_delay)) time.sleep(pattern.pre_seek_delay) self.debug('starting seek') self.debug('total duration {}'.format(total_duration)) self.debug('dwell duration {}'.format(duration)) if pattern.kind == 'DragonFlyPeakPattern': try: self._dragonfly_peak(st, pattern, lm, controller) except BaseException as e: self.critical('Dragonfly exception. {}'.format(e)) else: self._hill_climber(st, controller, pattern) sm.canvas.show_desired_position = osdp from pyface.gui import GUI GUI.invoke_later(self._info.dispose) def _dragonfly_peak(self, st, pattern, lm, controller): # imgplot, imgplot2, imgplot3 = pattern.setup_execution_graph() # imgplot, imgplot2 = pattern.setup_execution_graph() imgplot, imgplot2 = pattern.setup_execution_graph(nplots=2) cx, cy = pattern.cx, pattern.cy sm = lm.stage_manager linear_move = controller.linear_move in_motion = controller.in_motion find_lum_peak = sm.find_lum_peak set_data = imgplot.data.set_data set_data2 = imgplot2.data.set_data duration = pattern.duration sat_threshold = pattern.saturation_threshold total_duration = pattern.total_duration min_distance = pattern.min_distance aggressiveness = pattern.aggressiveness update_period = pattern.update_period / 1000. move_threshold = pattern.move_threshold blur = pattern.blur px, py = cx, cy point_gen = None cnt = 0 peak = None while time.time() - st < total_duration: if not self._alive: break sats = [] pts = [] ist = time.time() npt = None self.debug('starting iteration={}, in_motion={}'.format(cnt, in_motion())) while time.time() - ist < duration or in_motion(): args = find_lum_peak(min_distance, blur) if args is None: continue pt, peakcol, peakrow, peak_img, sat, src = args sats.append(sat) if peak is None: peak = peak_img else: peak = ((peak.astype('int16') - 2) + peak_img).clip(0, 255) img = gray2rgb(peak).astype(uint8) src = gray2rgb(src).astype(uint8) if pt: pts.append(pt) c = circle(peakrow, peakcol, min_distance / 2) img[c] = (255, 0, 0) src[c] = (255, 0, 0) # set_data('imagedata', src) set_data2('imagedata', src) set_data('imagedata', img) sleep(update_period) self.debug('iteration {} finished, npts={}'.format(cnt, len(pts))) pattern.position_str = '---' if pts: w = array(sats) avg_sat_score = w.mean() self.debug('Average Saturation: {} threshold={}'.format(avg_sat_score, sat_threshold)) pattern.average_saturation = avg_sat_score if avg_sat_score < sat_threshold: pts = array(pts) x, y, w = pts.T ws = w.sum() nx = (x * w).sum() / ws ny = (y * w).sum() / ws self.debug('New point {},{}'.format(nx, ny)) npt = nx, ny, 1 else: continue # if npt is None: # if not point_gen: # point_gen = pattern.point_generator() # # wait = False # npt = next(point_gen) # self.debug('generating new point={}'.format(npt)) # # else: # point_gen = None # wait = True if npt is None: block = total_duration - (time.time() - st) < duration linear_move(cx, cy, source='recenter_dragonfly{}'.format(cnt), block=block, velocity=pattern.velocity, use_calibration=False) pattern.position_str = 'Return to Center' px, py = cx, cy continue try: scalar = npt[2] except IndexError: scalar = 1 ascalar = scalar * aggressiveness dx = npt[0] / sm.pxpermm * ascalar dy = npt[1] / sm.pxpermm * ascalar if abs(dx) < move_threshold or abs(dy) < move_threshold: self.debug('Deviation too small dx={},dy={}'.format(dx, dy, move_threshold)) pattern.position_str = 'Deviation too small' continue px += dx py -= dy self.debug('i: {}. point={},{}. ' 'Intensitiy Scalar={}, Modified Scalar={}'.format(cnt, px, py, scalar, ascalar)) if not pattern.validate(px, py): self.debug('invalid position. {},{}'.format(px, py)) px, py = pattern.reduce_vector_magnitude(px, py, 0.85) self.debug('reduced vector magnitude. new pos={},{}'.format(px, py)) pattern.position_str = '{:0.5f},{:0.5f}'.format(px, py) # if there is less than 1 duration left then block is true block = total_duration - (time.time() - st) < duration self.debug('blocking ={}'.format(block)) linear_move(px, py, source='dragonfly{}'.format(cnt), block=block, velocity=pattern.velocity, use_calibration=False) # if wait: # et = time.time() - ist # d = duration - et # if d > 0: # time.sleep(d) cnt += 1 # time.sleep(1) self.debug('dragonfly complete') controller.block() def _hill_climber(self, st, controller, pattern): g = pattern.execution_graph imgplot, cp = pattern.setup_execution_graph() cx, cy = pattern.cx, pattern.cy sm = self.laser_manager.stage_manager linear_move = controller.linear_move get_scores = sm.get_scores moving = sm.moving update_axes = sm.update_axes set_data = imgplot.data.set_data sat_threshold = pattern.saturation_threshold total_duration = pattern.total_duration duration = pattern.duration pattern.perimeter_radius *= sm.pxpermm avg_sat_score = -1 # current_x, current_y =None, None for i, pt in enumerate(pattern.point_generator()): update_plot = True x, y = pt.x, pt.y ax, ay = cx + x, cy + y if not self._alive: break if time.time() - st > total_duration: break # use_update_point = False if avg_sat_score < sat_threshold: # use_update_point = False # current_x, current_y = x, y linear_move(ax, ay, block=False, velocity=pattern.velocity, use_calibration=False, update=False, immediate=True) else: self.debug('Saturation target reached. not moving') update_plot = False density_scores = [] ts = [] saturation_scores = [] positions = [] def measure_scores(update=False): if update: update_axes() positions.append((controller.x, controller.y)) score_density, score_saturation, img = get_scores() density_scores.append(score_density) saturation_scores.append(score_saturation) set_data('imagedata', img) ts.append(time.time() - st) time.sleep(0.1) while moving(force_query=True): measure_scores(update=True) mt = time.time() while time.time() - mt < duration: measure_scores() if density_scores: n = len(density_scores) density_scores = array(density_scores) saturation_scores = array(saturation_scores) weights = [1 / (max(0.0001, ((xi - ax) ** 2 + (yi - ay) ** 2)) ** 0.5) for xi, yi in positions] avg_score = average(density_scores, weights=weights) avg_sat_score = average(saturation_scores, weights=weights) score = avg_score m, b = polyfit(ts, density_scores, 1) if m > 0: score *= (1 + m) pattern.set_point(score, pt) self.debug('i:{} XY:({:0.5f},{:0.5f})'.format(i, x, y)) self.debug('Density. AVG:{:0.3f} N:{} Slope:{:0.3f}'.format(avg_score, n, m)) self.debug('Modified Density Score: {:0.3f}'.format(score)) self.debug('Saturation. AVG:{:0.3f}'.format(avg_sat_score)) if update_plot: cp.add_point((x, y)) g.add_datum((x, y), plotid=0) t = time.time() - st g.add_datum((t, avg_score), plotid=1) # g.add_bulk_data(ts, density_scores, plotid=1, series=1) g.add_datum((t, score), ypadding='0.1', ymin_anchor=-0.1, update_y_limits=True, plotid=1) update_axes()
class AutoFocusManager(Manager): """ currently uses passive focus techniques see http://en.wikipedia.org/wiki/Autofocus """ video = Any laser_manager = Any stage_controller = Any canvas = Any parameters = Instance(FocusParameters) configure_button = Button('configure') autofocus_button = Event autofocus_label = Property(depends_on='autofocusing') autofocusing = Bool # threading event for cancel signal _evt_autofocusing = None image = Instance(Image, ()) graph = None def dump_parameters(self): p = os.path.join(paths.hidden_dir, 'autofocus_configure') self.info('dumping parameters to {}'.format(p)) with open(p, 'wb') as f: pickle.dump(self.parameters, f) def load_parameter(self): p = os.path.join(paths.hidden_dir, 'autofocus_configure') if os.path.isfile(p): with open(p, 'rb') as f: try: params = pickle.load(f) self.info('loading parameters from {}'.format(p)) if not isinstance(params, FocusParameters): self.info('out of date parameters file. using default') params = FocusParameters() return params except Exception as e: print('autofocus load parameter', e) return FocusParameters() else: return FocusParameters() def passive_focus(self, block=False, **kw): self._evt_autofocusing = TEvent() self._evt_autofocusing.clear() # manager = self.laser_manager oper = self.parameters.operator self.info('passive focus. operator = {}'.format(oper)) g = self.graph if not g: g = Graph(plotcontainer_dict=dict(padding=10), window_x=0.70, window_y=20, window_width=325, window_height=325, window_title='Autofocus' ) self.graph = g g.clear() g.new_plot(padding=[40, 10, 10, 40], xtitle='Z (mm)', ytitle='Focus Measure ({})'.format(oper) ) g.new_series() g.new_series() invoke_in_main_thread(self._open_graph) target = self._passive_focus self._passive_focus_thread = Thread(name='autofocus', target=target, args=(self._evt_autofocusing, ), kwargs=kw ) self._passive_focus_thread.start() if block: # while 1: # if not self._passive_focus_thread.isRunning(): # break # time.sleep(0.25) self._passive_focus_thread.join() def _open_graph(self): ui = self.graph.edit_traits() self.add_window(ui) def stop_focus(self): if self.stage_controller: self.stage_controller.stop() self.info('autofocusing stopped by user') def _passive_focus(self, stop_signal, set_zoom=True): ''' sweep z looking for max focus measure FMgrad= roberts or sobel (sobel removes noise) FMvar = intensity variance ''' self.autofocusing = True manager = self.laser_manager fstart = self.parameters.fstart fend = self.parameters.fend step_scalar = self.parameters.step_scalar zoom = self.parameters.zoom operator = self.parameters.operator steps = step_scalar * (max(fend, fstart) - min(fend, fstart)) + 1 prev_zoom = None if set_zoom and \ manager is not None and \ zoom: motor = manager.get_motor('zoom') if motor: prev_zoom = motor.data_position self.info('setting zoom: {}'.format(zoom)) manager.set_motor('zoom', zoom, block=True) time.sleep(1.5) args = self._do_focusing(fstart, fend, steps, operator) if manager is not None: if prev_zoom is not None: self.info('returning to previous zoom: {}'.format(prev_zoom)) manager.set_motor('zoom', prev_zoom, block=True) if args: mi, fmi, ma, fma = args self.info('''passive focus results:Operator={} ImageGradmin={} (z={}) ImageGradmax={}, (z={})'''.format(operator, mi, fmi, ma, fma)) focus_pos = fma self.graph.add_vertical_rule(focus_pos) self.graph.redraw() # self.graph.add_vertical_rule(fma) self.info('calculated focus z= {}'.format(focus_pos)) # if set_z: controller = self.stage_controller if controller is not None: if not stop_signal.isSet(): controller.single_axis_move('z', focus_pos, block=True) controller._z_position = focus_pos controller.z_progress = focus_pos self.autofocusing = False def _cancel_sweep(self, vo): if self._evt_autofocusing.isSet(): # return to original velocity self.autofocusing = False self._reset_velocity(vo) return True def _reset_velocity(self, vo): if self.stage_controller: pdict = dict(velocity=vo, key='z') self.stage_controller.set_single_axis_motion_parameters(pdict=pdict) def _do_focusing(self, start, end, steps, operator): screen_roi = self._get_roi() self._add_focus_area_rect(*screen_roi) src = self._load_source() src = asarray(src) h, w, _d = src.shape cx = w / 2. cy = h / 2. cw = self.parameters.crop_width ch = self.parameters.crop_height roi = cx, cy, cw, ch ''' start the z in motion and take pictures as you go query stage_controller to get current z ''' self.info('focus sweep start={} end={}'.format(start, end)) # move to start position controller = self.stage_controller if controller: vo = controller.axes['z'].velocity if self._cancel_sweep(vo): return self.graph.set_x_limits(min(start, end), max(start, end), pad=2) # sweep 1 and velocity 1 self._do_sweep(start, end, velocity=self.parameters.velocity_scalar1) fms, focussteps = self._collect_focus_measures(operator, roi) if not (fms and focussteps): return # reached end of sweep # calculate a nominal focal point args = self._calculate_nominal_focal_point(fms, focussteps) if not args: return nfocal = args[3] nwin = self.parameters.negative_window pwin = self.parameters.positive_window if self._cancel_sweep(vo): return nstart, nend = max(0, nfocal - nwin), nfocal + pwin # mi = min(min(nstart, nend), min(start, end)) # ma = max(max(nstart, nend), max(start, end)) # self.graph.set_x_limits(mi, ma, pad=2) time.sleep(1) # do a slow tight sweep around the nominal focal point self._do_sweep(nstart, nend, velocity=self.parameters.velocity_scalar2) fms, focussteps = self._collect_focus_measures(operator, roi, series=1) self._reset_velocity(vo) else: focussteps = linspace(0, 10, 11) fms = -(focussteps - 5) ** 2 + 10 + random.random(11) self.info('frames analyzed {}'.format(len(fms))) # self.canvas.markupcontainer.pop('croprect') return self._calculate_nominal_focal_point(fms, focussteps) def _do_sweep(self, start, end, velocity=None): controller = self.stage_controller controller.single_axis_move('z', start, block=True) # time.sleep(0.1) # explicitly check for motion # controller.block(axis='z') if velocity: vo = controller.axes['z'].velocity controller.set_single_axis_motion_parameters(pdict=dict(velocity=vo * velocity, key='z')) self.info('starting sweep from {}'.format(controller.z_progress)) # pause before moving to end time.sleep(0.25) controller.single_axis_move('z', end, update=100, immediate=True) def _collect_focus_measures(self, operator, roi, series=0): controller = self.stage_controller focussteps = [] fms = [] if controller.timer: p = controller.timer.get_interval() self.debug('controller timer period {}'.format(p)) pz = controller.z_progress while 1: src = self._load_source() x = controller.z_progress if x != pz: y = self._calculate_focus_measure(src, operator, roi) self.graph.add_datum((x, y), series=series) focussteps.append(x) fms.append(y) pz = x if not (controller.timer.isActive() and \ not self._evt_autofocusing.isSet()): break time.sleep(p) self.debug('sweep finished') return fms, focussteps def _calculate_nominal_focal_point(self, fms, focussteps): if fms: sfms = smooth(fms) if sfms is not None: self.graph.new_series(focussteps, sfms) self.graph.redraw() fmi = focussteps[argmin(sfms)] fma = focussteps[argmax(sfms)] mi = min(sfms) ma = max(sfms) return mi, fmi, ma, fma def _calculate_focus_measure(self, src, operator, roi): ''' see IMPLEMENTATION OF A PASSIVE AUTOMATIC FOCUSING ALGORITHM FOR DIGITAL STILL CAMERA DOI 10.1109/30.468047 and http://cybertron.cg.tu-berlin.de/pdci10/frankencam/#autofocus ''' # need to resize to 640,480. this is the space the roi is in # s = resize(grayspace(pychron), 640, 480) src = grayspace(src) v = crop(src, *roi) di = dict(var=lambda x:variance(x), laplace=lambda x: get_focus_measure(x, 'laplace'), sobel=lambda x: ndsum(generic_gradient_magnitude(x, sobel, mode='nearest')) ) func = di[operator] return func(v) def image_view(self): v = View(Item('image', show_label=False, editor=ImageEditor(), width=640, height=480, style='custom')) return v def traits_view(self): v = View( HGroup(self._button_factory('autofocus_button', 'autofocus_label'), Item('configure_button', show_label=False), show_border=True, label='Autofocus' ) ) return v def configure_view(self): v = View(Item('parameters', style='custom', show_label=False), handler=ConfigureHandler, buttons=['OK', 'Cancel'], kind='livemodal', title='Configure Autofocus', x=0.80, y=0.05 ) return v def _load_source(self): src = self.video.get_frame() return src # if pychron: # return Image.new_frame(pychron) # self.image.load(pychron) # return self.image.source_frame def _get_roi(self): w = self.parameters.crop_width h = self.parameters.crop_height cx, cy = self.canvas.get_center_rect_position(w, h) # cw, ch = self.canvas.outer_bounds # print w, h, cw, ch # cx = cw / 2. - w / 2. # cy = ch / 2. - h / 2. # cx = (cw - w) / 2. # cy = (ch - h) / 2. # cx = (640 * self.canvas.scaling - w) / 2 # cy = (480 * self.canvas.scaling - h) / 2 roi = cx, cy, w, h return roi def _add_focus_area_rect(self, cx, cy, w, h): # pl = self.canvas.padding_left # pb = self.canvas.padding_bottom self.canvas.remove_item('croprect') self.canvas.add_markup_rect(cx, cy, w, h, identifier='croprect') def _autofocus_button_fired(self): if not self.autofocusing: self.autofocusing = True self.passive_focus() else: self.autofocusing = False self._evt_autofocusing.set() self.stop_focus() def _configure_button_fired(self): self._crop_rect_update() self.edit_traits(view='configure_view', kind='livemodal') self.canvas.remove_item('croprect') # try: # self.canvas.markupcontainer.pop('croprect') # except KeyError: # pass @on_trait_change('parameters:[_crop_width,_crop_height]') def _crop_rect_update(self): roi = self._get_roi() self._add_focus_area_rect(*roi) def _get_autofocus_label(self): return 'Autofocus' if not self.autofocusing else 'Stop' def _parameters_default(self): return self.load_parameter() def _autofocusing_changed(self, new): if not new: self.canvas.remove_item('croprect')
class PatternExecutor(Patternable): """ a pattern is only good for one execution. self.pattern needs to be reset after stop or finish using load_pattern(name_or_pickle) """ controller = Any laser_manager = Any show_patterning = Bool(False) _alive = Bool(False) def __init__(self, *args, **kw): super(PatternExecutor, self).__init__(*args, **kw) self._next_point = None self.pattern = None self._xy_thread = None self._power_thread = None self._z_thread = None def start(self, show=False): self._alive = True if show: self.show_pattern() if self.pattern: self.pattern.clear_graph() def finish(self): self._alive = False self.close_pattern() self.pattern = None def set_stage_values(self, sm): if self.pattern: self.pattern.set_stage_values(sm) def set_current_position(self, x, y, z): if self.isPatterning(): graph = self.pattern.graph graph.set_data([x], series=1, axis=0) graph.set_data([y], series=1, axis=1) graph.add_datum((x, y), series=2) graph.redraw() def load_pattern(self, name_or_pickle): """ look for name_or_pickle in local pattern dir if not found try interpreting name_or_pickle is a pickled name_or_pickle """ if name_or_pickle is None: path = self.open_file_dialog() if path is None: return else: path = self.is_local_pattern(name_or_pickle) if path: wfile = open(path, 'rb') else: # convert name_or_pickle into a file like obj wfile = StringIO(name_or_pickle) # self._load_pattern sets self.pattern pattern = self._load_pattern(wfile, path) self.on_trait_change(self.stop, 'canceled') return pattern def is_local_pattern(self, name): def test_name(ni): path = os.path.join(paths.pattern_dir, ni) if os.path.isfile(path): return path for ni in (name, name + '.lp'): p = test_name(ni) if p: return p def stop(self): self._alive = False if self.controller: self.info('User requested stop') self.controller.stop() if self.pattern is not None: if self.controller: self.controller.linear_move(self.pattern.cx, self.pattern.cy, source='pattern stop') # self.pattern.close_ui() self.info('Pattern {} stopped'.format(self.pattern_name)) # prevent future stops (AbortJogs from massspec) from executing self.pattern = None def isPatterning(self): return self._alive def close_pattern(self): pass def show_pattern(self): self.pattern.window_x = 50 self.pattern.window_y = 50 open_view(self.pattern, view='graph_view') def execute(self, block=False, duration=None, thread_safe=True): """ if block is true wait for patterning to finish before returning """ if not self.pattern: return self.start(show=self.show_patterning) evt = None # if current_thread().name != 'MainThread': if thread_safe: evt = Event() invoke_in_main_thread(self._pre_execute, evt) while not evt.is_set(): time.sleep(0.05) else: self._pre_execute(evt) self.debug('execute xy pattern') xyp = self.pattern.xy_pattern_enabled if duration: self.pattern.external_duration = float(duration) if xyp: self._xy_thread = Thread(target=self._execute_xy_pattern) self._xy_thread.start() pp = self.pattern.power_pattern if pp: self.debug('execute power pattern') self._power_thread = Thread(target=self._execute_power_pattern) self._power_thread.start() zp = self.pattern.z_pattern if zp: self.debug('execute z pattern') self._z_thread = Thread(target=self._execute_z_pattern) self._z_thread.start() if block: if self._xy_thread: self._xy_thread.join() if self._z_thread: self._z_thread.join() if self._power_thread: self._power_thread.join() self.finish() def _pre_execute(self, evt): self.debug('pre execute') pattern = self.pattern kind = pattern.kind if kind in ('SeekPattern', 'DragonFlyPeakPattern'): self._info = open_view(pattern, view='execution_graph_view') if evt is not None: evt.set() self.debug('pre execute finished') def _execute_power_pattern(self): pat = self.pattern self.info('starting power pattern {}'.format(pat.name)) def func(v): self.laser_manager.set_laser_power(v) self._execute_(func, pat.power_values(), pat.power_sample, 'power pattern setpoint={value}') def _execute_z_pattern(self): pat = self.pattern self.info('starting power pattern {}'.format(pat.name)) def func(v): self.controller.set_z(v) self._execute_(func, pat.z_values(), pat.z_sample, 'z pattern z={value}') def _execute_(self, func, vs, period, msg): for v in vs: st = time.time() self.debug(msg.format(value=v)) func(v) et = st - time.time() p = period - et time.sleep(p) def _execute_xy_pattern(self): pat = self.pattern self.info('starting pattern {}'.format(pat.name)) st = time.time() self.controller.update_position() time.sleep(1) pat.cx, pat.cy = self.controller.x, self.controller.y try: for ni in range(pat.niterations): if not self.isPatterning(): break self.info('doing pattern iteration {}'.format(ni)) self._execute_iteration(ni) self.controller.linear_move(pat.cx, pat.cy, block=True, source='execute_xy_pattern') if pat.disable_at_end: self.laser_manager.disable_device() self.finish() self.info( 'finished pattern: transit time={:0.1f}s'.format(time.time() - st)) except (TargetPositionError, PositionError) as e: self.finish() self.controller.stop() self.laser_manager.emergency_shutoff(str(e)) def _execute_iteration(self, iteration): controller = self.controller pattern = self.pattern if controller is not None: kind = pattern.kind if kind == 'ArcPattern': self._execute_arc(controller, pattern) elif kind == 'CircularContourPattern': self._execute_contour(controller, pattern) elif kind in ('SeekPattern', 'DragonFlyPeakPattern'): self._execute_seek(controller, pattern) else: self._execute_points(controller, pattern, iteration, multipoint=False) def _execute_points(self, controller, pattern, iteration, multipoint=False): pts = pattern.points_factory() if multipoint: controller.multiple_point_move(pts, velocity=pattern.velocity) else: for i, (x, y) in enumerate(pts): self.debug('Pattern Point. {},{}'.format(iteration, i)) if not self.isPatterning(): break # skip first point after first iteration if iteration and not i: self.debug('skipping first point') continue self.debug('Pattern Point. {},{}: {},{}'.format( iteration, i, x, y)) controller.linear_move(x, y, block=True, velocity=pattern.velocity) def _execute_contour(self, controller, pattern): for ni in range(pattern.nsteps): if not self.isPatterning(): break r = pattern.radius * (1 + ni * pattern.percent_change) self.info('doing circular contour {} {}'.format(ni + 1, r)) controller.single_axis_move('x', pattern.cx + r, block=True) controller.arc_move(pattern.cx, pattern.cy, 360, block=True) time.sleep(0.1) def _execute_arc(self, controller, pattern): controller.single_axis_move('x', pattern.radius, block=True) controller.arc_move(pattern.cx, pattern.cy, pattern.degrees, block=True) def _execute_seek(self, controller, pattern): duration = pattern.duration total_duration = pattern.total_duration lm = self.laser_manager sm = lm.stage_manager ld = sm.lumen_detector ld.mask_kind = pattern.mask_kind ld.custom_mask = pattern.custom_mask_radius osdp = sm.canvas.show_desired_position sm.canvas.show_desired_position = False st = time.time() self.debug('Pre seek delay {}'.format(pattern.pre_seek_delay)) time.sleep(pattern.pre_seek_delay) self.debug('starting seek') self.debug('total duration {}'.format(total_duration)) self.debug('dwell duration {}'.format(duration)) if pattern.kind == 'DragonFlyPeakPattern': try: self._dragonfly_peak(st, pattern, lm, controller) except BaseException as e: self.critical('Dragonfly exception. {}'.format(e)) self.debug_exception() else: self._hill_climber(st, controller, pattern) sm.canvas.show_desired_position = osdp from pyface.gui import GUI GUI.invoke_later(self._info.dispose) def _dragonfly_peak(self, st, pattern, lm, controller): # imgplot, imgplot2, imgplot3 = pattern.setup_execution_graph() # imgplot, imgplot2 = pattern.setup_execution_graph() imgplot, imgplot2 = pattern.setup_execution_graph(nplots=2) cx, cy = pattern.cx, pattern.cy sm = lm.stage_manager linear_move = controller.linear_move in_motion = controller.in_motion find_lum_peak = sm.find_lum_peak pxpermm = sm.pxpermm set_data = imgplot.data.set_data set_data2 = imgplot2.data.set_data # set_data3 = imgplot3.data.set_data duration = pattern.duration sat_threshold = pattern.saturation_threshold total_duration = pattern.total_duration min_distance = pattern.min_distance aggressiveness = pattern.aggressiveness update_period = pattern.update_period / 1000. move_threshold = pattern.move_threshold blur = pattern.blur px, py = cx, cy ncx, ncy = cx, cy point_gen = None cnt = 0 # peak = None oimg = sm.get_preprocessed_src() pos_img = zeros_like(oimg, dtype='int16') per_img = zeros_like(oimg, dtype='int16') img_h, img_w = pos_img.shape perimeter_circle = circle(img_h / 2, img_w / 2, pattern.perimeter_radius * pxpermm) color = 2**15 - 1 per_img[perimeter_circle] = 50 set_data('imagedata', gray2rgb(per_img.astype(uint8))) while time.time() - st < total_duration: if not self._alive: break sats = [] pts = [] ist = time.time() npt = None self.debug('starting iteration={}, in_motion={}'.format( cnt, in_motion())) while time.time() - ist < duration or in_motion(): args = find_lum_peak(min_distance, blur) if args is None: sleep(update_period / 5) continue sleep(update_period) pt, peakcol, peakrow, peak_img, sat, src = args sats.append(sat) # if peak is None: # peak = peak_img # else: # peak = ((peak.astype('int16') - 2) + peak_img).clip(0, 255) # img = gray2rgb(peak).astype(uint8) src = gray2rgb(src).astype(uint8) if pt: pts.append(pt) c = circle(peakrow, peakcol, 2) # img[c] = (255, 0, 0) src[c] = (255, 0, 0) # set_data('imagedata', src) set_data2('imagedata', src) # set_data('imagedata', img) self.debug('iteration {} finished, npts={}'.format(cnt, len(pts))) pattern.position_str = NULL_STR if pts: w = array(sats) avg_sat_score = w.mean() self.debug('Average Saturation: {} threshold={}'.format( avg_sat_score, sat_threshold)) pattern.average_saturation = avg_sat_score if avg_sat_score < sat_threshold: # pts = array(pts) x, y, w = array(pts).T ws = w.sum() nx = (x * w).sum() / ws ny = (y * w).sum() / ws self.debug('New point {},{}'.format(nx, ny)) npt = nx, ny, 1 else: continue if npt is None: if not point_gen: point_gen = pattern.point_generator() # wait = False x, y = next(point_gen) px, py = ncx + x, ncy + y self.debug('generating new point={},{} ---- {},{}'.format( x, y, px, py)) else: point_gen = None # wait = True if npt is None: block = total_duration - (time.time() - st) < duration linear_move(cx, cy, source='recenter_dragonfly{}'.format(cnt), block=block, velocity=pattern.velocity, use_calibration=False) pattern.position_str = 'Return to Center' px, py = cx, cy continue try: scalar = npt[2] except IndexError: scalar = 1 ascalar = scalar * aggressiveness dx = npt[0] / pxpermm * ascalar dy = npt[1] / pxpermm * ascalar if abs(dx) < move_threshold or abs(dy) < move_threshold: self.debug('Deviation too small dx={},dy={}'.format( dx, dy, move_threshold)) pattern.position_str = 'Deviation too small' continue px += dx py -= dy self.debug('i: {}. point={},{}. ' 'Intensitiy Scalar={}, Modified Scalar={}'.format( cnt, px, py, scalar, ascalar)) ncx, ncy = px, py if not pattern.validate(px, py): self.debug('invalid position. {},{}'.format(px, py)) curx = px - dx cury = py + dy vx = curx - cx vy = cury - cy px = vx * aggressiveness + cx py = vy * aggressiveness + cy self.debug('reduced vector magnitude. new pos={},{}'.format( px, py)) # for safety validate this new position # if above calculation is correct the new position should always be valid if not pattern.validate(px, py): self.debug( 'vector calculations incorrect. moving to center position' ) px, py = cx, cy ncx, ncy = px, py pattern.position_str = '{:0.5f},{:0.5f}'.format(px, py) # if there is less than 1 duration left then block is true block = total_duration - (time.time() - st) < duration self.debug('blocking ={}'.format(block)) linear_move(px, py, source='dragonfly{}'.format(cnt), block=block, velocity=pattern.velocity, use_calibration=False) ay, ax = py - cy, px - cx # self.debug('position mm ax={},ay={}'.format(ax, ay)) ay, ax = int(-ay * pxpermm) + img_h / 2, int( ax * pxpermm) + img_w / 2 # self.debug('position pixel ax={},ay={}'.format(ax, ay)) pos_img -= 5 pos_img = pos_img.clip(0, color) c = circle(ay, ax, 2) pos_img[c] = color - 60 nimg = ((pos_img + per_img).astype(uint8)) set_data('imagedata', gray2rgb(nimg)) cnt += 1 self.debug('dragonfly complete') controller.block() def _hill_climber(self, st, controller, pattern): g = pattern.execution_graph imgplot, cp = pattern.setup_execution_graph() cx, cy = pattern.cx, pattern.cy sm = self.laser_manager.stage_manager linear_move = controller.linear_move get_scores = sm.get_scores moving = sm.moving update_axes = sm.update_axes set_data = imgplot.data.set_data sat_threshold = pattern.saturation_threshold total_duration = pattern.total_duration duration = pattern.duration pattern.perimeter_radius *= sm.pxpermm avg_sat_score = -1 # current_x, current_y =None, None for i, pt in enumerate(pattern.point_generator()): update_plot = True x, y = pt.x, pt.y ax, ay = cx + x, cy + y if not self._alive: break if time.time() - st > total_duration: break # use_update_point = False if avg_sat_score < sat_threshold: # use_update_point = False # current_x, current_y = x, y linear_move(ax, ay, block=False, velocity=pattern.velocity, use_calibration=False, update=False, immediate=True) else: self.debug('Saturation target reached. not moving') update_plot = False density_scores = [] ts = [] saturation_scores = [] positions = [] def measure_scores(update=False): if update: update_axes() positions.append((controller.x, controller.y)) score_density, score_saturation, img = get_scores() density_scores.append(score_density) saturation_scores.append(score_saturation) set_data('imagedata', img) ts.append(time.time() - st) time.sleep(0.1) while moving(force_query=True): measure_scores(update=True) mt = time.time() while time.time() - mt < duration: measure_scores() if density_scores: n = len(density_scores) density_scores = array(density_scores) saturation_scores = array(saturation_scores) weights = [ 1 / (max(0.0001, ((xi - ax)**2 + (yi - ay)**2))**0.5) for xi, yi in positions ] avg_score = average(density_scores, weights=weights) avg_sat_score = average(saturation_scores, weights=weights) score = avg_score m, b = polyfit(ts, density_scores, 1) if m > 0: score *= (1 + m) pattern.set_point(score, pt) self.debug('i:{} XY:({:0.5f},{:0.5f})'.format(i, x, y)) self.debug('Density. AVG:{:0.3f} N:{} Slope:{:0.3f}'.format( avg_score, n, m)) self.debug('Modified Density Score: {:0.3f}'.format(score)) self.debug('Saturation. AVG:{:0.3f}'.format(avg_sat_score)) if update_plot: cp.add_point((x, y)) g.add_datum((x, y), plotid=0) t = time.time() - st g.add_datum((t, avg_score), plotid=1) # g.add_bulk_data(ts, density_scores, plotid=1, series=1) g.add_datum((t, score), ypadding='0.1', ymin_anchor=-0.1, update_y_limits=True, plotid=1) update_axes()
class AutoFocusManager(Manager): """ currently uses passive focus techniques see http://en.wikipedia.org/wiki/Autofocus """ video = Any laser_manager = Any stage_controller = Any canvas = Any parameters = Instance(FocusParameters) configure_button = Button('configure') autofocus_button = Event autofocus_label = Property(depends_on='autofocusing') autofocusing = Bool # threading event for cancel signal _evt_autofocusing = None image = Instance(Image, ()) graph = None def dump_parameters(self): p = os.path.join(paths.hidden_dir, 'autofocus_configure') self.info('dumping parameters to {}'.format(p)) with open(p, 'wb') as f: pickle.dump(self.parameters, f) def load_parameter(self): p = os.path.join(paths.hidden_dir, 'autofocus_configure') if os.path.isfile(p): with open(p, 'rb') as f: try: params = pickle.load(f) self.info('loading parameters from {}'.format(p)) if not isinstance(params, FocusParameters): self.info('out of date parameters file. using default') params = FocusParameters() return params except Exception as e: print('autofocus load parameter', e) return FocusParameters() else: return FocusParameters() def passive_focus(self, block=False, **kw): self._evt_autofocusing = TEvent() self._evt_autofocusing.clear() # manager = self.laser_manager oper = self.parameters.operator self.info('passive focus. operator = {}'.format(oper)) g = self.graph if not g: g = Graph(plotcontainer_dict=dict(padding=10), window_x=0.70, window_y=20, window_width=325, window_height=325, window_title='Autofocus') self.graph = g g.clear() g.new_plot(padding=[40, 10, 10, 40], xtitle='Z (mm)', ytitle='Focus Measure ({})'.format(oper)) g.new_series() g.new_series() invoke_in_main_thread(self._open_graph) target = self._passive_focus self._passive_focus_thread = Thread(name='autofocus', target=target, args=(self._evt_autofocusing, ), kwargs=kw) self._passive_focus_thread.start() if block: # while 1: # if not self._passive_focus_thread.isRunning(): # break # time.sleep(0.25) self._passive_focus_thread.join() def _open_graph(self): ui = self.graph.edit_traits() self.add_window(ui) def stop_focus(self): if self.stage_controller: self.stage_controller.stop() self.info('autofocusing stopped by user') def _passive_focus(self, stop_signal, set_zoom=True): ''' sweep z looking for max focus measure FMgrad= roberts or sobel (sobel removes noise) FMvar = intensity variance ''' self.autofocusing = True manager = self.laser_manager fstart = self.parameters.fstart fend = self.parameters.fend step_scalar = self.parameters.step_scalar zoom = self.parameters.zoom operator = self.parameters.operator steps = step_scalar * (max(fend, fstart) - min(fend, fstart)) + 1 prev_zoom = None if set_zoom and \ manager is not None and \ zoom: motor = manager.get_motor('zoom') if motor: prev_zoom = motor.data_position self.info('setting zoom: {}'.format(zoom)) manager.set_motor('zoom', zoom, block=True) time.sleep(1.5) args = self._do_focusing(fstart, fend, steps, operator) if manager is not None: if prev_zoom is not None: self.info('returning to previous zoom: {}'.format(prev_zoom)) manager.set_motor('zoom', prev_zoom, block=True) if args: mi, fmi, ma, fma = args self.info('''passive focus results:Operator={} ImageGradmin={} (z={}) ImageGradmax={}, (z={})'''.format(operator, mi, fmi, ma, fma)) focus_pos = fma self.graph.add_vertical_rule(focus_pos) self.graph.redraw() # self.graph.add_vertical_rule(fma) self.info('calculated focus z= {}'.format(focus_pos)) # if set_z: controller = self.stage_controller if controller is not None: if not stop_signal.isSet(): controller.single_axis_move('z', focus_pos, block=True) controller._z_position = focus_pos controller.z_progress = focus_pos self.autofocusing = False def _cancel_sweep(self, vo): if self._evt_autofocusing.isSet(): # return to original velocity self.autofocusing = False self._reset_velocity(vo) return True def _reset_velocity(self, vo): if self.stage_controller: pdict = dict(velocity=vo, key='z') self.stage_controller.set_single_axis_motion_parameters( pdict=pdict) def _do_focusing(self, start, end, steps, operator): screen_roi = self._get_roi() self._add_focus_area_rect(*screen_roi) src = self._load_source() src = asarray(src) h, w, _d = src.shape cx = w / 2. cy = h / 2. cw = self.parameters.crop_width ch = self.parameters.crop_height roi = cx, cy, cw, ch ''' start the z in motion and take pictures as you go query stage_controller to get current z ''' self.info('focus sweep start={} end={}'.format(start, end)) # move to start position controller = self.stage_controller if controller: vo = controller.axes['z'].velocity if self._cancel_sweep(vo): return self.graph.set_x_limits(min(start, end), max(start, end), pad=2) # sweep 1 and velocity 1 self._do_sweep(start, end, velocity=self.parameters.velocity_scalar1) fms, focussteps = self._collect_focus_measures(operator, roi) if not (fms and focussteps): return # reached end of sweep # calculate a nominal focal point args = self._calculate_nominal_focal_point(fms, focussteps) if not args: return nfocal = args[3] nwin = self.parameters.negative_window pwin = self.parameters.positive_window if self._cancel_sweep(vo): return nstart, nend = max(0, nfocal - nwin), nfocal + pwin # mi = min(min(nstart, nend), min(start, end)) # ma = max(max(nstart, nend), max(start, end)) # self.graph.set_x_limits(mi, ma, pad=2) time.sleep(1) # do a slow tight sweep around the nominal focal point self._do_sweep(nstart, nend, velocity=self.parameters.velocity_scalar2) fms, focussteps = self._collect_focus_measures(operator, roi, series=1) self._reset_velocity(vo) else: focussteps = linspace(0, 10, 11) fms = -(focussteps - 5)**2 + 10 + random.random(11) self.info('frames analyzed {}'.format(len(fms))) # self.canvas.markupcontainer.pop('croprect') return self._calculate_nominal_focal_point(fms, focussteps) def _do_sweep(self, start, end, velocity=None): controller = self.stage_controller controller.single_axis_move('z', start, block=True) # time.sleep(0.1) # explicitly check for motion # controller.block(axis='z') if velocity: vo = controller.axes['z'].velocity controller.set_single_axis_motion_parameters( pdict=dict(velocity=vo * velocity, key='z')) self.info('starting sweep from {}'.format(controller.z_progress)) # pause before moving to end time.sleep(0.25) controller.single_axis_move('z', end, update=100, immediate=True) def _collect_focus_measures(self, operator, roi, series=0): controller = self.stage_controller focussteps = [] fms = [] if controller.timer: p = controller.timer.get_interval() self.debug('controller timer period {}'.format(p)) pz = controller.z_progress while 1: src = self._load_source() x = controller.z_progress if x != pz: y = self._calculate_focus_measure(src, operator, roi) self.graph.add_datum((x, y), series=series) focussteps.append(x) fms.append(y) pz = x if not (controller.timer.isActive() and \ not self._evt_autofocusing.isSet()): break time.sleep(p) self.debug('sweep finished') return fms, focussteps def _calculate_nominal_focal_point(self, fms, focussteps): if fms: sfms = smooth(fms) if sfms is not None: self.graph.new_series(focussteps, sfms) self.graph.redraw() fmi = focussteps[argmin(sfms)] fma = focussteps[argmax(sfms)] mi = min(sfms) ma = max(sfms) return mi, fmi, ma, fma def _calculate_focus_measure(self, src, operator, roi): ''' see IMPLEMENTATION OF A PASSIVE AUTOMATIC FOCUSING ALGORITHM FOR DIGITAL STILL CAMERA DOI 10.1109/30.468047 and http://cybertron.cg.tu-berlin.de/pdci10/frankencam/#autofocus ''' # need to resize to 640,480. this is the space the roi is in # s = resize(grayspace(pychron), 640, 480) src = grayspace(src) v = crop(src, *roi) di = dict(var=lambda x: variance(x), laplace=lambda x: get_focus_measure(x, 'laplace'), sobel=lambda x: ndsum( generic_gradient_magnitude(x, sobel, mode='nearest'))) func = di[operator] return func(v) def image_view(self): v = View( Item('image', show_label=False, editor=ImageEditor(), width=640, height=480, style='custom')) return v def traits_view(self): v = View( HGroup(self._button_factory('autofocus_button', 'autofocus_label'), Item('configure_button', show_label=False), show_border=True, label='Autofocus')) return v def configure_view(self): v = View(Item('parameters', style='custom', show_label=False), handler=ConfigureHandler, buttons=['OK', 'Cancel'], kind='livemodal', title='Configure Autofocus', x=0.80, y=0.05) return v def _load_source(self): src = self.video.get_frame() return src # if pychron: # return Image.new_frame(pychron) # self.image.load(pychron) # return self.image.source_frame def _get_roi(self): w = self.parameters.crop_width h = self.parameters.crop_height cx, cy = self.canvas.get_center_rect_position(w, h) # cw, ch = self.canvas.outer_bounds # print w, h, cw, ch # cx = cw / 2. - w / 2. # cy = ch / 2. - h / 2. # cx = (cw - w) / 2. # cy = (ch - h) / 2. # cx = (640 * self.canvas.scaling - w) / 2 # cy = (480 * self.canvas.scaling - h) / 2 roi = cx, cy, w, h return roi def _add_focus_area_rect(self, cx, cy, w, h): # pl = self.canvas.padding_left # pb = self.canvas.padding_bottom self.canvas.remove_item('croprect') self.canvas.add_markup_rect(cx, cy, w, h, identifier='croprect') def _autofocus_button_fired(self): if not self.autofocusing: self.autofocusing = True self.passive_focus() else: self.autofocusing = False self._evt_autofocusing.set() self.stop_focus() def _configure_button_fired(self): self._crop_rect_update() self.edit_traits(view='configure_view', kind='livemodal') self.canvas.remove_item('croprect') # try: # self.canvas.markupcontainer.pop('croprect') # except KeyError: # pass @on_trait_change('parameters:[_crop_width,_crop_height]') def _crop_rect_update(self): roi = self._get_roi() self._add_focus_area_rect(*roi) def _get_autofocus_label(self): return 'Autofocus' if not self.autofocusing else 'Stop' def _parameters_default(self): return self.load_parameter() def _autofocusing_changed(self, new): if not new: self.canvas.remove_item('croprect')