FView plugin tutorial: change-based triggering of an external device

This tutorial illustrates the steps necessary to trigger an external device based on some change in the image. Specifically, we will generate a pulse on the CamTrig device when luminance changes more than a specified amount.

_images/fview-tutorial-change-screenshot.png

A working copy of this code can be found at http://github.com/motmot/fview_change_trigger/

Prerequisites

Introduction

From FView plugin tutorial: live histogram of image intensity values, you should have a working knowledge of how to create a Python package directory structure and a setup.py file with the appropriate entry point to make an FView plugin. Therefore, this tutorial will focus only on the unique aspects of change-based triggering.

Create the plugin logic

Now we’re going to create the module motmot.fview_change_trigger.fview_change_trigger with our new class FviewChangeTrigger. Open a new file named:

base/motmot/fview_change_trigger/fview_change_trigger.py

The contents of this file:

from __future__ import with_statement, division

import pkg_resources
import warnings, threading
import enthought.traits.api as traits

import motmot.fview.traited_plugin as traited_plugin
import motmot.fview_ext_trig.ttrigger as ttrigger
import numpy as np

from enthought.traits.ui.api import View, Item, Group

class FviewChangeTrigger(traited_plugin.HasTraits_FViewPlugin):
    plugin_name = 'change detecting trigger'

    trigger_device = traits.Instance(ttrigger.DeviceModel)
    enabled = traits.Bool(False)

    capture_background = traits.Button
    _capture_background_notify = traits.Bool(False) # pass value to realtime thread

    difference_mode = traits.Trait( 'darker', 'lighter', 'any change' )

    draw_roi_box = traits.Bool(False)

    roi_left = traits.Int(-1)
    roi_bottom = traits.Int(-1)
    roi_width = traits.Int(-1)
    roi_height = traits.Int(-1)

    threshold_value = traits.Float
    difference_value = traits.Float(5)

    # Store some values about the camera
    camera_cam_id = traits.String(transient=True)
    camera_max_width = traits.Int(transient=True)
    camera_max_height = traits.Int(transient=True)

    traits_view = View(Group(Item(name='enabled'),
                             Item(name='difference_mode'),
                             Item(name='capture_background',
                                  label='set threshold from image',
                                  show_label=False),
                             Item(name='threshold_value'),
                             Item(name='difference_value'),
                             Item(name='draw_roi_box'),
                             Group(Item(name='roi_left'),
                                   Item(name='roi_bottom'),
                                   Item(name='roi_width'),
                                   Item(name='roi_height'),
                                   )))

    def __init__(self,*args,**kwargs):
        super(FviewChangeTrigger,self).__init__(*args,**kwargs)

    def set_all_fview_plugins(self,plugins):
        """Get reference to 'FView external trigger' plugin"""

        # This method is called by FView to let plugins know about
        # each other.

        for plugin in plugins:
            if plugin.get_plugin_name()=='FView external trigger':
                self.trigger_device = plugin.trigger_device
        if self.trigger_device is None:
            raise RuntimeError('this plugin requires "FView external trigger"')

    def _capture_background_fired(self):
        self._capture_background_notify = True

    def camera_starting_notification(self,cam_id,
                                     pixel_format=None,
                                     max_width=None,
                                     max_height=None):
        if self.camera_cam_id != '':
            warnings.warn('FviewChangeTrigger only supports one camera')
            return
        self.camera_cam_id = cam_id
        self.camera_max_width = max_width
        self.camera_max_height = max_height

        # default margin ( in pixels )
        margin = 10
        if self.roi_left==-1:
            self.roi_left = margin
        if self.roi_bottom==-1:
            self.roi_bottom = margin
        if self.roi_width==-1:
            self.roi_width= self.camera_max_width-self.roi_left-margin
        if self.roi_height==-1:
            self.roi_height= self.camera_max_height-self.roi_bottom-margin

    def process_frame(self,cam_id,buf,buf_offset,timestamp,framenumber):
        draw_points = []
        draw_linesegs = []

        if cam_id != self.camera_cam_id:
            return draw_points, draw_linesegs

        l = self.roi_left
        r = l + self.roi_width
        b = self.roi_bottom
        t = b + self.roi_height

        if self.draw_roi_box:
            draw_linesegs.extend( [ (l,b,l,t),
                                    (l,t,r,t),
                                    (r,t,r,b),
                                    (r,b,l,b) ])

        npbuf = np.asarray(buf) # make sure it's a numpy array
        assert buf_offset==(0,0)
        roi_buf = npbuf[b:t,l:r]

        if self._capture_background_notify:
            self._capture_background_notify = False
            self.threshold_value = np.mean(roi_buf)

        # turn of LED from any previous runs
        self.trigger_device.led1 = False

        if self.enabled:
            current_value = np.mean(roi_buf)
            fire_trigger = False
            if self.difference_mode == 'darker':
                if (self.threshold_value - current_value) > self.difference_value:
                    fire_trigger = True
            elif self.difference_mode == 'lighter':
                if (current_value - self.threshold_value) > self.difference_value:
                    fire_trigger = True
            elif self.difference_mode == 'any change':
                if abs(current_value - self.threshold_value) > self.difference_value:
                    fire_trigger = True
            else:
                raise ValueError('unknown difference_mode')

            if fire_trigger:
                # fire pulse on EXT_TRIG1
                self.trigger_device.ext_trig1 = True

                # toggle LED
                self.trigger_device.led1 = True

        return draw_points, draw_linesegs

A description of the above should go here.