blob: bbf13784b0f87aff4c350e7ee85f278c8d70b96f [file] [log] [blame]
# Copyright 2015 The Android Open Source Project (lint as: python2)
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import math
import os.path
import cv2
import its.caps
import its.cv2image
import its.device
import its.image
import its.objects
import numpy as np
FOV_PERCENT_RTOL = 0.15 # Relative tolerance on circle FoV % to expected
LARGE_SIZE = 2000 # Define the size of a large image (compare against max(w,h))
NAME = os.path.basename(__file__).split(".")[0]
NUM_DISTORT_PARAMS = 5
THRESH_L_AR = 0.02 # aspect ratio test threshold of large images
THRESH_XS_AR = 0.075 # aspect ratio test threshold of mini images
THRESH_L_CP = 0.02 # Crop test threshold of large images
THRESH_XS_CP = 0.075 # Crop test threshold of mini images
THRESH_MIN_PIXEL = 4 # Crop test allowed offset
PREVIEW_SIZE = (1920, 1080) # preview size
# Before API level 30, only resolutions with the following listed aspect ratio
# are checked. Device launched after API level 30 will need to pass the test
# for all advertised resolutions. Device launched before API level 30 just
# needs to pass the test for all resolutions within these aspect ratios.
AR_CHECKED_PRE_API_30 = ["4:3", "16:9", "18:9"]
AR_DIFF_ATOL = 0.01
def print_failed_test_results(failed_ar, failed_fov, failed_crop):
"""Print failed test results."""
if failed_ar:
print "\nAspect ratio test summary"
print "Images failed in the aspect ratio test:"
print "Aspect ratio value: width / height"
for fa in failed_ar:
print "%s with %s %dx%d: %.3f;" % (
fa["fmt_iter"], fa["fmt_cmpr"],
fa["w"], fa["h"], fa["ar"]),
print "valid range: %.3f ~ %.3f" % (
fa["valid_range"][0], fa["valid_range"][1])
if failed_fov:
print "\nFoV test summary"
print "Images failed in the FoV test:"
for fov in failed_fov:
print fov
if failed_crop:
print "\nCrop test summary"
print "Images failed in the crop test:"
print "Circle center position, (horizontal x vertical), listed",
print "below is relative to the image center."
for fc in failed_crop:
print "%s with %s %dx%d: %.3f x %.3f;" % (
fc["fmt_iter"], fc["fmt_cmpr"], fc["w"], fc["h"],
fc["ct_hori"], fc["ct_vert"]),
print "valid horizontal range: %.3f ~ %.3f;" % (
fc["valid_range_h"][0], fc["valid_range_h"][1]),
print "valid vertical range: %.3f ~ %.3f" % (
fc["valid_range_v"][0], fc["valid_range_v"][1])
def is_checked_aspect_ratio(first_api_level, w, h):
if first_api_level >= 30:
return True
for ar_check in AR_CHECKED_PRE_API_30:
match_ar_list = [float(x) for x in ar_check.split(":")]
match_ar = match_ar_list[0] / match_ar_list[1]
if np.isclose(float(w)/h, match_ar, atol=AR_DIFF_ATOL):
return True
return False
def calc_expected_circle_image_ratio(ref_fov, img_w, img_h):
"""Determine the circle image area ratio in percentage for a given image size.
Args:
ref_fov: dict with [fmt, % coverage, w, h, circle_w, circle_h]
img_w: the image width
img_h: the image height
Returns:
chk_percent: the expected circle image area ratio in percentage
"""
ar_ref = float(ref_fov["w"]) / ref_fov["h"]
ar_target = float(img_w) / img_h
# The cropping will happen either horizontally or vertically.
# In both case a crop results in the visble area reduce by a ratio r (r < 1.0)
# and the circle will in turn occupy ref_pct / r (percent) on the target
# image size.
r = ar_ref / ar_target
if r < 1.0:
r = 1.0 / r
return ref_fov["percent"] * r
def find_raw_fov_reference(cam, req, props, debug):
"""Determine the circle coverage of the image in RAW reference image.
Args:
cam: camera object
req: camera request
props: camera properties
debug: perform debug dump or not
Returns:
ref_fov: dict with [fmt, % coverage, w, h, circle_w, circle_h]
cc_ct_gt: circle center position relative to the center of image.
aspect_ratio_gt: aspect ratio of the detected circle in float.
"""
# Capture full-frame raw. Use its aspect ratio and circle center
# location as ground truth for the other jpeg or yuv images.
print "Creating references for fov_coverage from RAW"
out_surface = {"format": "raw"}
cap_raw = cam.do_capture(req, out_surface)
print "Captured %s %dx%d" % ("raw", cap_raw["width"],
cap_raw["height"])
img_raw = its.image.convert_capture_to_rgb_image(cap_raw,
props=props)
# The intrinsics and distortion coefficients are meant for full
# size RAW, but convert_capture_to_rgb_image returns a 2x downsampled
# version, so resize back to full size here.
img_raw = cv2.resize(img_raw, (0, 0), fx=2.0, fy=2.0)
# If the device supports lens distortion correction, apply the
# coefficients on the RAW image so it can be compared to YUV/JPEG
# outputs which are subject to the same correction via ISP.
if its.caps.distortion_correction(props):
# Intrinsic cal is of format: [f_x, f_y, c_x, c_y, s]
# [f_x, f_y] is the horizontal and vertical focal lengths,
# [c_x, c_y] is the position of the optical axis,
# and s is skew of sensor plane vs lens plane.
print "Applying intrinsic calibration and distortion params"
ical = np.array(props["android.lens.intrinsicCalibration"])
msg = "Cannot include lens distortion without intrinsic cal!"
assert len(ical) == 5, msg
sensor_h = props["android.sensor.info.physicalSize"]["height"]
sensor_w = props["android.sensor.info.physicalSize"]["width"]
pixel_h = props["android.sensor.info.pixelArraySize"]["height"]
pixel_w = props["android.sensor.info.pixelArraySize"]["width"]
fd = float(cap_raw["metadata"]["android.lens.focalLength"])
fd_w_pix = pixel_w * fd / sensor_w
fd_h_pix = pixel_h * fd / sensor_h
# transformation matrix
# k = [[f_x, s, c_x],
# [0, f_y, c_y],
# [0, 0, 1]]
k = np.array([[ical[0], ical[4], ical[2]],
[0, ical[1], ical[3]],
[0, 0, 1]])
print "k:", k
e_msg = "fd_w(pixels): %.2f\tcal[0](pixels): %.2f\tTOL=20%%" % (
fd_w_pix, ical[0])
assert np.isclose(fd_w_pix, ical[0], rtol=0.20), e_msg
e_msg = "fd_h(pixels): %.2f\tcal[1](pixels): %.2f\tTOL=20%%" % (
fd_h_pix, ical[0])
assert np.isclose(fd_h_pix, ical[1], rtol=0.20), e_msg
# distortion
rad_dist = props["android.lens.distortion"]
print "android.lens.distortion:", rad_dist
e_msg = "%s param(s) found. %d expected." % (len(rad_dist),
NUM_DISTORT_PARAMS)
assert len(rad_dist) == NUM_DISTORT_PARAMS, e_msg
opencv_dist = np.array([rad_dist[0], rad_dist[1],
rad_dist[3], rad_dist[4],
rad_dist[2]])
print "dist:", opencv_dist
img_raw = cv2.undistort(img_raw, k, opencv_dist)
size_raw = img_raw.shape
w_raw = size_raw[1]
h_raw = size_raw[0]
img_name = "%s_%s_w%d_h%d.png" % (NAME, "raw", w_raw, h_raw)
its.image.write_image(img_raw, img_name, True)
aspect_ratio_gt, cc_ct_gt, circle_size_raw = measure_aspect_ratio(
img_raw, img_name, True, debug)
raw_fov_percent = calc_circle_image_ratio(
circle_size_raw[0], circle_size_raw[1], w_raw, h_raw)
ref_fov = {}
ref_fov["fmt"] = "RAW"
ref_fov["percent"] = raw_fov_percent
ref_fov["w"] = w_raw
ref_fov["h"] = h_raw
ref_fov["circle_w"] = circle_size_raw[0]
ref_fov["circle_h"] = circle_size_raw[1]
print "Using RAW reference:", ref_fov
return ref_fov, cc_ct_gt, aspect_ratio_gt
def find_jpeg_fov_reference(cam, req, props):
"""Determine the circle coverage of the image in JPEG reference image.
Args:
cam: camera object
req: camera request
props: camera properties
Returns:
ref_fov: dict with [fmt, % coverage, w, h, circle_w, circle_h]
cc_ct_gt: circle center position relative to the center of image.
"""
ref_fov = {}
fmt = its.objects.get_largest_jpeg_format(props)
# capture and determine circle area in image
cap = cam.do_capture(req, fmt)
w = cap["width"]
h = cap["height"]
img = its.image.convert_capture_to_rgb_image(cap, props=props)
print "Captured JPEG %dx%d" % (w, h)
img_name = "%s_jpeg_w%d_h%d.png" % (NAME, w, h)
# Set debug to True to save the reference image
_, cc_ct_gt, circle_size = measure_aspect_ratio(img, img_name, False, debug=True)
fov_percent = calc_circle_image_ratio(circle_size[0], circle_size[1], w, h)
ref_fov["fmt"] = "JPEG"
ref_fov["percent"] = fov_percent
ref_fov["w"] = w
ref_fov["h"] = h
ref_fov["circle_w"] = circle_size[0]
ref_fov["circle_h"] = circle_size[1]
print "Using JPEG reference:", ref_fov
return ref_fov, cc_ct_gt
def calc_circle_image_ratio(circle_w, circle_h, image_w, image_h):
"""Calculate the percent of area the input circle covers in input image.
Args:
circle_w (int): width of circle
circle_h (int): height of circle
image_w (int): width of image
image_h (int): height of image
Returns:
fov_percent (float): % of image covered by circle
"""
circle_area = math.pi * math.pow(np.mean([circle_w, circle_h])/2.0, 2)
image_area = image_w * image_h
fov_percent = 100*circle_area/image_area
return fov_percent
def measure_aspect_ratio(img, img_name, raw_avlb, debug):
"""Measure the aspect ratio of the black circle in the test image.
Args:
img: Numpy float image array in RGB, with pixel values in [0,1].
img_name: string with image info of format and size.
raw_avlb: True: raw capture is available; False: raw capture is not
available.
debug: boolean for whether in debug mode.
Returns:
aspect_ratio: aspect ratio number in float.
cc_ct: circle center position relative to the center of image.
(circle_w, circle_h): tuple of the circle size
"""
size = img.shape
img *= 255
# Gray image
img_gray = 0.299*img[:, :, 2] + 0.587*img[:, :, 1] + 0.114*img[:, :, 0]
# otsu threshold to binarize the image
_, img_bw = cv2.threshold(np.uint8(img_gray), 0, 255,
cv2.THRESH_BINARY + cv2.THRESH_OTSU)
# connected component
cv2_version = cv2.__version__
if cv2_version.startswith('3.'): # OpenCV 3.x
_, contours, hierarchy = cv2.findContours(
255-img_bw, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
else: # OpenCV 2.x and 4.x
contours, hierarchy = cv2.findContours(
255-img_bw, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
# Check each component and find the black circle
min_cmpt = size[0] * size[1] * 0.005
max_cmpt = size[0] * size[1] * 0.35
num_circle = 0
aspect_ratio = 0
for ct, hrch in zip(contours, hierarchy[0]):
# The radius of the circle is 1/3 of the length of the square, meaning
# around 1/3 of the area of the square
# Parental component should exist and the area is acceptable.
# The coutour of a circle should have at least 5 points
child_area = cv2.contourArea(ct)
if (hrch[3] == -1 or child_area < min_cmpt or child_area > max_cmpt
or len(ct) < 15):
continue
# Check the shapes of current component and its parent
child_shape = its.cv2image.component_shape(ct)
parent = hrch[3]
prt_shape = its.cv2image.component_shape(contours[parent])
prt_area = cv2.contourArea(contours[parent])
dist_x = abs(child_shape["ctx"]-prt_shape["ctx"])
dist_y = abs(child_shape["cty"]-prt_shape["cty"])
# 1. 0.56*Parent"s width < Child"s width < 0.76*Parent"s width.
# 2. 0.56*Parent"s height < Child"s height < 0.76*Parent"s height.
# 3. Child"s width > 0.1*Image width
# 4. Child"s height > 0.1*Image height
# 5. 0.25*Parent"s area < Child"s area < 0.45*Parent"s area
# 6. Child == 0, and Parent == 255
# 7. Center of Child and center of parent should overlap
if (prt_shape["width"] * 0.56 < child_shape["width"]
< prt_shape["width"] * 0.76
and prt_shape["height"] * 0.56 < child_shape["height"]
< prt_shape["height"] * 0.76
and child_shape["width"] > 0.1 * size[1]
and child_shape["height"] > 0.1 * size[0]
and 0.30 * prt_area < child_area < 0.50 * prt_area
and img_bw[child_shape["cty"]][child_shape["ctx"]] == 0
and img_bw[child_shape["top"]][child_shape["left"]] == 255
and dist_x < 0.1 * child_shape["width"]
and dist_y < 0.1 * child_shape["height"]):
# If raw capture is not available, check the camera is placed right
# in front of the test page:
# 1. Distances between parent and child horizontally on both side,0
# dist_left and dist_right, should be close.
# 2. Distances between parent and child vertically on both side,
# dist_top and dist_bottom, should be close.
if not raw_avlb:
dist_left = child_shape["left"] - prt_shape["left"]
dist_right = prt_shape["right"] - child_shape["right"]
dist_top = child_shape["top"] - prt_shape["top"]
dist_bottom = prt_shape["bottom"] - child_shape["bottom"]
if (abs(dist_left-dist_right) > 0.05 * child_shape["width"]
or abs(dist_top-dist_bottom) > 0.05 * child_shape["height"]):
continue
# Calculate aspect ratio
aspect_ratio = float(child_shape["width"]) / child_shape["height"]
circle_ctx = child_shape["ctx"]
circle_cty = child_shape["cty"]
circle_w = float(child_shape["width"])
circle_h = float(child_shape["height"])
cc_ct = {"hori": float(child_shape["ctx"]-size[1]/2) / circle_w,
"vert": float(child_shape["cty"]-size[0]/2) / circle_h}
num_circle += 1
# If more than one circle found, break
if num_circle == 2:
break
if num_circle == 0:
its.image.write_image(img/255, img_name, True)
print "No black circle was detected. Please take pictures according",
print "to instruction carefully!\n"
assert num_circle == 1
if num_circle > 1:
its.image.write_image(img/255, img_name, True)
print "More than one black circle was detected. Background of scene",
print "may be too complex.\n"
assert num_circle == 1
# draw circle center and image center, and save the image
line_width = max(1, max(size)/500)
move_text_dist = line_width * 3
cv2.line(img, (circle_ctx, circle_cty), (size[1]/2, size[0]/2),
(255, 0, 0), line_width)
if circle_cty > size[0]/2:
move_text_down_circle = 4
move_text_down_image = -1
else:
move_text_down_circle = -1
move_text_down_image = 4
if circle_ctx > size[1]/2:
move_text_right_circle = 2
move_text_right_image = -1
else:
move_text_right_circle = -1
move_text_right_image = 2
# circle center
text_circle_x = move_text_dist * move_text_right_circle + circle_ctx
text_circle_y = move_text_dist * move_text_down_circle + circle_cty
cv2.circle(img, (circle_ctx, circle_cty), line_width*2, (255, 0, 0), -1)
cv2.putText(img, "circle center", (text_circle_x, text_circle_y),
cv2.FONT_HERSHEY_SIMPLEX, line_width/2.0, (255, 0, 0),
line_width)
# image center
text_imgct_x = move_text_dist * move_text_right_image + size[1]/2
text_imgct_y = move_text_dist * move_text_down_image + size[0]/2
cv2.circle(img, (size[1]/2, size[0]/2), line_width*2, (255, 0, 0), -1)
cv2.putText(img, "image center", (text_imgct_x, text_imgct_y),
cv2.FONT_HERSHEY_SIMPLEX, line_width/2.0, (255, 0, 0),
line_width)
if debug:
its.image.write_image(img/255, img_name, True)
print "Aspect ratio: %.3f" % aspect_ratio
print "Circle center position wrt to image center:",
print "%.3fx%.3f" % (cc_ct["vert"], cc_ct["hori"])
return aspect_ratio, cc_ct, (circle_w, circle_h)
def main():
"""Test aspect ratio/field of view (FOV)/cropping for each tested formats combinations.
This test checks for:
1. Aspect ratio: images are not stretched
2. Crop: center of images is always center of the image sensor no matter
how the image is cropped from sensor's full FOV
3. FOV: images are always cropped to keep the maximum possible FOV with
only one dimension (horizontal or veritical) cropped.
Aspect ratio and FOV test runs on level3, full and limited devices.
Crop test only runs on full and level3 devices.
The test chart is a black circle inside a black square. When raw capture is
available, set the height vs. width ratio of the circle in the full-frame
raw as ground truth. In an ideal setup such ratio should be very close to
1.0, but here we just use the value derived from full resolution RAW as
ground truth to account for the possiblity that the chart is not well
positioned to be precisely parallel to image sensor plane.
The test then compare the ground truth ratio with the same ratio measured
on images captued using different stream combinations of varying formats
("jpeg" and "yuv") and resolutions.
If raw capture is unavailable, a full resolution JPEG image is used to setup
ground truth. In this case, the ground truth aspect ratio is defined as 1.0
and it is the tester's responsibility to make sure the test chart is
properly positioned so the detected circles indeed have aspect ratio close
to 1.0 assuming no bugs causing image stretched.
The aspect ratio test checks the aspect ratio of the detected circle and
it will fail if the aspect ratio differs too much from the ground truth
aspect ratio mentioned above.
The FOV test examines the ratio between the detected circle area and the
image size. When the aspect ratio of the test image is the same as the
ground truth image, the ratio should be very close to the ground truth
value. When the aspect ratio is different, the difference is factored in
per the expectation of the Camera2 API specification, which mandates the
FOV reduction from full sensor area must only occur in one dimension:
horizontally or vertically, and never both. For example, let's say a sensor
has a 16:10 full sensor FOV. For all 16:10 output images there should be no
FOV reduction on them. For 16:9 output images the FOV should be vertically
cropped by 9/10. For 4:3 output images the FOV should be cropped
horizontally instead and the ratio (r) can be calculated as follows:
(16 * r) / 10 = 4 / 3 => r = 40 / 48 = 0.8333
Say the circle is covering x percent of the 16:10 sensor on the full 16:10
FOV, and assume the circle in the center will never be cut in any output
sizes (this can be achieved by picking the right size and position of the
test circle), the from above cropping expectation we can derive on a 16:9
output image the circle will cover (x / 0.9) percent of the 16:9 image; on
a 4:3 output image the circle will cover (x / 0.8333) percent of the 4:3
image.
The crop test checks that the center of any output image remains aligned
with center of sensor's active area, no matter what kind of cropping or
scaling is applied. The test verified that by checking the relative vector
from the image center to the center of detected circle remains unchanged.
The relative part is normalized by the detected circle size to account for
scaling effect.
"""
aspect_ratio_gt = 1.0 # Ground truth circle width/height ratio.
# If full resolution RAW is available as reference
# then this will be updated to the value measured on
# the RAW image. Otherwise a full resolution JPEG
# will be used as reference and this value will be
# 1.0.
failed_ar = [] # streams failed the aspect ratio test
failed_crop = [] # streams failed the crop test
failed_fov = [] # streams that fail FoV test
format_list = [] # format list for multiple capture objects.
# Do multi-capture of "iter" and "cmpr". Iterate through all the
# available sizes of "iter", and only use the size specified for "cmpr"
# The "cmpr" capture is only used so that we have multiple capture target
# instead of just one, which should help catching more potential issues.
# The test doesn't look into the output of "cmpr" images at all.
# The "iter_max" or "cmpr_size" key defines the maximal size being iterated
# or selected for the "iter" and "cmpr" stream accordingly. None means no
# upper bound is specified.
format_list.append({"iter": "yuv", "iter_max": None,
"cmpr": "yuv", "cmpr_size": PREVIEW_SIZE})
format_list.append({"iter": "yuv", "iter_max": PREVIEW_SIZE,
"cmpr": "jpeg", "cmpr_size": None})
format_list.append({"iter": "yuv", "iter_max": PREVIEW_SIZE,
"cmpr": "raw", "cmpr_size": None})
format_list.append({"iter": "jpeg", "iter_max": None,
"cmpr": "raw", "cmpr_size": None})
format_list.append({"iter": "jpeg", "iter_max": None,
"cmpr": "yuv", "cmpr_size": PREVIEW_SIZE})
ref_fov = {} # Reference frame's FOV related information
# If RAW is available a full resolution RAW frame will be used
# as reference frame; otherwise the highest resolution JPEG is used.
with its.device.ItsSession() as cam:
props = cam.get_camera_properties()
fls_logical = props['android.lens.info.availableFocalLengths']
print 'logical available focal lengths: %s', str(fls_logical)
props = cam.override_with_hidden_physical_camera_props(props)
fls_physical = props['android.lens.info.availableFocalLengths']
print 'physical available focal lengths: %s', str(fls_physical)
# determine skip conditions
first_api = its.device.get_first_api_level(its.device.get_device_id())
if first_api < 30: # original constraint
its.caps.skip_unless(its.caps.read_3a(props))
else: # loosen from read_3a to enable LIMITED coverage
its.caps.skip_unless(its.caps.ae_lock(props) and
its.caps.awb_lock(props))
# determine capabilities
full_device = its.caps.full_or_better(props)
limited_device = its.caps.limited(props)
its.caps.skip_unless(full_device or limited_device)
level3_device = its.caps.level3(props)
raw_avlb = its.caps.raw16(props)
run_crop_test = (level3_device or full_device) and raw_avlb
if not run_crop_test:
print "Crop test skipped"
debug = its.caps.debug_mode()
# Converge 3A
cam.do_3a()
req = its.objects.auto_capture_request()
# If raw is available and main camera, use it as ground truth.
if raw_avlb and (fls_physical == fls_logical):
ref_fov, cc_ct_gt, aspect_ratio_gt = find_raw_fov_reference(cam, req, props, debug)
else:
ref_fov, cc_ct_gt = find_jpeg_fov_reference(cam, req, props)
if run_crop_test:
# Normalize the circle size to 1/4 of the image size, so that
# circle size won't affect the crop test result
factor_cp_thres = ((min(ref_fov["w"], ref_fov["h"])/4.0) /
max(ref_fov["circle_w"], ref_fov["circle_h"]))
thres_l_cp_test = THRESH_L_CP * factor_cp_thres
thres_xs_cp_test = THRESH_XS_CP * factor_cp_thres
# Take pictures of each settings with all the image sizes available.
for fmt in format_list:
fmt_iter = fmt["iter"]
fmt_cmpr = fmt["cmpr"]
dual_target = fmt_cmpr is not "none"
# Get the size of "cmpr"
if dual_target:
sizes = its.objects.get_available_output_sizes(
fmt_cmpr, props, fmt["cmpr_size"])
if not sizes: # device might not support RAW
continue
size_cmpr = sizes[0]
for size_iter in its.objects.get_available_output_sizes(
fmt_iter, props, fmt["iter_max"]):
w_iter = size_iter[0]
h_iter = size_iter[1]
# Skip testing same format/size combination
# ITS does not handle that properly now
if (dual_target
and w_iter*h_iter == size_cmpr[0]*size_cmpr[1]
and fmt_iter == fmt_cmpr):
continue
out_surface = [{"width": w_iter,
"height": h_iter,
"format": fmt_iter}]
if dual_target:
out_surface.append({"width": size_cmpr[0],
"height": size_cmpr[1],
"format": fmt_cmpr})
cap = cam.do_capture(req, out_surface)
if dual_target:
frm_iter = cap[0]
else:
frm_iter = cap
assert frm_iter["format"] == fmt_iter
assert frm_iter["width"] == w_iter
assert frm_iter["height"] == h_iter
print "Captured %s with %s %dx%d. Compared size: %dx%d" % (
fmt_iter, fmt_cmpr, w_iter, h_iter, size_cmpr[0],
size_cmpr[1])
img = its.image.convert_capture_to_rgb_image(frm_iter)
img_name = "%s_%s_with_%s_w%d_h%d.png" % (NAME,
fmt_iter, fmt_cmpr,
w_iter, h_iter)
aspect_ratio, cc_ct, (cc_w, cc_h) = measure_aspect_ratio(
img, img_name, raw_avlb, debug)
# check fov coverage for all fmts in AR_CHECKED
fov_percent = calc_circle_image_ratio(
cc_w, cc_h, w_iter, h_iter)
chk_percent = calc_expected_circle_image_ratio(ref_fov, w_iter, h_iter)
chk_enabled = is_checked_aspect_ratio(first_api, w_iter, h_iter)
if chk_enabled and not np.isclose(fov_percent, chk_percent,
rtol=FOV_PERCENT_RTOL):
msg = "FoV %%: %.2f, Ref FoV %%: %.2f, " % (
fov_percent, chk_percent)
msg += "TOL=%.f%%, img: %dx%d, ref: %dx%d" % (
FOV_PERCENT_RTOL*100, w_iter, h_iter,
ref_fov["w"], ref_fov["h"])
failed_fov.append(msg)
its.image.write_image(img/255, img_name, True)
# check pass/fail for aspect ratio
# image size: the larger one of image width and height
# image size >= LARGE_SIZE: use THRESH_L_AR
# image size == 0 (extreme case): THRESH_XS_AR
# 0 < image size < LARGE_SIZE: scale between THRESH_XS_AR
# and THRESH_L_AR
thres_ar_test = max(
THRESH_L_AR, THRESH_XS_AR + max(w_iter, h_iter) *
(THRESH_L_AR-THRESH_XS_AR)/LARGE_SIZE)
thres_range_ar = (aspect_ratio_gt-thres_ar_test,
aspect_ratio_gt+thres_ar_test)
if (aspect_ratio < thres_range_ar[0] or
aspect_ratio > thres_range_ar[1]):
failed_ar.append({"fmt_iter": fmt_iter,
"fmt_cmpr": fmt_cmpr,
"w": w_iter, "h": h_iter,
"ar": aspect_ratio,
"valid_range": thres_range_ar})
its.image.write_image(img/255, img_name, True)
# check pass/fail for crop
if run_crop_test:
# image size >= LARGE_SIZE: use thres_l_cp_test
# image size == 0 (extreme case): thres_xs_cp_test
# 0 < image size < LARGE_SIZE: scale between
# thres_xs_cp_test and thres_l_cp_test
# Also, allow at least THRESH_MIN_PIXEL off to
# prevent threshold being too tight for very
# small circle
thres_hori_cp_test = max(
thres_l_cp_test, thres_xs_cp_test + w_iter *
(thres_l_cp_test-thres_xs_cp_test)/LARGE_SIZE)
min_threshold_h = THRESH_MIN_PIXEL / cc_w
thres_hori_cp_test = max(thres_hori_cp_test,
min_threshold_h)
thres_range_h_cp = (cc_ct_gt["hori"]-thres_hori_cp_test,
cc_ct_gt["hori"]+thres_hori_cp_test)
thres_vert_cp_test = max(
thres_l_cp_test, thres_xs_cp_test + h_iter *
(thres_l_cp_test-thres_xs_cp_test)/LARGE_SIZE)
min_threshold_v = THRESH_MIN_PIXEL / cc_h
thres_vert_cp_test = max(thres_vert_cp_test,
min_threshold_v)
thres_range_v_cp = (cc_ct_gt["vert"]-thres_vert_cp_test,
cc_ct_gt["vert"]+thres_vert_cp_test)
if (cc_ct["hori"] < thres_range_h_cp[0]
or cc_ct["hori"] > thres_range_h_cp[1]
or cc_ct["vert"] < thres_range_v_cp[0]
or cc_ct["vert"] > thres_range_v_cp[1]):
failed_crop.append({"fmt_iter": fmt_iter,
"fmt_cmpr": fmt_cmpr,
"w": w_iter, "h": h_iter,
"ct_hori": cc_ct["hori"],
"ct_vert": cc_ct["vert"],
"valid_range_h": thres_range_h_cp,
"valid_range_v": thres_range_v_cp})
its.image.write_image(img/255, img_name, True)
# Print failed any test results
print_failed_test_results(failed_ar, failed_fov, failed_crop)
assert not failed_ar
assert not failed_fov
if level3_device:
assert not failed_crop
if __name__ == "__main__":
main()