blob: 99f2a7f9989da7b3fe2ce371db6dbcc4bd3892bd [file] [log] [blame]
# -*- coding: utf-8 -*-
#
# Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
# DESCRIPTION :
#
# This test searches for a v4l2 video capture device, and starts streaming
# captured frames on the monitor.
# The observer then decides if the captured image looks good or defective,
# pressing enter key to let it pass or tab key to fail.
#
# Then the test will start to test the LED indicator located near the webcam.
# The LED test will be repeated for a fixed number (=5 at time of writing)
# of rounds, each round it will randomly decide whether to capture from the
# cam (the LED turns on when capturing). The captured image will NOT be
# shown on the monitor, so the observer must answer what he really sees.
# The test passes only if the answer for all rounds are correct.
# The current configuration of buildbot will try to compile Python
# files for the remote test purpose. Since this is done on the host,
# it can't use any library that is not installed there even if the
# library might be available on the target. We currently do not have
# OpenCV on the host so we have to try-catch the import in order to
# avoid the compilation error.
#
# TODO: Fix it either when we have OpenCV on the host or the build
# configuration for Python files in the autotest changes.
try:
import cv
import cv2
except ImportError:
# We can't raise error because it will fail the interpreter.
pass
import gtk
import glib
import pango
import numpy
import time
import os
from gtk import gdk
from random import randrange
from autotest_lib.client.cros import factory
from autotest_lib.client.cros.factory import ui as ful
from autotest_lib.client.bin import test
from autotest_lib.client.common_lib import error
# OpenCV will automatically search for a working camera device if we use the
# index -1.
DEVICE_INDEX = -1
PREFERRED_FPS = 30
PREFERRED_INTERVAL = int(round(1000.0 / PREFERRED_FPS))
FPS_UPDATE_FACTOR = 0.1
GDK_PIXBUF_BIT_PER_SAMPLE = 8
KEY_GOOD = gdk.keyval_from_name('Return')
KEY_BAD = gdk.keyval_from_name('Tab')
LABEL_FONT = pango.FontDescription('courier new condensed 16')
MESSAGE_STR = ('hit TAB to fail and ENTER to pass\n' +
'錯誤請按 TAB,成功請按 ENTER\n')
MESSAGE_STR2 = ('hit TAB if the LED is off and ENTER if the LED is on\n' +
'請檢查攝像頭 LED 指示燈, 沒亮請按 TAB, 燈亮請按 ENTER\n')
class factory_Camera(test.test):
version = 1
def key_release_callback(self, widget, event):
factory.log('key_release_callback %s(%s)' %
(event.keyval, gdk.keyval_name(event.keyval)))
if event.keyval == KEY_GOOD or event.keyval == KEY_BAD:
if self.stage == 0:
self.capture_stop()
if event.keyval == KEY_BAD:
gtk.main_quit()
self.img.hide()
self.label.set_text(MESSAGE_STR2)
else:
if self.ledstats & 1:
self.capture_stop()
if bool(self.ledstats & 1) != (event.keyval == KEY_GOOD):
self.ledfail = True
self.ledstats >>= 1
if self.stage == self.led_rounds:
self.fail = False
gtk.main_quit()
self.stage += 1
if self.ledstats & 1:
self.capture_start()
self.label.hide()
glib.timeout_add(1000, lambda *x: self.label.show())
return True
def capture_core(self):
'''Captures an image and displays it
The FPS is determined by the camera hardware limit, the gtk display
overhead and the amount of memory copy operations. This subroutine
involves 3 copy operations of image data which usually takes less than
10 ms on an average machine.
'''
# Read image from camera.
ret, cvImg = self.dev.read()
if not ret:
raise IOError("Error while capturing. Camera disconnected?")
# Convert from BGR to RGB in-place.
cv2.cvtColor(cvImg, cv.CV_BGR2RGB, cvImg)
# Convert to gdk pixbuf format.
pbuf = gdk.pixbuf_new_from_data(cvImg.data,
gdk.COLORSPACE_RGB, False, GDK_PIXBUF_BIT_PER_SAMPLE,
cvImg.shape[1], cvImg.shape[0], cvImg.strides[0])
# Copy to the display buffer.
pbuf.copy_area(0, 0, pbuf.get_width(), pbuf.get_height(), self.pixbuf,
0, 0)
# Queue for refreshing.
self.img.queue_draw()
# Update FPS if required.
if self.show_fps:
current_time = time.clock()
self.current_fps = (self.current_fps * (1 - FPS_UPDATE_FACTOR) +
1.0 / (current_time - self.last_capture_time) *
FPS_UPDATE_FACTOR)
self.last_capture_time = current_time
self.label.set_text(MESSAGE_STR2 +
'FPS = ' + '%.2f\n' % self.current_fps)
return True
def register_callbacks(self, w):
w.connect('key-release-event', self.key_release_callback)
w.add_events(gdk.KEY_RELEASE_MASK)
def capture_start(self):
# Register the image capturing subroutine using glib.
# It will be called every PREFERRED_INTERVAL time.
self.gio_tag = glib.timeout_add( PREFERRED_INTERVAL,
lambda *x:self.capture_core(),
priority=glib.PRIORITY_LOW)
def capture_stop(self):
# Unregister the image capturing subroutine.
glib.source_remove(self.gio_tag)
def run_once(self,
led_rounds=1, show_fps=False, single_shot=False):
'''Run the camera test
Parameter
led_rounds: 0 to disable the LED test,
1 to check if the LED turns on,
2 or higher to have multiple random turn on/off
(at least one on round and one off round is guranteed)
'''
factory.log('%s run_once' % self.__class__)
self.fail = True
self.ledfail = False
self.led_rounds = led_rounds
self.ledstats = 0
if led_rounds == 1:
# Always on if only one round.
self.ledstats = 1
elif led_rounds > 1:
# Ensure one on round and one off round.
self.ledstats = randrange(2 ** led_rounds - 2) + 1
self.show_fps = show_fps
self.stage = 0
self.label = label = gtk.Label(MESSAGE_STR)
label.modify_font(LABEL_FONT)
label.modify_fg(gtk.STATE_NORMAL, gdk.color_parse('light green'))
test_widget = gtk.VBox()
test_widget.modify_bg(gtk.STATE_NORMAL, gdk.color_parse('black'))
test_widget.add(label)
self.test_widget = test_widget
self.img = None
# Initialize the camera with OpenCV. Since it's not too smart
# about finding the device, try to find the device for it. If
# multiple devices are present, this grabs the last one.
uvc_viddir = '/sys/bus/usb/drivers/uvcvideo'
for uvc_direntry in os.listdir(uvc_viddir):
if uvc_direntry[0].isdigit():
uvc_subdir = os.path.join(uvc_viddir, uvc_direntry,
'video4linux')
if not os.path.isdir(uvc_subdir):
continue
for uvc_devname in os.listdir(uvc_subdir):
if uvc_devname.startswith('video'):
DEVICE_INDEX = int(uvc_devname[5:])
self.dev = dev = cv2.VideoCapture(DEVICE_INDEX)
if not dev.isOpened():
raise IOError('Device #%s ' % DEVICE_INDEX +
'does not support video capture interface')
if single_shot:
# Read image from camera.
ret, cvImg = self.dev.read()
if not ret:
raise IOError("Error while capturing. Camera disconnected?")
else:
width, height = (dev.get(cv.CV_CAP_PROP_FRAME_WIDTH),
dev.get(cv.CV_CAP_PROP_FRAME_HEIGHT))
# Initialize the canvas.
self.pixbuf = gdk.Pixbuf(gdk.COLORSPACE_RGB, False, 8,
width, height)
self.img = gtk.image_new_from_pixbuf(self.pixbuf)
self.test_widget.add(self.img)
self.img.show()
if self.show_fps:
self.last_capture_time = time.clock()
self.current_fps = PREFERRED_FPS
self.capture_start()
ful.run_test_widget(self.job, test_widget,
window_registration_callback=self.register_callbacks)
if self.fail:
raise error.TestFail('Camera test failed by user ' \
'indication\n品管人員懷疑攝影' \
'鏡頭故障,請檢修')
if self.ledfail:
raise error.TestFail('Camera LED test failed\n' \
'攝影鏡頭 LED 測試不通過,' \
'請檢修')
factory.log('%s run_once finished' % self.__class__)