This tutorial illustrates the steps necessary to compute and display a live histogram of image intensity values.
A working copy of this code can be found at http://github.com/motmot/fview_histogram/
- Chaco installed (see the Chaco docs for more information).
Your plugin will be a standard Python package. Create the following directories (base should be your base directory, such as “fview_histogram”):
base
base/motmot
base/motmot/fview_histogram
And create the following empty files:
base/motmot/__init__.py
base/motmot/fview_histogram/__init__.py
Now, edit base/setup.py to contain the following (modify as necessary):
from setuptools import setup, find_packages
import sys,os
setup(name='motmot.fview_histogram',
description='live histogram plugin for FView',
version='0.0.1',
packages = find_packages(),
author='Andrew Straw',
author_email='strawman@astraw.com',
url='http://code.astraw.com/projects/motmot',
entry_points = {
'motmot.fview.plugins':'fview_histogram = motmot.fview_histogram.fview_histogram:FviewHistogram',
},
)
This is a standard setuptools file for distributing and installing Python packages that tells Python which files to install and some associated meta-data.
The packages = find_packages() line tells setuptools to look for standard Python packages (directories with an __init__.py file). Because you just created these directories and files in Step 1, setuptools automatically knows these directories contain the files to be installed.
The entry_points line tells setuptools that we want to register a plugin. Our plugin is registered under the motmot.fview.plugins key. FView inquires for any plugins under this key. This particular plugin is called fview_histogram. It is defined in the module motmot.fview_histogram.fview_histogram and the class FviewHistogram, which we create below.
Now we’re going to create the module motmot.fview_histogram.fview_histogram with our new class FviewHistogram. Open a new file named:
base/motmot/fview_histogram/fview_histogram.py
The contents of this file:
import motmot.fview.traited_plugin as traited_plugin
class FviewHistogram(traited_plugin.HasTraits_FViewPlugin):
plugin_name = 'image histogram'
def camera_starting_notification(self,cam_id,
pixel_format=None,
max_width=None,
max_height=None):
# This function gets called from FView when a camera is
# initialized.
return
def process_frame(self,cam_id,buf,buf_offset,timestamp,framenumber):
draw_points = []
draw_linesegs = []
# This function gets called from FView immediately after
# acquisition of each frame. Implement your image processing
# logic here.
return draw_points, draw_linesegs
From here, we’re going to fill in the relevant parts with the code that our plugin executes. The most important function is process_frame(). This function is called by FView immediately after every frame is acquired from the camera. The arguments to the function contain information about this latest frame, and the return values are used to plot points and line segments on the main FView display. For the live histogram, we don’t need to display anything, so we just return empty lists.
The contents of this file:
import warnings, time
import enthought.traits.api as traits
import motmot.fview.traited_plugin as traited_plugin
import numpy as np
from enthought.traits.ui.api import View, Item, Group
from enthought.chaco.chaco_plot_editor import ChacoPlotItem
# For a tutorial on Chaco and Traits, see
# http://code.enthought.com/projects/chaco/docs/html/user_manual/tutorial_2.html
class FviewHistogram(traited_plugin.HasTraits_FViewPlugin):
plugin_name = 'image histogram'
update_interval_msec = traits.Int(100)
# The following traits are "transient" -- do not attempt to make
# state persist across multiple runs of the application.
intensity = traits.Array(dtype=np.float,transient=True)
data = traits.Array(dtype=np.float,transient=True)
pixel_format = traits.String(None,transient=True)
last_update_time = traits.Float(-np.inf,transient=True)
# Define the view using Traits UI
traits_view = View(
Group(
ChacoPlotItem('intensity','data',
x_label = 'intensity',
y_label = '',
show_label=False,
y_auto=True,
resizable=True,
title = 'Image intensity histogram',
),
),
resizable=True,
)
def camera_starting_notification(self,cam_id,
pixel_format=None,
max_width=None,
max_height=None):
self.pixel_format = pixel_format
self.intensity = np.linspace(0,255,50) # 50 bins from 0-255
def process_frame(self,cam_id,buf,buf_offset,timestamp,framenumber):
draw_points = []
draw_linesegs = []
if self.frame.IsShown():
now = time.time()
# Throttle the computation and display to happen only
# occasionally. The display of the histogram, especially,
# is computationally intensive.
if (now - self.last_update_time)*1000.0 > self.update_interval_msec:
npbuf = np.array(buf)
if self.pixel_format == 'MONO8':
self.data, edges = np.histogram(npbuf,
bins=self.intensity,
new=True)
else:
warnings.warn("histogram for %s format not implemented"%(
self.pixel_format,))
self.last_update_time = now
return draw_points, draw_linesegs
As implemented above, the live plot of the histogram is automatically updated whenever the self.data attribute changes. While this is convenient, there’s a problem with this. The process_frame() method is called in the camera acquisition and processing thread, and any computationally expensive process will slow down this thread. Drawing auto-scaled plots certainly qualifies as computationally intensive. Therefore, it would be better to calculate the histogram values as done here, but then to send them to another thread for display in the GUI. This would allow the image acquisition thread to operate unimpeded, but would require multi-threaded programming, which is beyond the scope of this tutorial.