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