• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright 2014 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"""Utility functions to determine what functionality the camera supports."""
15
16
17import logging
18import math
19from mobly import asserts
20import numpy as np
21import capture_request_utils
22
23FD_CAL_RTOL = 0.20
24LENS_FACING_FRONT = 0
25LENS_FACING_BACK = 1
26LENS_FACING_EXTERNAL = 2
27MULTI_CAMERA_SYNC_CALIBRATED = 1
28NUM_DISTORTION_PARAMS = 5  # number of terms in lens.distortion
29NUM_INTRINSIC_CAL_PARAMS = 5  # number of terms in intrinsic calibration
30NUM_POSE_ROTATION_PARAMS = 4  # number of terms in poseRotation
31NUM_POSE_TRANSLATION_PARAMS = 3  # number of terms in poseTranslation
32SKIP_RET_MSG = 'Test skipped'
33SOLID_COLOR_TEST_PATTERN = 1
34COLOR_BARS_TEST_PATTERN = 2
35USE_CASE_STILL_CAPTURE = 2
36DEFAULT_AE_TARGET_FPS_RANGE = (15, 30)
37COLOR_SPACES = [
38    'SRGB', 'LINEAR_SRGB', 'EXTENDED_SRGB',
39    'LINEAR_EXTENDED_SRGB', 'BT709', 'BT2020',
40    'DCI_P3', 'DISPLAY_P3', 'NTSC_1953', 'SMPTE_C',
41    'ADOBE_RGB', 'PRO_PHOTO_RGB', 'ACES', 'ACESCG',
42    'CIE_XYZ', 'CIE_LAB', 'BT2020_HLG', 'BT2020_PQ'
43]
44SETTINGS_OVERRIDE_ZOOM = 1
45
46
47def legacy(props):
48  """Returns whether a device is a LEGACY capability camera2 device.
49
50  Args:
51    props: Camera properties object.
52
53  Returns:
54    Boolean. True if device is a LEGACY camera.
55  """
56  return props.get('android.info.supportedHardwareLevel') == 2
57
58
59def limited(props):
60  """Returns whether a device is a LIMITED capability camera2 device.
61
62  Args:
63    props: Camera properties object.
64
65  Returns:
66     Boolean. True if device is a LIMITED camera.
67  """
68  return props.get('android.info.supportedHardwareLevel') == 0
69
70
71def full_or_better(props):
72  """Returns whether a device is a FULL or better camera2 device.
73
74  Args:
75    props: Camera properties object.
76
77  Returns:
78     Boolean. True if device is FULL or LEVEL3 camera.
79  """
80  return (props.get('android.info.supportedHardwareLevel') >= 1 and
81          props.get('android.info.supportedHardwareLevel') != 2)
82
83
84def level3(props):
85  """Returns whether a device is a LEVEL3 capability camera2 device.
86
87  Args:
88    props: Camera properties object.
89
90  Returns:
91    Boolean. True if device is LEVEL3 camera.
92  """
93  return props.get('android.info.supportedHardwareLevel') == 3
94
95
96def manual_sensor(props):
97  """Returns whether a device supports MANUAL_SENSOR capabilities.
98
99  Args:
100    props: Camera properties object.
101
102  Returns:
103    Boolean. True if devices supports MANUAL_SENSOR capabilities.
104  """
105  return 1 in props.get('android.request.availableCapabilities', [])
106
107
108def manual_post_proc(props):
109  """Returns whether a device supports MANUAL_POST_PROCESSING capabilities.
110
111  Args:
112    props: Camera properties object.
113
114  Returns:
115    Boolean. True if device supports MANUAL_POST_PROCESSING capabilities.
116  """
117  return 2 in props.get('android.request.availableCapabilities', [])
118
119
120def raw(props):
121  """Returns whether a device supports RAW capabilities.
122
123  Args:
124    props: Camera properties object.
125
126  Returns:
127    Boolean. True if device supports RAW capabilities.
128  """
129  return 3 in props.get('android.request.availableCapabilities', [])
130
131
132def sensor_fusion(props):
133  """Checks the camera and motion sensor timestamps.
134
135  Returns whether the camera and motion sensor timestamps for the device
136  are in the same time domain and can be compared directly.
137
138  Args:
139    props: Camera properties object.
140
141  Returns:
142     Boolean. True if camera and motion sensor timestamps in same time domain.
143  """
144  return props.get('android.sensor.info.timestampSource') == 1
145
146
147def logical_multi_camera(props):
148  """Returns whether a device is a logical multi-camera.
149
150  Args:
151    props: Camera properties object.
152
153  Returns:
154     Boolean. True if the device is a logical multi-camera.
155  """
156  return 11 in props.get('android.request.availableCapabilities', [])
157
158
159def logical_multi_camera_physical_ids(props):
160  """Returns a logical multi-camera's underlying physical cameras.
161
162  Args:
163    props: Camera properties object.
164
165  Returns:
166    list of physical cameras backing the logical multi-camera.
167  """
168  physical_ids_list = []
169  if logical_multi_camera(props):
170    physical_ids_list = props['camera.characteristics.physicalCamIds']
171  return physical_ids_list
172
173
174def skip_unless(cond, msg=None):
175  """Skips the test if the condition is false.
176
177  If a test is skipped, then it is exited and returns the special code
178  of 101 to the calling shell, which can be used by an external test
179  harness to differentiate a skip from a pass or fail.
180
181  Args:
182    cond: Boolean, which must be true for the test to not skip.
183    msg: String, reason for test to skip
184
185  Returns:
186     Nothing.
187  """
188  if not cond:
189    skip_msg = SKIP_RET_MSG if not msg else f'{SKIP_RET_MSG}: {msg}'
190    asserts.skip(skip_msg)
191
192
193def backward_compatible(props):
194  """Returns whether a device supports BACKWARD_COMPATIBLE.
195
196  Args:
197    props: Camera properties object.
198
199  Returns:
200    Boolean. True if the devices supports BACKWARD_COMPATIBLE.
201  """
202  return 0 in props.get('android.request.availableCapabilities', [])
203
204
205def lens_calibrated(props):
206  """Returns whether lens position is calibrated or not.
207
208  android.lens.info.focusDistanceCalibration has 3 modes.
209  0: Uncalibrated
210  1: Approximate
211  2: Calibrated
212
213  Args:
214    props: Camera properties objects.
215
216  Returns:
217    Boolean. True if lens is CALIBRATED.
218  """
219  return 'android.lens.info.focusDistanceCalibration' in props and props[
220      'android.lens.info.focusDistanceCalibration'] == 2
221
222
223def lens_approx_calibrated(props):
224  """Returns whether lens position is calibrated or not.
225
226  android.lens.info.focusDistanceCalibration has 3 modes.
227  0: Uncalibrated
228  1: Approximate
229  2: Calibrated
230
231  Args:
232   props: Camera properties objects.
233
234  Returns:
235    Boolean. True if lens is APPROXIMATE or CALIBRATED.
236  """
237  return props.get('android.lens.info.focusDistanceCalibration') in [1, 2]
238
239
240def raw10(props):
241  """Returns whether a device supports RAW10 capabilities.
242
243  Args:
244    props: Camera properties object.
245
246  Returns:
247    Boolean. True if device supports RAW10 capabilities.
248  """
249  if capture_request_utils.get_available_output_sizes('raw10', props):
250    return True
251  return False
252
253
254def raw12(props):
255  """Returns whether a device supports RAW12 capabilities.
256
257  Args:
258    props: Camera properties object.
259
260  Returns:
261    Boolean. True if device supports RAW12 capabilities.
262  """
263  if capture_request_utils.get_available_output_sizes('raw12', props):
264    return True
265  return False
266
267
268def raw16(props):
269  """Returns whether a device supports RAW16 output.
270
271  Args:
272    props: Camera properties object.
273
274  Returns:
275    Boolean. True if device supports RAW16 capabilities.
276  """
277  if capture_request_utils.get_available_output_sizes('raw', props):
278    return True
279  return False
280
281
282def raw_output(props):
283  """Returns whether a device supports any of the RAW output formats.
284
285  Args:
286    props: Camera properties object.
287
288  Returns:
289    Boolean. True if device supports any of the RAW output formats
290  """
291  return raw16(props) or raw10(props) or raw12(props)
292
293
294def per_frame_control(props):
295  """Returns whether a device supports per frame control.
296
297  Args:
298    props: Camera properties object.
299
300  Returns: Boolean. True if devices supports per frame control.
301  """
302  return 'android.sync.maxLatency' in props and props[
303      'android.sync.maxLatency'] == 0
304
305
306def mono_camera(props):
307  """Returns whether a device is monochromatic.
308
309  Args:
310    props: Camera properties object.
311  Returns: Boolean. True if MONO camera.
312  """
313  return 12 in props.get('android.request.availableCapabilities', [])
314
315
316def fixed_focus(props):
317  """Returns whether a device is fixed focus.
318
319  props[android.lens.info.minimumFocusDistance] == 0 is fixed focus
320
321  Args:
322    props: Camera properties objects.
323
324  Returns:
325    Boolean. True if device is a fixed focus camera.
326  """
327  return 'android.lens.info.minimumFocusDistance' in props and props[
328      'android.lens.info.minimumFocusDistance'] == 0
329
330
331def face_detect(props):
332  """Returns whether a device has face detection mode.
333
334  props['android.statistics.info.availableFaceDetectModes'] != 0
335
336  Args:
337    props: Camera properties objects.
338
339  Returns:
340    Boolean. True if device supports face detection.
341  """
342  return 'android.statistics.info.availableFaceDetectModes' in props and props[
343      'android.statistics.info.availableFaceDetectModes'] != [0]
344
345
346def read_3a(props):
347  """Return whether a device supports reading out the below 3A settings.
348
349  sensitivity
350  exposure time
351  awb gain
352  awb cct
353  focus distance
354
355  Args:
356    props: Camera properties object.
357
358  Returns:
359     Boolean. True if device supports reading out 3A settings.
360  """
361  return manual_sensor(props) and manual_post_proc(props)
362
363
364def compute_target_exposure(props):
365  """Return whether a device supports target exposure computation.
366
367  Args:
368    props: Camera properties object.
369
370  Returns:
371    Boolean. True if device supports target exposure computation.
372  """
373  return manual_sensor(props) and manual_post_proc(props)
374
375
376def y8(props):
377  """Returns whether a device supports Y8 output.
378
379  Args:
380    props: Camera properties object.
381
382  Returns:
383     Boolean. True if device suupports Y8 output.
384  """
385  if capture_request_utils.get_available_output_sizes('y8', props):
386    return True
387  return False
388
389
390def jpeg_quality(props):
391  """Returns whether a device supports JPEG quality."""
392  return ('camera.characteristics.requestKeys' in props) and (
393      'android.jpeg.quality' in props['camera.characteristics.requestKeys'])
394
395
396def jpeg_orientation(props):
397  """Returns whether a device supports JPEG orientation."""
398  return ('camera.characteristics.requestKeys' in props) and (
399      'android.jpeg.orientation' in props['camera.characteristics.requestKeys'])
400
401
402def sensor_orientation(props):
403  """Returns the sensor orientation of the camera."""
404  return props['android.sensor.orientation']
405
406
407def zoom_ratio_range(props):
408  """Returns whether a device supports zoom capabilities.
409
410  Args:
411    props: Camera properties object.
412
413  Returns:
414    Boolean. True if device supports zoom capabilities.
415  """
416  return 'android.control.zoomRatioRange' in props and props[
417      'android.control.zoomRatioRange'] is not None
418
419def low_latency_zoom(props):
420  """Returns whether a device supports low latency zoom via settings override.
421
422  Args:
423    props: Camera properties object.
424
425  Returns:
426    Boolean. True if device supports SETTINGS_OVERRIDE_ZOOM.
427  """
428  return ('android.control.availableSettingsOverrides') in props and (
429      SETTINGS_OVERRIDE_ZOOM in props['android.control.availableSettingsOverrides'])
430
431
432def sync_latency(props):
433  """Returns sync latency in number of frames.
434
435  If undefined, 8 frames.
436
437  Args:
438    props: Camera properties object.
439
440  Returns:
441    integer number of frames.
442  """
443  latency = props['android.sync.maxLatency']
444  if latency < 0:
445    latency = 8
446  return latency
447
448
449def get_max_digital_zoom(props):
450  """Returns the maximum amount of zooming possible by the camera device.
451
452  Args:
453    props: Camera properties object.
454
455  Returns:
456    A float indicating the maximum amount of zooming possible by the
457    camera device.
458  """
459  z_max = 1.0
460  if 'android.scaler.availableMaxDigitalZoom' in props:
461    z_max = props['android.scaler.availableMaxDigitalZoom']
462  return z_max
463
464
465def get_ae_target_fps_ranges(props):
466  """Returns the AE target FPS ranges supported by the camera device.
467
468  Args:
469    props: Camera properties object.
470
471  Returns:
472    A list of AE target FPS ranges supported by the camera device.
473  """
474  ranges = []  # return empty list instead of Boolean if no FPS range in props
475  if 'android.control.aeAvailableTargetFpsRanges' in props:
476    ranges = props['android.control.aeAvailableTargetFpsRanges']
477  return ranges
478
479
480def get_fps_range_to_test(fps_ranges):
481  """Returns an AE target FPS range to test based on camera device properties.
482
483  Args:
484    fps_ranges: list of AE target FPS ranges supported by camera.
485      e.g. [[7, 30], [24, 30], [30, 30]]
486  Returns:
487    An AE target FPS range for testing.
488  """
489  default_range_min, default_range_max = DEFAULT_AE_TARGET_FPS_RANGE
490  default_range_size = default_range_max - default_range_min
491  logging.debug('AE target FPS ranges: %s', fps_ranges)
492  widest_fps_range = max(fps_ranges, key=lambda r: r[1] - r[0])
493  if widest_fps_range[1] - widest_fps_range[0] < default_range_size:
494    logging.debug('Default range %s is wider than widest '
495                  'available AE target FPS range %s.',
496                  DEFAULT_AE_TARGET_FPS_RANGE,
497                  widest_fps_range)
498  logging.debug('Accepted AE target FPS range: %s', widest_fps_range)
499  return widest_fps_range
500
501
502def ae_lock(props):
503  """Returns whether a device supports AE lock.
504
505  Args:
506    props: Camera properties object.
507
508  Returns:
509    Boolean. True if device supports AE lock.
510  """
511  return 'android.control.aeLockAvailable' in props and props[
512      'android.control.aeLockAvailable'] == 1
513
514
515def awb_lock(props):
516  """Returns whether a device supports AWB lock.
517
518  Args:
519    props: Camera properties object.
520
521  Returns:
522    Boolean. True if device supports AWB lock.
523  """
524  return 'android.control.awbLockAvailable' in props and props[
525      'android.control.awbLockAvailable'] == 1
526
527
528def ev_compensation(props):
529  """Returns whether a device supports ev compensation.
530
531  Args:
532    props: Camera properties object.
533
534  Returns:
535    Boolean. True if device supports EV compensation.
536  """
537  return 'android.control.aeCompensationRange' in props and props[
538      'android.control.aeCompensationRange'] != [0, 0]
539
540
541def flash(props):
542  """Returns whether a device supports flash control.
543
544  Args:
545    props: Camera properties object.
546
547  Returns:
548    Boolean. True if device supports flash control.
549  """
550  return 'android.flash.info.available' in props and props[
551      'android.flash.info.available'] == 1
552
553
554def distortion_correction(props):
555  """Returns whether a device supports android.lens.distortion capabilities.
556
557  Args:
558    props: Camera properties object.
559
560  Returns:
561    Boolean. True if device supports lens distortion correction capabilities.
562  """
563  return 'android.lens.distortion' in props and props[
564      'android.lens.distortion'] is not None
565
566
567def freeform_crop(props):
568  """Returns whether a device supports freefrom cropping.
569
570  Args:
571    props: Camera properties object.
572
573  Returns:
574    Boolean. True if device supports freeform cropping.
575  """
576  return 'android.scaler.croppingType' in props and props[
577      'android.scaler.croppingType'] == 1
578
579
580def noise_reduction_mode(props, mode):
581  """Returns whether a device supports the noise reduction mode.
582
583  Args:
584    props: Camera properties objects.
585    mode: Integer indicating noise reduction mode to check for availability.
586
587  Returns:
588    Boolean. True if devices supports noise reduction mode(s).
589  """
590  return ('android.noiseReduction.availableNoiseReductionModes' in props and
591          mode in props['android.noiseReduction.availableNoiseReductionModes'])
592
593
594def lsc_map(props):
595  """Returns whether a device supports lens shading map output.
596
597  Args:
598    props: Camera properties object.
599  Returns: Boolean. True if device supports lens shading map output.
600  """
601  return 1 in props.get('android.statistics.info.availableLensShadingMapModes',
602                        [])
603
604
605def lsc_off(props):
606  """Returns whether a device supports disabling lens shading correction.
607
608  Args:
609    props: Camera properties object.
610
611  Returns:
612    Boolean. True if device supports disabling lens shading correction.
613  """
614  return 0 in props.get('android.shading.availableModes', [])
615
616
617def edge_mode(props, mode):
618  """Returns whether a device supports the edge mode.
619
620  Args:
621    props: Camera properties objects.
622    mode: Integer, indicating the edge mode to check for availability.
623
624  Returns:
625    Boolean. True if device supports edge mode(s).
626  """
627  return 'android.edge.availableEdgeModes' in props and mode in props[
628      'android.edge.availableEdgeModes']
629
630
631def tonemap_mode(props, mode):
632    """Returns whether a device supports the tonemap mode.
633
634    Args:
635        props: Camera properties object.
636        mode: Integer, indicating the tonemap mode to check for availability.
637
638    Return:
639        Boolean.
640    """
641    return 'android.tonemap.availableToneMapModes' in props and mode in props[
642        'android.tonemap.availableToneMapModes']
643
644
645def yuv_reprocess(props):
646  """Returns whether a device supports YUV reprocessing.
647
648  Args:
649    props: Camera properties object.
650
651  Returns:
652    Boolean. True if device supports YUV reprocessing.
653  """
654  return 'android.request.availableCapabilities' in props and 7 in props[
655      'android.request.availableCapabilities']
656
657
658def private_reprocess(props):
659  """Returns whether a device supports PRIVATE reprocessing.
660
661  Args:
662    props: Camera properties object.
663
664  Returns:
665    Boolean. True if device supports PRIVATE reprocessing.
666  """
667  return 'android.request.availableCapabilities' in props and 4 in props[
668      'android.request.availableCapabilities']
669
670
671def stream_use_case(props):
672  """Returns whether a device has stream use case capability.
673
674  Args:
675    props: Camera properties object.
676
677  Returns:
678     Boolean. True if the device has stream use case capability.
679  """
680  return 'android.request.availableCapabilities' in props and 19 in props[
681      'android.request.availableCapabilities']
682
683def cropped_raw_stream_use_case(props):
684  """Returns whether a device supports the CROPPED_RAW stream use case.
685
686  Args:
687    props: Camera properties object.
688
689  Returns:
690     Boolean. True if the device supports the CROPPED_RAW stream use case.
691  """
692  return stream_use_case(props) and 6 in props['android.scaler.availableStreamUseCases']
693
694
695def intrinsic_calibration(props):
696  """Returns whether a device supports android.lens.intrinsicCalibration.
697
698  Args:
699    props: Camera properties object.
700
701  Returns:
702    Boolean. True if device supports android.lens.intrinsicCalibratino.
703  """
704  return props.get('android.lens.intrinsicCalibration') is not None
705
706
707def get_intrinsic_calibration(props, debug, fd=None):
708  """Get intrinsicCalibration and create intrisic matrix.
709
710  If intrinsic android.lens.intrinsicCalibration does not exist, return None.
711
712  Args:
713    props: camera properties
714    debug: bool to print more information
715    fd: focal length from capture metadata
716
717  Returns:
718    intrinsic transformation matrix
719    k = [[f_x, s, c_x],
720         [0, f_y, c_y],
721         [0,   0,   1]]
722  """
723  if props.get('android.lens.intrinsicCalibration'):
724    ical = np.array(props['android.lens.intrinsicCalibration'])
725  else:
726    logging.error('Device does not have android.lens.intrinsicCalibration.')
727    return None
728
729  # basic checks for parameter correctness
730  ical_len = len(ical)
731  if ical_len != NUM_INTRINSIC_CAL_PARAMS:
732    raise ValueError(
733        f'instrisicCalibration has wrong number of params: {ical_len}.')
734
735  if fd is not None:
736    # detailed checks for parameter correctness
737    # Intrinsic cal is of format: [f_x, f_y, c_x, c_y, s]
738    # [f_x, f_y] is the horizontal and vertical focal lengths,
739    # [c_x, c_y] is the position of the optical axis,
740    # and s is skew of sensor plane vs lens plane.
741    sensor_h = props['android.sensor.info.physicalSize']['height']
742    sensor_w = props['android.sensor.info.physicalSize']['width']
743    pixel_h = props['android.sensor.info.pixelArraySize']['height']
744    pixel_w = props['android.sensor.info.pixelArraySize']['width']
745    fd_w_pix = pixel_w * fd / sensor_w
746    fd_h_pix = pixel_h * fd / sensor_h
747
748    if not math.isclose(fd_w_pix, ical[0], rel_tol=FD_CAL_RTOL):
749      raise ValueError(f'fd_w(pixels): {fd_w_pix:.2f}\tcal[0](pixels): '
750                       f'{ical[0]:.2f}\tTOL=20%')
751    if not math.isclose(fd_h_pix, ical[1], rel_tol=FD_CAL_RTOL):
752      raise ValueError(f'fd_h(pixels): {fd_h_pix:.2f}\tcal[1](pixels): '
753                       f'{ical[1]:.2f}\tTOL=20%')
754
755  # generate instrinsic matrix
756  k = np.array([[ical[0], ical[4], ical[2]],
757                [0, ical[1], ical[3]],
758                [0, 0, 1]])
759  if debug:
760    logging.debug('k: %s', str(k))
761  return k
762
763
764def get_translation_matrix(props, debug):
765  """Get translation matrix.
766
767  Args:
768    props: dict of camera properties
769    debug: boolean flag to log more info
770
771  Returns:
772    android.lens.poseTranslation matrix if it exists, otherwise None.
773  """
774  if props['android.lens.poseTranslation']:
775    t = np.array(props['android.lens.poseTranslation'])
776  else:
777    logging.error('Device does not have android.lens.poseTranslation.')
778    return None
779
780  if debug:
781    logging.debug('translation: %s', str(t))
782  t_len = len(t)
783  if t_len != NUM_POSE_TRANSLATION_PARAMS:
784    raise ValueError(f'poseTranslation has wrong # of params: {t_len}.')
785  return t
786
787
788def get_rotation_matrix(props, debug):
789  """Convert the rotation parameters to 3-axis data.
790
791  Args:
792    props: camera properties
793    debug: boolean for more information
794
795  Returns:
796    3x3 matrix w/ rotation parameters if poseRotation exists, otherwise None
797  """
798  if props['android.lens.poseRotation']:
799    rotation = np.array(props['android.lens.poseRotation'])
800  else:
801    logging.error('Device does not have android.lens.poseRotation.')
802    return None
803
804  if debug:
805    logging.debug('rotation: %s', str(rotation))
806    rotation_len = len(rotation)
807    if rotation_len != NUM_POSE_ROTATION_PARAMS:
808      raise ValueError(f'poseRotation has wrong # of params: {rotation_len}.')
809  x = rotation[0]
810  y = rotation[1]
811  z = rotation[2]
812  w = rotation[3]
813  return np.array([[1-2*y**2-2*z**2, 2*x*y-2*z*w, 2*x*z+2*y*w],
814                   [2*x*y+2*z*w, 1-2*x**2-2*z**2, 2*y*z-2*x*w],
815                   [2*x*z-2*y*w, 2*y*z+2*x*w, 1-2*x**2-2*y**2]])
816
817
818def get_distortion_matrix(props):
819  """Get android.lens.distortion matrix and convert to cv2 fmt.
820
821  Args:
822    props: dict of camera properties
823
824  Returns:
825    cv2 reordered android.lens.distortion if it exists, otherwise None.
826  """
827  if props['android.lens.distortion']:
828    dist = np.array(props['android.lens.distortion'])
829  else:
830    logging.error('Device does not have android.lens.distortion.')
831    return None
832
833  dist_len = len(dist)
834  if len(dist) != NUM_DISTORTION_PARAMS:
835    raise ValueError(f'lens.distortion has wrong # of params: {dist_len}.')
836  cv2_distort = np.array([dist[0], dist[1], dist[3], dist[4], dist[2]])
837  logging.debug('cv2 distortion params: %s', str(cv2_distort))
838  return cv2_distort
839
840
841def post_raw_sensitivity_boost(props):
842  """Returns whether a device supports post RAW sensitivity boost.
843
844  Args:
845    props: Camera properties object.
846
847  Returns:
848    Boolean. True if android.control.postRawSensitivityBoost is supported.
849  """
850  return (
851      'android.control.postRawSensitivityBoostRange' in
852      props['camera.characteristics.keys'] and
853      props.get('android.control.postRawSensitivityBoostRange') != [100, 100])
854
855
856def sensor_fusion_capable(props):
857  """Determine if test_sensor_fusion is run."""
858  return all([sensor_fusion(props),
859              manual_sensor(props),
860              props['android.lens.facing'] != LENS_FACING_EXTERNAL])
861
862
863def continuous_picture(props):
864  """Returns whether a device supports CONTINUOUS_PICTURE.
865
866  Args:
867    props: Camera properties object.
868
869  Returns:
870    Boolean. True if CONTINUOUS_PICTURE in android.control.afAvailableModes.
871  """
872  return 4 in props.get('android.control.afAvailableModes', [])
873
874
875def af_scene_change(props):
876  """Returns whether a device supports AF_SCENE_CHANGE.
877
878  Args:
879    props: Camera properties object.
880
881  Returns:
882    Boolean. True if android.control.afSceneChange supported.
883  """
884  return 'android.control.afSceneChange' in props.get(
885      'camera.characteristics.resultKeys')
886
887
888def multi_camera_frame_sync_capable(props):
889  """Determines if test_multi_camera_frame_sync can be run."""
890  return all([
891      read_3a(props),
892      per_frame_control(props),
893      logical_multi_camera(props),
894      sensor_fusion(props),
895  ])
896
897
898def multi_camera_sync_calibrated(props):
899  """Determines if multi-camera sync type is CALIBRATED or APPROXIMATE.
900
901  Args:
902    props: Camera properties object.
903
904  Returns:
905    Boolean. True if android.logicalMultiCamera.sensorSyncType is CALIBRATED.
906  """
907  return props.get('android.logicalMultiCamera.sensorSyncType'
908                  ) == MULTI_CAMERA_SYNC_CALIBRATED
909
910
911def solid_color_test_pattern(props):
912  """Determines if camera supports solid color test pattern.
913
914  Args:
915    props: Camera properties object.
916
917  Returns:
918    Boolean. True if android.sensor.availableTestPatternModes has
919             SOLID_COLOR_TEST_PATTERN.
920  """
921  return SOLID_COLOR_TEST_PATTERN in props.get(
922      'android.sensor.availableTestPatternModes')
923
924
925def color_bars_test_pattern(props):
926  """Determines if camera supports color bars test pattern.
927
928  Args:
929    props: Camera properties object.
930
931  Returns:
932    Boolean. True if android.sensor.availableTestPatternModes has
933             COLOR_BARS_TEST_PATTERN.
934  """
935  return COLOR_BARS_TEST_PATTERN in props.get(
936      'android.sensor.availableTestPatternModes')
937
938
939def linear_tonemap(props):
940  """Determines if camera supports CONTRAST_CURVE or GAMMA_VALUE in tonemap.
941
942  Args:
943    props: Camera properties object.
944
945  Returns:
946    Boolean. True if android.tonemap.availableToneMapModes has
947             CONTRAST_CURVE (0) or GAMMA_VALUE (3).
948  """
949  return ('android.tonemap.availableToneMapModes' in props and
950          (0 in props.get('android.tonemap.availableToneMapModes') or
951           3 in props.get('android.tonemap.availableToneMapModes')))
952
953
954def get_reprocess_formats(props):
955  """Retrieve the list of supported reprocess formats.
956
957  Args:
958    props: The camera properties.
959
960  Returns:
961    A list of supported reprocess formats.
962  """
963  reprocess_formats = []
964  if yuv_reprocess(props):
965    reprocess_formats.append('yuv')
966  if private_reprocess(props):
967    reprocess_formats.append('private')
968  return reprocess_formats
969
970
971def color_space_to_int(color_space):
972  """Returns the integer ordinal of a named color space.
973
974  Args:
975    color_space: The color space string.
976
977  Returns:
978    Int. Ordinal of the color space.
979  """
980  if color_space == 'UNSPECIFIED':
981    return -1
982
983  return COLOR_SPACES.index(color_space)
984
985
986def autoframing(props):
987  """Returns whether a device supports autoframing.
988
989  Args:
990    props: Camera properties object.
991
992  Returns:
993    Boolean. True if android.control.autoframing is supported.
994  """
995  return 'android.control.autoframingAvailable' in props and props[
996    'android.control.autoframingAvailable'] == 1
997