Example #1
0
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()
Example #2
0
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')
Example #3
0
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')