factory: Migrate camera/ALS test to Chrome UI
The CL migrates the camera performance and ALS calibration test
to the HTML5 based UI. Other improvements include:
- Draw the pattern registration result with the captured image
so that the operator could be more informed about the test
status.
- Dump the ALS calibration data to vpd in the FATP test.
- Add ALS value sanity checks.
BUG=chrome-os-partner:10321
TEST=Tested on real devices -> works.
Change-Id: I7eb25f29d9ab208c2bac6ef9823f2e21dc1220f6
Reviewed-on: https://gerrit.chromium.org/gerrit/30153
Reviewed-by: Tai-Hsu Lin <sheckylin@chromium.org>
Tested-by: Tai-Hsu Lin <sheckylin@chromium.org>
diff --git a/client/cros/camera/renderer.py b/client/cros/camera/renderer.py
new file mode 100755
index 0000000..c9cd605
--- /dev/null
+++ b/client/cros/camera/renderer.py
@@ -0,0 +1,100 @@
+# Copyright (c) 2012 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.
+
+# Import guard for OpenCV.
+try:
+ import cv
+ import cv2
+except ImportError:
+ pass
+
+import math
+import numpy as np
+
+# Color constants.
+_COLORS = {
+ 'important': (0, 0, 255),
+ 'corner': (0, 255, 255),
+ 'success': (0, 255, 0),
+ 'deviation': (255, 0, 255),
+ }
+
+
+def _DrawLineByAngle(img, origin, length, angle, color,
+ thickness):
+ '''Draw a line by specifying the origin, length and angle.'''
+ dx1 = length * math.cos(angle)
+ dy1 = length * math.sin(angle)
+ cv2.line(img, origin, (origin[0] + dx1, origin[1] + dy1), color,
+ thickness)
+
+
+def _DrawArrowTip(img, tip, direction, tip_length, tip_angle, color,
+ thickness):
+ '''Draw an arrow tip.'''
+ theta1 = math.radians(direction + 180 + tip_angle)
+ _DrawLineByAngle(img, tip, tip_length, theta1, color, thickness)
+ theta2 = math.radians(direction + 180 - tip_angle)
+ _DrawLineByAngle(img, tip, tip_length, theta2, color, thickness)
+
+
+def _DrawArcWithArrow(img, center, radius, start_angle, delta_angle,
+ tip_length, tip_angle, color, thickness):
+ '''Draw an arc with an arrow tip on one end.'''
+ # Draw arc.
+ cv2.ellipse(img, center, (radius, radius), start_angle,
+ (0 if delta_angle > 0 else -delta_angle),
+ (-delta_angle if delta_angle > 0 else 0), color, thickness)
+
+ # Draw arrow tip.
+ # The minus sign is because the y axis is reversed for the actual image.
+ tip_point_angle = -(start_angle + delta_angle)
+ tip = (center[0] + radius * math.cos(math.radians(tip_point_angle)),
+ center[1] + radius * math.sin(math.radians(tip_point_angle)))
+ _DrawArrowTip(img, tip,
+ tip_point_angle - (90 if delta_angle > 0 else -90),
+ tip_length, tip_angle, color, thickness)
+
+
+def DrawVC(img, success, result):
+ '''Draw the result of the visual correctness test on the test image.'''
+ if hasattr(result, 'sample_corners'):
+ # Draw all corners.
+ for point in result.sample_corners:
+ cv2.circle(img, (point[0], point[1]), 2, _COLORS['corner'],
+ thickness=-1)
+
+ if hasattr(result, 'shift'):
+ # Draw the four corners of the corner grid.
+ for point in result.four_corners:
+ cv2.circle(img, (point[0], point[1]), 4,
+ _COLORS[('success' if success else 'important')],
+ thickness = -1)
+
+ # Draw the center and the shift vector.
+ center = ((img.shape[1] - 1) / 2.0, (img.shape[0] - 1) / 2.0)
+ tip = np.array(center) + result.v_shift
+ cv2.line(img, center, (tip[0], tip[1]),
+ _COLORS['deviation'], thickness=2)
+ diag_len = math.sqrt(img.shape[0] ** 2 + img.shape[1] ** 2)
+ angle = math.atan2(result.v_shift[1], result.v_shift[0])
+ _DrawArrowTip(img, (tip[0], tip[1]), math.degrees(angle),
+ result.shift * diag_len * 0.3, 60,
+ _COLORS['deviation'], thickness=2)
+ cv2.circle(img, center, 4,
+ _COLORS[('success' if success else 'important')],
+ thickness=-1)
+
+ # Draw the rotation indicator.
+ radius = max(img.shape) / 4
+ # Boost the amount so it is more easily visible.
+ angle = max(-90, min(90, result.tilt * 10))
+ tip_length = abs(math.radians(angle)) * radius * 0.3
+ tip_angle = 60
+ _DrawArcWithArrow(img, center, radius, 0, angle,
+ tip_length, tip_angle,
+ _COLORS['deviation'], thickness=2)
+ _DrawArcWithArrow(img, center, radius, 180, angle,
+ tip_length, tip_angle,
+ _COLORS['deviation'], thickness=2)
diff --git a/client/site_tests/factory_CameraPerformanceAls/factory_CameraPerformanceAls.py b/client/site_tests/factory_CameraPerformanceAls/factory_CameraPerformanceAls.py
index 1e52b3a..137de39 100644
--- a/client/site_tests/factory_CameraPerformanceAls/factory_CameraPerformanceAls.py
+++ b/client/site_tests/factory_CameraPerformanceAls/factory_CameraPerformanceAls.py
@@ -3,8 +3,6 @@
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
-# TODO(sheckylin): Refactor the code with the new HTML5 framework.
-
# Import guard for OpenCV.
try:
import cv
@@ -12,65 +10,32 @@
except ImportError:
pass
-import gtk
-import logging
+import base64
import numpy as np
import os
import pprint
+import pyudev
import re
+import select
import serial
import StringIO
+import subprocess
+import threading
import time
import autotest_lib.client.cros.camera.perf_tester as camperf
+import autotest_lib.client.cros.camera.renderer as renderer
from autotest_lib.client.bin import test
from autotest_lib.client.common_lib import error
from autotest_lib.client.cros import factory_setup_modules
from cros.factory.test import factory
-from cros.factory.test import ui as ful
from cros.factory.test import leds
-from cros.factory.test.media_util import MediaMonitor
from cros.factory.test.media_util import MountedMedia
from autotest_lib.client.cros.rf.config import PluggableConfig
from autotest_lib.client.cros import tty
+from cros.factory.test.test_ui import UI
-_MESSAGE_USB = (
- 'Please insert the usb stick to load parameters.\n'
- '請插入usb以讀取測試參數\n')
-_MESSAGE_PREPARE_MACHINE = (
- 'Please put the machine in the fixture and connect the keyboard.\n'
- 'Then press ENTER.\n'
- '請將待測機器放入盒中並連接鍵盤\n'
- '備妥後按ENTER\n')
-_MESSAGE_PREPARE_PANEL = (
- 'Please connect the next AB panel.\n'
- 'Then press ENTER to scan the barcode.\n'
- '請連接下一塊AB Panel\n'
- '備妥後按ENTER掃描序號\n')
-_MESSAGE_PREPARE_CAMERA = (
- 'Make sure the camera is connected\n'
- 'Then press ENTER to proceed, TAB to skip.\n'
- '確定 攝像頭 連接完成\n'
- '備妥後按ENTER繼續, 或按TAB跳過\n')
-_MESSAGE_PREPARE_ALS = (
- 'Make sure the light sensor is connected\n'
- 'Then press ENTER to proceed, TAB to skip.\n'
- '確定 光感測器 連接完成\n'
- '備妥後按ENTER繼續, 或按TAB跳過\n')
-_MESSAGE_RESULT_TAB_ABONLY = (
- 'Results are listed below.\n'
- 'Please disconnect the panel and press ENTER to write log.\n'
- '測試結果顯示如下\n'
- '請將AB Panel移除, 並按ENTER寫入測試結果\n')
-_MESSAGE_RESULT_TAB_FULL = (
- 'Results are listed below.\n'
- 'Please disconnect the machine and press ENTER to write log.\n'
- '測試結果顯示如下\n'
- '請將測試機器移除, 並按ENTER寫入測試結果\n')
-
-_TEST_SN_NUMBER = 'TEST-SN-NUMBER'
-_LABEL_SIZE = (300, 30)
# Test type constants:
_TEST_TYPE_AB = 'AB'
@@ -80,19 +45,6 @@
_CONTENT_IMG = 'image'
_CONTENT_TXT = 'text'
-def make_prepare_widget(message, on_key_enter, on_key_tab=None):
- """Returns a widget that display the message and bind proper functions."""
- widget = gtk.VBox()
- widget.add(ful.make_label(message))
- def key_release_callback(widget, event):
- if event.keyval == gtk.keysyms.Tab:
- if on_key_tab is not None:
- return on_key_tab()
- elif event.keyval == gtk.keysyms.Return:
- return on_key_enter()
- widget.key_callback = key_release_callback
- return widget
-
class ALS():
'''Class to interface the ambient light sensor over iio.'''
@@ -114,6 +66,12 @@
self.val_path = val_path
self.scale_path = scale_path
+ def _read_core(self):
+ fd = open(self.val_path)
+ val = int(fd.readline().rstrip())
+ fd.close()
+ return val
+
def _read(self, delay=None, samples=1):
'''Read the light sensor value.
@@ -131,11 +89,13 @@
delay = self._DEFAULT_MIN_DELAY
buf = []
+ # The first value might be contaminated by previous settings.
+ # We need to skip it for better accuracy.
+ self._read_core()
for dummy in range(samples):
- fd = open(self.val_path)
- buf.append(int(fd.readline().rstrip()))
- fd.close()
time.sleep(delay)
+ val = self._read_core()
+ buf.append(val)
return buf
@@ -144,7 +104,16 @@
return None
buf = self._read(delay, samples)
- return sum(buf) / len(buf)
+ return int(round(float(sum(buf)) / len(buf)))
+
+ def set_scale_factor(self, scale):
+ if not self.detected:
+ return None
+
+ fd = open(self.scale_path, 'w')
+ fd.write(str(int(round(scale))))
+ fd.close()
+ return
def get_scale_factor(self):
if not self.detected:
@@ -204,8 +173,46 @@
time.sleep(self.light_delay)
+class ConnectionMonitor():
+ """A wrapper to monitor hardware plug/unplug events."""
+ def __init__(self):
+ self._monitoring = False
+
+ def start(self, subsystem, device_type=None, on_insert=None,
+ on_remove=None):
+ if self._monitoring:
+ raise Exception("Multiple start() call is not allowed")
+ self.on_insert = on_insert
+ self.on_remove = on_remove
+
+ # Setup the media monitor,
+ context = pyudev.Context()
+ self.monitor = pyudev.Monitor.from_netlink(context)
+ self.monitor.filter_by(subsystem, device_type)
+ self.monitor.start()
+ self._monitoring = True
+ self._watch_thread = threading.Thread(target=self.watch)
+ self._watch_end = threading.Event()
+ self._watch_thread.start()
+
+ def watch(self):
+ fd = self.monitor.fileno()
+ while not self._watch_end.isSet():
+ ret, _, _ = select.select([fd],[],[])
+ if fd in ret:
+ action, dev = self.monitor.receive_device()
+ if action == 'add' and self.on_insert:
+ self.on_insert(dev.device_node)
+ elif action == 'remove' and self.on_remove:
+ self.on_remove(dev.device_node)
+
+ def stop(self):
+ self._monitoring = False
+ self._watch_end.set()
+
+
class factory_CameraPerformanceAls(test.test):
- version = 1
+ version = 2
preserve_srcdir = True
# OpenCV will automatically search for a working camera device if we use
@@ -214,54 +221,72 @@
_TEST_CHART_FILE = 'test_chart.png'
_TEST_SAMPLE_FILE = 'sample.png'
- # States for the state machine.
- _STATE_INITIAL = -1
- _STATE_WAIT_USB = 0
- _STATE_PREPARE_MACHINE = 1
- _STATE_ENTERING_SN = 2
- _STATE_PREPARE_CAMERA = 3
- _STATE_PREPARE_ALS = 4
- _STATE_RESULT_TAB = 5
+ _PACKET_SIZE = 65000
# Status in the final result tab.
- _STATUS_NAMES = ['sn', 'cam_stat', 'cam_vc', 'cam_ls', 'cam_mtf',
+ _STATUS_NAMES = ['cam_stat', 'cam_vc', 'cam_ls', 'cam_mtf',
'als_stat', 'result']
- _STATUS_LABELS = ['Serial Number',
- 'Camera Functionality',
+ _STATUS_LABELS = ['Camera Functionality',
'Camera Visual Correctness',
'Camera Lens Shading',
'Camera Image Sharpness',
'ALS Functionality',
'Test Result']
+ _CAM_TESTS = ['cam_stat', 'cam_vc', 'cam_ls', 'cam_mtf']
+ _ALS_TESTS = ['als_stat']
# LED patterns.
- _LED_PREPARE_CAM_TEST = ((leds.LED_NUM, 0.25), (0, 0.25))
- _LED_RUNNING_CAM_TEST = ((leds.LED_NUM, 0.05), (0, 0.05))
- _LED_PREPARE_ALS_TEST = ((leds.LED_NUM|leds.LED_CAP, 0.25),
- (leds.LED_NUM, 0.25))
- _LED_RUNNING_ALS_TEST = ((leds.LED_NUM|leds.LED_CAP, 0.05),
- (leds.LED_NUM, 0.05))
- _LED_FINISHED_ALL_TEST = ((leds.LED_NUM|leds.LED_CAP, 0.25),
- (leds.LED_NUM|leds.LED_CAP, 0.25))
+ _LED_RUNNING_TEST = ((leds.LED_NUM|leds.LED_CAP, 0.05), (0, 0.05))
- def advance_state(self):
- if self.type == _TEST_TYPE_FULL:
- self._state = self._state + 1
- # Skip entering SN for full machine test.
- if self._state == self._STATE_ENTERING_SN:
- self._state = self._state + 1
- else:
- if self._state == self._STATE_RESULT_TAB:
- self._state = self._STATE_PREPARE_MACHINE
+ # CSS style classes defined in the corresponding HTML file.
+ _STYLE_INFO = "color_idle"
+ _STYLE_PASS = "color_good"
+ _STYLE_FAIL = "color_bad"
+
+ def stylize(self, msg, style_type):
+ return '<span class="' + style_type + '">' + msg + '</span>'
+
+ def t_info(self, msg):
+ return self.stylize(msg, self._STYLE_INFO)
+
+ def t_pass(self, msg):
+ return self.stylize(msg, self._STYLE_PASS)
+
+ def t_fail(self, msg):
+ return self.stylize(msg, self._STYLE_FAIL)
+
+ def update_status(self, mid=None, msg=None):
+ message = ''
+ if msg:
+ message = msg
+ elif mid:
+ message = self.stylize(self.config['message'][mid],
+ self.config['msg_style'][mid])
+ self.ui.CallJSFunction("UpdateTestStatus", message)
+
+ def update_pbar(self, pid=None, value=None, add=True):
+ precent = 0
+ if value:
+ percent = value
+ elif pid:
+ all_time = self.config['chk_point'][self.type]
+ if add:
+ self.progress += self.config['chk_point'][pid]
else:
- self._state = self._state + 1
- self.switch_widget(self._state_widget[self._state])
+ self.progress = self.config['chk_point'][pid]
+ percent = int(round((float(self.progress) / all_time) * 100))
+ self.ui.CallJSFunction("UpdatePrograssBar", '%d%%' % percent)
+
+ def register_events(self, events):
+ for event in events:
+ assert hasattr(self, event)
+ self.ui.AddEventHandler(event, getattr(self, event))
def prepare_test(self):
self.ref_data = camperf.PrepareTest(self._TEST_CHART_FILE)
def on_usb_insert(self, dev_path):
- if self._state == self._STATE_WAIT_USB:
+ if not self.config_loaded:
# Initialize common test reference data.
self.prepare_test()
# Load config files and reset test results.
@@ -270,61 +295,19 @@
config_path = os.path.join(config_dir, 'camera.params')
self.config = self.base_config.Read(config_path)
self.reset_data()
- self.advance_state()
- factory.log("Config loaded.")
+ self.config_loaded = True
+ factory.console.info("Config loaded.")
+ self.ui.CallJSFunction("OnUSBInit", self.config['sn_format'])
+ else:
+ self.dev_path = dev_path
+ self.ui.CallJSFunction("OnUSBInsertion")
def on_usb_remove(self, dev_path):
- if self._state != self._STATE_WAIT_USB:
- raise Exception("USB removal is not allowed during test")
+ if self.config_loaded:
+ factory.console.info("USB removal is not allowed during test!")
+ self.ui.CallJSFunction("OnUSBRemoval")
- def register_callbacks(self, window):
- def key_press_callback(widget, event):
- if hasattr(self, 'last_widget'):
- if hasattr(self.last_widget, 'key_callback'):
- return self.last_widget.key_callback(widget, event)
- return False
- window.connect('key-press-event', key_press_callback)
- window.add_events(gtk.gdk.KEY_PRESS_MASK)
-
- def switch_widget(self, widget_to_display):
- if hasattr(self, 'last_widget'):
- if widget_to_display is not self.last_widget:
- self.last_widget.hide()
- self.test_widget.remove(self.last_widget)
- else:
- return
-
- self.last_widget = widget_to_display
- self.test_widget.add(widget_to_display)
- self.test_widget.show_all()
-
- def on_sn_keypress(self, entry, key):
- if key.keyval == gtk.keysyms.Tab:
- entry.set_text(_TEST_SN_NUMBER)
- return True
- return False
-
- def on_sn_complete(self, serial_number):
- self.serial_number = serial_number
- # TODO(itspeter): display the SN info in the result tab.
- self._update_status('sn', self.check_sn_format(serial_number))
- self.advance_state()
-
- def check_sn_format(self, sn):
- if re.search(self.config['sn_format'], sn):
- return True
- return False
-
- def write_to_usb(self, filename, content, content_type=_CONTENT_TXT):
- with MountedMedia(self.dev_path, 1) as mount_dir:
- if content_type == _CONTENT_TXT:
- with open(os.path.join(mount_dir, filename), 'w') as f:
- f.write(content)
- elif content_type == _CONTENT_IMG:
- cv2.imwrite(os.path.join(mount_dir, filename), content)
- return True
-
- def _setup_fixture(self):
+ def setup_fixture(self):
'''Initialize the communication with the fixture.'''
try:
self.fixture = Fixture(self.config['fixture'])
@@ -341,7 +324,171 @@
self.log('Test fixture successfully initialized.\n')
return True
- def _capture_low_noise_image(self, cam, n_samples):
+ def sync_fixture(self, event):
+ # Prevent multiple threads from running at the same time.
+ if hasattr(self, 'detecting_fixture') and self.detecting_fixture:
+ return
+ self.detecting_fixture = True
+ self.ui.CallJSFunction("OnDetectFixtureConnection")
+ cnt = 0
+ while not self.setup_fixture():
+ cnt += 1
+ if cnt >= self.config['fixture']['n_retry']:
+ self.detecting_fixture = False
+ self.ui.CallJSFunction("OnRemoveFixtureConnection")
+ return
+ time.sleep(self.config['fixture']['retry_delay'])
+ self.detecting_fixture = False
+ self.ui.CallJSFunction("OnAddFixtureConnection")
+
+ def on_u2s_insert(self, dev_path):
+ if self.config_loaded:
+ self.sync_fixture(None)
+
+ def on_u2s_remove(self, dev_path):
+ if self.config_loaded:
+ self.ui.CallJSFunction("OnRemoveFixtureConnection")
+
+ def update_result(self, row_name, result):
+ result_map = {
+ True: 'PASSED',
+ False: 'FAILED',
+ None: 'UNTESTED'
+ }
+ self.result_dict[row_name] = result_map[result]
+
+ def reset_data(self):
+ self.target = None
+ self.target_colorful = None
+ if self.type == _TEST_TYPE_FULL:
+ self.log = factory.console.info
+ else:
+ self.log_to_file = StringIO.StringIO()
+ self.log = lambda *x: (factory.console.info(*x),
+ self.log_to_file.write(*x))
+
+ for var in self.status_names:
+ self.update_result(var, None)
+ self.progress = 0
+ self.ui.CallJSFunction("ResetUiData", "")
+
+ def send_img_to_ui(self, data):
+ self.ui.CallJSFunction("ClearBuffer", "")
+ # Send the data in 64K packets due to the socket packet size limit.
+ data_len = len(data)
+ p = 0
+ while p < data_len:
+ if p + self._PACKET_SIZE > data_len:
+ self.ui.CallJSFunction("AddBuffer", data[p:data_len-1])
+ p = data_len
+ else:
+ self.ui.CallJSFunction("AddBuffer",
+ data[p:p+self._PACKET_SIZE])
+ p += self._PACKET_SIZE
+
+ def update_preview(self, img, container_id, scale=0.5):
+ # Encode the image in the JPEG format.
+ preview = cv2.resize(img, None, fx=scale, fy=scale,
+ interpolation=cv2.INTER_AREA)
+ cv2.imwrite('temp.jpg', preview)
+ with open('temp.jpg', 'r') as fd:
+ img_data = base64.b64encode(fd.read()) + "="
+
+ # Update the preview screen with javascript.
+ self.send_img_to_ui(img_data)
+ self.ui.CallJSFunction("UpdateImage", container_id)
+ return
+
+ def compile_result(self, test_list, use_untest=True):
+ ret = self.result_dict
+ if all('PASSED' == ret[x] for x in test_list):
+ return True
+ if use_untest and any('UNTESTED' == ret[x] for x in test_list):
+ return None
+ return False
+
+ def generate_final_result(self):
+ self.update_status(mid='end_test')
+ self.cam_pass = self.compile_result(self._CAM_TESTS)
+ self.als_pass = self.compile_result(self._ALS_TESTS)
+ result = self.compile_result(self.status_names[:-1], use_untest=False)
+ self.update_result('result', result)
+ self.log("Result in summary:\n%s\n" %
+ pprint.pformat(self.result_dict))
+ self.update_pbar(pid='end_test')
+
+ def write_to_usb(self, filename, content, content_type=_CONTENT_TXT):
+ with MountedMedia(self.dev_path, 1) as mount_dir:
+ if content_type == _CONTENT_TXT:
+ with open(os.path.join(mount_dir, filename), 'a') as f:
+ f.write(content)
+ elif content_type == _CONTENT_IMG:
+ cv2.imwrite(os.path.join(mount_dir, filename), content)
+ return True
+
+ def save_log_to_usb(self):
+ # Save an image for further analysis in case of the camera
+ # performance fail.
+ self.update_status(mid='save_to_usb')
+ if (not self.cam_pass) and (self.target is not None):
+ if not self.write_to_usb(self.serial_number + ".bmp",
+ self.target, _CONTENT_IMG):
+ return False
+ return self.write_to_usb(
+ self.serial_number + ".txt", self.log_to_file.getvalue())
+
+ def finalize_test(self):
+ self.generate_final_result()
+ if self.type == _TEST_TYPE_AB:
+ # We block the test flow until we successfully dumped the result.
+ while not self.save_log_to_usb():
+ time.sleep(0.5)
+ self.update_pbar(pid='save_to_usb')
+
+ # Display final result.
+ def get_str(ret, prefix, use_untest=True):
+ if ret:
+ return self.t_pass(prefix + 'PASS')
+ if use_untest and (ret is None):
+ return self.t_fail(prefix + 'UNFINISHED')
+ return self.t_fail(prefix + 'FAIL')
+ cam_result = get_str(self.cam_pass, 'Camera: ', False)
+ als_result = get_str(self.als_pass, 'ALS: ')
+ self.update_status(msg=cam_result + '<br>' + als_result)
+ self.update_pbar(value=100)
+
+ def exit_test(self, event):
+ factory.log('%s run_once finished' % self.__class__)
+ if self.result_dict['result'] == 'PASSED':
+ self.ui.Pass()
+ else:
+ self.ui.Fail('Camera/ALS test failed.')
+
+ def run_test(self, event=None):
+ self.reset_data()
+ self.update_status(mid='start_test')
+ if self.type == _TEST_TYPE_AB:
+ self.serial_number = event.data.get('sn', '')
+ if not self.setup_fixture():
+ self.update_status(mid='fixture_fail')
+ self.ui.CallJSFunction("OnRemoveFixtureConnection")
+ return
+ self.update_pbar(pid='start_test')
+
+ if self.type == _TEST_TYPE_FULL:
+ with leds.Blinker(self._LED_RUNNING_TEST):
+ self.test_camera_performance()
+ self.update_pbar(pid='cam_finish', add=False)
+ self.test_als_calibration()
+ else:
+ self.test_camera_performance()
+ self.update_pbar(pid='cam_finish', add=False)
+ self.test_als_calibration()
+ self.update_pbar(pid='als_finish' + self.type, add=False)
+
+ self.finalize_test()
+
+ def capture_low_noise_image(self, cam, n_samples):
'''Capture a sequence of images and average them to reduce noise.'''
if n_samples < 1:
n_samples = 1
@@ -353,66 +500,81 @@
img /= n_samples
return img.round().astype(np.uint8)
- def _test_camera_functionality(self):
+ def test_camera_functionality(self):
# Initialize the camera with OpenCV.
+ self.update_status(mid='init_cam')
cam = cv2.VideoCapture(self._DEVICE_INDEX)
if not cam.isOpened():
cam.release()
- self._update_status('cam_stat', False)
+ self.update_result('cam_stat', False)
self.log('Failed to initialize the camera. '
'Could be bad module, bad connection or '
- 'insufficient USB bandwidth.\n')
+ 'bad USB initialization.\n')
return False
+ self.update_pbar(pid='init_cam')
# Set resolution.
+ self.update_status(mid='set_cam_res')
conf = self.config['cam_stat']
cam.set(cv.CV_CAP_PROP_FRAME_WIDTH, conf['img_width'])
cam.set(cv.CV_CAP_PROP_FRAME_HEIGHT, conf['img_height'])
if (conf['img_width'] != cam.get(cv.CV_CAP_PROP_FRAME_WIDTH) or
conf['img_height'] != cam.get(cv.CV_CAP_PROP_FRAME_HEIGHT)):
cam.release()
- self._update_status('cam_stat', False)
+ self.update_result('cam_stat', False)
self.log("Can't set the image size. "
- "Possibly caused by insufficient USB bandwidth.\n")
+ "Possibly caused by bad USB initialization.\n")
return False
+ self.update_pbar(pid='set_cam_res')
# Try reading an image from the camera.
+ self.update_status(mid='try_read_cam')
success, _ = cam.read()
if not success:
cam.release()
- self._update_status('cam_stat', False)
+ self.update_result('cam_stat', False)
self.log("Failed to capture an image with the camera.\n")
return False
+ self.update_pbar(pid='try_read_cam')
# Let the camera's auto-exposure algorithm adjust to the fixture
# lighting condition.
- start = time.clock()
- while time.clock() - start < conf['buf_time']:
+ self.update_status(mid='wait_cam_awb')
+ start = time.time()
+ while time.time() - start < conf['buf_time']:
_, _ = cam.read()
+ self.update_pbar(pid='wait_cam_awb')
# Read the image that we will use.
+ self.update_status(mid='record_img')
n_samples = conf['n_samples']
- img = self._capture_low_noise_image(cam, n_samples)
- self.target = cv2.cvtColor(img, cv.CV_BGR2GRAY)
- self._update_status('cam_stat', True)
- self.log('Successfully captured an image.\n')
-
- # Use the sample image in the unit-test mode.
+ self.target_colorful = self.capture_low_noise_image(cam, n_samples)
if self.unit_test:
- self.target = cv2.imread(self._TEST_SAMPLE_FILE,
- cv.CV_LOAD_IMAGE_GRAYSCALE)
+ self.target_colorful = cv2.imread(self._TEST_SAMPLE_FILE)
+
+ self.target = cv2.cvtColor(self.target_colorful, cv.CV_BGR2GRAY)
+ self.update_result('cam_stat', True)
+ self.log('Successfully captured a sample image.\n')
+ self.update_preview(self.target_colorful, "camera_image",
+ scale=self.config['preview']['scale'])
cam.release()
+ self.update_pbar(pid='record_img')
return True
- def _test_camera_core(self):
- if not self._test_camera_functionality():
+ def test_camera_performance(self):
+ if not self.test_camera_functionality():
return
# Check the captured test pattern image validity.
+ self.update_status(mid='check_vc')
success, tar_data = camperf.CheckVisualCorrectness(
self.target, self.ref_data, **self.config['cam_vc'])
+ analyzed = self.target_colorful.copy()
+ renderer.DrawVC(analyzed, success, tar_data)
+ self.update_preview(analyzed, "analyzed_image",
+ scale=self.config['preview']['scale'])
- self._update_status('cam_vc', success)
+ self.update_result('cam_vc', success)
if hasattr(tar_data, 'shift'):
self.log('Image shift percentage: %f\n' % tar_data.shift)
self.log('Image tilt: %f degrees\n' % tar_data.tilt)
@@ -425,188 +587,139 @@
tar_data.edges.shape[0])
self.log('Visual correctness: %s\n' % tar_data.msg)
return
+ self.update_pbar(pid='check_vc')
# Check if the lens shading is present.
+ self.update_status(mid='check_ls')
success, tar_ls = camperf.CheckLensShading(
self.target, **self.config['cam_ls'])
- self._update_status('cam_ls', success)
+ self.update_result('cam_ls', success)
if tar_ls.check_low_freq:
self.log('Low-frequency response value: %f\n' %
tar_ls.response)
if not success:
self.log('Lens shading: %s\n' % tar_ls.msg)
return
+ self.update_pbar(pid='check_ls')
# Check the image sharpness.
+ self.update_status(mid='check_mtf')
success, tar_mtf = camperf.CheckSharpness(
self.target, tar_data.edges, **self.config['cam_mtf'])
- self._update_status('cam_mtf', success)
+ self.update_result('cam_mtf', success)
self.log('MTF value: %f\n' % tar_mtf.mtf)
if hasattr(tar_mtf, 'min_mtf'):
self.log('Lowest MTF value: %f\n' % tar_mtf.min_mtf)
if not success:
self.log('Sharpness: %s\n' % tar_mtf.msg)
+ self.update_pbar(pid='check_mtf')
return
- def _test_als_core(self):
+ def test_als_write_vpd(self, calib_result):
+ self.update_status(mid='dump_to_vpd')
+ conf = self.config['als']
+ if not calib_result:
+ self.update_result('als_stat', False)
+ self.log('ALS calibration data is incorrect.\n')
+ return False
+ if subprocess.call(conf['save_vpd'] % calib_result, shell=True):
+ self.update_result('als_stat', False)
+ self.log('Writing VPD data failed!\n')
+ return False
+ self.log('Successfully calibrated ALS scales.\n')
+ self.update_pbar(pid='dump_to_vpd')
+ return True
+
+ def test_als_switch_to_next_light(self):
+ self.update_status(mid='adjust_light')
+ conf = self.config['als']
+ self.light_state += 1
+ self.fixture.set_light(self.light_state)
+ self.update_pbar(pid='adjust_light')
+ if not self.unit_test:
+ self.fixture.assert_success()
+ if self.light_state >= len(conf['luxs']):
+ return False
+ self.update_status(mid='wait_fixture')
+ self.fixture.wait_for_light_switch()
+ self.update_pbar(pid='wait_fixture')
+ return True
+
+ def test_als_calibration(self):
# Initialize the ALS.
+ self.update_status(mid='init_als')
conf = self.config['als']
self.als = ALS(val_path=conf['val_path'],
scale_path=conf['scale_path'])
if not self.als.detected:
- self._update_status('als_stat', False)
+ self.update_result('als_stat', False)
self.log('Failed to initialize the ALS.\n')
return
+ self.als.set_scale_factor(conf['calibscale'])
+ self.update_pbar(pid='init_als')
# Go through all different lighting settings
# and record ALS values.
+ calib_result = 0
try:
+ vals = []
while True:
# Get ALS values.
+ self.update_status(mid='read_als%d' % self.light_state)
scale = self.als.get_scale_factor()
- val = self.als.read_mean(samples=5, delay=0)
+ val = self.als.read_mean(samples=conf['n_samples'],
+ delay=conf['read_delay'])
+ vals.append(val)
self.log('Lighting preset lux value: %d\n' %
conf['luxs'][self.light_state])
self.log('ALS value: %d\n' % val)
self.log('ALS calibration scale: %d\n' % scale)
+ # Check if it is a false read.
+ if not val:
+ self.update_result('als_stat', False)
+ self.log('The ALS value is stuck at zero.\n')
+ return
+ # Compute calibration data if it is the calibration target.
+ if conf['luxs'][self.light_state] == conf['calib_lux']:
+ calib_result = int(round(float(conf['calib_target']) /
+ val * scale))
+ self.log('ALS calibration data will be %d\n' %
+ calib_result)
+ self.update_pbar(pid='read_als%d' % self.light_state)
# Go to the next lighting preset.
- self.light_state += 1
- self.fixture.set_light(self.light_state)
- if not self.unit_test:
- self.fixture.assert_success()
- if self.light_state >= len(conf['luxs']):
+ if not self.test_als_switch_to_next_light():
break
- self.fixture.wait_for_light_switch()
+
+ # Check value ordering.
+ for i, li in enumerate(conf['luxs']):
+ for j in range(i):
+ if ((li > conf['luxs'][j] and vals[j] >= vals[i]) or
+ (li < conf['luxs'][j] and vals[j] <= vals[i])):
+ self.update_result('als_stat', False)
+ self.log('The ordering of ALS values is wrong.\n')
+ return
except (FixtureException, serial.serialutil.SerialException) as e:
self.fixture = None
- self._update_status('als_stat', None)
+ self.update_result('als_stat', None)
self.log("The test fixture was disconnected!\n")
+ self.ui.CallJSFunction("OnRemoveFixtureConnection")
return
except:
- self._update_status('als_stat', False)
- self.log('Failed to read values from ALS.\n')
+ self.update_result('als_stat', False)
+ self.log('Failed to read values from ALS or unknown error.\n')
return
- self._update_status('als_stat', True)
self.log('Successfully recorded ALS values.\n')
+
+ # Save ALS values to vpd for FATP test.
+ if self.type == _TEST_TYPE_FULL:
+ if not self.test_als_write_vpd(calib_result):
+ return
+ self.update_result('als_stat', True)
return
- def test_camera(self, skip_flag):
- if self.type == _TEST_TYPE_FULL:
- self.blinker.Stop()
-
- if not skip_flag:
- with leds.Blinker(self._LED_RUNNING_CAM_TEST):
- self._test_camera_core()
-
- self.blinker = leds.Blinker(self._LED_PREPARE_ALS_TEST)
- self.blinker.Start()
- else:
- if not skip_flag:
- self._test_camera_core()
-
- self.advance_state()
-
- def test_als(self, skip_flag):
- if self.type == _TEST_TYPE_FULL:
- self.blinker.Stop()
-
- if not skip_flag:
- with leds.Blinker(self._LED_RUNNING_ALS_TEST):
- self._test_als_core()
-
- self.blinker = leds.Blinker(self._LED_FINISHED_ALL_TEST)
- self.blinker.Start()
- else:
- if not skip_flag:
- self._test_als_core()
-
- self.generate_final_result()
- self.advance_state()
-
- def _update_status(self, row_name, result):
- """Updates status in display_dict."""
- result_map = {
- True: ful.PASSED,
- False: ful.FAILED,
- None: ful.UNTESTED
- }
- assert result in result_map, "Unknown result"
- self.display_dict[row_name]['status'] = result_map[result]
-
- def generate_final_result(self):
- self._result = all(
- ful.PASSED == self.display_dict[var]['status']
- for var in self.status_names[:-1])
- self._update_status('result', self._result)
- self.log("Result in summary:\n%s\n" %
- pprint.pformat(self.display_dict))
-
- def save_log(self):
- # Save an image for further analysis in case of the camera
- # performance fail.
- cam_perf_pass = all(ful.PASSED == self.display_dict[var]['status']
- for var in ['cam_vc', 'cam_ls', 'cam_mtf'])
- if (not cam_perf_pass) and (self.target is not None):
- if not self.write_to_usb(self.serial_number + ".bmp",
- self.target, _CONTENT_IMG):
- return False
- return self.write_to_usb(
- self.serial_number + ".txt", self.log_to_file.getvalue())
-
- def reset_data(self):
- self.target = None
- if self.type == _TEST_TYPE_FULL:
- self.log = logging.info
- else:
- self.log_to_file = StringIO.StringIO()
- self.sn_input_widget.get_entry().set_text('')
- self.log = self.log_to_file.write
-
- for var in self.status_names:
- self._update_status(var, None)
-
- def on_result_enter(self):
- if self.type == _TEST_TYPE_FULL:
- self.blinker.Stop()
- gtk.main_quit()
- else:
- # The UI will stop in this screen unless log is saved.
- if self.save_log():
- self.reset_data()
- self.advance_state()
- return False
-
- def on_close_prepare_machine(self):
- if self.type == _TEST_TYPE_FULL:
- self.blinker = leds.Blinker(self._LED_PREPARE_CAM_TEST)
- self.blinker.Start()
- # Try to setup the fixture. This step blocks until we can find the
- # fixture successfully.
- if not self._setup_fixture():
- return False
- self.advance_state()
- return True
-
- def make_result_widget(self, on_key_enter):
- widget = gtk.VBox()
- widget.add(ful.make_label(_MESSAGE_RESULT_TAB_FULL
- if self.type == _TEST_TYPE_FULL
- else _MESSAGE_RESULT_TAB_ABONLY))
-
- for name, label in zip(self.status_names, self.status_labels):
- td, tw = ful.make_status_row(label, ful.UNTESTED, _LABEL_SIZE)
- self.display_dict[name] = td
- widget.add(tw)
- def key_press_callback(widget, event):
- if event.keyval == gtk.keysyms.Return:
- on_key_enter()
-
- widget.key_callback = key_press_callback
- return widget
-
def run_once(self, test_type=_TEST_TYPE_FULL, unit_test=False):
'''The entry point of the test.
@@ -629,77 +742,30 @@
'''
factory.log('%s run_once' % self.__class__)
- # Initialize variables.
+ # Initialize variables and environment.
assert test_type in [_TEST_TYPE_FULL, _TEST_TYPE_AB]
assert unit_test in [True, False]
self.type = test_type
self.unit_test = unit_test
- self.display_dict = {}
+ self.config_loaded = False
+ self.status_names = self._STATUS_NAMES
+ self.status_labels = self._STATUS_LABELS
+ self.result_dict = {}
self.base_config = PluggableConfig({})
- self.last_handler = None
os.chdir(self.srcdir)
- if self.type == _TEST_TYPE_FULL:
- self.status_names = self._STATUS_NAMES[1:]
- self.status_labels = self._STATUS_LABELS[1:]
- else:
- self.status_names = self._STATUS_NAMES
- self.status_labels = self._STATUS_LABELS
- # Set up the UI widgets.
- self.usb_prompt_widget = gtk.VBox()
- self.usb_prompt_widget.add(ful.make_label(_MESSAGE_USB))
- self.prepare_machine_widget = make_prepare_widget(
- (_MESSAGE_PREPARE_MACHINE if self.type == _TEST_TYPE_FULL
- else _MESSAGE_PREPARE_PANEL),
- self.on_close_prepare_machine)
- self.prepare_camera_widget = make_prepare_widget(
- _MESSAGE_PREPARE_CAMERA,
- lambda : self.test_camera(skip_flag=False),
- lambda : self.test_camera(skip_flag=True))
- self.prepare_als_widget = make_prepare_widget(
- _MESSAGE_PREPARE_ALS,
- lambda : self.test_als(skip_flag=False),
- lambda : self.test_als(skip_flag=True))
- self.result_widget = self.make_result_widget(self.on_result_enter)
+ # Setup the usb disk and usb-to-serial adapter monitor.
+ usb_monitor = ConnectionMonitor()
+ usb_monitor.start(subsystem='block', device_type='disk',
+ on_insert=self.on_usb_insert,
+ on_remove=self.on_usb_remove)
+ u2s_monitor = ConnectionMonitor()
+ u2s_monitor.start(subsystem='usb-serial',
+ on_insert=self.on_u2s_insert,
+ on_remove=self.on_u2s_remove)
- self.sn_input_widget = ful.make_input_window(
- prompt='Enter Serial Number (TAB to use testing sample SN):',
- on_validate=self.check_sn_format,
- on_keypress=self.on_sn_keypress,
- on_complete=self.on_sn_complete)
-
- # Make sure the entry in widget will have focus.
- self.sn_input_widget.connect(
- "show",
- lambda *x : self.sn_input_widget.get_entry().grab_focus())
-
- # Setup the relation of states and widgets.
- self._state_widget = {
- self._STATE_INITIAL: None,
- self._STATE_WAIT_USB: self.usb_prompt_widget,
- self._STATE_PREPARE_MACHINE: self.prepare_machine_widget,
- self._STATE_ENTERING_SN: self.sn_input_widget,
- self._STATE_PREPARE_CAMERA: self.prepare_camera_widget,
- self._STATE_PREPARE_ALS: self.prepare_als_widget,
- self._STATE_RESULT_TAB: self.result_widget
- }
-
- # Setup the usb monitor,
- monitor = MediaMonitor()
- monitor.start(on_insert=self.on_usb_insert,
- on_remove=self.on_usb_remove)
-
- # Setup the initial display.
- self.test_widget = gtk.VBox()
- self._state = self._STATE_INITIAL
- self.advance_state()
- ful.run_test_widget(
- self.job,
- self.test_widget,
- window_registration_callback=self.register_callbacks)
-
- if not self._result:
- raise error.TestFail('Camera/ALS test failed by user indication\n' +
- '品管人員懷疑故障,請檢修')
-
- factory.log('%s run_once finished' % self.__class__)
+ # Startup the UI.
+ self.ui = UI()
+ self.register_events(['sync_fixture', 'exit_test', 'run_test'])
+ self.ui.CallJSFunction("InitLayout", self.type == _TEST_TYPE_FULL)
+ self.ui.Run()
diff --git a/client/site_tests/factory_CameraPerformanceAls/static/factory_CameraPerformanceAls.html b/client/site_tests/factory_CameraPerformanceAls/static/factory_CameraPerformanceAls.html
new file mode 100755
index 0000000..9023a1a
--- /dev/null
+++ b/client/site_tests/factory_CameraPerformanceAls/static/factory_CameraPerformanceAls.html
@@ -0,0 +1,365 @@
+<html>
+<head>
+<meta charset="UTF-8">
+<style type="text/css">
+
+.button {
+ display: inline-block;
+ vertical-align: baseline;
+ margin: 0 2px;
+ outline: none;
+ cursor: pointer;
+ text-align: center;
+ text-decoration: none;
+ padding: .5em 2em .55em;
+ text-shadow: 2px 2px 4px rgba(0,0,0,.5);
+ -webkit-box-shadow: 0 1px 2px rgba(0,0,0,.2);
+}
+.button:hover {
+ text-decoration: none;
+}
+
+.bigrounded {
+ -webkit-border-radius: 2em;
+}
+
+.title_box {
+ background: -webkit-gradient(linear, left top, left bottom, from(#666), to(#000));
+}
+
+.panel_box{
+ color: #e9e9e9;
+ background: -webkit-gradient(linear, left top, left bottom, from(#888), to(#575757));
+}
+
+.panel_good{
+ background: -webkit-gradient(linear, left top, left bottom, from(#7db72f), to(#4e7d0e));
+}
+
+.panel_bad{
+ background: -webkit-gradient(linear, left top, left bottom, from(#ed1c24), to(#aa1317));
+}
+
+.text_shadow {
+ text-shadow: 4px 4px 6px #333333;
+}
+
+.text_shadow_white {
+ text-shadow: 1px 1px 3px #E0E0E0;
+}
+
+.text_shadow_small {
+ text-shadow: 2px 2px 3px #333333;
+}
+
+.color_good {
+ color:#7db72f;
+}
+
+.color_bad {
+ color:#c9151b;
+}
+
+.color_idle {
+ color:LightSkyBlue;
+}
+
+.font_title {
+ font-family:Arial;
+ font-size:150%;
+ font-weight:bold;
+ color:white;
+}
+
+.font_label {
+ font-family:Arial;
+ font-size:110%;
+}
+
+.font_input {
+ font-family:Monospace;
+ font-size:150%;
+ font-weight:bold;
+}
+
+.font_status {
+ font-family:Monospace;
+ font-size:250%;
+ font-weight:bold;
+}
+
+.input_box {
+ text-align: center;
+ width: 100%;
+ height: 100%;
+ border: none;
+ color: -webkit-text;
+ background: -webkit-gradient(linear, left top, left bottom, from(#7db72f), to(#4e7d0e));
+ font-family:Monospace;
+ font-size:100%;
+ font-weight:bold;
+}
+
+.input_box:invalid {
+ background: -webkit-gradient(linear, left top, left bottom, from(#ed1c24), to(#aa1317));
+}
+
+.input_box:valid {
+ background: -webkit-gradient(linear, left top, left bottom, from(#7db72f), to(#4e7d0e));
+}
+
+/* color styles */
+/* black */
+.black {
+ color: #d7d7d7;
+ border:none;
+ background: -webkit-gradient(linear, left top, left bottom, from(#666), to(#222));
+ -webkit-box-shadow: 0 0 10px rgba(0, 0, 0, 0.8);
+}
+.black:hover {
+ background: -webkit-gradient(linear, left top, left bottom, from(#444), to(#222));
+}
+.black:active {
+ color: #666;
+ background: -webkit-gradient(linear, left top, left bottom, from(#222), to(#444));
+}
+
+/* gray */
+.gray {
+ color: #e9e9e9;
+ background: -webkit-gradient(linear, left top, left bottom, from(#888), to(#575757));
+}
+/*
+.gray:hover {
+ background: #616161;
+ background: -webkit-gradient(linear, left top, left bottom, from(#757575), to(#4b4b4b));
+}
+.gray:active {
+ color: #afafaf;
+ background: -webkit-gradient(linear, left top, left bottom, from(#575757), to(#888));
+}
+*/
+/* white */
+.white {
+ color: #606060;
+ background:-webkit-gradient(linear, left top, left bottom, from(#fff), to(#dcdcdc));
+}
+/*
+.white:hover {
+ background: #ededed;
+ background: -webkit-gradient(linear, left top, left bottom, from(#fff), to(#dcdcdc));
+}
+.white:active {
+ color: #999;
+ background: -webkit-gradient(linear, left top, left bottom, from(#ededed), to(#fff));
+}
+*/
+
+/* main image container */
+.popup_img{
+ position: relative;
+ z-index: 1;
+}
+
+/* CSS for image within container */
+.popup_img img.original_img{
+ position: relative;
+ z-index: 3;
+ opacity: 1;
+ -webkit-transition: all 0.5s ease-in-out;
+}
+
+/* CSS for image when mouse hovers over main container */
+.popup_img:hover img.original_img{
+ -webkit-box-shadow: 5px 5px 5px rgba(0, 0, 0, 0.5);
+ -webkit-transform: scale(1.20, 1.20);
+ opacity: 0;
+ /*left: -15%;*/
+}
+
+/* CSS for desc div of each image. */
+.popup_img div.desc{
+ position: absolute;
+ width: 20%;
+ /* Set z-index to that less than image's, so it's hidden beneath it */
+ z-index: 1;
+ padding: 8px;
+ background: rgba(0, 0, 0, 0.8); /* black bg with 80% opacity */
+ color: white;
+ -webkit-border-radius: 0 0 8px 8px;
+ opacity: 0; /* Set initial opacity to 0 */
+ -webkit-box-shadow: 0 0 6px rgba(0, 0, 0, 0.8);
+ -webkit-transition: all 0.5s ease 0.1s;
+}
+
+.popup_img div.desc a{
+ color: white;
+}
+
+/* CSS for desc div when mouse hovers over main container */
+.popup_img:hover div.desc{
+ opacity:1; /* Reveal desc DIV fully */
+}
+
+.popup_img div.rightslide{
+ width:26%;
+ height:84%;
+ top:7%;
+ right:10%;
+ padding-left:25px;
+ -webkit-border-radius: 0 8px 8px 0;
+}
+
+.popup_img:hover div.rightslide{
+ -webkit-transform: translate(110%, 0%);
+}
+
+.popup_img img.analyzed_img{
+ position:absolute;
+ margin: auto;
+ top:0%;
+ z-index: 2;
+ opacity: 1;
+ -webkit-transition: all 0.5s ease-in-out;
+}
+
+.popup_img:hover img.analyzed_img{
+ -webkit-box-shadow: 5px 5px 5px rgba(0, 0, 0, 0.5);
+ -webkit-transform: scale(1.20, 1.20);
+ /*left: -15%;*/
+}
+
+.progress div.text{
+ position: relative;
+ z-index: 2;
+}
+
+.progress div.bar_holder{
+ position:absolute;
+ width:98.5%;
+ z-index:1;
+ height:2.5%;
+ top:94.5%;
+ left:0%;
+ right:0%;
+ bottom:0%;
+ margin:auto;
+}
+
+.progress div.bar{
+ position: relative;
+ z-index: 1;
+ width: 0%;
+ height: 100%;
+ -webkit-border-radius: 5px;
+ -webkit-box-shadow: 0 1px 5px #000 inset, 0 1px 0 #444;
+ -webkit-transition: width .4s ease-in-out;
+}
+
+.progress div.bar span{
+ display: inline-block;
+ height: 100%;
+ -webkit-border-radius: 3px;
+ -webkit-box-shadow: 0 1px 0 rgba(255, 255, 255, .5) inset;
+ -webkit-transition: width .4s ease-in-out;
+}
+
+.progress div.pgray span {
+ background-color: #999;
+}
+
+.progress div.stripes span {
+ -webkit-background-size: 30px 30px;
+ -webkit-box-shadow: -2px -2px 10px rgba(0, 0, 0, .7) inset;
+ background-image: -webkit-gradient(linear, left top, right bottom,
+ color-stop(.25, rgba(255, 255, 255, .15)), color-stop(.25, transparent),
+ color-stop(.5, transparent), color-stop(.5, rgba(255, 255, 255, .15)),
+ color-stop(.75, rgba(255, 255, 255, .15)), color-stop(.75, transparent),
+ to(transparent));
+ background-image: -webkit-linear-gradient(135deg, rgba(255, 255, 255, .15) 25%, transparent 25%,
+ transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%,
+ transparent 75%, transparent);
+ -webkit-animation: animate-stripes 3s linear infinite;
+}
+
+@-webkit-keyframes animate-stripes {
+ 0% {background-position: 0 0;} 100% {background-position: 60px 0;}
+}
+
+</style>
+</head>
+<body>
+<div id='prompt_usb' class="panel_box text_shadow" style="height:100%;clear:both;text-align:center">
+<table class="font_status" style="margin:auto;height:100%;"><tr><td style="vertical-align:middle;text-align:center;">
+<div class="text">
+<span class="color_idle">Please insert the USB stick to load test parameters.<br>請插入U盤讀取測試參數</span>
+</div>
+</td></tr></table>
+</div>
+<div id="container" hidden>
+<div id="Title" class="title_box" style="height:5%;">
+<table class="text_shadow_white font_title" style="margin:auto;height:100%;"><tr><td style="vertical-align:middle">
+Camera Performance Test and ALS Calibration
+</td></tr></table>
+</div>
+<div id="menu" class="panel_box" style="height:75%;width:20%;float:left;">
+<div class="panel_box text_shadow_small font_label" style="height:5%;padding-left:3%">Params(測試參數):</div>
+<div id="test_param" class="panel_bad" style="height:10%;text-align:center">
+<table class="font_input" style="margin:auto;height:100%;"><tr><td style="vertical-align:middle">
+<div id="usb_status">
+UNLOADED
+</div>
+</td></tr></table>
+</div>
+<div id="test_sn_label" class="panel_box text_shadow_small font_label" style="height:5%;padding-left:3%">SN(序號):</div>
+<div id="test_sn" class="panel_good" style="height:10%;text-align:center;">
+<table class="font_input" style="margin:auto;height:100%;border:0;border-spacing:0" cellpadding="0" cellspacing="0">
+<tr><td style="vertical-align:middle">
+<input id="serial_number" class="input_box" disabled type="text" value="" required pattern="TEST_SN_NUMBER" onclick="OnSnInputBoxClick();" onkeypress="UpdateTestBottonStatus();">
+</td></tr></table>
+</div>
+<div class="panel_box text_shadow_small font_label" style="height:5%;padding-left:3%">Fixture(製具連結):</div>
+<div id="test_fixture" class="panel_bad" style="height:10%;text-align:center">
+<table class="font_input" style="margin:auto;height:100%;"><tr><td style="vertical-align:middle">
+<div id="fixture_status">
+UNAVAILABLE
+</div>
+</td></tr></table>
+</div>
+<div id="menu_placeholder" style="height:15%">
+</div>
+<div style="height:20%;text-align:center">
+<button id="btn_run_test" class="button black" disabled type="button" onclick="BottonRunTestClick();" style="width:100%;height:97%;font-size:140%">Run Test<br>開始測試</button>
+</div>
+<div style="height:20%;text-align:center">
+<button id="btn_exit_test" class="button black" type="button" onclick="BottonExitTestClick();" style="width:100%;height:97%;font-size:140%">Exit Test<br>離開測試</button>
+</div>
+</div>
+<div id="content" class="white" style="height:75%;width:80%;float:right;">
+<table style="margin:auto;height:100%;"><tr><td style="vertical-align:middle">
+<div class="popup_img" style="">
+<img id="camera_image" class="original_img" hidden src="" alt="Camera Image"></img>
+<div class="desc rightslide" hidden>
+</div>
+<img id="analyzed_image" class="analyzed_img" hidden src=""></img>
+</div>
+</td></tr></table>
+</div>
+<div id="test_console" class="panel_box text_shadow" style="height:20%;clear:both;text-align:center">
+<table class="font_status" style="margin:auto;height:100%;"><tr><td style="vertical-align:middle;text-align:center;">
+<div class="progress">
+<div id="test_status" class="text">
+<span class="color_idle">IDLE</span>
+</div>
+<div class="bar_holder">
+<div id="progress_bar" class="bar pgray stripes">
+<span style="width: 100%"></span>
+</div>
+</div>
+</div>
+</div>
+</td></tr></table>
+</div>
+</div>
+</div>
+</body></html>
diff --git a/client/site_tests/factory_CameraPerformanceAls/static/factory_CameraPerformanceAls.js b/client/site_tests/factory_CameraPerformanceAls/static/factory_CameraPerformanceAls.js
new file mode 100755
index 0000000..f4e4da7
--- /dev/null
+++ b/client/site_tests/factory_CameraPerformanceAls/static/factory_CameraPerformanceAls.js
@@ -0,0 +1,125 @@
+window.onkeydown = function(event) {
+ if (event.keyCode == 13 || event.keyCode == 32) {
+ var testButton = document.getElementById("btn_run_test");
+ if (!testButton.disabled)
+ BottonRunTestClick();
+ }
+}
+
+function InitLayout(testFull) {
+ if (testFull) {
+ document.getElementById("test_sn_label").hidden = true;
+ document.getElementById("test_sn").hidden = true;
+ document.getElementById("menu_placeholder").style.height = "30%";
+ } else {
+ var snInputBox = document.getElementById("serial_number");
+ snInputBox.disabled = false;
+ snInputBox.autofocus = true;
+ }
+}
+
+function UpdateTestBottonStatus() {
+ var testButton = document.getElementById("btn_run_test");
+ testButton.disabled =
+ !(document.getElementById('serial_number').validity.valid &&
+ (document.getElementById('usb_status').innerHTML == 'LOADED') &&
+ (document.getElementById('fixture_status').innerHTML == 'OK'));
+}
+
+function OnSnInputBoxClick() {
+ var snInputBox = document.getElementById("serial_number");
+ snInputBox.value="";
+}
+
+function OnUSBInsertion() {
+ document.getElementById("usb_status").innerHTML = "LOADED";
+ document.getElementById("test_param").className = "panel_good";
+ UpdateTestBottonStatus();
+}
+
+function OnUSBInit(pattern) {
+ document.getElementById("prompt_usb").hidden = true;
+ document.getElementById("container").hidden = false;
+ document.getElementById("usb_status").innerHTML = "LOADED";
+ document.getElementById("test_param").className = "panel_good";
+ ConfigureSNInputbox(pattern);
+ test.sendTestEvent('sync_fixture', {});
+}
+
+function OnUSBRemoval() {
+ document.getElementById("usb_status").innerHTML = "UNLOADED";
+ document.getElementById("test_param").className = "panel_bad";
+ UpdateTestBottonStatus();
+}
+
+function ConfigureSNInputbox(pattern) {
+ var snInputBox = document.getElementById("serial_number");
+ if (!snInputBox.disabled) {
+ snInputBox.pattern = pattern;
+ snInputBox.focus();
+ }
+}
+
+function OnAddFixtureConnection() {
+ document.getElementById("fixture_status").innerHTML = "OK";
+ document.getElementById("test_fixture").className = "panel_good";
+ UpdateTestBottonStatus();
+}
+
+function OnRemoveFixtureConnection() {
+ document.getElementById("fixture_status").innerHTML = "UNAVAILABLE";
+ document.getElementById("test_fixture").className = "panel_bad";
+ UpdateTestBottonStatus();
+}
+
+function OnDetectFixtureConnection() {
+ document.getElementById("fixture_status").innerHTML = "DETECTING";
+ document.getElementById("test_fixture").className = "panel_bad";
+ UpdateTestBottonStatus();
+}
+
+function BottonRunTestClick() {
+ var testButton = document.getElementById("btn_run_test");
+ testButton.disabled = true;
+ test.sendTestEvent("run_test",
+ {"sn": document.getElementById("serial_number").value});
+ testButton.disabled = false;
+ UpdateTestBottonStatus();
+}
+
+function BottonExitTestClick() {
+ test.sendTestEvent("exit_test", {});
+}
+
+function ResetUiData() {
+ document.getElementById("camera_image").hidden = true;
+ document.getElementById("camera_image").src = "";
+ document.getElementById("analyzed_image").hidden = true;
+ document.getElementById("analyzed_image").src = "";
+ UpdateTestStatus("<span class=\"color_idle\">IDLE</span>");
+ UpdatePrograssBar("0%");
+}
+
+function ClearBuffer() {
+ buf = ""
+}
+
+function AddBuffer(value) {
+ buf += value
+}
+
+function UpdateImage(container_id) {
+ var img = document.getElementById(container_id);
+ img.src = "data:image/jpeg;base64," + buf;
+ img.hidden = false;
+}
+
+function UpdateTestStatus(msg) {
+ var statusText = document.getElementById("test_status");
+ statusText.innerHTML = msg;
+}
+
+function UpdatePrograssBar(progress) {
+ var pBar = document.getElementById("progress_bar");
+ pBar.style.width = progress;
+}