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