| # -*- 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__) |