1# Copyright 2015 The Android Open Source Project (lint as: python2) 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 math 16import os.path 17import cv2 18import its.caps 19import its.cv2image 20import its.device 21import its.image 22import its.objects 23import numpy as np 24 25FOV_PERCENT_RTOL = 0.15 # Relative tolerance on circle FoV % to expected 26LARGE_SIZE = 2000 # Define the size of a large image (compare against max(w,h)) 27NAME = os.path.basename(__file__).split(".")[0] 28NUM_DISTORT_PARAMS = 5 29THRESH_L_AR = 0.02 # aspect ratio test threshold of large images 30THRESH_XS_AR = 0.075 # aspect ratio test threshold of mini images 31THRESH_L_CP = 0.02 # Crop test threshold of large images 32THRESH_XS_CP = 0.075 # Crop test threshold of mini images 33THRESH_MIN_PIXEL = 4 # Crop test allowed offset 34PREVIEW_SIZE = (1920, 1080) # preview size 35 36# Before API level 30, only resolutions with the following listed aspect ratio 37# are checked. Device launched after API level 30 will need to pass the test 38# for all advertised resolutions. Device launched before API level 30 just 39# needs to pass the test for all resolutions within these aspect ratios. 40AR_CHECKED_PRE_API_30 = ["4:3", "16:9", "18:9"] 41AR_DIFF_ATOL = 0.01 42 43 44def print_failed_test_results(failed_ar, failed_fov, failed_crop): 45 """Print failed test results.""" 46 if failed_ar: 47 print "\nAspect ratio test summary" 48 print "Images failed in the aspect ratio test:" 49 print "Aspect ratio value: width / height" 50 for fa in failed_ar: 51 print "%s with %s %dx%d: %.3f;" % ( 52 fa["fmt_iter"], fa["fmt_cmpr"], 53 fa["w"], fa["h"], fa["ar"]), 54 print "valid range: %.3f ~ %.3f" % ( 55 fa["valid_range"][0], fa["valid_range"][1]) 56 57 if failed_fov: 58 print "\nFoV test summary" 59 print "Images failed in the FoV test:" 60 for fov in failed_fov: 61 print fov 62 63 if failed_crop: 64 print "\nCrop test summary" 65 print "Images failed in the crop test:" 66 print "Circle center position, (horizontal x vertical), listed", 67 print "below is relative to the image center." 68 for fc in failed_crop: 69 print "%s with %s %dx%d: %.3f x %.3f;" % ( 70 fc["fmt_iter"], fc["fmt_cmpr"], fc["w"], fc["h"], 71 fc["ct_hori"], fc["ct_vert"]), 72 print "valid horizontal range: %.3f ~ %.3f;" % ( 73 fc["valid_range_h"][0], fc["valid_range_h"][1]), 74 print "valid vertical range: %.3f ~ %.3f" % ( 75 fc["valid_range_v"][0], fc["valid_range_v"][1]) 76 77 78def is_checked_aspect_ratio(first_api_level, w, h): 79 if first_api_level >= 30: 80 return True 81 82 for ar_check in AR_CHECKED_PRE_API_30: 83 match_ar_list = [float(x) for x in ar_check.split(":")] 84 match_ar = match_ar_list[0] / match_ar_list[1] 85 if np.isclose(float(w)/h, match_ar, atol=AR_DIFF_ATOL): 86 return True 87 88 return False 89 90def calc_expected_circle_image_ratio(ref_fov, img_w, img_h): 91 """Determine the circle image area ratio in percentage for a given image size. 92 93 Args: 94 ref_fov: dict with [fmt, % coverage, w, h, circle_w, circle_h] 95 img_w: the image width 96 img_h: the image height 97 98 Returns: 99 chk_percent: the expected circle image area ratio in percentage 100 """ 101 102 ar_ref = float(ref_fov["w"]) / ref_fov["h"] 103 ar_target = float(img_w) / img_h 104 # The cropping will happen either horizontally or vertically. 105 # In both case a crop results in the visble area reduce by a ratio r (r < 1.0) 106 # and the circle will in turn occupy ref_pct / r (percent) on the target 107 # image size. 108 r = ar_ref / ar_target 109 if r < 1.0: 110 r = 1.0 / r 111 return ref_fov["percent"] * r 112 113 114def find_raw_fov_reference(cam, req, props, debug): 115 """Determine the circle coverage of the image in RAW reference image. 116 117 Args: 118 cam: camera object 119 req: camera request 120 props: camera properties 121 debug: perform debug dump or not 122 123 Returns: 124 ref_fov: dict with [fmt, % coverage, w, h, circle_w, circle_h] 125 cc_ct_gt: circle center position relative to the center of image. 126 aspect_ratio_gt: aspect ratio of the detected circle in float. 127 """ 128 129 # Capture full-frame raw. Use its aspect ratio and circle center 130 # location as ground truth for the other jpeg or yuv images. 131 print "Creating references for fov_coverage from RAW" 132 out_surface = {"format": "raw"} 133 cap_raw = cam.do_capture(req, out_surface) 134 print "Captured %s %dx%d" % ("raw", cap_raw["width"], 135 cap_raw["height"]) 136 img_raw = its.image.convert_capture_to_rgb_image(cap_raw, 137 props=props) 138 139 # The intrinsics and distortion coefficients are meant for full 140 # size RAW, but convert_capture_to_rgb_image returns a 2x downsampled 141 # version, so resize back to full size here. 142 img_raw = cv2.resize(img_raw, (0, 0), fx=2.0, fy=2.0) 143 144 # If the device supports lens distortion correction, apply the 145 # coefficients on the RAW image so it can be compared to YUV/JPEG 146 # outputs which are subject to the same correction via ISP. 147 if its.caps.distortion_correction(props): 148 # Intrinsic cal is of format: [f_x, f_y, c_x, c_y, s] 149 # [f_x, f_y] is the horizontal and vertical focal lengths, 150 # [c_x, c_y] is the position of the optical axis, 151 # and s is skew of sensor plane vs lens plane. 152 print "Applying intrinsic calibration and distortion params" 153 ical = np.array(props["android.lens.intrinsicCalibration"]) 154 msg = "Cannot include lens distortion without intrinsic cal!" 155 assert len(ical) == 5, msg 156 sensor_h = props["android.sensor.info.physicalSize"]["height"] 157 sensor_w = props["android.sensor.info.physicalSize"]["width"] 158 pixel_h = props["android.sensor.info.pixelArraySize"]["height"] 159 pixel_w = props["android.sensor.info.pixelArraySize"]["width"] 160 fd = float(cap_raw["metadata"]["android.lens.focalLength"]) 161 fd_w_pix = pixel_w * fd / sensor_w 162 fd_h_pix = pixel_h * fd / sensor_h 163 # transformation matrix 164 # k = [[f_x, s, c_x], 165 # [0, f_y, c_y], 166 # [0, 0, 1]] 167 k = np.array([[ical[0], ical[4], ical[2]], 168 [0, ical[1], ical[3]], 169 [0, 0, 1]]) 170 print "k:", k 171 e_msg = "fd_w(pixels): %.2f\tcal[0](pixels): %.2f\tTOL=20%%" % ( 172 fd_w_pix, ical[0]) 173 assert np.isclose(fd_w_pix, ical[0], rtol=0.20), e_msg 174 e_msg = "fd_h(pixels): %.2f\tcal[1](pixels): %.2f\tTOL=20%%" % ( 175 fd_h_pix, ical[0]) 176 assert np.isclose(fd_h_pix, ical[1], rtol=0.20), e_msg 177 178 # distortion 179 rad_dist = props["android.lens.distortion"] 180 print "android.lens.distortion:", rad_dist 181 e_msg = "%s param(s) found. %d expected." % (len(rad_dist), 182 NUM_DISTORT_PARAMS) 183 assert len(rad_dist) == NUM_DISTORT_PARAMS, e_msg 184 opencv_dist = np.array([rad_dist[0], rad_dist[1], 185 rad_dist[3], rad_dist[4], 186 rad_dist[2]]) 187 print "dist:", opencv_dist 188 img_raw = cv2.undistort(img_raw, k, opencv_dist) 189 size_raw = img_raw.shape 190 w_raw = size_raw[1] 191 h_raw = size_raw[0] 192 img_name = "%s_%s_w%d_h%d.png" % (NAME, "raw", w_raw, h_raw) 193 its.image.write_image(img_raw, img_name, True) 194 aspect_ratio_gt, cc_ct_gt, circle_size_raw = measure_aspect_ratio( 195 img_raw, img_name, True, debug) 196 raw_fov_percent = calc_circle_image_ratio( 197 circle_size_raw[0], circle_size_raw[1], w_raw, h_raw) 198 ref_fov = {} 199 ref_fov["fmt"] = "RAW" 200 ref_fov["percent"] = raw_fov_percent 201 ref_fov["w"] = w_raw 202 ref_fov["h"] = h_raw 203 ref_fov["circle_w"] = circle_size_raw[0] 204 ref_fov["circle_h"] = circle_size_raw[1] 205 print "Using RAW reference:", ref_fov 206 return ref_fov, cc_ct_gt, aspect_ratio_gt 207 208 209def find_jpeg_fov_reference(cam, req, props): 210 """Determine the circle coverage of the image in JPEG reference image. 211 212 Args: 213 cam: camera object 214 req: camera request 215 props: camera properties 216 217 Returns: 218 ref_fov: dict with [fmt, % coverage, w, h, circle_w, circle_h] 219 cc_ct_gt: circle center position relative to the center of image. 220 """ 221 ref_fov = {} 222 fmt = its.objects.get_largest_jpeg_format(props) 223 # capture and determine circle area in image 224 cap = cam.do_capture(req, fmt) 225 w = cap["width"] 226 h = cap["height"] 227 228 img = its.image.convert_capture_to_rgb_image(cap, props=props) 229 print "Captured JPEG %dx%d" % (w, h) 230 img_name = "%s_jpeg_w%d_h%d.png" % (NAME, w, h) 231 # Set debug to True to save the reference image 232 _, cc_ct_gt, circle_size = measure_aspect_ratio(img, img_name, False, debug=True) 233 fov_percent = calc_circle_image_ratio(circle_size[0], circle_size[1], w, h) 234 ref_fov["fmt"] = "JPEG" 235 ref_fov["percent"] = fov_percent 236 ref_fov["w"] = w 237 ref_fov["h"] = h 238 ref_fov["circle_w"] = circle_size[0] 239 ref_fov["circle_h"] = circle_size[1] 240 print "Using JPEG reference:", ref_fov 241 return ref_fov, cc_ct_gt 242 243 244def calc_circle_image_ratio(circle_w, circle_h, image_w, image_h): 245 """Calculate the percent of area the input circle covers in input image. 246 247 Args: 248 circle_w (int): width of circle 249 circle_h (int): height of circle 250 image_w (int): width of image 251 image_h (int): height of image 252 Returns: 253 fov_percent (float): % of image covered by circle 254 """ 255 circle_area = math.pi * math.pow(np.mean([circle_w, circle_h])/2.0, 2) 256 image_area = image_w * image_h 257 fov_percent = 100*circle_area/image_area 258 return fov_percent 259 260 261def measure_aspect_ratio(img, img_name, raw_avlb, debug): 262 """Measure the aspect ratio of the black circle in the test image. 263 264 Args: 265 img: Numpy float image array in RGB, with pixel values in [0,1]. 266 img_name: string with image info of format and size. 267 raw_avlb: True: raw capture is available; False: raw capture is not 268 available. 269 debug: boolean for whether in debug mode. 270 Returns: 271 aspect_ratio: aspect ratio number in float. 272 cc_ct: circle center position relative to the center of image. 273 (circle_w, circle_h): tuple of the circle size 274 """ 275 size = img.shape 276 img *= 255 277 # Gray image 278 img_gray = 0.299*img[:, :, 2] + 0.587*img[:, :, 1] + 0.114*img[:, :, 0] 279 280 # otsu threshold to binarize the image 281 _, img_bw = cv2.threshold(np.uint8(img_gray), 0, 255, 282 cv2.THRESH_BINARY + cv2.THRESH_OTSU) 283 284 # connected component 285 cv2_version = cv2.__version__ 286 if cv2_version.startswith('3.'): # OpenCV 3.x 287 _, contours, hierarchy = cv2.findContours( 288 255-img_bw, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE) 289 else: # OpenCV 2.x and 4.x 290 contours, hierarchy = cv2.findContours( 291 255-img_bw, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE) 292 293 # Check each component and find the black circle 294 min_cmpt = size[0] * size[1] * 0.005 295 max_cmpt = size[0] * size[1] * 0.35 296 num_circle = 0 297 aspect_ratio = 0 298 for ct, hrch in zip(contours, hierarchy[0]): 299 # The radius of the circle is 1/3 of the length of the square, meaning 300 # around 1/3 of the area of the square 301 # Parental component should exist and the area is acceptable. 302 # The coutour of a circle should have at least 5 points 303 child_area = cv2.contourArea(ct) 304 if (hrch[3] == -1 or child_area < min_cmpt or child_area > max_cmpt 305 or len(ct) < 15): 306 continue 307 # Check the shapes of current component and its parent 308 child_shape = its.cv2image.component_shape(ct) 309 parent = hrch[3] 310 prt_shape = its.cv2image.component_shape(contours[parent]) 311 prt_area = cv2.contourArea(contours[parent]) 312 dist_x = abs(child_shape["ctx"]-prt_shape["ctx"]) 313 dist_y = abs(child_shape["cty"]-prt_shape["cty"]) 314 # 1. 0.56*Parent"s width < Child"s width < 0.76*Parent"s width. 315 # 2. 0.56*Parent"s height < Child"s height < 0.76*Parent"s height. 316 # 3. Child"s width > 0.1*Image width 317 # 4. Child"s height > 0.1*Image height 318 # 5. 0.25*Parent"s area < Child"s area < 0.45*Parent"s area 319 # 6. Child == 0, and Parent == 255 320 # 7. Center of Child and center of parent should overlap 321 if (prt_shape["width"] * 0.56 < child_shape["width"] 322 < prt_shape["width"] * 0.76 323 and prt_shape["height"] * 0.56 < child_shape["height"] 324 < prt_shape["height"] * 0.76 325 and child_shape["width"] > 0.1 * size[1] 326 and child_shape["height"] > 0.1 * size[0] 327 and 0.30 * prt_area < child_area < 0.50 * prt_area 328 and img_bw[child_shape["cty"]][child_shape["ctx"]] == 0 329 and img_bw[child_shape["top"]][child_shape["left"]] == 255 330 and dist_x < 0.1 * child_shape["width"] 331 and dist_y < 0.1 * child_shape["height"]): 332 # If raw capture is not available, check the camera is placed right 333 # in front of the test page: 334 # 1. Distances between parent and child horizontally on both side,0 335 # dist_left and dist_right, should be close. 336 # 2. Distances between parent and child vertically on both side, 337 # dist_top and dist_bottom, should be close. 338 if not raw_avlb: 339 dist_left = child_shape["left"] - prt_shape["left"] 340 dist_right = prt_shape["right"] - child_shape["right"] 341 dist_top = child_shape["top"] - prt_shape["top"] 342 dist_bottom = prt_shape["bottom"] - child_shape["bottom"] 343 if (abs(dist_left-dist_right) > 0.05 * child_shape["width"] 344 or abs(dist_top-dist_bottom) > 0.05 * child_shape["height"]): 345 continue 346 # Calculate aspect ratio 347 aspect_ratio = float(child_shape["width"]) / child_shape["height"] 348 circle_ctx = child_shape["ctx"] 349 circle_cty = child_shape["cty"] 350 circle_w = float(child_shape["width"]) 351 circle_h = float(child_shape["height"]) 352 cc_ct = {"hori": float(child_shape["ctx"]-size[1]/2) / circle_w, 353 "vert": float(child_shape["cty"]-size[0]/2) / circle_h} 354 num_circle += 1 355 # If more than one circle found, break 356 if num_circle == 2: 357 break 358 359 if num_circle == 0: 360 its.image.write_image(img/255, img_name, True) 361 print "No black circle was detected. Please take pictures according", 362 print "to instruction carefully!\n" 363 assert num_circle == 1 364 365 if num_circle > 1: 366 its.image.write_image(img/255, img_name, True) 367 print "More than one black circle was detected. Background of scene", 368 print "may be too complex.\n" 369 assert num_circle == 1 370 371 # draw circle center and image center, and save the image 372 line_width = max(1, max(size)/500) 373 move_text_dist = line_width * 3 374 cv2.line(img, (circle_ctx, circle_cty), (size[1]/2, size[0]/2), 375 (255, 0, 0), line_width) 376 if circle_cty > size[0]/2: 377 move_text_down_circle = 4 378 move_text_down_image = -1 379 else: 380 move_text_down_circle = -1 381 move_text_down_image = 4 382 if circle_ctx > size[1]/2: 383 move_text_right_circle = 2 384 move_text_right_image = -1 385 else: 386 move_text_right_circle = -1 387 move_text_right_image = 2 388 # circle center 389 text_circle_x = move_text_dist * move_text_right_circle + circle_ctx 390 text_circle_y = move_text_dist * move_text_down_circle + circle_cty 391 cv2.circle(img, (circle_ctx, circle_cty), line_width*2, (255, 0, 0), -1) 392 cv2.putText(img, "circle center", (text_circle_x, text_circle_y), 393 cv2.FONT_HERSHEY_SIMPLEX, line_width/2.0, (255, 0, 0), 394 line_width) 395 # image center 396 text_imgct_x = move_text_dist * move_text_right_image + size[1]/2 397 text_imgct_y = move_text_dist * move_text_down_image + size[0]/2 398 cv2.circle(img, (size[1]/2, size[0]/2), line_width*2, (255, 0, 0), -1) 399 cv2.putText(img, "image center", (text_imgct_x, text_imgct_y), 400 cv2.FONT_HERSHEY_SIMPLEX, line_width/2.0, (255, 0, 0), 401 line_width) 402 if debug: 403 its.image.write_image(img/255, img_name, True) 404 405 print "Aspect ratio: %.3f" % aspect_ratio 406 print "Circle center position wrt to image center:", 407 print "%.3fx%.3f" % (cc_ct["vert"], cc_ct["hori"]) 408 return aspect_ratio, cc_ct, (circle_w, circle_h) 409 410 411def main(): 412 """Test aspect ratio/field of view (FOV)/cropping for each tested formats combinations. 413 414 This test checks for: 415 1. Aspect ratio: images are not stretched 416 2. Crop: center of images is always center of the image sensor no matter 417 how the image is cropped from sensor's full FOV 418 3. FOV: images are always cropped to keep the maximum possible FOV with 419 only one dimension (horizontal or veritical) cropped. 420 421 Aspect ratio and FOV test runs on level3, full and limited devices. 422 Crop test only runs on full and level3 devices. 423 424 The test chart is a black circle inside a black square. When raw capture is 425 available, set the height vs. width ratio of the circle in the full-frame 426 raw as ground truth. In an ideal setup such ratio should be very close to 427 1.0, but here we just use the value derived from full resolution RAW as 428 ground truth to account for the possiblity that the chart is not well 429 positioned to be precisely parallel to image sensor plane. 430 The test then compare the ground truth ratio with the same ratio measured 431 on images captued using different stream combinations of varying formats 432 ("jpeg" and "yuv") and resolutions. 433 If raw capture is unavailable, a full resolution JPEG image is used to setup 434 ground truth. In this case, the ground truth aspect ratio is defined as 1.0 435 and it is the tester's responsibility to make sure the test chart is 436 properly positioned so the detected circles indeed have aspect ratio close 437 to 1.0 assuming no bugs causing image stretched. 438 439 The aspect ratio test checks the aspect ratio of the detected circle and 440 it will fail if the aspect ratio differs too much from the ground truth 441 aspect ratio mentioned above. 442 443 The FOV test examines the ratio between the detected circle area and the 444 image size. When the aspect ratio of the test image is the same as the 445 ground truth image, the ratio should be very close to the ground truth 446 value. When the aspect ratio is different, the difference is factored in 447 per the expectation of the Camera2 API specification, which mandates the 448 FOV reduction from full sensor area must only occur in one dimension: 449 horizontally or vertically, and never both. For example, let's say a sensor 450 has a 16:10 full sensor FOV. For all 16:10 output images there should be no 451 FOV reduction on them. For 16:9 output images the FOV should be vertically 452 cropped by 9/10. For 4:3 output images the FOV should be cropped 453 horizontally instead and the ratio (r) can be calculated as follows: 454 (16 * r) / 10 = 4 / 3 => r = 40 / 48 = 0.8333 455 Say the circle is covering x percent of the 16:10 sensor on the full 16:10 456 FOV, and assume the circle in the center will never be cut in any output 457 sizes (this can be achieved by picking the right size and position of the 458 test circle), the from above cropping expectation we can derive on a 16:9 459 output image the circle will cover (x / 0.9) percent of the 16:9 image; on 460 a 4:3 output image the circle will cover (x / 0.8333) percent of the 4:3 461 image. 462 463 The crop test checks that the center of any output image remains aligned 464 with center of sensor's active area, no matter what kind of cropping or 465 scaling is applied. The test verified that by checking the relative vector 466 from the image center to the center of detected circle remains unchanged. 467 The relative part is normalized by the detected circle size to account for 468 scaling effect. 469 """ 470 aspect_ratio_gt = 1.0 # Ground truth circle width/height ratio. 471 # If full resolution RAW is available as reference 472 # then this will be updated to the value measured on 473 # the RAW image. Otherwise a full resolution JPEG 474 # will be used as reference and this value will be 475 # 1.0. 476 failed_ar = [] # streams failed the aspect ratio test 477 failed_crop = [] # streams failed the crop test 478 failed_fov = [] # streams that fail FoV test 479 format_list = [] # format list for multiple capture objects. 480 481 # Do multi-capture of "iter" and "cmpr". Iterate through all the 482 # available sizes of "iter", and only use the size specified for "cmpr" 483 # The "cmpr" capture is only used so that we have multiple capture target 484 # instead of just one, which should help catching more potential issues. 485 # The test doesn't look into the output of "cmpr" images at all. 486 # The "iter_max" or "cmpr_size" key defines the maximal size being iterated 487 # or selected for the "iter" and "cmpr" stream accordingly. None means no 488 # upper bound is specified. 489 format_list.append({"iter": "yuv", "iter_max": None, 490 "cmpr": "yuv", "cmpr_size": PREVIEW_SIZE}) 491 format_list.append({"iter": "yuv", "iter_max": PREVIEW_SIZE, 492 "cmpr": "jpeg", "cmpr_size": None}) 493 format_list.append({"iter": "yuv", "iter_max": PREVIEW_SIZE, 494 "cmpr": "raw", "cmpr_size": None}) 495 format_list.append({"iter": "jpeg", "iter_max": None, 496 "cmpr": "raw", "cmpr_size": None}) 497 format_list.append({"iter": "jpeg", "iter_max": None, 498 "cmpr": "yuv", "cmpr_size": PREVIEW_SIZE}) 499 ref_fov = {} # Reference frame's FOV related information 500 # If RAW is available a full resolution RAW frame will be used 501 # as reference frame; otherwise the highest resolution JPEG is used. 502 with its.device.ItsSession() as cam: 503 props = cam.get_camera_properties() 504 fls_logical = props['android.lens.info.availableFocalLengths'] 505 print 'logical available focal lengths: %s', str(fls_logical) 506 props = cam.override_with_hidden_physical_camera_props(props) 507 fls_physical = props['android.lens.info.availableFocalLengths'] 508 print 'physical available focal lengths: %s', str(fls_physical) 509 # determine skip conditions 510 first_api = its.device.get_first_api_level(its.device.get_device_id()) 511 if first_api < 30: # original constraint 512 its.caps.skip_unless(its.caps.read_3a(props)) 513 else: # loosen from read_3a to enable LIMITED coverage 514 its.caps.skip_unless(its.caps.ae_lock(props) and 515 its.caps.awb_lock(props)) 516 # determine capabilities 517 full_device = its.caps.full_or_better(props) 518 limited_device = its.caps.limited(props) 519 its.caps.skip_unless(full_device or limited_device) 520 level3_device = its.caps.level3(props) 521 raw_avlb = its.caps.raw16(props) 522 run_crop_test = (level3_device or full_device) and raw_avlb 523 if not run_crop_test: 524 print "Crop test skipped" 525 debug = its.caps.debug_mode() 526 # Converge 3A 527 cam.do_3a() 528 req = its.objects.auto_capture_request() 529 530 # If raw is available and main camera, use it as ground truth. 531 if raw_avlb and (fls_physical == fls_logical): 532 ref_fov, cc_ct_gt, aspect_ratio_gt = find_raw_fov_reference(cam, req, props, debug) 533 else: 534 ref_fov, cc_ct_gt = find_jpeg_fov_reference(cam, req, props) 535 536 if run_crop_test: 537 # Normalize the circle size to 1/4 of the image size, so that 538 # circle size won't affect the crop test result 539 factor_cp_thres = ((min(ref_fov["w"], ref_fov["h"])/4.0) / 540 max(ref_fov["circle_w"], ref_fov["circle_h"])) 541 thres_l_cp_test = THRESH_L_CP * factor_cp_thres 542 thres_xs_cp_test = THRESH_XS_CP * factor_cp_thres 543 544 # Take pictures of each settings with all the image sizes available. 545 for fmt in format_list: 546 fmt_iter = fmt["iter"] 547 fmt_cmpr = fmt["cmpr"] 548 dual_target = fmt_cmpr is not "none" 549 # Get the size of "cmpr" 550 if dual_target: 551 sizes = its.objects.get_available_output_sizes( 552 fmt_cmpr, props, fmt["cmpr_size"]) 553 if not sizes: # device might not support RAW 554 continue 555 size_cmpr = sizes[0] 556 for size_iter in its.objects.get_available_output_sizes( 557 fmt_iter, props, fmt["iter_max"]): 558 w_iter = size_iter[0] 559 h_iter = size_iter[1] 560 # Skip testing same format/size combination 561 # ITS does not handle that properly now 562 if (dual_target 563 and w_iter*h_iter == size_cmpr[0]*size_cmpr[1] 564 and fmt_iter == fmt_cmpr): 565 continue 566 out_surface = [{"width": w_iter, 567 "height": h_iter, 568 "format": fmt_iter}] 569 if dual_target: 570 out_surface.append({"width": size_cmpr[0], 571 "height": size_cmpr[1], 572 "format": fmt_cmpr}) 573 cap = cam.do_capture(req, out_surface) 574 if dual_target: 575 frm_iter = cap[0] 576 else: 577 frm_iter = cap 578 assert frm_iter["format"] == fmt_iter 579 assert frm_iter["width"] == w_iter 580 assert frm_iter["height"] == h_iter 581 print "Captured %s with %s %dx%d. Compared size: %dx%d" % ( 582 fmt_iter, fmt_cmpr, w_iter, h_iter, size_cmpr[0], 583 size_cmpr[1]) 584 img = its.image.convert_capture_to_rgb_image(frm_iter) 585 img_name = "%s_%s_with_%s_w%d_h%d.png" % (NAME, 586 fmt_iter, fmt_cmpr, 587 w_iter, h_iter) 588 aspect_ratio, cc_ct, (cc_w, cc_h) = measure_aspect_ratio( 589 img, img_name, raw_avlb, debug) 590 # check fov coverage for all fmts in AR_CHECKED 591 fov_percent = calc_circle_image_ratio( 592 cc_w, cc_h, w_iter, h_iter) 593 chk_percent = calc_expected_circle_image_ratio(ref_fov, w_iter, h_iter) 594 chk_enabled = is_checked_aspect_ratio(first_api, w_iter, h_iter) 595 if chk_enabled and not np.isclose(fov_percent, chk_percent, 596 rtol=FOV_PERCENT_RTOL): 597 msg = "FoV %%: %.2f, Ref FoV %%: %.2f, " % ( 598 fov_percent, chk_percent) 599 msg += "TOL=%.f%%, img: %dx%d, ref: %dx%d" % ( 600 FOV_PERCENT_RTOL*100, w_iter, h_iter, 601 ref_fov["w"], ref_fov["h"]) 602 failed_fov.append(msg) 603 its.image.write_image(img/255, img_name, True) 604 605 # check pass/fail for aspect ratio 606 # image size: the larger one of image width and height 607 # image size >= LARGE_SIZE: use THRESH_L_AR 608 # image size == 0 (extreme case): THRESH_XS_AR 609 # 0 < image size < LARGE_SIZE: scale between THRESH_XS_AR 610 # and THRESH_L_AR 611 thres_ar_test = max( 612 THRESH_L_AR, THRESH_XS_AR + max(w_iter, h_iter) * 613 (THRESH_L_AR-THRESH_XS_AR)/LARGE_SIZE) 614 thres_range_ar = (aspect_ratio_gt-thres_ar_test, 615 aspect_ratio_gt+thres_ar_test) 616 if (aspect_ratio < thres_range_ar[0] or 617 aspect_ratio > thres_range_ar[1]): 618 failed_ar.append({"fmt_iter": fmt_iter, 619 "fmt_cmpr": fmt_cmpr, 620 "w": w_iter, "h": h_iter, 621 "ar": aspect_ratio, 622 "valid_range": thres_range_ar}) 623 its.image.write_image(img/255, img_name, True) 624 625 # check pass/fail for crop 626 if run_crop_test: 627 # image size >= LARGE_SIZE: use thres_l_cp_test 628 # image size == 0 (extreme case): thres_xs_cp_test 629 # 0 < image size < LARGE_SIZE: scale between 630 # thres_xs_cp_test and thres_l_cp_test 631 # Also, allow at least THRESH_MIN_PIXEL off to 632 # prevent threshold being too tight for very 633 # small circle 634 thres_hori_cp_test = max( 635 thres_l_cp_test, thres_xs_cp_test + w_iter * 636 (thres_l_cp_test-thres_xs_cp_test)/LARGE_SIZE) 637 min_threshold_h = THRESH_MIN_PIXEL / cc_w 638 thres_hori_cp_test = max(thres_hori_cp_test, 639 min_threshold_h) 640 thres_range_h_cp = (cc_ct_gt["hori"]-thres_hori_cp_test, 641 cc_ct_gt["hori"]+thres_hori_cp_test) 642 thres_vert_cp_test = max( 643 thres_l_cp_test, thres_xs_cp_test + h_iter * 644 (thres_l_cp_test-thres_xs_cp_test)/LARGE_SIZE) 645 min_threshold_v = THRESH_MIN_PIXEL / cc_h 646 thres_vert_cp_test = max(thres_vert_cp_test, 647 min_threshold_v) 648 thres_range_v_cp = (cc_ct_gt["vert"]-thres_vert_cp_test, 649 cc_ct_gt["vert"]+thres_vert_cp_test) 650 if (cc_ct["hori"] < thres_range_h_cp[0] 651 or cc_ct["hori"] > thres_range_h_cp[1] 652 or cc_ct["vert"] < thres_range_v_cp[0] 653 or cc_ct["vert"] > thres_range_v_cp[1]): 654 failed_crop.append({"fmt_iter": fmt_iter, 655 "fmt_cmpr": fmt_cmpr, 656 "w": w_iter, "h": h_iter, 657 "ct_hori": cc_ct["hori"], 658 "ct_vert": cc_ct["vert"], 659 "valid_range_h": thres_range_h_cp, 660 "valid_range_v": thres_range_v_cp}) 661 its.image.write_image(img/255, img_name, True) 662 663 # Print failed any test results 664 print_failed_test_results(failed_ar, failed_fov, failed_crop) 665 assert not failed_ar 666 assert not failed_fov 667 if level3_device: 668 assert not failed_crop 669 670 671if __name__ == "__main__": 672 main() 673