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