1# Copyright 2016 The Android Open Source Project 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); 4# you may not use this file except in compliance with the License. 5# You may obtain a copy of the License at 6# 7# http://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12# See the License for the specific language governing permissions and 13# limitations under the License. 14 15import os 16import os.path 17import re 18import subprocess 19import sys 20import tempfile 21import time 22 23import its.device 24import numpy 25 26SCENE_NAME = 'sensor_fusion' 27SKIP_RET_CODE = 101 28TEST_NAME = 'test_sensor_fusion' 29TEST_DIR = os.path.join(os.getcwd(), 'tests', SCENE_NAME) 30W, H = 640, 480 31 32# For finding best correlation shifts from test output logs. 33SHIFT_RE = re.compile('^Best correlation of [0-9.]+ at shift of [-0-9.]+ms$') 34# For finding lines that indicate socket issues in failed test runs. 35SOCKET_FAIL_RE = re.compile( 36 r'.*((socket\.(error|timeout))|(Problem with socket)).*') 37 38FPS = 30 39TEST_LENGTH = 7 # seconds 40 41 42def main(): 43 """Run test_sensor_fusion NUM_RUNS times. 44 45 Save intermediate files and produce a summary/report of the results. 46 47 Script should be run from the top-level CameraITS directory. 48 49 Command line arguments: 50 camera: Camera(s) to be tested. Use comma to separate multiple 51 camera Ids. Ex: 'camera=0,1' or 'camera=1' 52 device: Device id for adb 53 fps: FPS to capture with during the test 54 img_size: Comma-separated dimensions of captured images (defaults to 55 640x480). Ex: 'img_size=<width>,<height>' 56 num_runs: Number of times to repeat the test 57 rotator: String for rotator id in for vid:pid:ch 58 test_length: How long the test should run for (in seconds) 59 tmp_dir: Location of temp directory for output files 60 """ 61 62 camera_id = '0' 63 fps = str(FPS) 64 img_size = '%s,%s' % (W, H) 65 num_runs = 1 66 rotator_ids = 'default' 67 test_length = str(TEST_LENGTH) 68 tmp_dir = None 69 for s in sys.argv[1:]: 70 if s[:7] == 'camera=' and len(s) > 7: 71 camera_id = s[7:] 72 if s[:4] == 'fps=' and len(s) > 4: 73 fps = s[4:] 74 elif s[:9] == 'img_size=' and len(s) > 9: 75 img_size = s[9:] 76 elif s[:9] == 'num_runs=' and len(s) > 9: 77 num_runs = int(s[9:]) 78 elif s[:8] == 'rotator=' and len(s) > 8: 79 rotator_ids = s[8:] 80 elif s[:12] == 'test_length=' and len(s) > 12: 81 test_length = s[12:] 82 elif s[:8] == 'tmp_dir=' and len(s) > 8: 83 tmp_dir = s[8:] 84 85 # Make output directories to hold the generated files. 86 tmpdir = tempfile.mkdtemp(dir=tmp_dir) 87 print 'Saving output files to:', tmpdir, '\n' 88 89 device_id = its.device.get_device_id() 90 device_id_arg = 'device=' + device_id 91 print 'Testing device ' + device_id 92 93 # ensure camera_id is valid 94 avail_camera_ids = find_avail_camera_ids() 95 if camera_id not in avail_camera_ids: 96 print 'Need to specify valid camera_id in ', avail_camera_ids 97 sys.exit() 98 99 camera_id_arg = 'camera=' + camera_id 100 if rotator_ids: 101 rotator_id_arg = 'rotator=' + rotator_ids 102 print 'Preparing to run sensor_fusion on camera', camera_id 103 104 img_size_arg = 'img_size=' + img_size 105 print 'Image dimensions are ' + 'x'.join(img_size.split(',')) 106 107 fps_arg = 'fps=' + fps 108 test_length_arg = 'test_length=' + test_length 109 print 'Capturing at %sfps' % fps 110 111 os.mkdir(os.path.join(tmpdir, camera_id)) 112 113 # Run test "num_runs" times, capturing stdout and stderr. 114 num_pass = 0 115 num_fail = 0 116 num_skip = 0 117 num_socket_fails = 0 118 num_non_socket_fails = 0 119 shift_list = [] 120 for i in range(num_runs): 121 os.mkdir(os.path.join(tmpdir, camera_id, SCENE_NAME+'_'+str(i))) 122 cmd = 'python tools/rotation_rig.py rotator=%s' % rotator_ids 123 subprocess.Popen(cmd.split()) 124 cmd = ['python', os.path.join(TEST_DIR, TEST_NAME+'.py'), 125 device_id_arg, camera_id_arg, rotator_id_arg, img_size_arg, 126 fps_arg, test_length_arg] 127 outdir = os.path.join(tmpdir, camera_id, SCENE_NAME+'_'+str(i)) 128 outpath = os.path.join(outdir, TEST_NAME+'_stdout.txt') 129 errpath = os.path.join(outdir, TEST_NAME+'_stderr.txt') 130 t0 = time.time() 131 with open(outpath, 'w') as fout, open(errpath, 'w') as ferr: 132 retcode = subprocess.call( 133 cmd, stderr=ferr, stdout=fout, cwd=outdir) 134 t1 = time.time() 135 136 if retcode == 0: 137 retstr = 'PASS ' 138 time_shift = find_time_shift(outpath) 139 shift_list.append(time_shift) 140 num_pass += 1 141 elif retcode == SKIP_RET_CODE: 142 retstr = 'SKIP ' 143 num_skip += 1 144 else: 145 retstr = 'FAIL ' 146 time_shift = find_time_shift(outpath) 147 if time_shift is None: 148 if is_socket_fail(errpath): 149 num_socket_fails += 1 150 else: 151 num_non_socket_fails += 1 152 else: 153 shift_list.append(time_shift) 154 num_fail += 1 155 msg = '%s %s/%s [%.1fs]' % (retstr, SCENE_NAME, TEST_NAME, t1-t0) 156 print msg 157 158 if num_pass == 1: 159 print 'Best shift is %sms' % shift_list[0] 160 elif num_pass > 1: 161 shift_arr = numpy.array(shift_list) 162 mean, std = numpy.mean(shift_arr), numpy.std(shift_arr) 163 print 'Best shift mean is %sms with std. dev. of %sms' % (mean, std) 164 165 pass_percentage = 100*float(num_pass+num_skip)/num_runs 166 print '%d / %d tests passed (%.1f%%)' % (num_pass+num_skip, 167 num_runs, 168 pass_percentage) 169 170 if num_socket_fails != 0: 171 print '%s failure(s) due to socket issues' % num_socket_fails 172 if num_non_socket_fails != 0: 173 print '%s non-socket failure(s)' % num_non_socket_fails 174 175 176def is_socket_fail(err_file_path): 177 """Search through a test run's stderr log for any mention of socket issues. 178 179 Args: 180 err_file_path: File path for stderr logs to search through 181 182 Returns: 183 True if the test run failed and it was due to socket issues. Otherwise, 184 False. 185 """ 186 return find_matching_line(err_file_path, SOCKET_FAIL_RE) is not None 187 188 189def find_time_shift(out_file_path): 190 """Search through a test run's stdout log for the best time shift. 191 192 Args: 193 out_file_path: File path for stdout logs to search through 194 195 Returns: 196 The best time shift, if one is found. Otherwise, returns None. 197 """ 198 line = find_matching_line(out_file_path, SHIFT_RE) 199 if line is None: 200 return None 201 else: 202 words = line.split(' ') 203 # Get last word and strip off 'ms\n' before converting to a float. 204 return float(words[-1][:-3]) 205 206 207def find_matching_line(file_path, regex): 208 """Search each line in the file at 'file_path' for a line matching 'regex'. 209 210 Args: 211 file_path: File path for file being searched 212 regex: Regex used to match against lines 213 214 Returns: 215 The first matching line. If none exists, returns None. 216 """ 217 with open(file_path) as f: 218 for line in f: 219 if regex.match(line): 220 return line 221 return None 222 223def find_avail_camera_ids(): 224 """Find the available camera IDs. 225 226 Returns: 227 list of available cameras 228 """ 229 with its.device.ItsSession() as cam: 230 avail_camera_ids = cam.get_camera_ids() 231 return avail_camera_ids 232 233 234if __name__ == '__main__': 235 main() 236 237