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