blob: 91e2c58bfa2602200491f3baf23d18e6f6401433 [file] [log] [blame]
# Copyright 2014 The Android Open Source Project
#
# 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.
"""Utility functions to determine what functionality the camera supports."""
import logging
import unittest
from mobly import asserts
import numpy as np
import capture_request_utils
LENS_FACING_FRONT = 0
LENS_FACING_BACK = 1
LENS_FACING_EXTERNAL = 2
MULTI_CAMERA_SYNC_CALIBRATED = 1
NUM_DISTORTION_PARAMS = 5 # number of terms in lens.distortion
NUM_INTRINSIC_CAL_PARAMS = 5 # number of terms in intrinsic calibration
NUM_POSE_ROTATION_PARAMS = 4 # number of terms in poseRotation
NUM_POSE_TRANSLATION_PARAMS = 3 # number of terms in poseTranslation
SKIP_RET_MSG = 'Test skipped'
SOLID_COLOR_TEST_PATTERN = 1
COLOR_BARS_TEST_PATTERN = 2
def legacy(props):
"""Returns whether a device is a LEGACY capability camera2 device.
Args:
props: Camera properties object.
Returns:
Boolean. True if device is a LEGACY camera.
"""
return props.get('android.info.supportedHardwareLevel') == 2
def limited(props):
"""Returns whether a device is a LIMITED capability camera2 device.
Args:
props: Camera properties object.
Returns:
Boolean. True if device is a LIMITED camera.
"""
return props.get('android.info.supportedHardwareLevel') == 0
def full_or_better(props):
"""Returns whether a device is a FULL or better camera2 device.
Args:
props: Camera properties object.
Returns:
Boolean. True if device is FULL or LEVEL3 camera.
"""
return (props.get('android.info.supportedHardwareLevel') >= 1 and
props.get('android.info.supportedHardwareLevel') != 2)
def level3(props):
"""Returns whether a device is a LEVEL3 capability camera2 device.
Args:
props: Camera properties object.
Returns:
Boolean. True if device is LEVEL3 camera.
"""
return props.get('android.info.supportedHardwareLevel') == 3
def manual_sensor(props):
"""Returns whether a device supports MANUAL_SENSOR capabilities.
Args:
props: Camera properties object.
Returns:
Boolean. True if devices supports MANUAL_SENSOR capabilities.
"""
return 1 in props.get('android.request.availableCapabilities', [])
def manual_post_proc(props):
"""Returns whether a device supports MANUAL_POST_PROCESSING capabilities.
Args:
props: Camera properties object.
Returns:
Boolean. True if device supports MANUAL_POST_PROCESSING capabilities.
"""
return 2 in props.get('android.request.availableCapabilities', [])
def raw(props):
"""Returns whether a device supports RAW capabilities.
Args:
props: Camera properties object.
Returns:
Boolean. True if device supports RAW capabilities.
"""
return 3 in props.get('android.request.availableCapabilities', [])
def sensor_fusion(props):
"""Checks the camera and motion sensor timestamps.
Returns whether the camera and motion sensor timestamps for the device
are in the same time domain and can be compared directly.
Args:
props: Camera properties object.
Returns:
Boolean. True if camera and motion sensor timestamps in same time domain.
"""
return props.get('android.sensor.info.timestampSource') == 1
def logical_multi_camera(props):
"""Returns whether a device is a logical multi-camera.
Args:
props: Camera properties object.
Returns:
Boolean. True if the device is a logical multi-camera.
"""
return 11 in props.get('android.request.availableCapabilities', [])
def logical_multi_camera_physical_ids(props):
"""Returns a logical multi-camera's underlying physical cameras.
Args:
props: Camera properties object.
Returns:
list of physical cameras backing the logical multi-camera.
"""
physical_ids_list = []
if logical_multi_camera(props):
physical_ids_list = props['camera.characteristics.physicalCamIds']
return physical_ids_list
def skip_unless(cond):
"""Skips the test if the condition is false.
If a test is skipped, then it is exited and returns the special code
of 101 to the calling shell, which can be used by an external test
harness to differentiate a skip from a pass or fail.
Args:
cond: Boolean, which must be true for the test to not skip.
Returns:
Nothing.
"""
if not cond:
asserts.skip(SKIP_RET_MSG)
def backward_compatible(props):
"""Returns whether a device supports BACKWARD_COMPATIBLE.
Args:
props: Camera properties object.
Returns:
Boolean. True if the devices supports BACKWARD_COMPATIBLE.
"""
return 0 in props.get('android.request.availableCapabilities', [])
def lens_calibrated(props):
"""Returns whether lens position is calibrated or not.
android.lens.info.focusDistanceCalibration has 3 modes.
0: Uncalibrated
1: Approximate
2: Calibrated
Args:
props: Camera properties objects.
Returns:
Boolean. True if lens is CALIBRATED.
"""
return 'android.lens.info.focusDistanceCalibration' in props and props[
'android.lens.info.focusDistanceCalibration'] == 2
def lens_approx_calibrated(props):
"""Returns whether lens position is calibrated or not.
android.lens.info.focusDistanceCalibration has 3 modes.
0: Uncalibrated
1: Approximate
2: Calibrated
Args:
props: Camera properties objects.
Returns:
Boolean. True if lens is APPROXIMATE or CALIBRATED.
"""
return props.get('android.lens.info.focusDistanceCalibration') in [1, 2]
def raw10(props):
"""Returns whether a device supports RAW10 capabilities.
Args:
props: Camera properties object.
Returns:
Boolean. True if device supports RAW10 capabilities.
"""
if capture_request_utils.get_available_output_sizes('raw10', props):
return True
return False
def raw12(props):
"""Returns whether a device supports RAW12 capabilities.
Args:
props: Camera properties object.
Returns:
Boolean. True if device supports RAW12 capabilities.
"""
if capture_request_utils.get_available_output_sizes('raw12', props):
return True
return False
def raw16(props):
"""Returns whether a device supports RAW16 output.
Args:
props: Camera properties object.
Returns:
Boolean. True if device supports RAW16 capabilities.
"""
if capture_request_utils.get_available_output_sizes('raw', props):
return True
return False
def raw_output(props):
"""Returns whether a device supports any of the RAW output formats.
Args:
props: Camera properties object.
Returns:
Boolean. True if device supports any of the RAW output formats
"""
return raw16(props) or raw10(props) or raw12(props)
def per_frame_control(props):
"""Returns whether a device supports per frame control.
Args:
props: Camera properties object.
Returns: Boolean. True if devices supports per frame control.
"""
return 'android.sync.maxLatency' in props and props[
'android.sync.maxLatency'] == 0
def mono_camera(props):
"""Returns whether a device is monochromatic.
Args:
props: Camera properties object.
Returns: Boolean. True if MONO camera.
"""
return 12 in props.get('android.request.availableCapabilities', [])
def fixed_focus(props):
"""Returns whether a device is fixed focus.
props[android.lens.info.minimumFocusDistance] == 0 is fixed focus
Args:
props: Camera properties objects.
Returns:
Boolean. True if device is a fixed focus camera.
"""
return 'android.lens.info.minimumFocusDistance' in props and props[
'android.lens.info.minimumFocusDistance'] == 0
def face_detect(props):
"""Returns whether a device has face detection mode.
props['android.statistics.info.availableFaceDetectModes'] != 0
Args:
props: Camera properties objects.
Returns:
Boolean. True if device supports face detection.
"""
return 'android.statistics.info.availableFaceDetectModes' in props and props[
'android.statistics.info.availableFaceDetectModes'] != [0]
def read_3a(props):
"""Return whether a device supports reading out the below 3A settings.
sensitivity
exposure time
awb gain
awb cct
focus distance
Args:
props: Camera properties object.
Returns:
Boolean. True if device supports reading out 3A settings.
"""
return manual_sensor(props) and manual_post_proc(props)
def compute_target_exposure(props):
"""Return whether a device supports target exposure computation.
Args:
props: Camera properties object.
Returns:
Boolean. True if device supports target exposure computation.
"""
return manual_sensor(props) and manual_post_proc(props)
def y8(props):
"""Returns whether a device supports Y8 output.
Args:
props: Camera properties object.
Returns:
Boolean. True if device suupports Y8 output.
"""
if capture_request_utils.get_available_output_sizes('y8', props):
return True
return False
def jpeg_quality(props):
"""Returns whether a device supports JPEG quality."""
return ('camera.characteristics.requestKeys' in props) and (
'android.jpeg.quality' in props['camera.characteristics.requestKeys'])
def zoom_ratio_range(props):
"""Returns whether a device supports zoom capabilities.
Args:
props: Camera properties object.
Returns:
Boolean. True if device supports zoom capabilities.
"""
return 'android.control.zoomRatioRange' in props and props[
'android.control.zoomRatioRange'] is not None
def sync_latency(props):
"""Returns sync latency in number of frames.
If undefined, 8 frames.
Args:
props: Camera properties object.
Returns:
integer number of frames.
"""
latency = props['android.sync.maxLatency']
if latency < 0:
latency = 8
return latency
def get_max_digital_zoom(props):
"""Returns the maximum amount of zooming possible by the camera device.
Args:
props: Camera properties object.
Returns:
A float indicating the maximum amount of zooming possible by the
camera device.
"""
z_max = 1.0
if 'android.scaler.availableMaxDigitalZoom' in props:
z_max = props['android.scaler.availableMaxDigitalZoom']
return z_max
def ae_lock(props):
"""Returns whether a device supports AE lock.
Args:
props: Camera properties object.
Returns:
Boolean. True if device supports AE lock.
"""
return 'android.control.aeLockAvailable' in props and props[
'android.control.aeLockAvailable'] == 1
def awb_lock(props):
"""Returns whether a device supports AWB lock.
Args:
props: Camera properties object.
Returns:
Boolean. True if device supports AWB lock.
"""
return 'android.control.awbLockAvailable' in props and props[
'android.control.awbLockAvailable'] == 1
def ev_compensation(props):
"""Returns whether a device supports ev compensation.
Args:
props: Camera properties object.
Returns:
Boolean. True if device supports EV compensation.
"""
return 'android.control.aeCompensationRange' in props and props[
'android.control.aeCompensationRange'] != [0, 0]
def flash(props):
"""Returns whether a device supports flash control.
Args:
props: Camera properties object.
Returns:
Boolean. True if device supports flash control.
"""
return 'android.flash.info.available' in props and props[
'android.flash.info.available'] == 1
def distortion_correction(props):
"""Returns whether a device supports android.lens.distortion capabilities.
Args:
props: Camera properties object.
Returns:
Boolean. True if device supports lens distortion correction capabilities.
"""
return 'android.lens.distortion' in props and props[
'android.lens.distortion'] is not None
def freeform_crop(props):
"""Returns whether a device supports freefrom cropping.
Args:
props: Camera properties object.
Returns:
Boolean. True if device supports freeform cropping.
"""
return 'android.scaler.croppingType' in props and props[
'android.scaler.croppingType'] == 1
def noise_reduction_mode(props, mode):
"""Returns whether a device supports the noise reduction mode.
Args:
props: Camera properties objects.
mode: Integer indicating noise reduction mode to check for availability.
Returns:
Boolean. Ture if devices supports noise reduction mode(s).
"""
return ('android.noiseReduction.availableNoiseReductionModes' in props and
mode in props['android.noiseReduction.availableNoiseReductionModes'])
def lsc_map(props):
"""Returns whether a device supports lens shading map output.
Args:
props: Camera properties object.
Returns: Boolean. True if device supports lens shading map output.
"""
return 1 in props.get('android.statistics.info.availableLensShadingMapModes',
[])
def lsc_off(props):
"""Returns whether a device supports disabling lens shading correction.
Args:
props: Camera properties object.
Returns:
Boolean. True if device supports disabling lens shading correction.
"""
return 0 in props.get('android.shading.availableModes', [])
def edge_mode(props, mode):
"""Returns whether a device supports the edge mode.
Args:
props: Camera properties objects.
mode: Integer, indicating the edge mode to check for availability.
Returns:
Boolean. True if device supports edge mode(s).
"""
return 'android.edge.availableEdgeModes' in props and mode in props[
'android.edge.availableEdgeModes']
def yuv_reprocess(props):
"""Returns whether a device supports YUV reprocessing.
Args:
props: Camera properties object.
Returns:
Boolean. True if device supports YUV reprocessing.
"""
return 'android.request.availableCapabilities' in props and 7 in props[
'android.request.availableCapabilities']
def private_reprocess(props):
"""Returns whether a device supports PRIVATE reprocessing.
Args:
props: Camera properties object.
Returns:
Boolean. True if device supports PRIVATE reprocessing.
"""
return 'android.request.availableCapabilities' in props and 4 in props[
'android.request.availableCapabilities']
def intrinsic_calibration(props):
"""Returns whether a device supports android.lens.intrinsicCalibration.
Args:
props: Camera properties object.
Returns:
Boolean. True if device supports android.lens.intrinsicCalibratino.
"""
return props.get('android.lens.intrinsicCalibration') is not None
def get_intrinsic_calibration(props, debug, fd=None):
"""Get intrinsicCalibration and create intrisic matrix.
If intrinsic android.lens.intrinsicCalibration does not exist, return None.
Args:
props: camera properties
debug: bool to print more information
fd: focal length from capture metadata
Returns:
intrinsic transformation matrix
k = [[f_x, s, c_x],
[0, f_y, c_y],
[0, 0, 1]]
"""
if props.get('android.lens.intrinsicCalibration'):
ical = np.array(props['android.lens.intrinsicCalibration'])
else:
logging.error('Device does not have android.lens.intrinsicCalibration.')
return None
# basic checks for parameter correctness
ical_len = len(ical)
if ical_len != NUM_INTRINSIC_CAL_PARAMS:
raise ValueError(
f'instrisicCalibration has wrong number of params: {ical_len}.')
if fd is not None:
# detailed checks for parameter correctness
# 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.
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_w_pix = pixel_w * fd / sensor_w
fd_h_pix = pixel_h * fd / sensor_h
if not np.isclose(fd_w_pix, ical[0], rtol=0.20):
raise ValueError('fd_w(pixels): %.2f\tcal[0](pixels): %.2f\tTOL=20%%' % (
fd_w_pix, ical[0]))
if not np.isclose(fd_h_pix, ical[1], rtol=0.20):
raise ValueError('fd_h(pixels): %.2f\tcal[1](pixels): %.2f\tTOL=20%%' % (
fd_h_pix, ical[0]))
# generate instrinsic matrix
k = np.array([[ical[0], ical[4], ical[2]],
[0, ical[1], ical[3]],
[0, 0, 1]])
if debug:
logging.debug('k: %s', str(k))
return k
def get_translation_matrix(props, debug):
"""Get translation matrix.
Args:
props: dict of camera properties
debug: boolean flag to log more info
Returns:
android.lens.poseTranslation matrix if it exists, otherwise None.
"""
if props['android.lens.poseTranslation']:
t = np.array(props['android.lens.poseTranslation'])
else:
logging.error('Device does not have android.lens.poseTranslation.')
return None
if debug:
logging.debug('translation: %s', str(t))
t_len = len(t)
if t_len != NUM_POSE_TRANSLATION_PARAMS:
raise ValueError(f'poseTranslation has wrong # of params: {t_len}.')
return t
def get_rotation_matrix(props, debug):
"""Convert the rotation parameters to 3-axis data.
Args:
props: camera properties
debug: boolean for more information
Returns:
3x3 matrix w/ rotation parameters if poseRotation exists, otherwise None
"""
if props['android.lens.poseRotation']:
rotation = np.array(props['android.lens.poseRotation'])
else:
logging.error('Device does not have android.lens.poseRotation.')
return None
if debug:
logging.debug('rotation: %s', str(rotation))
rotation_len = len(rotation)
if rotation_len != NUM_POSE_ROTATION_PARAMS:
raise ValueError(f'poseRotation has wrong # of params: {rotation_len}.')
x = rotation[0]
y = rotation[1]
z = rotation[2]
w = rotation[3]
return np.array([[1-2*y**2-2*z**2, 2*x*y-2*z*w, 2*x*z+2*y*w],
[2*x*y+2*z*w, 1-2*x**2-2*z**2, 2*y*z-2*x*w],
[2*x*z-2*y*w, 2*y*z+2*x*w, 1-2*x**2-2*y**2]])
def get_distortion_matrix(props):
"""Get android.lens.distortion matrix and convert to cv2 fmt.
Args:
props: dict of camera properties
Returns:
cv2 reordered android.lens.distortion if it exists, otherwise None.
"""
if props['android.lens.distortion']:
dist = np.array(props['android.lens.distortion'])
else:
logging.error('Device does not have android.lens.distortion.')
return None
dist_len = len(dist)
if len(dist) != NUM_DISTORTION_PARAMS:
raise ValueError(f'lens.distortion has wrong # of params: {dist_len}.')
cv2_distort = np.array([dist[0], dist[1], dist[3], dist[4], dist[2]])
logging.debug('cv2 distortion params: %s', str(cv2_distort))
return cv2_distort
def post_raw_sensitivity_boost(props):
"""Returns whether a device supports post RAW sensitivity boost.
Args:
props: Camera properties object.
Returns:
Boolean. True if android.control.postRawSensitivityBoost is supported.
"""
return (
'android.control.postRawSensitivityBoostRange' in props['camera.characteristics.keys'] and
props.get('android.control.postRawSensitivityBoostRange') != [100, 100])
def sensor_fusion_capable(props):
"""Determine if test_sensor_fusion is run."""
return all([sensor_fusion(props),
manual_sensor(props),
props['android.lens.facing'] != LENS_FACING_EXTERNAL])
def continuous_picture(props):
"""Returns whether a device supports CONTINUOUS_PICTURE.
Args:
props: Camera properties object.
Returns:
Boolean. True if CONTINUOUS_PICTURE in android.control.afAvailableModes.
"""
return 4 in props.get('android.control.afAvailableModes', [])
def af_scene_change(props):
"""Returns whether a device supports AF_SCENE_CHANGE.
Args:
props: Camera properties object.
Returns:
Boolean. True if android.control.afSceneChange supported.
"""
return 'android.control.afSceneChange' in props.get(
'camera.characteristics.resultKeys')
def multi_camera_frame_sync_capable(props):
"""Determines if test_multi_camera_frame_sync can be run."""
return all([
read_3a(props),
per_frame_control(props),
logical_multi_camera(props),
sensor_fusion(props),
])
def multi_camera_sync_calibrated(props):
"""Determines if multi-camera sync type is CALIBRATED or APPROXIMATE.
Args:
props: Camera properties object.
Returns:
Boolean. True if android.logicalMultiCamera.sensorSyncType is CALIBRATED.
"""
return props.get('android.logicalMultiCamera.sensorSyncType'
) == MULTI_CAMERA_SYNC_CALIBRATED
def solid_color_test_pattern(props):
"""Determines if camera supports solid color test pattern.
Args:
props: Camera properties object.
Returns:
Boolean. True if android.sensor.availableTestPatternModes has
SOLID_COLOR_TEST_PATTERN.
"""
return SOLID_COLOR_TEST_PATTERN in props.get(
'android.sensor.availableTestPatternModes')
def color_bars_test_pattern(props):
"""Determines if camera supports color bars test pattern.
Args:
props: Camera properties object.
Returns:
Boolean. True if android.sensor.availableTestPatternModes has
COLOR_BARS_TEST_PATTERN.
"""
return COLOR_BARS_TEST_PATTERN in props.get(
'android.sensor.availableTestPatternModes')
def linear_tonemap(props):
"""Determines if camera supports CONTRAST_CURVE or GAMMA_VALUE in tonemap.
Args:
props: Camera properties object.
Returns:
Boolean. True if android.tonemap.availableToneMapModes has
CONTRAST_CURVE (0) or GAMMA_VALUE (3).
"""
return ('android.tonemap.availableToneMapModes' in props and
(0 in props.get('android.tonemap.availableToneMapModes') or
3 in props.get('android.tonemap.availableToneMapModes')))
if __name__ == '__main__':
unittest.main()