def _home_fired(self): """ """ t = Thread(name='stage.home', target=self._home) t.start() # need to store a reference to thread so it is not garbage collected self.move_thread = t
def _do_execute(self): mapper = self.mapper mapper.laser_manager = self._laser_manager editor = self.editor padding = editor.padding # if editor.discrete_scan: # mapper.canvas = self.canvas # self.component = self.canvas # else: c = mapper.make_component(padding) self.component = c bd = editor.beam_diameter rp = editor.request_power cx = editor.center_x cy = editor.center_y step_len = editor.step_length t = Thread(target=mapper.do_power_mapping, args=(bd, rp, cx, cy, padding, step_len)) t.start() self._execute_thread = t return True
def _test1(self): print('test1') ld = LumenDetector() def func(): src = copy(self.manager.video.get_cached_frame()) # dim = self.stage_map.g_dimension ld.pxpermm = 31 dim = 1.5 mask_dim = dim * 1.05 offx, offy = 0, 0 cropdim = dim * 2.5 src = ld.crop(src, cropdim, cropdim, offx, offy, verbose=False) ld.find_targets( self.display_image, src, dim, mask=mask_dim, search={ 'start_offset_scalar': 1, # 'width': 2 }) # self.manager.calculate_new_center(0, 0, 0, 0, dim=1.25) t = Thread(target=func) t.start() self.t = t
def _test1(self): self.test_image.setup_images( 3, # (475, 613) (640, 480)) t = Thread(target=self._test) t.start() self._t = t
def _test1(self): self.test_image.setup_images(3, # (475, 613) (640, 480) ) t = Thread(target=self._test) t.start() self._t = t
def _test_fired(self): from pychron.globals import globalv p = '/Users/ross/Sandbox/test_target.jpg' # p = '/Users/ross/Sandbox/pos_err/pos_err_200_0-002.jpg' p = '/Users/ross/Sandbox/poserror/pos_err_221_0-007.jpg' # p = '/Users/ross/Sandbox/poserror/snapshot009.jpg' # force video to reload test image self.video.source_frame = None globalv.video_test_path = p im = self.setup_image() # self._test2(im) from pychron.core.ui.thread import Thread t = Thread(target=self._test2, args=(im,)) t.start() self._t = t
def _test_fired(self): from pychron.globals import globalv p = '/Users/ross/Sandbox/test_target.jpg' # p = '/Users/ross/Sandbox/pos_err/pos_err_200_0-002.jpg' p = '/Users/ross/Sandbox/poserror/pos_err_221_0-007.jpg' # p = '/Users/ross/Sandbox/poserror/snapshot009.jpg' # force video to reload test image self.video.source_frame = None globalv.video_test_path = p im = self.setup_image() # self._test2(im) from pychron.core.ui.thread import Thread t = Thread(target=self._test2, args=(im, )) t.start() self._t = t
def do_peak_center(self, save=True, confirm_save=False, warn=False, new_thread=True): self.debug('doing pc') self.canceled = False self.alive = True args = (save, confirm_save, warn) if new_thread: t = Thread(name='ion_optics.peak_center', target=self._peak_center, args=args) t.start() self._thread = t return t else: self._peak_center(*args)
def do_peak_center(self, save=True, confirm_save=False, warn=False, new_thread=True, message='', on_end=None, timeout=None): self.debug('doing pc') self.canceled = False self.alive = True self.peak_center_result = None args = (save, confirm_save, warn, message, on_end, timeout) if new_thread: t = Thread(name='ion_optics.peak_center', target=self._peak_center, args=args) t.start() self._centering_thread = t return t else: self._peak_center(*args)
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 Scanner(Loggable): """ Scanner is a base class for displaying a scan of device data ScanableDevices has this ability built in but more complicated scans are best done here. ScanableDevice scan is best used from continuous long term recording of a single or multiple values """ _graph = Instance(StreamStackedGraph) manager = Instance(ILaserManager) data_manager = Instance(CSVDataManager, ()) ''' list of callables. should return a signal value for graphing ''' functions = List static_values = List scan_period = Int # ms stop_event = Event setpoint = Float(enter_set=True, auto_set=False) control_path = File _warned = False _scanning = Bool _directory = None _base_frame_name = None def new_static_value(self, name, *args, **kw): self.static_values.append((name, None)) if args: self.set_static_value(name, *args, **kw) def set_static_value(self, name_or_idx, value, plotid=None): """ if the plotid is not None add a horizontal guide at value """ if isinstance(name_or_idx, str): name_or_idx = next( (i for i, (e, a) in enumerate(self.static_values)), None) if name_or_idx is not None: name = self.static_values[name_or_idx][0] self.static_values[name_or_idx] = (name, value) if plotid is not None: self._graph.add_horizontal_rule(value, plotid=plotid) def setup(self, directory=None, base_frame_name=None): self._directory = directory self._base_frame_name = base_frame_name def new_function(self, function, name=None): if name is None: name = function.func_name # g = self.graph # func = self.functions # kw = dict(ytitle=name,) # n = len(func) # if n == 0: # kw['xtitle'] = 'Time' self.functions.append((function, name)) def make_graph(self): g = StreamStackedGraph(window_x=50, window_y=100) for i, (_, name) in enumerate(self.functions): kw = dict(ytitle=name, ) if i == 0: kw['xtitle'] = 'Time' g.new_plot(data_limit=3000, **kw) g.new_series(plotid=i) self._graph = g return g def stop(self): self._timer.Stop() self._scanning = False self.stop_event = True self.info('scan stopped') if self.manager: self._stop_hook() self.manager.disable_device() def _stop_hook(self): pass def execute(self): if self._scanning: self.stop() else: self.data_manager.new_frame(directory=self._directory, base_frame_name=self._base_frame_name) # write metadata if available self._write_metadata() # make header row header = ['t'] + self._make_func_names() + \ [n for n, _ in self.static_values] self.data_manager.write_to_frame(header) self._scanning = True if self.manager: if self.manager.enable_device(): self._scanning = True return self._scanning def do_scan(self): self._starttime = time.time() self._execute() def _execute(self): yd = self._read_control_path() if yd is None: sp = 1000 else: sp = yd['period'] # starts automatically self.debug('scan starting') self._timer = Timer(sp, self._scan) self.info('scan started') # yd = self._read_control_path() if yd is not None: # start a control thread self._control_thread = Thread(target=self._control, args=(yd, )) self._control_thread.start() self.info('control started') # if self.manager: # if self.manager.enable_device(): # # # starts automatically # self.debug('scan starting') # self._timer = Timer(sp, self._scan) # # self.info('scan started') # self._scanning = True # # yd = self._read_control_path() # if yd is not None: # # start a control thread # self._control_thread = Thread(target=self._control, # args=(yd,) # ) # self._control_thread.start() # self.info('control started') # else: # self.warning('Could not enable device') # else: # self.warning('no manager available') def _control(self, ydict): self.start_control_hook(ydict) # if self.manager: # if self.manager.temperature_controller: # self.manager.temperature_controller.enable_tru_tune = True start_delay = ydict['start_delay'] end_delay = ydict['end_delay'] setpoints = ydict['setpoints'] print setpoints self.set_static_value('Setpoint', 0) time.sleep(start_delay) for args in setpoints: t = args[0] if self._scanning: self.setpoint = t self._set_power_hook(t) self.set_static_value('Setpoint', t, plotid=0) self._maintain_setpoint(t, *args[1:]) if self._scanning: self.setpoint = 0 self.set_static_value('Setpoint', 0) self._set_power_hook(0) # if self.manager: # self.manager.set_laser_temperature(0) time.sleep(end_delay) self.stop() self.end_control_hook(self._scanning) def _set_power_hook(self, t): if self.manager: self.manager.set_laser_temperature(t) def _maintain_setpoint(self, t, d, *args): self.info('setting setpoint to {} for {}s'.format(t, d)) st = time.time() while time.time() - st < d and self._scanning: time.sleep(1) def start_control_hook(self, *args, **kw): pass def end_control_hook(self, ok): pass def _make_func_names(self): return [name for _, name in self.functions] def _write_metadata(self): pass def _scan(self): functions = self.functions data = [] record = self._graph.record # record = self.graph.record x = time.time() - self._starttime for i, (func, _) in enumerate(functions): try: if hasattr(func, 'next'): func = func.next v = float(func()) data.append(v) # do_after no longer necessary with Qt record(v, plotid=i, x=x, do_after=None) except Exception, e: print 'exception', e sv = [] for _, v in self.static_values: if v is None: v = '' sv.append(v) data = [x] + data + sv self.data_manager.write_to_frame(data)
class StageManager(BaseStageManager): """ """ autocenter_button = Button stage_controller_klass = String('Newport') stage_controller = Instance(MotionController) points_programmer = Instance(PointsProgrammer) motion_controller_manager = Instance(MotionControllerManager) # canvas = Instance(LaserTrayCanvas) simulation = DelegatesTo('stage_controller') # stage_map_klass = StageMap # _stage_map = Instance(StageMap) # stage_map = Property(depends_on='_stage_map') # stage_maps = Property(depends_on='_stage_maps') # _stage_maps = List # =========================================================================== # buttons # =========================================================================== home = Button('home') home_option = String('Home All') home_options = List ejoystick = Event joystick_label = String('Enable Joystick') joystick = Bool(False) joystick_timer = None back_button = Button stop_button = Button('Stop') use_autocenter = Bool keep_images_open = Bool(False) _default_z = 0 _cached_position = None _cached_current_hole = None _homing = False def __init__(self, *args, **kw): """ """ super(StageManager, self).__init__(*args, **kw) self.stage_controller = self._stage_controller_factory() def measure_grain_polygon(self): pass def stop_measure_grain_polygon(self): pass def shutdown(self): self._save_stage_map() def create_device(self, *args, **kw): dev = super(StageManager, self).create_device(*args, **kw) dev.parent = self return dev def goto_position(self, v, **kw): if XY_REGEX[0].match(v): self._move_to_calibrated_position(v) elif POINT_REGEX.match(v) or TRANSECT_REGEX[0].match(v): self.move_to_point(v) else: self.move_to_hole(v, user_entry=True, **kw) def get_current_position(self): if self.stage_controller: x = self.stage_controller.x y = self.stage_controller.y return x, y def get_current_hole(self): pos = self.get_current_position() if self.stage_map: if distance_threshold(pos, self._cached_position, self.stage_map.g_dimension / 4): h = self.get_calibrated_hole(*pos, tol=self.stage_map.g_dimension / 2.) if h is not None: self._cached_current_hole = h self._cached_position = pos return self._cached_current_hole def is_auto_correcting(self): return False def bind_preferences(self, pref_id): bind_preference(self.canvas, 'show_grids', '{}.show_grids'.format(pref_id)) self.canvas.change_grid_visibility() bind_preference(self.canvas, 'show_laser_position', '{}.show_laser_position'.format(pref_id)) bind_preference(self.canvas, 'show_desired_position', '{}.show_desired_position'.format(pref_id)) bind_preference(self.canvas, 'desired_position_color', '{}.desired_position_color'.format(pref_id), factory=ColorPreferenceBinding) # bind_preference(self.canvas, 'render_map', '{}.render_map'.format(pref_id)) # bind_preference(self.canvas, 'crosshairs_kind', '{}.crosshairs_kind'.format(pref_id)) bind_preference(self.canvas, 'crosshairs_line_width', '{}.crosshairs_line_width'.format(pref_id)) bind_preference(self.canvas, 'crosshairs_color', '{}.crosshairs_color'.format(pref_id), factory=ColorPreferenceBinding) bind_preference(self.canvas, 'crosshairs_radius', '{}.crosshairs_radius'.format(pref_id)) bind_preference(self.canvas, 'crosshairs_offsetx', '{}.crosshairs_offsetx'.format(pref_id)) bind_preference(self.canvas, 'crosshairs_offsety', '{}.crosshairs_offsety'.format(pref_id)) bind_preference(self.canvas, 'show_hole_label', '{}.show_hole_label'.format(pref_id)) bind_preference(self.canvas, 'hole_label_color', '{}.hole_label_color'.format(pref_id)) bind_preference(self.canvas, 'hole_label_size', '{}.hole_label_size'.format(pref_id)) self.canvas.handle_hole_label_size(self.canvas.hole_label_size) bind_preference(self.canvas, 'scaling', '{}.scaling'.format(pref_id)) bind_preference(self.canvas, 'show_bounds_rect', '{}.show_bounds_rect'.format(pref_id)) self.canvas.request_redraw() def load(self): super(StageManager, self).load() config = self.get_configuration() if config: self._default_z = self.config_get(config, 'Defaults', 'z', default=13, cast='float') self.points_programmer.load_stage_map(self.stage_map_name) # load the calibration file # should have calibration files for each stage map self.tray_calibration_manager.load_calibration() def finish_loading(self): self.initialize_stage() def initialize_stage(self): self.update_axes() axes = self.stage_controller.axes self.home_options = ['Home All', 'XY'] + sorted([axes[a].name.upper() for a in axes]) self.canvas.parent = self def save_calibration(self, name): self.tray_calibration_manager.save_calibration(name=name) # def add_stage_map(self, v): # sm = self.stage_map_klass(file_path=v) # psm = self._get_stage_map_by_name(sm.name) # if psm: # self._stage_maps.remove(psm) # self._stage_maps.append(sm) def accept_point(self): self.points_programmer.accept_point() def set_stage_map(self, v): return self._set_stage_map(v) def single_axis_move(self, *args, **kw): return self.stage_controller.single_axis_move(*args, **kw) def linear_move(self, x, y, use_calibration=True, check_moving=False, abort_if_moving=False, **kw): if check_moving: if self.moving(): self.warning('MotionController already in motion') if abort_if_moving: self.warning('Move to {},{} aborted'.format(x, y)) return else: self.stop() self.debug('Motion stopped. moving to {},{}'.format(x, y)) pos = (x, y) if use_calibration: pos = self.get_calibrated_position(pos) f = lambda x: '{:0.5f},{:0.5f}'.format(*x) self.debug('%%%%%%%%%%%%%%%%% mapped {} to {}'.format(f((x, y)), f(pos))) self.stage_controller.linear_move(*pos, **kw) def move_to_hole(self, hole, **kw): if self.stage_map.check_valid_hole(hole, **kw): self._move(self._move_to_hole, hole, name='move_to_hole', **kw) def move_to_point(self, pt): self._move(self._move_to_point, pt, name='move_to_point') def move_polyline(self, line): self._move(self._move_to_line, line, name='move_to_line') def move_polygon(self, poly): self._move(self._move_polygon, poly, name='move_polygon') def drill_point(self, pt): self._move(self._drill_point, pt, name='drill_point') def set_x(self, value, **kw): return self.stage_controller.single_axis_move('x', value, **kw) def set_y(self, value, **kw): return self.stage_controller.single_axis_move('y', value, **kw) def set_z(self, value, **kw): return self.stage_controller.single_axis_move('z', value, **kw) def set_xy(self, x, y, **kw): hole = self._get_hole_by_position(x, y) if hole: self.move_to_hole(hole) # self._set_hole(hole.id) # self.move_to_hole(hole.id) # self._set_hole(hole.id) else: return self.linear_move(x, y, **kw) def get_hole(self, name): if self.stage_map: return self.stage_map.get_hole(name) def move_to_load_position(self): """ """ x, y, z = self.stage_controller.get_load_position() self.info('moving to load position, x={}, y={}, z={}'.format(x, y, z)) self.stage_controller.linear_move(x, y, grouped_move=False, block=False) self.stage_controller.set_z(z) self.stage_controller.block() def stop(self, ax_key=None, verbose=False): self._stop(ax_key, verbose) def relative_move(self, *args, **kw): self.stage_controller.relative_move(*args, **kw) def key_released(self): sc = self.stage_controller sc.add_consumable((sc.update_axes, tuple())) def moving(self, force_query=False, **kw): moving = False if force_query: moving = self.stage_controller.moving(**kw) elif self.stage_controller.timer is not None: moving = self.stage_controller.timer.isActive() return moving def get_brightness(self, **kw): return 0 def get_scores(self, **kw): return 0, 0 def define_home(self, **kw): self.stage_controller.define_home(**kw) def get_z(self): return self.stage_controller._z_position def get_uncalibrated_xy(self, pos=None): if pos is None: pos = (self.stage_controller.x, self.stage_controller.y) if self.stage_controller.xy_swapped(): pos = pos[1], pos[0] canvas = self.canvas ca = canvas.calibration_item if ca: pos = self.stage_map.map_to_uncalibration(pos, ca.center, ca.rotation, ca.scale) return pos def get_calibrated_xy(self): pos = (self.stage_controller.x, self.stage_controller.y) if self.stage_controller.xy_swapped(): pos = pos[1], pos[0] pos = self.canvas.map_offset_position(pos) return self.get_calibrated_position(pos) def get_calibrated_hole(self, x, y, tol): ca = self.canvas.calibration_item if ca is not None: smap = self.stage_map xx, yy = smap.map_to_uncalibration((x, y), ca.center, ca.rotation) return next((hole for hole in smap.sample_holes if abs(hole.x - xx) < tol and abs(hole.y - yy) < tol), None) def get_hole_xy(self, key): pos = self.stage_map.get_hole_pos(key) # map the position to calibrated space pos = self.get_calibrated_position(pos) return pos def finish_move_to_hole(self, user_entry): pass # private def _update_axes(self): if self.stage_controller: self.stage_controller.update_axes() def _home(self): """ """ if self._homing: return self._homing = True if self.home_option == 'Home All': msg = 'homing all motors' homed = ['x', 'y', 'z'] home_kwargs = dict(x=-25, y=-25, z=50) elif self.home_option == 'XY': msg = 'homing x,y' homed = ['x', 'y'] home_kwargs = dict(x=-25, y=-25) else: # define_home = msg = 'homing {}'.format(self.home_option) home_kwargs = {self.home_option: -25 if self.home_option in ['X', 'Y'] else 50} homed = [self.home_option.lower().strip()] self.info(msg) # if define_home: self.stage_controller.set_home_position(**home_kwargs) self.stage_controller.home(homed) # explicitly block # self.stage_controller.block() if 'z' in homed and 'z' in self.stage_controller.axes: # will be a positive limit error in z # self.stage_controller.read_error() time.sleep(1) self.info('setting z to nominal position. {} mm '.format(self._default_z)) self.stage_controller.single_axis_move('z', self._default_z, block=True) self.stage_controller._z_position = self._default_z if self.home_option in ['XY', 'Home All']: time.sleep(0.25) # the stage controller should think x and y are at -25,-25 self.stage_controller._x_position = -25 self.stage_controller._y_position = -25 self.info('moving to center') try: self.stage_controller.linear_move(0, 0, block=True, sign_correct=False) except TargetPositionError as e: self.warning_dialog('Move Failed. {}'.format(e)) self._homing = False def _get_hole_by_position(self, x, y): if self.stage_map: return self.stage_map._get_hole_by_position(x, y) def _get_hole_by_name(self, key): sm = self.stage_map return sm.get_hole(key) # =============================================================================== # special move # =============================================================================== def _stop(self, ax_key=None, verbose=False): self.stage_controller.stop(ax_key=ax_key, verbose=verbose) if self.parent.pattern_executor: self.parent.pattern_executor.stop() # def _move(self, func, pos, name=None, *args, **kw): # if pos is None: # return # # if self.move_thread and self.move_thread.isRunning(): # self.stage_controller.stop() # if name is None: # name = func.func_name # # self.move_thread = Thread(name='stage.{}'.format(name), # target=func, args=(pos,) + args, kwargs=kw) # self.move_thread.start() def _drill_point(self, pt): zend = pt.zend vel = pt.velocity # assume already at zstart st = time.time() self.info('start drilling. move to {}. velocity={}'.format(zend, vel)) self.set_z(zend, velocity=vel, block=True) et = time.time() - st self.info('drilling complete. drilled for {}s'.format(et)) def _move_polygon(self, pts, velocity=5, offset=50, use_outline=True, find_min=False, scan_size=None, use_move=True, use_convex_hull=True, motors=None, verbose=True, start_callback=None, end_callback=None): """ motors is a dict of motor_name:value pairs """ if pts is None: return if not isinstance(pts, list): velocity = pts.velocity use_convex_hull = pts.use_convex_hull if scan_size is None: scan_size = pts.scan_size use_outline = pts.use_outline offset = pts.offset find_min = pts.find_min pts = [dict(xy=(pi.x, pi.y), z=pi.z, ) for pi in pts.points] # set motors if motors is not None: for k, v in motors.values(): ''' motor will not set if it has been locked using set_motor_lock or remotely using SetMotorLock ''' if use_move: self.parent.set_motor(k, v, block=True) xy = [pi['xy'] for pi in pts] n = 1000 if scan_size is None: scan_size = n / 2 # convert points to um pts = array(xy) pts *= n pts = asarray(pts, dtype=int) ''' sort clockwise ensures consistent offset behavior a polygon gain have a inner or outer sense depending on order of vertices always use sort_clockwise prior to any polygon manipulation ''' pts = sort_clockwise(pts, pts) sc = self.stage_controller sc.set_program_mode('absolute') # do smooth transitions between points sc.set_smooth_transitions(True) if use_convex_hull: pts = convex_hull(pts) if use_outline: # calculate new polygon offset_pts = polygon_offset(pts, -offset) offset_pts = array(offset_pts, dtype=int) # polygon offset used 3D vectors. # trim to only x,y pts = offset_pts[:, (0, 1)] # trace perimeter if use_move: p0 = xy[0] self.linear_move(p0[0], p0[1], mode='absolute', block=True) sc.timer = sc.timer_factory() if start_callback is not None: start_callback() # buf=[] for pi in xy[1:]: self.linear_move(pi[0], pi[1], velocity=velocity, mode='absolute', set_stage=False) # finish at first point self.linear_move(p0[0], p0[1], velocity=velocity, mode='absolute', set_stage=False) sc.block() self.info('polygon perimeter trace complete') ''' have the oppurtunity here to turn off laser and change parameters i.e mask ''' if use_move: # calculate and step thru scan lines self._raster(pts, velocity, step=scan_size, scale=n, find_min=find_min, start_callback=start_callback, end_callback=end_callback, verbose=verbose) sc.set_program_mode('relative') if end_callback is not None: end_callback() self.info('polygon raster complete') def _raster(self, points, velocity, step=500, scale=1000, find_min=False, start_callback=None, end_callback=None, verbose=False): from pychron.core.geometry.scan_line import raster lines = raster(points, step=step, find_min=find_min) # initialize variables cnt = 0 direction = 1 flip = False lasing = False sc = self.stage_controller if verbose: self.info('start raster') # print lines # loop thru each scan line # for yi, xs in lines[::skip]: for yi, xs in lines: if direction == -1: xs = list(reversed(xs)) # convert odd numbers lists to even n = len(xs) if n % 2 != 0: xs = sorted(list(set(xs))) # traverse each x-intersection pair n = len(xs) for i in range(0, n, 2): if len(xs) <= 1: continue x1, x2, yy = xs[i] / scale, xs[i + 1] / scale, yi / scale if abs(x1 - x2) > 1e-10: if not lasing: if verbose: self.info('fast to {} {},{}'.format(cnt, x1, yy)) self.linear_move(x1, yy, mode='absolute', set_stage=False, block=True) if start_callback is not None: start_callback() lasing = True else: if verbose: self.info('slow to {} {},{}'.format(cnt, x1, yy)) sc.timer = sc.timer_factory() self.linear_move(x1, yy, mode='absolute', set_stage=False, velocity=velocity) if verbose: self.info('move to {}a {},{}'.format(cnt, x2, yy)) # if n > 2 and not i * 2 >= n: # line this scan line has more then 1 segment turn off laser at end of segment if i + 2 < n and not xs[i + 1] == xs[i + 2]: self.linear_move(x2, yy, velocity=velocity, mode='absolute', set_stage=False, block=True) self.info('wait for move complete') if end_callback is not None: end_callback() lasing = False else: self.linear_move(x2, yy, velocity=velocity, mode='absolute', set_stage=False, ) cnt += 1 flip = True else: flip = False if flip: direction *= -1 sc.block() if verbose: self.info('end raster') def _move_polyline(self, pts, start_callback=None, end_callback=None): if not isinstance(pts, list): segs = pts.velocity_segments segs = segs[:1] + segs pts = [dict(xy=(pi.x, pi.y), z=pi.z, velocity=vi) for vi, pi in zip(segs, pts.points)] sc = self.stage_controller self.linear_move(pts[0]['xy'][0], pts[0]['xy'][1], update_hole=False, use_calibration=False, block=True) sc.set_z(pts[0]['z'], block=True) cpos = dict() # set motors for motor in ('mask', 'attenuator'): if motor in pts[0]: self.parent.set_motor(motor, pts[0][motor]) cpos[motor] = pts[0][motor] sc.set_program_mode('absolute') sc.timer = sc.timer_factory() if start_callback: start_callback() npts = pts[1:] setmotors = dict() for i, di in enumerate(npts): xi, yi, zi, vi = di['xy'][0], di['xy'][1], di['z'], di['velocity'] sc.set_z(zi) block = False for motor in ('mask', 'attenuator'): # fix next step sets motor should block if i + 1 < len(npts): dii = npts[i + 1] if motor in dii and dii[motor] != cpos[motor]: m = self.parent.get_motor(motor) if not m.locked: block = True setmotors[motor] = dii[motor] self.linear_move(xi, yi, velocity=vi, block=block, mode='absolute', # use absolute mode because commands are queued set_stage=False) if block: if end_callback: end_callback() for k, v in setmotors.items(): self.parent.set_motor(k, v, block=True) if start_callback: start_callback() # wait until motion complete sc.block() if end_callback: end_callback() sc.set_program_mode('relative') # if start and smooth: # sc.execute_command_buffer() # sc.end_command_buffer() # def start_enqueued(self): # sc = self.stage_controller # sc.execute_command_buffer() # sc.end_command_buffer() def _move_to_point(self, pt): self.debug('move to point={}'.format(pt)) if isinstance(pt, str): pt = self.canvas.get_point(pt) self.debug('move to point canvas pt={}'.format(pt)) if pt is not None: pos = pt.x, pt.y self.info('Move to point {}: {:0.5f},{:0.5f},{:0.5f}'.format(pt.identifier, pt.x, pt.y, pt.z)) self.stage_controller.linear_move(block=True, *pos) if hasattr(pt, 'z'): self.stage_controller.set_z(pt.z, block=True) self.debug('Not setting motors for pt') # self.parent.set_motors_for_point(pt) self._move_to_point_hook() self.info('Move complete') self.update_axes() def _move_to_hole(self, key, correct_position=True, user_entry=False, autocenter_only=False): self.info('Move to hole {} type={}'.format(key, str(type(key)))) autocentered_position = False if not autocenter_only: self.temp_hole = key self.temp_position = self.stage_map.get_hole_pos(key) pos = self.stage_map.get_corrected_hole_pos(key) self.info('position {}'.format(pos)) if pos is not None: if abs(pos[0]) < 1e-6: pos = self.stage_map.get_hole_pos(key) # map the position to calibrated space pos = self.get_calibrated_position(pos, key=key) else: # check if this is an interpolated position # if so probably want to do an autocentering routine hole = self.stage_map.get_hole(key) if hole.interpolated: self.info('using an interpolated value') else: self.info('using previously calculated corrected position') autocentered_position = True try: self.stage_controller.linear_move(block=True, source='move_to_hole {}'.format(pos), raise_zero_displacement=True, *pos) except TargetPositionError as e: self.warning('(001) Move to {} failed'.format(pos)) self.parent.emergency_shutoff(str(e)) return except ZeroDisplacementException: correct_position = False try: self._move_to_hole_hook(key, correct_position, autocentered_position) except TargetPositionError as e: self.warning('(002) Move failed. {}'.format(e)) self.parent.emergency_shutoff(str(e)) return self.finish_move_to_hole(user_entry) self.info('Move complete') def _move_to_hole_hook(self, *args): pass def _move_to_point_hook(self): pass # =============================================================================== # Property Get / Set # =============================================================================== def _set_stage_map(self, v): if v in self.stage_map_names: for root, ext in ((self.root, '.txt'), (paths.user_points_dir, '.yaml')): p = os.path.join(root, add_extension(v, ext)) if os.path.isfile(p): self.info('setting stage map to {}'.format(v)) sm = self.stage_map_klass(file_path=p) self.canvas.set_map(sm) self.tray_calibration_manager.load_calibration(stage_map=v) self.points_programmer.load_stage_map(sm) return True else: self.warning('No stage map named "{}"'.format(v)) return False def _get_calibrate_stage_label(self): if self._calibration_state == 'set_center': r = 'Locate Center' elif self._calibration_state == 'set_right': r = 'Locate Right' else: r = 'Calibrate Stage' return r def _get_program_points_label(self): return 'Program Points' if not self.canvas.markup else 'End Program' def _validate_hole(self, v): nv = None try: if v.strip(): nv = int(v) except TypeError: self.warning('invalid hole {}'.format(v)) return nv # def _get_calibrated_position_entry(self): # return self._calibrated_position # # def _set_calibrated_position_entry(self, v): # self._calibrated_position = v # if XY_REGEX.match(v): # self._move_to_calibrated_position(v) # else: # self.move_to_hole(v) def _move_to_calibrated_position(self, pos): try: args = csv_to_floats(pos) except ValueError: self.warning('invalid calibrated position "{}". Could not convert to floats'.format(pos)) return if len(args) == 2: x, y = args self.linear_move(x, y, use_calibration=True, block=False) else: self.warning('invalid calibrated position. incorrect number of arguments "{}"'.format(args)) # def _set_hole(self, v): # if v is None: # return # # if self.canvas.calibrate: # self.warning_dialog('Cannot move while calibrating') # return # # if self.canvas.markup: # self.warning_dialog('Cannot move while adding/editing points') # return # # v = str(v) # # if self.move_thread is not None: # self.stage_controller.stop() # # # if self.move_thread is None: # # pos = self._stage_map.get_hole_pos(v) # if pos is not None: # self.visualizer.set_current_hole(v) # # self._hole = v # self.move_thread = Thread(name='stage.move_to_hole', # target=self._move_to_hole, args=(v,)) # self.move_thread.start() # else: # err = 'Invalid hole {}'.format(v) # self.warning(err) # return err # def _get_hole(self): # return self._hole def _set_point(self, v): if self.canvas.calibrate: self.warning_dialog('Cannot move while calibrating') return if self.canvas.markup: self.warning_dialog('Cannot move while adding/editing points') return if (self.move_thread is None or not self.move_thread.isRunning()) and v is not self._point: pos = self.canvas.get_item('point', int(v) - 1) if pos is not None: self._point = v self.move_thread = Thread(target=self._move_to_point, args=(pos,)) self.move_thread.start() else: err = 'Invalid point {}'.format(v) self.warning(err) return err def _get_point(self): return self._point # =============================================================================== # handlers # =============================================================================== def _stop_button_fired(self): self._stop() def _ejoystick_fired(self): self.joystick = not self.joystick if self.joystick: self.stage_controller.enable_joystick() self.joystick_label = 'Disable Joystick' self.joystick_timer = self.timer_factory(func=self._joystick_inprogress_update) else: if self.joystick_timer is not None: self.joystick_timer.Stop() self.stage_controller.disable_joystick() self.joystick_label = 'Enable Joystick' def _home_fired(self): """ """ t = Thread(name='stage.home', target=self._home) t.start() # need to store a reference to thread so it is not garbage collected self.move_thread = t # do_later(self._home) def _test_fired(self): # self.do_pattern('testpattern') self.do_pattern('pattern003') # =============================================================================== # factories # =============================================================================== def _motion_configure_factory(self, **kw): return MotionControllerManager(motion_controller=self.stage_controller, application=self.application, **kw) def _stage_controller_factory(self): if self.stage_controller_klass == 'Newport': from pychron.hardware.newport.newport_motion_controller import NewportMotionController factory = NewportMotionController elif self.stage_controller_klass == 'Aerotech': from pychron.hardware.aerotech.aerotech_motion_controller import AerotechMotionController factory = AerotechMotionController m = factory(name='{}controller'.format(self.name), configuration_name='stage_controller', configuration_dir_name=self.configuration_dir_name, parent=self) return m def _canvas_factory(self): """ """ w = 640 / 2.0 / 23.2 h = 0.75 * w l = LaserTrayCanvas(stage_manager=self, padding=[30, 5, 5, 30], map=self.stage_map, view_x_range=[-w, w], view_y_range=[-h, h]) return l # =============================================================================== # defaults # =============================================================================== def _motion_controller_manager_default(self): return self._motion_configure_factory() def _title_default(self): return '%s Stage Manager' % self.name[:-5].capitalize() def _points_programmer_default(self): pp = PointsProgrammer(canvas=self.canvas, stage_map_klass=self.stage_map_klass, stage_manager=self) pp.on_trait_change(self.move_to_point, 'point') pp.on_trait_change(self.move_polygon, 'polygon') pp.on_trait_change(self.move_polyline, 'line') return pp
def execute(self): self.canceled = False t = Thread(target=self._execute) t.start() self._t = t
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')
class StageManager(BaseStageManager): """ """ stage_controller_klass = String('Newport') stage_controller = Instance(MotionController) points_programmer = Instance(PointsProgrammer) motion_controller_manager = Instance(MotionControllerManager) # canvas = Instance(LaserTrayCanvas) simulation = DelegatesTo('stage_controller') # stage_map_klass = StageMap # _stage_map = Instance(StageMap) # stage_map = Property(depends_on='_stage_map') # stage_maps = Property(depends_on='_stage_maps') # _stage_maps = List # =========================================================================== # buttons # =========================================================================== home = Button('home') home_option = String('Home All') home_options = List manual_override_position_button = Event ejoystick = Event joystick_label = String('Enable Joystick') joystick = Bool(False) joystick_timer = None back_button = Button stop_button = Button('Stop') _default_z = 0 _cached_position = None _cached_current_hole = None _homing = False def __init__(self, *args, **kw): """ """ super(StageManager, self).__init__(*args, **kw) self.stage_controller = self._stage_controller_factory() def measure_grain_polygon(self): pass def stop_measure_grain_polygon(self): pass def shutdown(self): self._save_stage_map() def create_device(self, *args, **kw): dev = super(StageManager, self).create_device(*args, **kw) dev.parent = self return dev def goto_position(self, v, **kw): if XY_REGEX[0].match(v): self._move_to_calibrated_position(v) elif POINT_REGEX.match(v) or TRANSECT_REGEX[0].match(v): self.move_to_point(v) else: self.move_to_hole(v, user_entry=True, **kw) def get_current_position(self): if self.stage_controller: x = self.stage_controller.x y = self.stage_controller.y return x, y def get_current_hole(self): pos = self.get_current_position() if self.stage_map: if distance_threshold(pos, self._cached_position, self.stage_map.g_dimension / 4): h = self.get_calibrated_hole(*pos, tol=self.stage_map.g_dimension / 2.) if h is not None: self._cached_current_hole = h self._cached_position = pos return self._cached_current_hole def is_auto_correcting(self): return False def bind_preferences(self, pref_id): bind_preference(self.canvas, 'show_grids', '{}.show_grids'.format(pref_id)) self.canvas.change_grid_visibility() bind_preference(self.canvas, 'show_laser_position', '{}.show_laser_position'.format(pref_id)) bind_preference(self.canvas, 'show_desired_position', '{}.show_desired_position'.format(pref_id)) bind_preference(self.canvas, 'desired_position_color', '{}.desired_position_color'.format(pref_id), factory=ColorPreferenceBinding) # bind_preference(self.canvas, 'render_map', '{}.render_map'.format(pref_id)) # bind_preference(self.canvas, 'crosshairs_kind', '{}.crosshairs_kind'.format(pref_id)) for tag in ('', 'aux_'): for key in ('line_width', 'color', 'radius', 'offsetx', 'offsety'): key = '{}crosshairs_{}'.format(tag, key) factory = ColorPreferenceBinding if key.endswith('color') else None pref = '{}.{}'.format(pref_id, key) bind_preference(self.canvas, key, pref, factory=factory) # bind_preference(self.canvas, '{}crosshairs_line_width', '{}.{}crosshairs_line_width'.format(pref_id)) # bind_preference(self.canvas, 'crosshairs_color', # '{}.crosshairs_color'.format(pref_id), # factory=ColorPreferenceBinding) # bind_preference(self.canvas, 'crosshairs_radius', '{}.crosshairs_radius'.format(pref_id)) # bind_preference(self.canvas, 'crosshairs_offsetx', '{}.crosshairs_offsetx'.format(pref_id)) # bind_preference(self.canvas, 'crosshairs_offsety', '{}.crosshairs_offsety'.format(pref_id)) bind_preference(self.canvas, 'show_hole_label', '{}.show_hole_label'.format(pref_id)) bind_preference(self.canvas, 'hole_label_color', '{}.hole_label_color'.format(pref_id)) bind_preference(self.canvas, 'hole_label_size', '{}.hole_label_size'.format(pref_id)) self.canvas.handle_hole_label_size(self.canvas.hole_label_size) bind_preference(self.canvas, 'scaling', '{}.scaling'.format(pref_id)) bind_preference(self.canvas, 'show_bounds_rect', '{}.show_bounds_rect'.format(pref_id)) self.canvas.request_redraw() def load(self): super(StageManager, self).load() config = self.get_configuration() if config: self._default_z = self.config_get(config, 'Defaults', 'z', default=13, cast='float') self.points_programmer.load_stage_map(self.stage_map_name) # load the calibration file # should have calibration files for each stage map self.tray_calibration_manager.load_calibration() def finish_loading(self): self.initialize_stage() def initialize_stage(self): self.update_axes() axes = self.stage_controller.axes self.home_options = ['Home All', 'XY'] + sorted([axes[a].name.upper() for a in axes]) self.canvas.parent = self def save_calibration(self, name): self.tray_calibration_manager.save_calibration(name=name) # def add_stage_map(self, v): # sm = self.stage_map_klass(file_path=v) # psm = self._get_stage_map_by_name(sm.name) # if psm: # self._stage_maps.remove(psm) # self._stage_maps.append(sm) def accept_point(self): self.points_programmer.accept_point() def set_stage_map(self, v): return self._set_stage_map(v) def single_axis_move(self, *args, **kw): return self.stage_controller.single_axis_move(*args, **kw) def linear_move(self, x, y, use_calibration=True, check_moving=False, abort_if_moving=False, **kw): if check_moving: if self.moving(): self.warning('MotionController already in motion') if abort_if_moving: self.warning('Move to {},{} aborted'.format(x, y)) return else: self.stop() self.debug('Motion stopped. moving to {},{}'.format(x, y)) pos = (x, y) if use_calibration: pos = self.get_calibrated_position(pos) f = lambda x: '{:0.5f},{:0.5f}'.format(*x) self.debug('%%%%%%%%%%%%%%%%% mapped {} to {}'.format(f((x, y)), f(pos))) self.stage_controller.linear_move(*pos, **kw) def move_to_hole(self, hole, **kw): if self.stage_map.check_valid_hole(hole, **kw): self._move(self._move_to_hole, hole, name='move_to_hole', **kw) def move_to_point(self, pt): self._move(self._move_to_point, pt, name='move_to_point') def move_polyline(self, line): self._move(self._move_to_line, line, name='move_to_line') def move_polygon(self, poly): self._move(self._move_polygon, poly, name='move_polygon') def drill_point(self, pt): self._move(self._drill_point, pt, name='drill_point') def set_x(self, value, **kw): return self.stage_controller.single_axis_move('x', value, **kw) def set_y(self, value, **kw): return self.stage_controller.single_axis_move('y', value, **kw) def set_z(self, value, **kw): return self.stage_controller.single_axis_move('z', value, **kw) def set_xy(self, x, y, **kw): hole = self._get_hole_by_position(x, y) if hole: self.move_to_hole(hole) # self._set_hole(hole.id) # self.move_to_hole(hole.id) # self._set_hole(hole.id) else: return self.linear_move(x, y, **kw) def get_hole(self, name): if self.stage_map: return self.stage_map.get_hole(name) def move_to_load_position(self): """ """ x, y, z = self.stage_controller.get_load_position() self.info('moving to load position, x={}, y={}, z={}'.format(x, y, z)) self.stage_controller.linear_move(x, y, grouped_move=False, block=False) self.stage_controller.set_z(z) self.stage_controller.block() def stop(self, ax_key=None, verbose=False): self._stop(ax_key, verbose) def relative_move(self, *args, **kw): self.stage_controller.relative_move(*args, **kw) def key_released(self): sc = self.stage_controller sc.add_consumable((sc.update_axes, tuple())) def moving(self, force_query=False, **kw): moving = False if force_query: moving = self.stage_controller.moving(**kw) elif self.stage_controller.timer is not None: moving = self.stage_controller.timer.isActive() return moving def get_brightness(self, **kw): return 0 def get_scores(self, **kw): return 0, 0 def define_home(self, **kw): self.stage_controller.define_home(**kw) def get_z(self): return self.stage_controller._z_position def get_uncalibrated_xy(self, pos=None): if pos is None: pos = (self.stage_controller.x, self.stage_controller.y) if self.stage_controller.xy_swapped(): pos = pos[1], pos[0] canvas = self.canvas ca = canvas.calibration_item if ca: pos = self.stage_map.map_to_uncalibration(pos, ca.center, ca.rotation, ca.scale) return pos def get_calibrated_xy(self): pos = (self.stage_controller.x, self.stage_controller.y) if self.stage_controller.xy_swapped(): pos = pos[1], pos[0] pos = self.canvas.map_offset_position(pos) return self.get_calibrated_position(pos) def get_calibrated_hole(self, x, y, tol): ca = self.canvas.calibration_item if ca is not None: smap = self.stage_map xx, yy = smap.map_to_uncalibration((x, y), ca.center, ca.rotation) return next((hole for hole in smap.sample_holes if abs(hole.x - xx) < tol and abs(hole.y - yy) < tol), None) def get_hole_xy(self, key): pos = self.stage_map.get_hole_pos(key) # map the position to calibrated space pos = self.get_calibrated_position(pos) return pos def finish_move_to_hole(self, user_entry): pass # private def _update_axes(self): if self.stage_controller: self.stage_controller.update_axes() def _home(self): """ """ if self._homing: return self._homing = True if self.home_option == 'Home All': msg = 'homing all motors' homed = ['x', 'y', 'z'] home_kwargs = dict(x=-25, y=-25, z=50) elif self.home_option == 'XY': msg = 'homing x,y' homed = ['x', 'y'] home_kwargs = dict(x=-25, y=-25) else: # define_home = msg = 'homing {}'.format(self.home_option) home_kwargs = {self.home_option: -25 if self.home_option in ['X', 'Y'] else 50} homed = [self.home_option.lower().strip()] self.info(msg) # if define_home: self.stage_controller.set_home_position(**home_kwargs) self.stage_controller.home(homed) # explicitly block # self.stage_controller.block() if 'z' in homed and 'z' in self.stage_controller.axes: # will be a positive limit error in z # self.stage_controller.read_error() time.sleep(1) self.info('setting z to nominal position. {} mm '.format(self._default_z)) self.stage_controller.single_axis_move('z', self._default_z, block=True) self.stage_controller._z_position = self._default_z if self.home_option in ['XY', 'Home All']: time.sleep(0.25) # the stage controller should think x and y are at -25,-25 self.stage_controller._x_position = -25 self.stage_controller._y_position = -25 self.info('moving to center') try: self.stage_controller.linear_move(0, 0, block=True, sign_correct=False) except TargetPositionError as e: self.warning_dialog('Move Failed. {}'.format(e)) self._homing = False def _get_hole_by_position(self, x, y): if self.stage_map: return self.stage_map._get_hole_by_position(x, y) def _get_hole_by_name(self, key): sm = self.stage_map return sm.get_hole(key) # =============================================================================== # special move # =============================================================================== def _stop(self, ax_key=None, verbose=False): self.stage_controller.stop(ax_key=ax_key, verbose=verbose) if self.parent.pattern_executor: self.parent.pattern_executor.stop() # def _move(self, func, pos, name=None, *args, **kw): # if pos is None: # return # # if self.move_thread and self.move_thread.isRunning(): # self.stage_controller.stop() # if name is None: # name = func.func_name # # self.move_thread = Thread(name='stage.{}'.format(name), # target=func, args=(pos,) + args, kwargs=kw) # self.move_thread.start() def _drill_point(self, pt): zend = pt.zend vel = pt.velocity # assume already at zstart st = time.time() self.info('start drilling. move to {}. velocity={}'.format(zend, vel)) self.set_z(zend, velocity=vel, block=True) et = time.time() - st self.info('drilling complete. drilled for {}s'.format(et)) def _move_polygon(self, pts, velocity=5, offset=50, use_outline=True, find_min=False, scan_size=None, use_move=True, use_convex_hull=True, motors=None, verbose=True, start_callback=None, end_callback=None): """ motors is a dict of motor_name:value pairs """ if pts is None: return if not isinstance(pts, list): velocity = pts.velocity use_convex_hull = pts.use_convex_hull if scan_size is None: scan_size = pts.scan_size use_outline = pts.use_outline offset = pts.offset find_min = pts.find_min pts = [dict(xy=(pi.x, pi.y), z=pi.z, ) for pi in pts.points] # set motors if motors is not None: for k, v in motors.values(): ''' motor will not set if it has been locked using set_motor_lock or remotely using SetMotorLock ''' if use_move: self.parent.set_motor(k, v, block=True) xy = [pi['xy'] for pi in pts] n = 1000 if scan_size is None: scan_size = n / 2 # convert points to um pts = array(xy) pts *= n pts = asarray(pts, dtype=int) ''' sort clockwise ensures consistent offset behavior a polygon gain have a inner or outer sense depending on order of vertices always use sort_clockwise prior to any polygon manipulation ''' pts = sort_clockwise(pts, pts) sc = self.stage_controller sc.set_program_mode('absolute') # do smooth transitions between points sc.set_smooth_transitions(True) if use_convex_hull: pts = convex_hull(pts) if use_outline: # calculate new polygon offset_pts = polygon_offset(pts, -offset) offset_pts = array(offset_pts, dtype=int) # polygon offset used 3D vectors. # trim to only x,y pts = offset_pts[:, (0, 1)] # trace perimeter if use_move: p0 = xy[0] self.linear_move(p0[0], p0[1], mode='absolute', block=True) sc.timer = sc.timer_factory() if start_callback is not None: start_callback() # buf=[] for pi in xy[1:]: self.linear_move(pi[0], pi[1], velocity=velocity, mode='absolute', set_stage=False) # finish at first point self.linear_move(p0[0], p0[1], velocity=velocity, mode='absolute', set_stage=False) sc.block() self.info('polygon perimeter trace complete') ''' have the oppurtunity here to turn off laser and change parameters i.e mask ''' if use_move: # calculate and step thru scan lines self._raster(pts, velocity, step=scan_size, scale=n, find_min=find_min, start_callback=start_callback, end_callback=end_callback, verbose=verbose) sc.set_program_mode('relative') if end_callback is not None: end_callback() self.info('polygon raster complete') def _raster(self, points, velocity, step=500, scale=1000, find_min=False, start_callback=None, end_callback=None, verbose=False): from pychron.core.geometry.scan_line import raster lines = raster(points, step=step, find_min=find_min) # initialize variables cnt = 0 direction = 1 flip = False lasing = False sc = self.stage_controller if verbose: self.info('start raster') # print lines # loop thru each scan line # for yi, xs in lines[::skip]: for yi, xs in lines: if direction == -1: xs = list(reversed(xs)) # convert odd numbers lists to even n = len(xs) if n % 2 != 0: xs = sorted(list(set(xs))) # traverse each x-intersection pair n = len(xs) for i in range(0, n, 2): if len(xs) <= 1: continue x1, x2, yy = xs[i] / scale, xs[i + 1] / scale, yi / scale if abs(x1 - x2) > 1e-10: if not lasing: if verbose: self.info('fast to {} {},{}'.format(cnt, x1, yy)) self.linear_move(x1, yy, mode='absolute', set_stage=False, block=True) if start_callback is not None: start_callback() lasing = True else: if verbose: self.info('slow to {} {},{}'.format(cnt, x1, yy)) sc.timer = sc.timer_factory() self.linear_move(x1, yy, mode='absolute', set_stage=False, velocity=velocity) if verbose: self.info('move to {}a {},{}'.format(cnt, x2, yy)) # if n > 2 and not i * 2 >= n: # line this scan line has more then 1 segment turn off laser at end of segment if i + 2 < n and not xs[i + 1] == xs[i + 2]: self.linear_move(x2, yy, velocity=velocity, mode='absolute', set_stage=False, block=True) self.info('wait for move complete') if end_callback is not None: end_callback() lasing = False else: self.linear_move(x2, yy, velocity=velocity, mode='absolute', set_stage=False, ) cnt += 1 flip = True else: flip = False if flip: direction *= -1 sc.block() if verbose: self.info('end raster') def _move_polyline(self, pts, start_callback=None, end_callback=None): if not isinstance(pts, list): segs = pts.velocity_segments segs = segs[:1] + segs pts = [dict(xy=(pi.x, pi.y), z=pi.z, velocity=vi) for vi, pi in zip(segs, pts.points)] sc = self.stage_controller self.linear_move(pts[0]['xy'][0], pts[0]['xy'][1], update_hole=False, use_calibration=False, block=True) sc.set_z(pts[0]['z'], block=True) cpos = dict() # set motors for motor in ('mask', 'attenuator'): if motor in pts[0]: self.parent.set_motor(motor, pts[0][motor]) cpos[motor] = pts[0][motor] sc.set_program_mode('absolute') sc.timer = sc.timer_factory() if start_callback: start_callback() npts = pts[1:] setmotors = dict() for i, di in enumerate(npts): xi, yi, zi, vi = di['xy'][0], di['xy'][1], di['z'], di['velocity'] sc.set_z(zi) block = False for motor in ('mask', 'attenuator'): # fix next step sets motor should block if i + 1 < len(npts): dii = npts[i + 1] if motor in dii and dii[motor] != cpos[motor]: m = self.parent.get_motor(motor) if not m.locked: block = True setmotors[motor] = dii[motor] self.linear_move(xi, yi, velocity=vi, block=block, mode='absolute', # use absolute mode because commands are queued set_stage=False) if block: if end_callback: end_callback() for k, v in setmotors.items(): self.parent.set_motor(k, v, block=True) if start_callback: start_callback() # wait until motion complete sc.block() if end_callback: end_callback() sc.set_program_mode('relative') # if start and smooth: # sc.execute_command_buffer() # sc.end_command_buffer() # def start_enqueued(self): # sc = self.stage_controller # sc.execute_command_buffer() # sc.end_command_buffer() def _move_to_point(self, pt): self.debug('move to point={}'.format(pt)) if isinstance(pt, str): pt = self.canvas.get_point(pt) self.debug('move to point canvas pt={}'.format(pt)) if pt is not None: pos = pt.x, pt.y self.info('Move to point {}: {:0.5f},{:0.5f},{:0.5f}'.format(pt.identifier, pt.x, pt.y, pt.z)) self.stage_controller.linear_move(block=True, *pos) if hasattr(pt, 'z'): self.stage_controller.set_z(pt.z, block=True) self.debug('Not setting motors for pt') # self.parent.set_motors_for_point(pt) self._move_to_point_hook() self.info('Move complete') self.update_axes() def _move_to_hole(self, key, correct_position=True, user_entry=False, autocenter_only=False): self.info('Move to hole {} type={}'.format(key, str(type(key)))) autocentered_position = False if not autocenter_only: self.temp_hole = key self.temp_position = self.stage_map.get_hole_pos(key) pos = self.stage_map.get_corrected_hole_pos(key) self.info('position {}'.format(pos)) if pos is not None: if abs(pos[0]) < 1e-6: pos = self.stage_map.get_hole_pos(key) # map the position to calibrated space pos = self.get_calibrated_position(pos, key=key) else: # check if this is an interpolated position # if so probably want to do an autocentering routine hole = self.stage_map.get_hole(key) if hole.interpolated: self.info('using an interpolated value') else: self.info('using previously calculated corrected position') autocentered_position = True try: self.stage_controller.linear_move(block=True, source='move_to_hole {}'.format(pos), raise_zero_displacement=True, *pos) except TargetPositionError as e: self.warning('(001) Move to {} failed'.format(pos)) self.parent.emergency_shutoff(str(e)) return except ZeroDisplacementException: correct_position = False try: self._move_to_hole_hook(key, correct_position, autocentered_position) except TargetPositionError as e: self.warning('(002) Move failed. {}'.format(e)) self.parent.emergency_shutoff(str(e)) return self.finish_move_to_hole(user_entry) self.info('Move complete') def _move_to_hole_hook(self, *args): pass def _move_to_point_hook(self): pass # =============================================================================== # Property Get / Set # =============================================================================== def _set_stage_map(self, v): if v in self.stage_map_names: for root, ext in ((self.root, '.txt'), (paths.user_points_dir, '.yaml')): p = os.path.join(root, add_extension(v, ext)) if os.path.isfile(p): self.info('setting stage map to {}'.format(v)) sm = self.stage_map_klass(file_path=p) self.canvas.set_map(sm) self.tray_calibration_manager.load_calibration(stage_map=v) self.points_programmer.load_stage_map(sm) return True else: self.warning('No stage map named "{}"'.format(v)) return False def _get_calibrate_stage_label(self): if self._calibration_state == 'set_center': r = 'Locate Center' elif self._calibration_state == 'set_right': r = 'Locate Right' else: r = 'Calibrate Stage' return r def _get_program_points_label(self): return 'Program Points' if not self.canvas.markup else 'End Program' def _validate_hole(self, v): nv = None try: if v.strip(): nv = int(v) except TypeError: self.warning('invalid hole {}'.format(v)) return nv # def _get_calibrated_position_entry(self): # return self._calibrated_position # # def _set_calibrated_position_entry(self, v): # self._calibrated_position = v # if XY_REGEX.match(v): # self._move_to_calibrated_position(v) # else: # self.move_to_hole(v) def _move_to_calibrated_position(self, pos): try: args = csv_to_floats(pos) except ValueError: self.warning('invalid calibrated position "{}". Could not convert to floats'.format(pos)) return if len(args) == 2: x, y = args self.linear_move(x, y, use_calibration=True, block=False) else: self.warning('invalid calibrated position. incorrect number of arguments "{}"'.format(args)) def _set_point(self, v): if self.canvas.calibrate: self.warning_dialog('Cannot move while calibrating') return if self.canvas.markup: self.warning_dialog('Cannot move while adding/editing points') return if (self.move_thread is None or not self.move_thread.isRunning()) and v is not self._point: pos = self.canvas.get_item('point', int(v) - 1) if pos is not None: self._point = v self.move_thread = Thread(target=self._move_to_point, args=(pos,)) self.move_thread.start() else: err = 'Invalid point {}'.format(v) self.warning(err) return err def _get_point(self): return self._point # =============================================================================== # handlers # =============================================================================== def _manual_override_position_button_fired(self): sm = self.stage_map pos = self.calibrated_position_entry hole = self.stage_map.get_hole(pos) if hole is not None: x, y = self.stage_controller.x, self.stage_controller.y sm.set_hole_correction(pos, x, y) sm.dump_correction_file() self.info('updated {} correction file. Saved {}: {},{}'.format(sm.name, pos, x, y)) def _stop_button_fired(self): self._stop() def _ejoystick_fired(self): self.joystick = not self.joystick if self.joystick: self.stage_controller.enable_joystick() self.joystick_label = 'Disable Joystick' self.joystick_timer = self.timer_factory(func=self._joystick_inprogress_update) else: if self.joystick_timer is not None: self.joystick_timer.Stop() self.stage_controller.disable_joystick() self.joystick_label = 'Enable Joystick' def _home_fired(self): """ """ t = Thread(name='stage.home', target=self._home) t.start() # need to store a reference to thread so it is not garbage collected self.move_thread = t # do_later(self._home) def _test_fired(self): # self.do_pattern('testpattern') self.do_pattern('pattern003') # =============================================================================== # factories # =============================================================================== def _motion_configure_factory(self, **kw): return MotionControllerManager(motion_controller=self.stage_controller, application=self.application, **kw) def _stage_controller_factory(self): if self.stage_controller_klass == 'Newport': from pychron.hardware.newport.newport_motion_controller import NewportMotionController factory = NewportMotionController elif self.stage_controller_klass == 'Aerotech': from pychron.hardware.aerotech.aerotech_motion_controller import AerotechMotionController factory = AerotechMotionController m = factory(name='{}controller'.format(self.name), configuration_name='stage_controller', configuration_dir_name=self.configuration_dir_name, parent=self) return m def _canvas_factory(self): """ """ w = 640 / 2.0 / 23.2 h = 0.75 * w l = LaserTrayCanvas(stage_manager=self, padding=[30, 5, 5, 30], map=self.stage_map, view_x_range=[-w, w], view_y_range=[-h, h]) return l # =============================================================================== # defaults # =============================================================================== def _motion_controller_manager_default(self): return self._motion_configure_factory() def _title_default(self): return '%s Stage Manager' % self.name[:-5].capitalize() def _points_programmer_default(self): pp = PointsProgrammer(canvas=self.canvas, stage_map_klass=self.stage_map_klass, stage_manager=self) pp.on_trait_change(self.move_to_point, 'point') pp.on_trait_change(self.move_polygon, 'polygon') pp.on_trait_change(self.move_polyline, 'line') return pp
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 BaseStageManager(Manager): keyboard_focus = Event canvas_editor_klass = LaserComponentEditor tray_calibration_manager = Instance(TrayCalibrationManager) stage_map_klass = LaserStageMap stage_map_name = Str stage_map_names = List stage_map = Instance(BaseStageMap) canvas = Instance(MapCanvas) # root = Str(paths.map_dir) calibrated_position_entry = String(enter_set=True, auto_set=False) move_thread = None temp_position = None temp_hole = None root = Str # use_modified = Bool(True) # set true to use modified affine calculation def motor_event_hook(self, name, value, *args, **kw): pass def goto_position(self, pos): raise NotImplementedError def refresh_stage_map_names(self): sms = get_stage_map_names(root=self.root) self.stage_map_names = sms def load(self): self.refresh_stage_map_names() psm = self._load_previous_stage_map() sms = self.stage_map_names sm = None if psm in sms: sm = psm elif sms: sm = sms[0] if sm: self.stage_map_name = '' self.stage_map_name = sm def kill(self): r = super(BaseStageManager, self).kill() self._save_stage_map() return r @property def stage_map_path(self): return os.path.join(paths.hidden_dir, '{}.stage_map'.format(self.id)) def canvas_editor_factory(self): return self.canvas_editor_klass(keyboard_focus='keyboard_focus') def move_to_hole(self, hole, **kw): self._move(self._move_to_hole, hole, name='move_to_hole', **kw) def get_calibrated_position(self, pos, key=None): smap = self.stage_map # use a affine transform object to map canvas = self.canvas ca = canvas.calibration_item # check if a calibration applies to this hole hole_calibration = get_hole_calibration(smap.name, key) if hole_calibration: self.debug('Using hole calibration') ca = hole_calibration if ca: rot = ca.rotation cpos = ca.center scale = ca.scale self.debug('Calibration parameters: ' 'rot={:0.3f}, cpos={} scale={:0.3f}'.format(rot, cpos, scale)) pos = smap.map_to_calibration(pos, cpos, rot, scale=scale) return pos def update_axes(self): """ """ self.info('querying axis positions') self._update_axes() # private def _update_axes(self): pass def _move_to_hole(self, key, correct_position=True, **kw): pass def _stop(self): pass def _move(self, func, pos, name=None, *args, **kw): if pos is None: return if self.move_thread and self.move_thread.isRunning(): self._stop() if name is None: name = func.__name__ self.move_thread = Thread(name='stage.{}'.format(name), target=func, args=(pos,) + args, kwargs=kw) self.move_thread.start() def _canvas_factory(self): raise NotImplementedError def _stage_map_changed_hook(self): pass # handlers def _calibrated_position_entry_changed(self, new): self.debug('User entered calibrated position {}'.format(new)) self.goto_position(new) def _stage_map_name_changed(self, new): if new: self.debug('setting stage map to {}'.format(new)) root = self.root path = os.path.join(root, add_extension(new, '.txt')) sm = self.stage_map_klass(file_path=path) self.tray_calibration_manager.load_calibration(stage_map=new) self.canvas.set_map(sm) self.canvas.request_redraw() self.stage_map = sm self._stage_map_changed_hook() # defaults def _root_default(self): return paths.map_dir def _tray_calibration_manager_default(self): t = TrayCalibrationManager(parent=self, canvas=self.canvas) return t def _canvas_default(self): return self._canvas_factory() def _save_stage_map(self): p = self.stage_map_path self.info('saving stage_map {} to {}'.format(self.stage_map_name, p)) with open(p, 'wb') as f: pickle.dump(self.stage_map_name, f) def _load_previous_stage_map(self): p = self.stage_map_path if os.path.isfile(p): self.info('loading previous stage map from {}'.format(p)) with open(p, 'rb') as f: try: sm = pickle.load(f) if not sm.endswith('.center'): return sm except (pickle.PickleError, ValueError): pass
class Scanner(Loggable): """ Scanner is a base class for displaying a scan of device data ScanableDevices has this ability built in but more complicated scans are best done here. ScanableDevice scan is best used from continuous long term recording of a single or multiple values """ _graph = Instance(StreamStackedGraph) manager = Instance(ILaserManager) data_manager = Instance(CSVDataManager, ()) ''' list of callables. should return a signal value for graphing ''' functions = List static_values = List scan_period = Int # ms stop_event = Event setpoint = Float(enter_set=True, auto_set=False) control_path = File _warned = False _scanning = Bool _directory = None _base_frame_name = None def new_static_value(self, name, *args, **kw): self.static_values.append((name, None)) if args: self.set_static_value(name, *args, **kw) def set_static_value(self, name_or_idx, value, plotid=None): """ if the plotid is not None add a horizontal guide at value """ if isinstance(name_or_idx, str): name_or_idx = next((i for i, (e, a) in enumerate(self.static_values)), None) if name_or_idx is not None: name = self.static_values[name_or_idx][0] self.static_values[name_or_idx] = (name, value) if plotid is not None: self._graph.add_horizontal_rule(value, plotid=plotid) def setup(self, directory=None, base_frame_name=None): self._directory = directory self._base_frame_name = base_frame_name def new_function(self, function, name=None): if name is None: name = function.func_name # g = self.graph # func = self.functions # kw = dict(ytitle=name,) # n = len(func) # if n == 0: # kw['xtitle'] = 'Time' self.functions.append((function, name)) def make_graph(self): g = StreamStackedGraph(window_x=50, window_y=100) for i, (_, name) in enumerate(self.functions): kw = dict(ytitle=name, ) if i == 0: kw['xtitle'] = 'Time' g.new_plot( data_limit=3000, **kw) g.new_series(plotid=i) self._graph = g return g def stop(self): self._timer.Stop() self._scanning = False self.stop_event = True self.info('scan stopped') if self.manager: self._stop_hook() self.manager.disable_device() def _stop_hook(self): pass def execute(self): if self._scanning: self.stop() else: self.data_manager.new_frame(directory=self._directory, base_frame_name=self._base_frame_name) # write metadata if available self._write_metadata() # make header row header = ['t'] + \ self._make_func_names() + \ [n for n, _ in self.static_values] self.data_manager.write_to_frame(header) self._scanning = True if self.manager: if self.manager.enable_device(): self._scanning = True return self._scanning def do_scan(self): self._starttime = time.time() self._execute() def _execute(self): yd = self._read_control_path() if yd is None: sp = 1000 else: sp = yd['period'] # starts automatically self.debug('scan starting') self._timer = Timer(sp, self._scan) self.info('scan started') # yd = self._read_control_path() if yd is not None: # start a control thread self._control_thread = Thread(target=self._control, args=(yd,) ) self._control_thread.start() self.info('control started') # if self.manager: # if self.manager.enable_device(): # # # starts automatically # self.debug('scan starting') # self._timer = Timer(sp, self._scan) # # self.info('scan started') # self._scanning = True # # yd = self._read_control_path() # if yd is not None: # # start a control thread # self._control_thread = Thread(target=self._control, # args=(yd,) # ) # self._control_thread.start() # self.info('control started') # else: # self.warning('Could not enable device') # else: # self.warning('no manager available') def _control(self, ydict): self.start_control_hook(ydict) # if self.manager: # if self.manager.temperature_controller: # self.manager.temperature_controller.enable_tru_tune = True start_delay = ydict['start_delay'] end_delay = ydict['end_delay'] setpoints = ydict['setpoints'] print setpoints self.set_static_value('Setpoint', 0) time.sleep(start_delay) for args in setpoints: t=args[0] if self._scanning: self.setpoint = t self._set_power_hook(t) self.set_static_value('Setpoint', t, plotid=0) self._maintain_setpoint(t, *args[1:]) if self._scanning: self.setpoint = 0 self.set_static_value('Setpoint', 0) self._set_power_hook(0) # if self.manager: # self.manager.set_laser_temperature(0) time.sleep(end_delay) self.stop() self.end_control_hook(self._scanning) def _set_power_hook(self, t): if self.manager: self.manager.set_laser_temperature(t) def _maintain_setpoint(self, t, d, *args): self.info('setting setpoint to {} for {}s'.format(t, d)) st = time.time() while time.time() - st < d and self._scanning: time.sleep(1) def start_control_hook(self, *args, **kw): pass def end_control_hook(self, ok): pass def _make_func_names(self): return [name for _, name in self.functions] def _write_metadata(self): pass def _scan(self): functions = self.functions data = [] record = self._graph.record # record = self.graph.record x = time.time() - self._starttime for i, (func, _) in enumerate(functions): try: if hasattr(func, 'next'): func = func.next v = float(func()) data.append(v) # do_after no longer necessary with Qt record(v, plotid=i, x=x, do_after=None) except Exception, e: print 'exception', e sv = [] for _, v in self.static_values: if v is None: v = '' sv.append(v) data = [x] + data + sv self.data_manager.write_to_frame(data)
class VideoStageManager(StageManager): """ """ video = Instance(Video) camera = Instance(BaseCamera) canvas_editor_klass = VideoComponentEditor camera_zoom_coefficients = Property(String(enter_set=True, auto_set=False), depends_on='_camera_zoom_coefficients') _camera_zoom_coefficients = String use_auto_center_interpolation = Bool(False) configure_camera_device_button = Button autocenter_button = Button('AutoCenter') configure_autocenter_button = Button('Configure') autocenter_manager = Instance( 'pychron.mv.autocenter_manager.AutoCenterManager') autofocus_manager = Instance( 'pychron.mv.focus.autofocus_manager.AutoFocusManager') # zoom_calibration_manager = Instance( # 'pychron.mv.zoom.zoom_calibration.ZoomCalibrationManager') snapshot_button = Button('Snapshot') snapshot_mode = Enum('Single', '3 Burst', '10 Burst') auto_save_snapshot = Bool(True) record = Event record_label = Property(depends_on='is_recording') is_recording = Bool use_db = False use_video_archiver = Bool(True) video_archiver = Instance('pychron.core.helpers.archiver.Archiver') video_identifier = Str # use_video_server = Bool(False) # video_server_port = Int # video_server_quality = Int # video_server = Instance('pychron.image.video_server.VideoServer') use_media_storage = Bool(False) auto_upload = Bool(False) keep_local_copy = Bool(False) lumen_detector = Instance(LumenDetector) render_with_markup = Bool(False) burst_delay = Int(250) _auto_correcting = False stop_timer = Event pxpermm = Float(23) _measure_grain_t = None _measure_grain_evt = None grain_polygons = None # test_button = Button # _test_state = False # def _test_button_fired(self): # if self._test_state: # # self.stop_measure_grain_polygon() # # # # time.sleep(2) # # # # d = self.get_grain_polygon_blob() # # print d # self.parent.disable_laser() # else: # self.parent.luminosity_degas_test() # # self.start_measure_grain_polygon() # self._test_state = not self._test_state def motor_event_hook(self, name, value, *args, **kw): if name == 'zoom': self._update_zoom(value) def bind_preferences(self, pref_id): self.debug('binding preferences') super(VideoStageManager, self).bind_preferences(pref_id) if self.autocenter_manager: self.autocenter_manager.bind_preferences(pref_id) # bind_preference(self.autocenter_manager, 'use_autocenter', # '{}.use_autocenter'.format(pref_id)) bind_preference(self, 'render_with_markup', '{}.render_with_markup'.format(pref_id)) bind_preference(self, 'burst_delay', '{}.burst_delay'.format(pref_id)) bind_preference(self, 'auto_upload', '{}.auto_upload'.format(pref_id)) bind_preference(self, 'use_media_storage', '{}.use_media_storage'.format(pref_id)) bind_preference(self, 'keep_local_copy', '{}.keep_local_copy'.format(pref_id)) bind_preference(self, 'use_video_archiver', '{}.use_video_archiver'.format(pref_id)) bind_preference(self, 'video_identifier', '{}.video_identifier'.format(pref_id)) bind_preference(self, 'use_video_server', '{}.use_video_server'.format(pref_id)) bind_preference(self.video_archiver, 'archive_months', '{}.video_archive_months'.format(pref_id)) bind_preference(self.video_archiver, 'archive_days', '{}.video_archive_days'.format(pref_id)) bind_preference(self.video_archiver, 'archive_hours', '{}.video_archive_hours'.format(pref_id)) bind_preference(self.video_archiver, 'root', '{}.video_directory'.format(pref_id)) # bind_preference(self.video, 'output_mode', # '{}.video_output_mode'.format(pref_id)) # bind_preference(self.video, 'ffmpeg_path', # '{}.ffmpeg_path'.format(pref_id)) def get_grain_polygon(self): ld = self.lumen_detector l, m = ld.lum() return m.tostring() def get_grain_polygon_blob(self): # self.debug('Get grain polygons n={}'.format(len(self.grain_polygons))) try: t, md, p = next(self.grain_polygons) a = pack('ff', ((t, md), )) b = pack('HH', p) return encode_blob(a + b) except (StopIteration, TypeError) as e: self.debug('No more grain polygons. {}'.format(e)) def stop_measure_grain_polygon(self): self.debug('Stop measure polygons {}'.format(self._measure_grain_evt)) if self._measure_grain_evt: self._measure_grain_evt.set() return True def start_measure_grain_polygon(self): self._measure_grain_evt = evt = TEvent() def _measure_grain_polygon(): ld = self.lumen_detector dim = self.stage_map.g_dimension ld.pxpermm = self.pxpermm self.debug('Starting measure grain polygon') masks = [] display_image = self.autocenter_manager.display_image mask_dim = dim * 1.05 mask_dim_mm = mask_dim * self.pxpermm ld.grain_measuring = True while not evt.is_set(): src = self._get_preprocessed_src() if src is not None: targets = ld.find_targets( display_image, src, dim, mask=mask_dim, search={'start_offset_scalar': 1}) if targets: t = time.time() targets = [(t, mask_dim_mm, ti.poly_points.tolist()) for ti in targets] masks.extend(targets) sleep(0.1) ld.grain_measuring = False self.grain_polygons = (m for m in masks) self.debug('exiting measure grain') self._measure_grain_t = QThread(target=_measure_grain_polygon) self._measure_grain_t.start() return True def start_recording(self, path=None, use_dialog=False, basename='vm_recording', **kw): """ """ directory = None if os.path.sep in basename: args = os.path.split(basename) directory, basename = os.path.sep.join(args[:-1]), args[-1] if path is None: if use_dialog: path = self.save_file_dialog() else: vd = self.video_archiver.root self.debug('video archiver root {}'.format(vd)) if not vd: vd = paths.video_dir if directory: vd = os.path.join(vd, directory) if not os.path.isdir(vd): os.mkdir(vd) path = unique_path_from_manifest(vd, basename, extension='avi') kw['path'] = path kw['basename'] = basename self._start_recording(**kw) self.is_recording = True return path def stop_recording(self, user='******', delay=None): """ """ def close(): self.is_recording = False self.info('stop video recording') p = self.video.output_path if self.video.stop_recording(wait=True): if self.auto_upload: try: p = self._upload(p, inform=False) except BaseException as e: self.critical('Failed uploading {}. error={}'.format( p, e)) return p if self.video.is_recording(): if delay: t = Timer(delay, close) t.start() else: return close() @property def video_configuration_path(self): if self.configuration_dir_path: return os.path.join(self.configuration_dir_path, 'camera.yaml') def initialize_video(self): if self.video: identifier = 0 p = self.video_configuration_path if os.path.isfile(p): with open(p, 'r') as rfile: yd = yaml.load(rfile) vid = yd['Device'] identifier = vid.get('identifier', 0) self.video.open(identifier=identifier) self.video.load_configuration(p) self.lumen_detector.pixel_depth = self.video.pixel_depth def initialize_stage(self): super(VideoStageManager, self).initialize_stage() self.initialize_video() # s = self.stage_controller # if s.axes: # xa = s.axes['x'].drive_ratio # ya = s.axes['y'].drive_ratio # self._drive_xratio = xa # self._drive_yratio = ya self._update_zoom(0) def autocenter(self, *args, **kw): return self._autocenter(*args, **kw) def snapshot(self, path=None, name=None, auto=False, inform=True, return_blob=False, pic_format='.jpg', include_raw=True): """ path: abs path to use name: base name to use if auto saving in default dir auto: force auto save returns: path: local abs path upath: remote abs path """ if path is None: if self.auto_save_snapshot or auto: if name is None: name = 'snapshot' path = unique_path_from_manifest(paths.snapshot_dir, name, pic_format) elif name is not None: if not os.path.isdir(os.path.dirname(name)): path = unique_path_from_manifest(paths.snapshot_dir, name, pic_format) else: path = name else: path = self.save_file_dialog() if path: self.info('saving snapshot {}'.format(path)) # play camera shutter sound # play_sound('shutter') if include_raw: frame = self.video.get_cached_frame() head, _ = os.path.splitext(path) raw_path = '{}.tif'.format(head) pil_save(frame, raw_path) self._render_snapshot(path) if self.auto_upload: if include_raw: self._upload(raw_path) upath = self._upload(path, inform=inform) if upath is None: upath = '' if inform: if self.keep_local_copy: self.information_dialog( 'Snapshot saved: "{}".\nUploaded : "{}"'.format( path, upath)) else: self.information_dialog( 'Snapshot uploaded to "{}"'.format(upath)) else: upath = None if inform: self.information_dialog( 'Snapshot saved to "{}"'.format(path)) if return_blob: with open(path, 'rb') as rfile: im = rfile.read() return path, upath, im else: return path, upath def kill(self): """ """ super(VideoStageManager, self).kill() if self.camera: self.camera.save_calibration() self.stop_timer = True self.canvas.close_video() if self.video: self.video.close(force=True) # if self.use_video_server: # self.video_server.stop() # if self._stage_maps: # for s in self._stage_maps: # s.dump_correction_file() self.clean_video_archive() def clean_video_archive(self): if self.use_video_archiver: self.info('Cleaning video directory') self.video_archiver.clean(('manifest.yaml', )) def is_auto_correcting(self): return self._auto_correcting crop_width = 5 crop_height = 5 def get_scores(self, **kw): ld = self.lumen_detector src = self._get_preprocessed_src() return ld.get_scores(src, **kw) def find_lum_peak(self, min_distance, blur, **kw): ld = self.lumen_detector src = self._get_preprocessed_src() dim = self.stage_map.g_dimension mask_dim = dim * 1.05 # mask_dim_mm = mask_dim * self.pxpermm if src is not None and src.ndim >= 2: return ld.find_lum_peak(src, dim, mask_dim, blur=blur, min_distance=min_distance, **kw) def get_brightness(self, **kw): ld = self.lumen_detector src = self._get_preprocessed_src() dim = self.stage_map.g_dimension return ld.get_value(src, dim, **kw) # src = self.video.get_cached_frame() # csrc = copy(src) # src, v = ld.get_value(csrc, **kw) # return csrc, src, v def get_frame_size(self): cw = 2 * self.crop_width * self.pxpermm ch = 2 * self.crop_height * self.pxpermm return cw, ch def close_open_images(self): if self.autocenter_manager: self.autocenter_manager.close_open_images() def finish_move_to_hole(self, user_entry): self.debug('finish move to hole') # if user_entry and not self.keep_images_open: # self.close_open_images() def get_preprocessed_src(self): return self._get_preprocessed_src() # private def _get_preprocessed_src(self): ld = self.lumen_detector src = copy(self.video.get_cached_frame()) dim = self.stage_map.g_dimension ld.pxpermm = self.pxpermm offx, offy = self.canvas.get_screen_offset() cropdim = dim * 2.5 if src is not None: if len(src.shape): src = ld.crop(src, cropdim, cropdim, offx, offy, verbose=False) return src def _stage_map_changed_hook(self): self.lumen_detector.hole_radius = self.stage_map.g_dimension def _upload(self, src, inform=True): if not self.use_media_storage: msg = 'Use Media Storage not enabled in Laser preferences' if inform: self.warning_dialog(msg) else: self.warning(msg) else: srv = 'pychron.media_storage.manager.MediaStorageManager' msm = self.parent.application.get_service(srv) if msm is not None: d = os.path.split(os.path.dirname(src))[-1] dest = os.path.join(self.parent.name, d, os.path.basename(src)) msm.put(src, dest) if not self.keep_local_copy: self.debug('removing {}'.format(src)) if src.endswith('.avi'): head, ext = os.path.splitext(src) vd = '{}-images'.format(head) self.debug( 'removing video build directory {}'.format(vd)) shutil.rmtree(vd) os.remove(src) dest = '{}/{}'.format(msm.get_base_url(), dest) return dest else: msg = 'Media Storage Plugin not enabled' if inform: self.warning_dialog(msg) else: self.warning(msg) def _render_snapshot(self, path): from chaco.plot_graphics_context import PlotGraphicsContext c = self.canvas p = None was_visible = False if not self.render_with_markup: p = c.show_laser_position c.show_laser_position = False if self.points_programmer.is_visible: c.hide_all() was_visible = True gc = PlotGraphicsContext((int(c.outer_width), int(c.outer_height))) c.do_layout() gc.render_component(c) # gc.save(path) from pychron.core.helpers import save_gc save_gc.save(gc, path) if p is not None: c.show_laser_position = p if was_visible: c.show_all() def _start_recording(self, path, basename): self.info('start video recording {}'.format(path)) d = os.path.dirname(path) if not os.path.isdir(d): self.warning('invalid directory {}'.format(d)) self.warning('using default directory') path, _ = unique_path(paths.video_dir, basename, extension='avi') self.info('saving recording to path {}'.format(path)) # if self.use_db: # db = self.get_video_database() # db.connect() # # v = db.add_video_record(rid=basename) # db.add_path(v, path) # self.info('saving {} to database'.format(basename)) # db.commit() video = self.video crop_to_hole = True dim = self.stage_map.g_dimension cropdim = dim * 8 * self.pxpermm color = self.canvas.crosshairs_color.getRgb()[:3] r = int(self.canvas.get_crosshairs_radius() * self.pxpermm) # offx, offy = self.canvas.get_screen_offset() def renderer(p): # cw, ch = self.get_frame_size() frame = video.get_cached_frame() if frame is not None: if not len(frame.shape): return frame = copy(frame) # ch, cw, _ = frame.shape # ch, cw = int(ch), int(cw) if crop_to_hole: frame = video.crop(frame, 0, 0, cropdim, cropdim) if self.render_with_markup: # draw crosshairs if len(frame.shape) == 2: frame = gray2rgb(frame) ch, cw, _ = frame.shape ch, cw = int(ch), int(cw) y = ch // 2 x = cw // 2 cp = circle_perimeter(y, x, r, shape=(ch, cw)) frame[cp] = color frame[line(y, 0, y, x - r)] = color # left frame[line(y, x + r, y, int(cw) - 1)] = color # right frame[line(0, x, y - r, x)] = color # bottom frame[line(y + r, x, int(ch) - 1, x)] = color # top if frame is not None: pil_save(frame, p) self.video.start_recording(path, renderer) def _move_to_hole_hook(self, holenum, correct, autocentered_position): args = holenum, correct, autocentered_position self.debug('move to hole hook holenum={}, ' 'correct={}, autocentered_position={}'.format(*args)) if correct: ntries = 1 if autocentered_position else 3 self._auto_correcting = True try: self._autocenter(holenum=holenum, ntries=ntries, save=True) except BaseException as e: self.critical('Autocentering failed. {}'.format(e)) self._auto_correcting = False # def find_center(self): # ox, oy = self.canvas.get_screen_offset() # rpos, src = self.autocenter_manager.calculate_new_center( # self.stage_controller.x, # self.stage_controller.y, # ox, oy, # dim=self.stage_map.g_dimension, open_image=False) # # return rpos, src # def find_target(self): # if self.video: # ox, oy = self.canvas.get_screen_offset() # src = self.video.get_cached_frame() # # ch = cw = self.pxpermm * self.stage_map.g_dimension * 2.5 # src = self.video.crop(src, ox, oy, cw, ch) # return self.lumen_detector.find_target(src) # # def find_best_target(self): # if self.video: # src = self.video.get_cached_frame() # src = self.autocenter_manager.crop(src) # return self.lumen_detector.find_best_target(src) def _autocenter(self, holenum=None, ntries=3, save=False, inform=False): self.debug('do autocenter') rpos = None interp = False sm = self.stage_map st = time.time() if self.autocenter_manager.use_autocenter: time.sleep(0.1) dim = sm.g_dimension shape = sm.g_shape if holenum is not None: hole = sm.get_hole(holenum) if hole is not None: dim = hole.dimension shape = hole.shape ox, oy = self.canvas.get_screen_offset() for ti in range(max(1, ntries)): # use machine vision to calculate positioning error rpos = self.autocenter_manager.calculate_new_center( self.stage_controller.x, self.stage_controller.y, ox, oy, dim=dim, shape=shape) if rpos is not None: self.linear_move(*rpos, block=True, source='autocenter', use_calibration=False, update_hole=False, velocity_scalar=0.1) time.sleep(0.1) else: self.snapshot(auto=True, name='pos_err_{}_{}'.format(holenum, ti), inform=inform) break # if use_interpolation and rpos is None: # self.info('trying to get interpolated position') # rpos = sm.get_interpolated_position(holenum) # if rpos: # s = '{:0.3f},{:0.3f}' # interp = True # else: # s = 'None' # self.info('interpolated position= {}'.format(s)) if rpos: corrected = True # add an adjustment value to the stage map if save and holenum is not None: sm.set_hole_correction(holenum, *rpos) sm.dump_correction_file() # f = 'interpolation' if interp else 'correction' else: # f = 'uncorrected' corrected = False if holenum is not None: hole = sm.get_hole(holenum) if hole: rpos = hole.nominal_position self.debug('Autocenter duration ={}'.format(time.time() - st)) return rpos, corrected, interp # =============================================================================== # views # =============================================================================== # =============================================================================== # view groups # =============================================================================== # =============================================================================== # handlers # =============================================================================== def _configure_camera_device_button_fired(self): if self.video: self.video.load_configuration(self.video_configuration_path) if hasattr(self.video.cap, 'reload_configuration'): self.video.cap.reload_configuration( self.video_configuration_path) self.lumen_detector.pixel_depth = self.video.pixel_depth def _update_zoom(self, v): if self.camera: self._update_xy_limits() @on_trait_change('parent:motor_event') def _update_motor(self, new): print('motor event', new, self.canvas, self.canvas.camera) # s = self.stage_controller if self.camera: if not isinstance(new, (int, float)): args, _ = new name, v = args[:2] else: name = 'zoom' v = new if name == 'zoom': self._update_xy_limits() # pxpermm = self.canvas.camera.set_limits_by_zoom(v, s.x, s.y) # self.pxpermm = pxpermm elif name == 'beam': self.lumen_detector.beam_radius = v / 2.0 def _pxpermm_changed(self, new): if self.autocenter_manager: self.autocenter_manager.pxpermm = new self.lumen_detector.pxpermm = new # self.lumen_detector.mask_radius = new*self.stage_map.g_dimension def _autocenter_button_fired(self): self.goto_position(self.calibrated_position_entry, autocenter_only=True) # def _configure_autocenter_button_fired(self): # info = self.autocenter_manager.edit_traits(view='configure_view', # kind='livemodal') # if info.result: # self.autocenter_manager.dump_detector() def _snapshot_button_fired(self): n = 1 if self.snapshot_mode == '3 Burst': n = 3 elif self.snapshot_mode == '10 Burst': n = 10 bd = self.burst_delay * 0.001 delay = n > 1 for i in range(n): st = time.time() self.snapshot(inform=False) if delay: time.sleep(max(0, bd - time.time() + st)) def _record_fired(self): # time.sleep(4) # self.stop_recording() if self.is_recording: self.stop_recording() else: self.start_recording() def _use_video_server_changed(self): if self.use_video_server: self.video_server.start() else: self.video_server.stop() def _get_camera_zoom_coefficients(self): return self.camera.zoom_coefficients def _set_camera_zoom_coefficients(self, v): self.camera.zoom_coefficients = ','.join(map(str, v)) self._update_xy_limits() def _validate_camera_zoom_coefficients(self, v): try: return list(map(float, v.split(','))) except ValueError: pass def _update_xy_limits(self): z = 0 if self.parent is not None: zoom = self.parent.get_motor('zoom') if zoom is not None: z = zoom.data_position x = self.stage_controller.get_current_position('x') y = self.stage_controller.get_current_position('y') if self.camera: pxpermm = self.camera.set_limits_by_zoom(z, x, y, self.canvas) self.pxpermm = pxpermm self.debug('updated xy limits zoom={}, pxpermm={}'.format( z, pxpermm)) self.canvas.request_redraw() def _get_record_label(self): return 'Start Recording' if not self.is_recording else 'Stop' # =============================================================================== # factories # =============================================================================== def _canvas_factory(self): """ """ v = VideoLaserTrayCanvas(stage_manager=self, padding=30) return v def _canvas_editor_factory(self): e = super(VideoStageManager, self)._canvas_editor_factory() e.stop_timer = 'stop_timer' return e # =============================================================================== # defaults # =============================================================================== def _camera_default(self): klass = YamlCamera # p = os.path.join(self.configuration_dir_path, 'camera.yaml') p = self.video_configuration_path if p is not None: if not os.path.isfile(p): klass = Camera pp = os.path.join(self.configuration_dir_path, 'camera.cfg') if not os.path.isfile(pp): self.warning_dialog( 'No Camera configuration file a {} or {}'.format( p, pp)) p = pp camera = klass() camera.load(p) else: camera = Camera() camera.set_limits_by_zoom(0, 0, 0, self.canvas) self._camera_zoom_coefficients = camera.zoom_coefficients return camera def _lumen_detector_default(self): ld = LumenDetector() ld.pixel_depth = self.video.pixel_depth return ld def _video_default(self): v = Video() self.canvas.video = v return v def _video_server_default(self): from pychron.image.video_server import VideoServer return VideoServer(video=self.video) def _video_archiver_default(self): from pychron.core.helpers.archiver import Archiver return Archiver() def _autocenter_manager_default(self): if self.parent.mode != 'client': # from pychron.mv.autocenter_manager import AutoCenterManager if 'co2' in self.parent.name.lower(): from pychron.mv.autocenter_manager import CO2AutocenterManager klass = CO2AutocenterManager else: from pychron.mv.autocenter_manager import DiodeAutocenterManager klass = DiodeAutocenterManager return klass(video=self.video, canvas=self.canvas, application=self.application) def _autofocus_manager_default(self): if self.parent.mode != 'client': from pychron.mv.focus.autofocus_manager import AutoFocusManager return AutoFocusManager(video=self.video, laser_manager=self.parent, stage_controller=self.stage_controller, canvas=self.canvas, application=self.application)
class FusionsLaserManager(LaserManager): """ """ laser_controller = Instance(FusionsLogicBoard) fiber_light = Instance(FiberLight) # optics_view = Instance(OpticsView) # beam = DelegatesTo('laser_controller') # beammin = DelegatesTo('laser_controller') # beammax = DelegatesTo('laser_controller') # update_beam = DelegatesTo('laser_controller') # beam_enabled = DelegatesTo('laser_controller') # # beam_enabled = Bool(True) # # zoom = DelegatesTo('laser_controller') # zoommin = DelegatesTo('laser_controller') # zoommax = DelegatesTo('laser_controller') # update_zoom = DelegatesTo('laser_controller') # execute_button = DelegatesTo('laser_script_executor') # execute_label = DelegatesTo('laser_script_executor') pointer = Event pointer_state = Bool(False) pointer_label = Property(depends_on='pointer_state') step_heat_manager = None lens_configuration = Str('gaussian') lens_configuration_dict = Dict lens_configuration_names = List power_timer = None brightness_timer = None power_graph = None _prev_power = 0 record_brightness = Bool # recording_zoom = Float # record = Event # record_label = Property(depends_on='_recording_power_state') _recording_power_state = Bool(False) simulation = DelegatesTo('laser_controller') data_manager = None _data_manager_lock = None _current_rid = None # brightness_meter = Instance(BrightnessPIDManager) chiller = Any motor_event = Event _degas_thread = None @on_trait_change('laser_controller:refresh_canvas') def refresh_canvas(self): if self.stage_manager: self.stage_manager.canvas.request_redraw() # =============================================================================== # IExtractionDevice interface # =============================================================================== def extract(self, power, **kw): self.enable_laser() self.set_laser_power(power, **kw) def end_extract(self): self.disable_laser() self.stop_pattern() def open_motor_configure(self): self.laser_controller.open_motor_configure() # def _record_fired(self): # if self._recording_power_state: # save = self.db_save_dialog() # self.stop_power_recording(delay=0, save=save) # else: # t = Thread(name='fusions.power_record', # target=self.start_power_recording, args=('Manual',)) # t.start() # self._recording_power_state = not self._recording_power_state def bind_preferences(self, pref_id): self.debug('binding preferences') super(FusionsLaserManager, self).bind_preferences(pref_id) bind_preference(self, 'recording_zoom', '{}.recording_zoom'.format(pref_id)) bind_preference(self, 'record_brightness', '{}.record_brightness'.format(pref_id)) self.debug('preferences bound') def set_light(self, value): self.set_light_state(value > 0) self.set_light_intensity(value) def set_light_state(self, state): if state: self.fiber_light.power_off() else: self.fiber_light.power_on() def set_light_intensity(self, v): self.fiber_light.intensity = min(max(0, v), 100) @on_trait_change('pointer') def pointer_ononff(self): """ """ self.pointer_state = not self.pointer_state self.laser_controller.set_pointer_onoff(self.pointer_state) def get_laser_watts(self): return self._requested_power def get_coolant_temperature(self, **kw): """ """ chiller = self.chiller if chiller is not None: return chiller.get_coolant_out_temperature(**kw) def get_coolant_status(self, **kw): chiller = self.chiller if chiller is not None: return chiller.get_faults(**kw) def do_motor_initialization(self, name): if self.laser_controller: motor = self.laser_controller.get_motor(name) if motor is not None: n = 4 from pychron.core.ui.progress_dialog import myProgressDialog pd = myProgressDialog(max=n, size=(550, 15)) pd.open() motor.initialize(progress=pd) pd.close() def set_beam_diameter(self, bd, force=False, **kw): """ """ result = False motor = self.get_motor('beam') if motor is not None: if motor.enabled or force: self.set_motor('beam', bd, **kw) result = True else: self.info('beam disabled by lens configuration {}'.format(self.lens_configuration)) return result def set_zoom(self, z, **kw): """ """ self.set_motor('zoom', z, **kw) def set_motor_lock(self, name, value): m = self.get_motor(name) if m is not None: m.locked = to_bool(value) return True def set_motor(self, *args, **kw): self.motor_event = (args, kw) return self.laser_controller.set_motor(*args, **kw) def get_motor(self, name): return next((mi for mi in self.laser_controller.motors if mi.name == name), None) def do_autofocus(self, **kw): if self.use_video: am = self.stage_manager.autofocus_manager am.passive_focus(block=True, **kw) def take_snapshot(self, *args, **kw): if self.use_video: return self.stage_manager.snapshot( auto=True, inform=False, *args, **kw) def start_video_recording(self, name='video', *args, **kw): if self.use_video: self.stage_manager.start_recording(basename=name) def stop_video_recording(self, *args, **kw): if self.use_video: self.stage_manager.stop_recording() def degasser_factory(self): from pychron.mv.degas.degasser import Degasser dm = Degasser( laser_manager=self, video=self.stage_manager.video, ) return dm def do_machine_vision_degas(self, lumens, duration, new_thread=False): if self.use_video: dm = self.degasser_factory() def func(): dm.degas(lumens, duration) if new_thread: self._degas_thread = Thread(target=func) self._degas_thread.start() else: func() def get_brightness(self): if self.use_video: return self.stage_manager.get_brightness() else: return super(FusionsLaserManager, self).get_brightness() def is_degassing(self): if self._degas_thread: return self._degas_thread.isRunning() # =============================================================================== # pyscript interface # =============================================================================== def _move_to_position(self, position, autocenter): if self.stage_manager is not None: if isinstance(position, tuple): if len(position) > 1: x, y = position[:2] self.stage_manager.linear_move(x, y) if len(position) == 3: self.stage_manager.set_z(position[2]) else: self.stage_manager.move_to_hole(position) return True def set_stage_map(self, mapname): if self.stage_manager is not None: self.stage_manager.set_stage_map(mapname) def _enable_hook(self, **kw): resp = self.laser_controller._enable_laser(**kw) if self.laser_controller.simulation: resp = True return resp def _disable_hook(self): resp = self.laser_controller._disable_laser() if self.laser_controller.simulation: resp = True return resp def show_motion_controller_manager(self): """ """ stage_controller = self.stage_manager.stage_controller package = 'pychron.managers.motion_controller_managers' if 'Aerotech' in stage_controller.__class__.__name__: klass = 'AerotechMotionControllerManager' package += '.aerotech_motion_controller_manager' else: klass = 'NewportMotionControllerManager' package += '.newport_motion_controller_manager' module = __import__(package, globals(), locals(), [klass], -1) factory = getattr(module, klass) m = factory(motion_controller=stage_controller) self.open_view(m) # ========================= views ========================= def get_control_buttons(self): """ """ return [('enable', 'enable_label', None), ] # def get_control_items(self): # ''' # ''' # # return Item('laser_controller', show_label=False, # # editor=InstanceEditor(view='control_view'), # # style='custom', # # springy=False, height= -100) # # # return self.laser_controller.get_control_group() # s = [('zoom', 'zoom', {}), # ('beam', 'beam', {'enabled_when':'object.beam_enabled'}) # ] # return self._update_slider_group_factory(s) # def get_lens_configuration_group(self): # return Item('lens_configuration', # editor=EnumEditor(values=self.lens_configuration_names) # ) # def get_optics_group(self): # csliders = self.get_control_items() # vg = HGroup(csliders, # show_border=True, # label='Optics', springy=False # ) # # lens_config = self.get_lens_configuration_group() # if lens_config: # vg.content.insert(0, lens_config) # # return vg # def get_control_button_group(self): # grp = HGroup(spring, Item('enabled_led', show_label=False, style='custom', editor=LEDEditor()), # self._button_group_factory(self.get_control_buttons(), orientation='h'), # # springy=True # ) # return grp def get_power_group(self): power_grp = VGroup( self.get_control_button_group(), HGroup( Item('requested_power', style='readonly', format_str='%0.2f', width=100), spring, Item('units', show_label=False, style='readonly'), spring), # Item('laser_script_executor', show_label=False, style='custom'), # self._button_factory('execute_button', 'execute_label'), show_border=True, # springy=True, label='Power') ps = self.get_power_slider() if ps: # ps.springy = True power_grp.content.append(ps) return power_grp # def get_additional_group(self): # og = Group(Item('laser_controller', show_label=False, # editor=InstanceEditor(view='control_view'), # style='custom'), # label='Optics', # ) # ac = Group( # og, # show_border=True, # label='Additional Controls', # layout='tabbed') # # aclist = self.get_additional_controls() # if aclist is None: # og.label = 'Optics' # og.show_border = True # ac = og # else: # for ai in aclist: # ac.content.append(ai) # return ac # def get_control_group(self): # ''' # ''' # power_grp = self.get_power_group() # pulse_grp = Group(Item('pulse', style='custom', show_label=False), # label='Pulse', show_border=True # ) # power_grp = HGroup(power_grp, pulse_grp) # ac = self.get_additional_group() # g = HGroup(power_grp, ac) # # return g def _get_pointer_label(self): """ """ return 'Pointer ON' if not self.pointer_state else 'Pointer OFF' def _get_record_label(self): return 'Record' if not self._recording_power_state else 'Stop' def _get_record_brightness(self): return self.record_brightness and self._get_machine_vision() is not None # ========================= defaults ======================= # def get_power_database(self): # from pychron.database.adapters.power_adapter import PowerAdapter # # db = PowerAdapter(name=self.dbname, # kind='sqlite') # return db # def get_power_calibration_database(self): # from pychron.database.adapters.power_calibration_adapter import PowerCalibrationAdapter # # db = PowerCalibrationAdapter(name=self.dbname, # kind='sqlite') # return db # def _subsystem_default(self): # ''' # ''' # return ArduinoSubsystem(name='arduino_subsystem_2') # def _brightness_meter_default(self): # if self.use_video: # b = BrightnessPIDManager(parent=self) # # b.brightness_manager.video = self.stage_manager.video # # return b def _fiber_light_default(self): """ """ return FiberLight(name='fiber_light')
def do_coincidence_scan(self, new_thread=True): if new_thread: t = Thread(name='ion_optics.coincidence', target=self._coincidence) t.start() self._centering_thread = t
class NMGRLFurnaceManager(BaseFurnaceManager): funnel = Instance(NMGRLFunnel) loader_logic = Instance(LoaderLogic) magnets = Instance(NMGRLMagnetDumper) setpoint_readback_min = Float(0) setpoint_readback_max = Float(1600.0) graph = Instance(StreamGraph) update_period = Int(2) dumper_canvas = Instance(DumperCanvas) _alive = False _guide_overlay = None _dumper_thread = None mode = 'normal' def activate(self): # pref_id = 'pychron.furnace' # bind_preference(self, 'update_period', '{}.update_period'.format(pref_id)) self._start_update() def prepare_destroy(self): self._stop_update() self.loader_logic.manager = None def dump_sample(self): self.debug('dump sample') if self._dumper_thread is None: self._dumper_thread = Thread(name='DumpSample', target=self._dump_sample) self._dumper_thread.start() def is_dump_complete(self): ret = self._dumper_thread is None return ret def actuate_magnets(self): self.debug('actuate magnets') if self.loader_logic.check('AM'): self.magnet.open() # wait for actuate magnets pass else: self.warning('actuate magnets not enabled') def lower_funnel(self): self.debug('lower funnel') if self.loader_logic.check('FD'): self.funnel.set_value(self.funnel.down_position) # todo: update canvas state return True else: self.warning('lowering funnel not enabled') def raise_funnel(self): self.debug('raise funnel') if self.loader_logic.check('FU'): self.funnel.set_value(self.funnel.up_position) # todo: update canvas state return True else: self.warning('raising funnel not enabled') def set_setpoint(self, v): if self.controller: # print self.controller, self.controller._cdevice self.controller.set_setpoint(v) if not self._guide_overlay: self._guide_overlay = self.graph.add_horizontal_rule(v) self._guide_overlay.visible = bool(v) self._guide_overlay.value = v # ymi, yma = self.graph.get_y_limits() d = self.graph.get_data(axis=1) self.graph.set_y_limits(min_=0, max_=max(d.max(), v * 1.1)) self.graph.redraw() def read_setpoint(self, update=False): v = 0 if self.controller: force = update and not self.controller.is_scanning() v = self.controller.read_setpoint(force=force) try: self.setpoint_readback = v return v except TraitError: pass # canvas def set_software_lock(self, name, lock): if self.switch_manager is not None: if lock: self.switch_manager.lock(name) else: self.switch_manager.unlock(name) def open_valve(self, name, **kw): if not self._open_logic(name): self.debug('logic failed') return False, False if self.switch_manager: return self.switch_manager.open_switch(name, **kw) def close_valve(self, name, **kw): if not self._close_logic(name): self.debug('logic failed') return False, False if self.switch_manager: return self.switch_manager.close_switch(name, **kw) def set_selected_explanation_item(self, item): pass # logic def get_switch_state(self, name): if self.switch_manager: return self.switch_manager.get_state_by_name(name, force=True) def get_flag_state(self, flag): self.debug('get_flag_state {}'.format(flag)) if flag in ('no_motion', 'no_dump', 'funnel_up', 'funnel_down'): return getattr(self, flag)() return False def funnel_up(self): return self.funnel.in_up_position() def funnel_down(self): return self.funnel.in_down_position() def no_motion(self): v = not self.stage_manager.in_motion() self.debug('no motion {}'.format(v)) return v def no_dump(self): v = not self.magnets.dump_in_progress() self.debug('no dump {}'.format(v)) return v # private def _open_logic(self, name): """ check the logic rules to see if its ok to open "name" return True if ok """ return self.loader_logic.open(name) def _close_logic(self, name): """ check the logic rules to see if its ok to close "name" return True if ok """ return self.loader_logic.close(name) def _stop_update(self): self._alive = False def _start_update(self): self._alive = True self.graph = g = StreamGraph() # g.plotcontainer.padding_top = 5 # g.plotcontainer.padding_right = 5 g.new_plot(xtitle='Time (s)', ytitle='Temp. (C)', padding_top=5, padding_right=5) g.set_scan_width(600) g.new_series() self._update_readback() def _update_readback(self): v = self.read_setpoint(update=True) self.graph.record(v, track_y=False) if self._alive: do_after(self.update_period * 1000, self._update_readback) def _dump_sample(self): """ 1. open gate valve 2. open shutters 3. lower funnel 4. actuate magnets 5. raise funnel 6. close shutters 7. close gate valve :return: """ self.debug('dump sample started') for line in self._load_dump_script(): self.debug(line) self._execute_script_line(line) self.stage_manager.set_sample_dumped() self._dumper_thread = None def _load_dump_script(self): p = os.path.join(paths.device_dir, 'furnace', 'dump_sequence.txt') return pathtolist(p) def _execute_script_line(self, line): if ' ' in line: cmd, args = line.split(' ') else: cmd, args = line, None if cmd == 'sleep': time.sleep(float(args)) elif cmd == 'open': self.switch_manager.open_switch(args) self.dumper_canvas.set_item_state(args, True) elif cmd == 'close': self.switch_manager.close_switch(args) self.dumper_canvas.set_item_state(args, False) elif cmd == 'lower_funnel': self.lower_funnel() self.dumper_canvas.set_item_state(args, True) elif cmd == 'raise_funnel': self.raise_funnel() self.dumper_canvas.set_item_state(args, False) elif cmd == 'actuate_magnets': self.actuate_magnets() self.dumper_canvas.request_redraw() # handlers def _setpoint_changed(self, new): self.set_setpoint(new) def _stage_manager_default(self): sm = NMGRLFurnaceStageManager( stage_manager_id='nmgrl.furnace.stage_map') return sm def _switch_manager_default(self): sm = SwitchManager() return sm def _dumper_canvas_default(self): dc = DumperCanvas(manager=self) pathname = os.path.join(paths.canvas2D_dir, 'dumper.xml') configpath = os.path.join(paths.canvas2D_dir, 'dumper_config.xml') valvepath = os.path.join(paths.extraction_line_dir, 'valves.xml') dc.load_canvas_file(pathname, configpath, valvepath, dc) return dc def _funnel_default(self): f = NMGRLFunnel(name='funnel', configuration_dir_name='furnace') return f def _loader_logic_default(self): l = LoaderLogic(manager=self) l.load_config() return l def _magnets_default(self): m = NMGRLMagnetDumper(name='magnets', configuration_dir_name='furnace') return m
class NMGRLFurnaceManager(BaseFurnaceManager): funnel = Instance(NMGRLFunnel) loader_logic = Instance(LoaderLogic) magnets = Instance(NMGRLMagnetDumper) setpoint_readback_min = Float(0) setpoint_readback_max = Float(1600.0) graph = Instance(StreamGraph) update_period = Int(2) dumper_canvas = Instance(DumperCanvas) _alive = False _guide_overlay = None _dumper_thread = None mode = 'normal' def activate(self): # pref_id = 'pychron.furnace' # bind_preference(self, 'update_period', '{}.update_period'.format(pref_id)) self._start_update() def prepare_destroy(self): self._stop_update() self.loader_logic.manager = None def dump_sample(self): self.debug('dump sample') if self._dumper_thread is None: self._dumper_thread = Thread(name='DumpSample', target=self._dump_sample) self._dumper_thread.start() def is_dump_complete(self): ret = self._dumper_thread is None return ret def actuate_magnets(self): self.debug('actuate magnets') if self.loader_logic.check('AM'): self.magnet.open() # wait for actuate magnets pass else: self.warning('actuate magnets not enabled') def lower_funnel(self): self.debug('lower funnel') if self.loader_logic.check('FD'): self.funnel.set_value(self.funnel.down_position) # todo: update canvas state return True else: self.warning('lowering funnel not enabled') def raise_funnel(self): self.debug('raise funnel') if self.loader_logic.check('FU'): self.funnel.set_value(self.funnel.up_position) # todo: update canvas state return True else: self.warning('raising funnel not enabled') def set_setpoint(self, v): if self.controller: # print self.controller, self.controller._cdevice self.controller.set_setpoint(v) if not self._guide_overlay: self._guide_overlay = self.graph.add_horizontal_rule(v) self._guide_overlay.visible = bool(v) self._guide_overlay.value = v # ymi, yma = self.graph.get_y_limits() d = self.graph.get_data(axis=1) self.graph.set_y_limits(min_=0, max_=max(d.max(), v * 1.1)) self.graph.redraw() def read_setpoint(self, update=False): v = 0 if self.controller: force = update and not self.controller.is_scanning() v = self.controller.read_setpoint(force=force) try: self.setpoint_readback = v return v except TraitError: pass # canvas def set_software_lock(self, name, lock): if self.switch_manager is not None: if lock: self.switch_manager.lock(name) else: self.switch_manager.unlock(name) def open_valve(self, name, **kw): if not self._open_logic(name): self.debug('logic failed') return False, False if self.switch_manager: return self.switch_manager.open_switch(name, **kw) def close_valve(self, name, **kw): if not self._close_logic(name): self.debug('logic failed') return False, False if self.switch_manager: return self.switch_manager.close_switch(name, **kw) def set_selected_explanation_item(self, item): pass # logic def get_switch_state(self, name): if self.switch_manager: return self.switch_manager.get_state_by_name(name, force=True) def get_flag_state(self, flag): self.debug('get_flag_state {}'.format(flag)) if flag in ('no_motion', 'no_dump', 'funnel_up', 'funnel_down'): return getattr(self, flag)() return False def funnel_up(self): return self.funnel.in_up_position() def funnel_down(self): return self.funnel.in_down_position() def no_motion(self): v = not self.stage_manager.in_motion() self.debug('no motion {}'.format(v)) return v def no_dump(self): v = not self.magnets.dump_in_progress() self.debug('no dump {}'.format(v)) return v # private def _open_logic(self, name): """ check the logic rules to see if its ok to open "name" return True if ok """ return self.loader_logic.open(name) def _close_logic(self, name): """ check the logic rules to see if its ok to close "name" return True if ok """ return self.loader_logic.close(name) def _stop_update(self): self._alive = False def _start_update(self): self._alive = True self.graph = g = StreamGraph() # g.plotcontainer.padding_top = 5 # g.plotcontainer.padding_right = 5 g.new_plot(xtitle='Time (s)', ytitle='Temp. (C)', padding_top=5, padding_right=5) g.set_scan_width(600) g.new_series() self._update_readback() def _update_readback(self): v = self.read_setpoint(update=True) self.graph.record(v, track_y=False) if self._alive: do_after(self.update_period * 1000, self._update_readback) def _dump_sample(self): """ 1. open gate valve 2. open shutters 3. lower funnel 4. actuate magnets 5. raise funnel 6. close shutters 7. close gate valve :return: """ self.debug('dump sample started') for line in self._load_dump_script(): self.debug(line) self._execute_script_line(line) self.stage_manager.set_sample_dumped() self._dumper_thread = None def _load_dump_script(self): p = os.path.join(paths.device_dir, 'furnace', 'dump_sequence.txt') return pathtolist(p) def _execute_script_line(self, line): if ' ' in line: cmd, args = line.split(' ') else: cmd, args = line, None if cmd == 'sleep': time.sleep(float(args)) elif cmd == 'open': self.switch_manager.open_switch(args) self.dumper_canvas.set_item_state(args, True) elif cmd == 'close': self.switch_manager.close_switch(args) self.dumper_canvas.set_item_state(args, False) elif cmd == 'lower_funnel': self.lower_funnel() self.dumper_canvas.set_item_state(args, True) elif cmd == 'raise_funnel': self.raise_funnel() self.dumper_canvas.set_item_state(args, False) elif cmd == 'actuate_magnets': self.actuate_magnets() self.dumper_canvas.request_redraw() # handlers def _setpoint_changed(self, new): self.set_setpoint(new) def _stage_manager_default(self): sm = NMGRLFurnaceStageManager(stage_manager_id='nmgrl.furnace.stage_map') return sm def _switch_manager_default(self): sm = SwitchManager() return sm def _dumper_canvas_default(self): dc = DumperCanvas(manager=self) pathname = os.path.join(paths.canvas2D_dir, 'dumper.xml') configpath = os.path.join(paths.canvas2D_dir, 'dumper_config.xml') valvepath = os.path.join(paths.extraction_line_dir, 'valves.xml') dc.load_canvas_file(pathname, configpath, valvepath, dc) return dc def _funnel_default(self): f = NMGRLFunnel(name='funnel', configuration_dir_name='furnace') return f def _loader_logic_default(self): l = LoaderLogic(manager=self) l.load_config() return l def _magnets_default(self): m = NMGRLMagnetDumper(name='magnets', configuration_dir_name='furnace') return m
def _position_changed(self): if self.position is not None: t = Thread(target=self._move_to_position, args=(self.position, self.use_autocenter)) t.start() self._position_thread = t
class IonOpticsManager(Manager): reference_detector = Instance(BaseDetector) reference_isotope = Any magnet_dac = Range(0.0, 6.0) graph = Instance(Graph) peak_center_button = Button('Peak Center') stop_button = Button('Stop') alive = Bool(False) spectrometer = Any peak_center = Instance(PeakCenter) coincidence = Instance(Coincidence) peak_center_config = Instance(PeakCenterConfigurer) # coincidence_config = Instance(CoincidenceConfig) canceled = False peak_center_result = None _centering_thread = None def close(self): self.cancel_peak_center() def cancel_peak_center(self): self.alive = False self.canceled = True self.peak_center.canceled = True self.peak_center.stop() self.info('peak center canceled') def get_mass(self, isotope_key): spec = self.spectrometer molweights = spec.molecular_weights return molweights[isotope_key] def set_mftable(self, name=None): """ if mt is None set to the default mftable located at setupfiles/spectrometer/mftable.csv :param mt: :return: """ if name and name != os.path.splitext(os.path.basename(paths.mftable))[0]: self.spectrometer.use_deflection_correction = False else: self.spectrometer.use_deflection_correction = True self.spectrometer.magnet.set_mftable(name) def get_position(self, *args, **kw): kw['update_isotopes'] = False return self._get_position(*args, **kw) def position(self, pos, detector, *args, **kw): dac = self._get_position(pos, detector, *args, **kw) mag = self.spectrometer.magnet self.info('positioning {} ({}) on {}'.format(pos, dac, detector)) return mag.set_dac(dac) def do_coincidence_scan(self, new_thread=True): if new_thread: t = Thread(name='ion_optics.coincidence', target=self._coincidence) t.start() self._centering_thread = t def setup_coincidence(self): pcc = self.coincidence_config pcc.dac = self.spectrometer.magnet.dac info = pcc.edit_traits() if not info.result: return detector = pcc.detector isotope = pcc.isotope detectors = [d for d in pcc.additional_detectors] # integration_time = pcc.integration_time if pcc.use_nominal_dac: center_dac = self.get_position(isotope, detector) elif pcc.use_current_dac: center_dac = self.spectrometer.magnet.dac else: center_dac = pcc.dac # self.spectrometer.save_integration() # self.spectrometer.set_integration(integration_time) cs = Coincidence(spectrometer=self.spectrometer, center_dac=center_dac, reference_detector=detector, reference_isotope=isotope, additional_detectors=detectors) self.coincidence = cs return cs def get_center_dac(self, det, iso): spec = self.spectrometer det = spec.get_detector(det) molweights = spec.molecular_weights mass = molweights[iso] dac = spec.magnet.map_mass_to_dac(mass, det.name) # correct for deflection return spec.correct_dac(det, dac) def do_peak_center(self, save=True, confirm_save=False, warn=False, new_thread=True, message='', on_end=None, timeout=None): self.debug('doing pc') self.canceled = False self.alive = True self.peak_center_result = None args = (save, confirm_save, warn, message, on_end, timeout) if new_thread: t = Thread(name='ion_optics.peak_center', target=self._peak_center, args=args) t.start() self._centering_thread = t return t else: self._peak_center(*args) def setup_peak_center(self, detector=None, isotope=None, integration_time=1.04, directions='Increase', center_dac=None, plot_panel=None, new=False, standalone_graph=True, name='', show_label=False, window=0.015, step_width=0.0005, min_peak_height=1.0, percent=80, deconvolve=None, use_interpolation=False, interpolation_kind='linear', dac_offset=None, calculate_all_peaks=False, config_name=None, use_configuration_dac=True): if deconvolve is None: n_peaks, select_peak = 1, 1 if dac_offset is not None: use_dac_offset = True spec = self.spectrometer spec.save_integration() self.debug('setup peak center. detector={}, isotope={}'.format(detector, isotope)) self._setup_config() pcc = None if detector is None or isotope is None: self.debug('ask user for peak center configuration') self.peak_center_config.load(dac=spec.magnet.dac) if config_name: self.peak_center_config.active_name = config_name info = self.peak_center_config.edit_traits() if not info.result: return else: pcc = self.peak_center_config.active_item elif config_name: self.peak_center_config.load(dac=spec.magnet.dac) self.peak_center_config.active_name = config_name pcc = self.peak_center_config.active_item if pcc: if not detector: detector = pcc.active_detectors if not isotope: isotope = pcc.isotope directions = pcc.directions integration_time = pcc.integration_time window = pcc.window min_peak_height = pcc.min_peak_height step_width = pcc.step_width percent = pcc.percent use_interpolation = pcc.use_interpolation interpolation_kind = pcc.interpolation_kind n_peaks = pcc.n_peaks select_peak = pcc.select_n_peak use_dac_offset = pcc.use_dac_offset dac_offset = pcc.dac_offset calculate_all_peaks = pcc.calculate_all_peaks if center_dac is None and use_configuration_dac: center_dac = pcc.dac spec.set_integration_time(integration_time) period = int(integration_time * 1000 * 0.9) if not isinstance(detector, (tuple, list)): detector = (detector,) ref = detector[0] ref = self.spectrometer.get_detector(ref) self.reference_detector = ref self.reference_isotope = isotope if center_dac is None: center_dac = self.get_center_dac(ref, isotope) if len(detector) > 1: ad = detector[1:] else: ad = [] pc = self.peak_center if not pc or new: pc = PeakCenter() pc.trait_set(center_dac=center_dac, period=period, window=window, percent=percent, min_peak_height=min_peak_height, step_width=step_width, directions=directions, reference_detector=ref, additional_detectors=ad, reference_isotope=isotope, spectrometer=spec, show_label=show_label, use_interpolation=use_interpolation, interpolation_kind=interpolation_kind, n_peaks=n_peaks, select_peak=select_peak, use_dac_offset=use_dac_offset, dac_offset=dac_offset, calculate_all_peaks=calculate_all_peaks) self.peak_center = pc graph = pc.graph graph.name = name if plot_panel: plot_panel.set_peak_center_graph(graph) else: graph.close_func = self.close if standalone_graph: # set graph window attributes graph.window_title = 'Peak Center {}({}) @ {:0.3f}'.format(ref, isotope, center_dac) graph.window_width = 300 graph.window_height = 250 open_view(graph) return self.peak_center # private def _setup_config(self): config = self.peak_center_config config.detectors = self.spectrometer.detector_names keys = self.spectrometer.molecular_weights.keys() config.isotopes = sort_isotopes(keys) def _get_peak_center_config(self, config_name): if config_name is None: config_name = 'default' config = self.peak_center_config.get(config_name) config.detectors = self.spectrometer.detectors_names if config.detector_name: config.detector = next((di for di in config.detectors if di == config.detector_name), None) if not config.detector: config.detector = config.detectors[0] keys = self.spectrometer.molecular_weights.keys() config.isotopes = sort_isotopes(keys) return config def _timeout_func(self, timeout, evt): st = time.time() while not evt.is_set(): if not self.alive: break if time.time() - st > timeout: self.warning('Peak Centering timed out after {}s'.format(timeout)) self.cancel_peak_center() break time.sleep(0.01) def _peak_center(self, save, confirm_save, warn, message, on_end, timeout): pc = self.peak_center spec = self.spectrometer ref = self.reference_detector isotope = self.reference_isotope if timeout: evt = Event() self.timeout_thread = Thread(target=self._timeout_func, args=(timeout, evt)) self.timeout_thread.start() dac_d = pc.get_peak_center() self.peak_center_result = dac_d if dac_d: args = ref, isotope, dac_d self.info('new center pos {} ({}) @ {}'.format(*args)) det = spec.get_detector(ref) dac_a = spec.uncorrect_dac(det, dac_d) self.info('dac uncorrected for HV and deflection {}'.format(dac_a)) if save: if confirm_save: msg = 'Update Magnet Field Table with new peak center- {} ({}) @ RefDetUnits= {}'.format(*args) save = self.confirmation_dialog(msg) if save: spec.magnet.update_field_table(det, isotope, dac_a, message) spec.magnet.set_dac(dac_d) elif not self.canceled: msg = 'centering failed' if warn: self.warning_dialog(msg) self.warning(msg) # needs to be called on the main thread to properly update # the menubar actions. alive=False enables IonOptics>Peak Center # d = lambda:self.trait_set(alive=False) # still necessary with qt? and tasks if on_end: on_end() self.trait_set(alive=False) if timeout: evt.set() self.spectrometer.restore_integration() def _get_position(self, pos, detector, use_dac=False, update_isotopes=True): """ pos can be str or float "Ar40", "39.962", 39.962 to set in DAC space set use_dac=True """ if pos == NULL_STR: return spec = self.spectrometer mag = spec.magnet if isinstance(detector, str): det = spec.get_detector(detector) else: det = detector self.debug('detector {}'.format(det)) if use_dac: dac = pos else: self.debug('POSITION {} {}'.format(pos, detector)) if isinstance(pos, str): try: pos = float(pos) except ValueError: # pos is isotope if update_isotopes: # if the pos is an isotope then update the detectors spec.update_isotopes(pos, detector) pos = self.get_mass(pos) mag.mass_change(pos) # pos is mass i.e 39.962 dac = mag.map_mass_to_dac(pos, det.name) dac = spec.correct_dac(det, dac) return dac def _coincidence(self): self.coincidence.get_peak_center() self.info('coincidence finished') self.spectrometer.restore_integration() # =============================================================================== # handler # =============================================================================== def _coincidence_config_default(self): config = None p = os.path.join(paths.hidden_dir, 'coincidence_config.p') if os.path.isfile(p): try: with open(p) as rfile: config = pickle.load(rfile) config.detectors = dets = self.spectrometer.detectors config.detector = next((di for di in dets if di.name == config.detector_name), None) except Exception, e: print 'coincidence config', e if config is None: config = CoincidenceConfig() config.detectors = self.spectrometer.detectors config.detector = config.detectors[0] keys = self.spectrometer.molecular_weights.keys() config.isotopes = sort_isotopes(keys) return config
def _back_button_fired(self): pos, kw = self.linear_move_history.pop(-1) t = Thread(target=self.stage_controller.linear_move, args=pos, kwargs=kw) t.start() self.move_thread = t
class BaseStageManager(Manager): keyboard_focus = Event canvas_editor_klass = LaserComponentEditor tray_calibration_manager = Instance(TrayCalibrationManager) stage_map_klass = LaserStageMap stage_map_name = Str stage_map_names = List stage_map = Instance(BaseStageMap) canvas = Instance(MapCanvas) # root = Str(paths.map_dir) calibrated_position_entry = String(enter_set=True, auto_set=False) move_thread = None temp_position = None temp_hole = None root = Str # use_modified = Bool(True) # set true to use modified affine calculation def motor_event_hook(self, name, value, *args, **kw): pass def goto_position(self, pos): raise NotImplementedError def refresh_stage_map_names(self): sms = get_stage_map_names(root=self.root) self.stage_map_names = sms def load(self): self.refresh_stage_map_names() psm = self._load_previous_stage_map() sms = self.stage_map_names sm = None if psm in sms: sm = psm elif sms: sm = sms[0] if sm: self.stage_map_name = '' self.stage_map_name = sm def kill(self): r = super(BaseStageManager, self).kill() self._save_stage_map() return r @property def stage_map_path(self): return os.path.join(paths.hidden_dir, '{}.stage_map'.format(self.id)) def canvas_editor_factory(self): return self.canvas_editor_klass(keyboard_focus='keyboard_focus') def move_to_hole(self, hole, **kw): self._move(self._move_to_hole, hole, name='move_to_hole', **kw) def get_calibrated_position(self, pos, key=None): smap = self.stage_map # use a affine transform object to map canvas = self.canvas ca = canvas.calibration_item # check if a calibration applies to this hole hole_calibration = get_hole_calibration(smap.name, key) if hole_calibration: self.debug('Using hole calibration') ca = hole_calibration if ca: rot = ca.rotation cpos = ca.center scale = ca.scale self.debug('Calibration parameters: ' 'rot={:0.3f}, cpos={} scale={:0.3f}'.format( rot, cpos, scale)) pos = smap.map_to_calibration(pos, cpos, rot, scale=scale) return pos def update_axes(self): """ """ self.info('querying axis positions') self._update_axes() # private def _update_axes(self): pass def _move_to_hole(self, key, correct_position=True, **kw): pass def _stop(self): pass def _move(self, func, pos, name=None, *args, **kw): if pos is None: return if self.move_thread and self.move_thread.isRunning(): self._stop() if name is None: name = func.__name__ self.move_thread = Thread(name='stage.{}'.format(name), target=func, args=(pos, ) + args, kwargs=kw) self.move_thread.start() def _canvas_factory(self): raise NotImplementedError def _stage_map_changed_hook(self): pass # handlers def _calibrated_position_entry_changed(self, new): self.debug('User entered calibrated position {}'.format(new)) self.goto_position(new) def _stage_map_name_changed(self, new): if new: self.debug('setting stage map to {}'.format(new)) root = self.root path = os.path.join(root, add_extension(new, '.txt')) sm = self.stage_map_klass(file_path=path) self.tray_calibration_manager.load_calibration(stage_map=new) self.canvas.set_map(sm) self.canvas.request_redraw() self.stage_map = sm self._stage_map_changed_hook() # defaults def _root_default(self): return paths.map_dir def _tray_calibration_manager_default(self): t = TrayCalibrationManager(parent=self, canvas=self.canvas) return t def _canvas_default(self): return self._canvas_factory() def _save_stage_map(self): p = self.stage_map_path self.info('saving stage_map {} to {}'.format(self.stage_map_name, p)) with open(p, 'wb') as f: pickle.dump(self.stage_map_name, f) def _load_previous_stage_map(self): p = self.stage_map_path if os.path.isfile(p): self.info('loading previous stage map from {}'.format(p)) with open(p, 'rb') as f: try: sm = pickle.load(f) if not sm.endswith('.center'): return sm except (pickle.PickleError, ValueError): pass
class FusionsLaserManager(LaserManager): """ """ laser_controller = Instance(FusionsLogicBoard) fiber_light = Instance(FiberLight) # optics_view = Instance(OpticsView) # beam = DelegatesTo('laser_controller') # beammin = DelegatesTo('laser_controller') # beammax = DelegatesTo('laser_controller') # update_beam = DelegatesTo('laser_controller') # beam_enabled = DelegatesTo('laser_controller') # # beam_enabled = Bool(True) # # zoom = DelegatesTo('laser_controller') # zoommin = DelegatesTo('laser_controller') # zoommax = DelegatesTo('laser_controller') # update_zoom = DelegatesTo('laser_controller') # execute_button = DelegatesTo('laser_script_executor') # execute_label = DelegatesTo('laser_script_executor') pointer = Event pointer_state = Bool(False) pointer_label = Property(depends_on='pointer_state') step_heat_manager = None lens_configuration = Str('gaussian') lens_configuration_dict = Dict lens_configuration_names = List power_timer = None brightness_timer = None power_graph = None _prev_power = 0 record_brightness = Bool # recording_zoom = Float # record = Event # record_label = Property(depends_on='_recording_power_state') _recording_power_state = Bool(False) simulation = DelegatesTo('laser_controller') data_manager = None _data_manager_lock = None _current_rid = None # brightness_meter = Instance(BrightnessPIDManager) chiller = Any motor_event = Event _degas_thread = None @on_trait_change('laser_controller:refresh_canvas') def refresh_canvas(self): if self.stage_manager: self.stage_manager.canvas.request_redraw() # =============================================================================== # IExtractionDevice interface # =============================================================================== def extract(self, power, **kw): self.enable_laser() self.set_laser_power(power, **kw) def end_extract(self): self.disable_laser() self.stop_pattern() def open_motor_configure(self): self.laser_controller.open_motor_configure() # def _record_fired(self): # if self._recording_power_state: # save = self.db_save_dialog() # self.stop_power_recording(delay=0, save=save) # else: # t = Thread(name='fusions.power_record', # target=self.start_power_recording, args=('Manual',)) # t.start() # self._recording_power_state = not self._recording_power_state def bind_preferences(self, pref_id): self.debug('binding preferences') super(FusionsLaserManager, self).bind_preferences(pref_id) bind_preference(self, 'recording_zoom', '{}.recording_zoom'.format(pref_id)) bind_preference(self, 'record_brightness', '{}.record_brightness'.format(pref_id)) self.debug('preferences bound') def set_light(self, value): self.set_light_state(value > 0) self.set_light_intensity(value) def set_light_state(self, state): if state: self.fiber_light.power_off() else: self.fiber_light.power_on() def set_light_intensity(self, v): self.fiber_light.intensity = min(max(0, v), 100) @on_trait_change('pointer') def pointer_ononff(self): """ """ self.pointer_state = not self.pointer_state self.laser_controller.set_pointer_onoff(self.pointer_state) def get_laser_watts(self): return self._requested_power def get_coolant_temperature(self, **kw): """ """ chiller = self.chiller if chiller is not None: return chiller.get_coolant_out_temperature(**kw) def get_coolant_status(self, **kw): chiller = self.chiller if chiller is not None: return chiller.get_faults(**kw) def do_motor_initialization(self, name): if self.laser_controller: motor = self.laser_controller.get_motor(name) if motor is not None: n = 4 from pychron.core.ui.progress_dialog import myProgressDialog pd = myProgressDialog(max=n, size=(550, 15)) pd.open() motor.initialize(progress=pd) pd.close() def set_beam_diameter(self, bd, force=False, **kw): """ """ result = False motor = self.get_motor('beam') if motor is not None: if motor.enabled or force: self.set_motor('beam', bd, **kw) result = True else: self.info('beam disabled by lens configuration {}'.format( self.lens_configuration)) return result def set_zoom(self, z, **kw): """ """ self.set_motor('zoom', z, **kw) def set_motor_lock(self, name, value): m = self.get_motor(name) if m is not None: m.locked = to_bool(value) return True def set_motor(self, *args, **kw): self.motor_event = (args, kw) return self.laser_controller.set_motor(*args, **kw) def get_motor(self, name): return next( (mi for mi in self.laser_controller.motors if mi.name == name), None) def do_autofocus(self, **kw): if self.use_video: am = self.stage_manager.autofocus_manager am.passive_focus(block=True, **kw) def take_snapshot(self, *args, **kw): if self.use_video: return self.stage_manager.snapshot(auto=True, inform=False, *args, **kw) def start_video_recording(self, name='video', *args, **kw): if self.use_video: self.stage_manager.start_recording(basename=name) def stop_video_recording(self, *args, **kw): if self.use_video: self.stage_manager.stop_recording() def degasser_factory(self): from pychron.mv.degas.degasser import Degasser dm = Degasser( laser_manager=self, video=self.stage_manager.video, ) return dm def do_machine_vision_degas(self, lumens, duration, new_thread=False): if self.use_video: dm = self.degasser_factory() def func(): dm.degas(lumens, duration) if new_thread: self._degas_thread = Thread(target=func) self._degas_thread.start() else: func() def get_brightness(self): if self.use_video: return self.stage_manager.get_brightness() else: return super(FusionsLaserManager, self).get_brightness() def is_degassing(self): if self._degas_thread: return self._degas_thread.isRunning() # =============================================================================== # pyscript interface # =============================================================================== def _move_to_position(self, position, autocenter): if self.stage_manager is not None: if isinstance(position, tuple): if len(position) > 1: x, y = position[:2] self.stage_manager.linear_move(x, y) if len(position) == 3: self.stage_manager.set_z(position[2]) else: self.stage_manager.move_to_hole(position) return True def set_stage_map(self, mapname): if self.stage_manager is not None: self.stage_manager.set_stage_map(mapname) def _enable_hook(self, **kw): resp = self.laser_controller._enable_laser(**kw) if self.laser_controller.simulation: resp = True return resp def _disable_hook(self): resp = self.laser_controller._disable_laser() if self.laser_controller.simulation: resp = True return resp def show_motion_controller_manager(self): """ """ stage_controller = self.stage_manager.stage_controller package = 'pychron.managers.motion_controller_managers' if 'Aerotech' in stage_controller.__class__.__name__: klass = 'AerotechMotionControllerManager' package += '.aerotech_motion_controller_manager' else: klass = 'NewportMotionControllerManager' package += '.newport_motion_controller_manager' module = __import__(package, globals(), locals(), [klass], -1) factory = getattr(module, klass) m = factory(motion_controller=stage_controller) self.open_view(m) # ========================= views ========================= def get_control_buttons(self): """ """ return [ ('enable', 'enable_label', None), ] # def get_control_items(self): # ''' # ''' # # return Item('laser_controller', show_label=False, # # editor=InstanceEditor(view='control_view'), # # style='custom', # # springy=False, height= -100) # # # return self.laser_controller.get_control_group() # s = [('zoom', 'zoom', {}), # ('beam', 'beam', {'enabled_when':'object.beam_enabled'}) # ] # return self._update_slider_group_factory(s) # def get_lens_configuration_group(self): # return Item('lens_configuration', # editor=EnumEditor(values=self.lens_configuration_names) # ) # def get_optics_group(self): # csliders = self.get_control_items() # vg = HGroup(csliders, # show_border=True, # label='Optics', springy=False # ) # # lens_config = self.get_lens_configuration_group() # if lens_config: # vg.content.insert(0, lens_config) # # return vg # def get_control_button_group(self): # grp = HGroup(spring, Item('enabled_led', show_label=False, style='custom', editor=LEDEditor()), # self._button_group_factory(self.get_control_buttons(), orientation='h'), # # springy=True # ) # return grp def get_power_group(self): power_grp = VGroup( self.get_control_button_group(), HGroup( Item('requested_power', style='readonly', format_str='%0.2f', width=100), spring, Item('units', show_label=False, style='readonly'), spring), # Item('laser_script_executor', show_label=False, style='custom'), # self._button_factory('execute_button', 'execute_label'), show_border=True, # springy=True, label='Power') ps = self.get_power_slider() if ps: # ps.springy = True power_grp.content.append(ps) return power_grp # def get_additional_group(self): # og = Group(Item('laser_controller', show_label=False, # editor=InstanceEditor(view='control_view'), # style='custom'), # label='Optics', # ) # ac = Group( # og, # show_border=True, # label='Additional Controls', # layout='tabbed') # # aclist = self.get_additional_controls() # if aclist is None: # og.label = 'Optics' # og.show_border = True # ac = og # else: # for ai in aclist: # ac.content.append(ai) # return ac # def get_control_group(self): # ''' # ''' # power_grp = self.get_power_group() # pulse_grp = Group(Item('pulse', style='custom', show_label=False), # label='Pulse', show_border=True # ) # power_grp = HGroup(power_grp, pulse_grp) # ac = self.get_additional_group() # g = HGroup(power_grp, ac) # # return g def _get_pointer_label(self): """ """ return 'Pointer ON' if not self.pointer_state else 'Pointer OFF' def _get_record_label(self): return 'Record' if not self._recording_power_state else 'Stop' def _get_record_brightness(self): return self.record_brightness and self._get_machine_vision( ) is not None # ========================= defaults ======================= # def get_power_database(self): # from pychron.database.adapters.power_adapter import PowerAdapter # # db = PowerAdapter(name=self.dbname, # kind='sqlite') # return db # def get_power_calibration_database(self): # from pychron.database.adapters.power_calibration_adapter import PowerCalibrationAdapter # # db = PowerCalibrationAdapter(name=self.dbname, # kind='sqlite') # return db # def _subsystem_default(self): # ''' # ''' # return ArduinoSubsystem(name='arduino_subsystem_2') # def _brightness_meter_default(self): # if self.use_video: # b = BrightnessPIDManager(parent=self) # # b.brightness_manager.video = self.stage_manager.video # # return b def _fiber_light_default(self): """ """ return FiberLight(name='fiber_light')
class BaseStageManager(Manager): keyboard_focus = Event canvas_editor_klass = LaserComponentEditor tray_calibration_manager = Instance(TrayCalibrationManager) stage_map_klass = LaserStageMap stage_map_name = Str stage_map_names = List stage_map = Instance(BaseStageMap) canvas = Instance(MapCanvas) root = Str(paths.map_dir) calibrated_position_entry = String(enter_set=True, auto_set=False) move_thread = None temp_position = None temp_hole = None use_modified = Bool(True) # set true to use modified affine calculation def goto_position(self, pos): raise NotImplementedError def refresh_stage_map_names(self): sms = list_directory2(self.root, '.txt', remove_extension=True) us = list_directory2(paths.user_points_dir, '.yaml', remove_extension=True) if us: sms.extend(us) self.stage_map_names = sms def load(self): self.refresh_stage_map_names() psm = self._load_previous_stage_map() sms = self.stage_map_names sm = None if psm in sms: sm = psm elif sms: sm = sms[0] if sm: self.stage_map_name = sm if self.stage_map: self.canvas.set_map(self.stage_map) self.canvas.request_redraw() # self.stage_maps = [] # config = self.get_configuration() # if config: # load the stage maps # mapfiles = self.config_get(config, 'General', 'mapfiles') # self.stage_map_names = mapfiles.split(',') # for mapfile in mapfiles.split(','): # path = os.path.join(paths.map_dir, mapfile.strip()) # sm = StageMap(file_path=path) # sm.load_correction_file() # self.stage_maps.append(sm) # load user points as stage map # for di in os.listdir(paths.user_points_dir): # if di.endswith('.yaml'): # path = os.path.join(paths.user_points_dir, di) # sm = self.stage_map_klass(file_path=path) # self.stage_maps.append(sm) # load the saved stage map # sp = self._get_stage_map_by_name(self._load_previous_stage_map()) # if sp is not None: # sm = sp # self.stage_map_name = sm # load the points file # self.canvas.load_points_file(self.points_file) # load defaults # self._default_z = self.config_get(config, 'Defaults', 'z', default=13, cast='float') # self.canvas.set_map(sm) # self.canvas.request_redraw() def kill(self): r = super(BaseStageManager, self).kill() self._save_stage_map() return r @property def stage_map_path(self): return os.path.join(paths.hidden_dir, '{}.stage_map'.format(self.id)) def canvas_editor_factory(self): return self.canvas_editor_klass(keyboard_focus='keyboard_focus') def move_to_hole(self, hole, **kw): self._move(self._move_to_hole, hole, name='move_to_hole', **kw) def get_calibrated_position(self, pos, key=None): smap = self.stage_map # use a affine transform object to map canvas = self.canvas ca = canvas.calibration_item # check if a calibration applies to this hole hole_calibration = get_hole_calibration(smap.name, key) if hole_calibration: self.debug('Using hole calibration') ca = hole_calibration if ca: rot = ca.rotation cpos = ca.center scale = ca.scale self.debug('Calibration parameters: rot={:0.3f}, cpos={} scale={:0.3f}'.format(rot, cpos, scale)) pos = smap.map_to_calibration(pos, cpos, rot, scale=scale, use_modified=self.use_modified) return pos def update_axes(self): """ """ self.info('querying axis positions') self._update_axes() # private def _update_axes(self): pass def _move_to_hole(self, key, correct_position=True): pass def _stop(self): pass def _move(self, func, pos, name=None, *args, **kw): if pos is None: return if self.move_thread and self.move_thread.isRunning(): self._stop() if name is None: name = func.func_name self.move_thread = Thread(name='stage.{}'.format(name), target=func, args=(pos,) + args, kwargs=kw) self.move_thread.start() def _canvas_factory(self): raise NotImplementedError # handlers def _calibrated_position_entry_changed(self, new): self.debug('User entered calibrated position {}'.format(new)) self.goto_position(new) def _stage_map_name_changed(self, new): if new: self.debug('setting stage map to {}'.format(new)) sm = self.stage_map_klass(file_path=os.path.join(self.root, add_extension(new, '.txt'))) self.stage_map = sm self.tray_calibration_manager.load_calibration(stage_map=new) self.canvas.set_map(sm) self.canvas.request_redraw() # defaults def _tray_calibration_manager_default(self): t = TrayCalibrationManager(parent=self, canvas=self.canvas) return t def _canvas_default(self): return self._canvas_factory() def _save_stage_map(self): p = self.stage_map_path self.info('saving stage_map {} to {}'.format(self.stage_map_name, p)) with open(p, 'wb') as f: pickle.dump(self.stage_map_name, f) def _load_previous_stage_map(self): p = self.stage_map_path if os.path.isfile(p): self.info('loading previous stage map from {}'.format(p)) with open(p, 'rb') as f: try: return pickle.load(f) except pickle.PickleError: pass
class IonOpticsManager(Manager): reference_detector = Instance(BaseDetector) reference_isotope = Any magnet_dac = Range(0.0, 6.0) graph = Instance(Graph) peak_center_button = Button('Peak Center') stop_button = Button('Stop') alive = Bool(False) spectrometer = Any peak_center = Instance(PeakCenter) coincidence = Instance(Coincidence) peak_center_config = Instance(PeakCenterConfigurer) # coincidence_config = Instance(CoincidenceConfig) canceled = False peak_center_result = None _centering_thread = None def close(self): self.cancel_peak_center() def cancel_peak_center(self): self.alive = False self.canceled = True self.peak_center.canceled = True self.peak_center.stop() self.info('peak center canceled') def get_mass(self, isotope_key): spec = self.spectrometer molweights = spec.molecular_weights return molweights[isotope_key] def set_mftable(self, name=None): """ if mt is None set to the default mftable located at setupfiles/spectrometer/mftable.csv :param mt: :return: """ if name and name != os.path.splitext(os.path.basename( paths.mftable))[0]: self.spectrometer.use_deflection_correction = False else: self.spectrometer.use_deflection_correction = True self.spectrometer.magnet.set_mftable(name) def get_position(self, *args, **kw): kw['update_isotopes'] = False return self._get_position(*args, **kw) def position(self, pos, detector, *args, **kw): dac = self._get_position(pos, detector, *args, **kw) mag = self.spectrometer.magnet self.info('positioning {} ({}) on {}'.format(pos, dac, detector)) return mag.set_dac(dac) def do_coincidence_scan(self, new_thread=True): if new_thread: t = Thread(name='ion_optics.coincidence', target=self._coincidence) t.start() self._centering_thread = t def setup_coincidence(self): pcc = self.coincidence_config pcc.dac = self.spectrometer.magnet.dac info = pcc.edit_traits() if not info.result: return detector = pcc.detector isotope = pcc.isotope detectors = [d for d in pcc.additional_detectors] # integration_time = pcc.integration_time if pcc.use_nominal_dac: center_dac = self.get_position(isotope, detector) elif pcc.use_current_dac: center_dac = self.spectrometer.magnet.dac else: center_dac = pcc.dac # self.spectrometer.save_integration() # self.spectrometer.set_integration(integration_time) cs = Coincidence(spectrometer=self.spectrometer, center_dac=center_dac, reference_detector=detector, reference_isotope=isotope, additional_detectors=detectors) self.coincidence = cs return cs def get_center_dac(self, det, iso): spec = self.spectrometer det = spec.get_detector(det) molweights = spec.molecular_weights mass = molweights[iso] dac = spec.magnet.map_mass_to_dac(mass, det.name) # correct for deflection return spec.correct_dac(det, dac) def do_peak_center(self, save=True, confirm_save=False, warn=False, new_thread=True, message='', on_end=None, timeout=None): self.debug('doing pc') self.canceled = False self.alive = True self.peak_center_result = None args = (save, confirm_save, warn, message, on_end, timeout) if new_thread: t = Thread(name='ion_optics.peak_center', target=self._peak_center, args=args) t.start() self._centering_thread = t return t else: self._peak_center(*args) def setup_peak_center(self, detector=None, isotope=None, integration_time=1.04, directions='Increase', center_dac=None, plot_panel=None, new=False, standalone_graph=True, name='', show_label=False, window=0.015, step_width=0.0005, min_peak_height=1.0, percent=80, deconvolve=None, use_interpolation=False, interpolation_kind='linear', dac_offset=None, calculate_all_peaks=False, config_name=None, use_configuration_dac=True): if deconvolve is None: n_peaks, select_peak = 1, 1 if dac_offset is not None: use_dac_offset = True spec = self.spectrometer spec.save_integration() self.debug('setup peak center. detector={}, isotope={}'.format( detector, isotope)) self._setup_config() pcc = None if detector is None or isotope is None: self.debug('ask user for peak center configuration') self.peak_center_config.load(dac=spec.magnet.dac) if config_name: self.peak_center_config.active_name = config_name info = self.peak_center_config.edit_traits() if not info.result: return else: pcc = self.peak_center_config.active_item elif config_name: self.peak_center_config.load(dac=spec.magnet.dac) self.peak_center_config.active_name = config_name pcc = self.peak_center_config.active_item if pcc: if not detector: detector = pcc.active_detectors if not isotope: isotope = pcc.isotope directions = pcc.directions integration_time = pcc.integration_time window = pcc.window min_peak_height = pcc.min_peak_height step_width = pcc.step_width percent = pcc.percent use_interpolation = pcc.use_interpolation interpolation_kind = pcc.interpolation_kind n_peaks = pcc.n_peaks select_peak = pcc.select_n_peak use_dac_offset = pcc.use_dac_offset dac_offset = pcc.dac_offset calculate_all_peaks = pcc.calculate_all_peaks if center_dac is None and use_configuration_dac: center_dac = pcc.dac spec.set_integration_time(integration_time) period = int(integration_time * 1000 * 0.9) if not isinstance(detector, (tuple, list)): detector = (detector, ) ref = detector[0] ref = self.spectrometer.get_detector(ref) self.reference_detector = ref self.reference_isotope = isotope if center_dac is None: center_dac = self.get_center_dac(ref, isotope) if len(detector) > 1: ad = detector[1:] else: ad = [] pc = self.peak_center if not pc or new: pc = PeakCenter() pc.trait_set(center_dac=center_dac, period=period, window=window, percent=percent, min_peak_height=min_peak_height, step_width=step_width, directions=directions, reference_detector=ref, additional_detectors=ad, reference_isotope=isotope, spectrometer=spec, show_label=show_label, use_interpolation=use_interpolation, interpolation_kind=interpolation_kind, n_peaks=n_peaks, select_peak=select_peak, use_dac_offset=use_dac_offset, dac_offset=dac_offset, calculate_all_peaks=calculate_all_peaks) self.peak_center = pc graph = pc.graph graph.name = name if plot_panel: plot_panel.set_peak_center_graph(graph) else: graph.close_func = self.close if standalone_graph: # set graph window attributes graph.window_title = 'Peak Center {}({}) @ {:0.3f}'.format( ref, isotope, center_dac) graph.window_width = 300 graph.window_height = 250 open_view(graph) return self.peak_center # private def _setup_config(self): config = self.peak_center_config config.detectors = self.spectrometer.detector_names keys = self.spectrometer.molecular_weights.keys() config.isotopes = sort_isotopes(keys) def _get_peak_center_config(self, config_name): if config_name is None: config_name = 'default' config = self.peak_center_config.get(config_name) config.detectors = self.spectrometer.detectors_names if config.detector_name: config.detector = next( (di for di in config.detectors if di == config.detector_name), None) if not config.detector: config.detector = config.detectors[0] keys = self.spectrometer.molecular_weights.keys() config.isotopes = sort_isotopes(keys) return config def _timeout_func(self, timeout, evt): st = time.time() while not evt.is_set(): if not self.alive: break if time.time() - st > timeout: self.warning( 'Peak Centering timed out after {}s'.format(timeout)) self.cancel_peak_center() break time.sleep(0.01) def _peak_center(self, save, confirm_save, warn, message, on_end, timeout): pc = self.peak_center spec = self.spectrometer ref = self.reference_detector isotope = self.reference_isotope if timeout: evt = Event() self.timeout_thread = Thread(target=self._timeout_func, args=(timeout, evt)) self.timeout_thread.start() dac_d = pc.get_peak_center() self.peak_center_result = dac_d if dac_d: args = ref, isotope, dac_d self.info('new center pos {} ({}) @ {}'.format(*args)) det = spec.get_detector(ref) dac_a = spec.uncorrect_dac(det, dac_d) self.info('dac uncorrected for HV and deflection {}'.format(dac_a)) if save: if confirm_save: msg = 'Update Magnet Field Table with new peak center- {} ({}) @ RefDetUnits= {}'.format( *args) save = self.confirmation_dialog(msg) if save: spec.magnet.update_field_table(det, isotope, dac_a, message) spec.magnet.set_dac(dac_d) elif not self.canceled: msg = 'centering failed' if warn: self.warning_dialog(msg) self.warning(msg) # needs to be called on the main thread to properly update # the menubar actions. alive=False enables IonOptics>Peak Center # d = lambda:self.trait_set(alive=False) # still necessary with qt? and tasks if on_end: on_end() self.trait_set(alive=False) if timeout: evt.set() self.spectrometer.restore_integration() def _get_position(self, pos, detector, use_dac=False, update_isotopes=True): """ pos can be str or float "Ar40", "39.962", 39.962 to set in DAC space set use_dac=True """ if pos == NULL_STR: return spec = self.spectrometer mag = spec.magnet if isinstance(detector, str): det = spec.get_detector(detector) else: det = detector self.debug('detector {}'.format(det)) if use_dac: dac = pos else: self.debug('POSITION {} {}'.format(pos, detector)) if isinstance(pos, str): try: pos = float(pos) except ValueError: # pos is isotope if update_isotopes: # if the pos is an isotope then update the detectors spec.update_isotopes(pos, detector) pos = self.get_mass(pos) mag.mass_change(pos) # pos is mass i.e 39.962 dac = mag.map_mass_to_dac(pos, det.name) dac = spec.correct_dac(det, dac) return dac def _coincidence(self): self.coincidence.get_peak_center() self.info('coincidence finished') self.spectrometer.restore_integration() # =============================================================================== # handler # =============================================================================== def _coincidence_config_default(self): config = None p = os.path.join(paths.hidden_dir, 'coincidence_config.p') if os.path.isfile(p): try: with open(p) as rfile: config = pickle.load(rfile) config.detectors = dets = self.spectrometer.detectors config.detector = next( (di for di in dets if di.name == config.detector_name), None) except Exception, e: print 'coincidence config', e if config is None: config = CoincidenceConfig() config.detectors = self.spectrometer.detectors config.detector = config.detectors[0] keys = self.spectrometer.molecular_weights.keys() config.isotopes = sort_isotopes(keys) return config