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