1# Copyright 2013 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 create custom capture requests.""" 15 16 17import error_util 18import logging 19import math 20 21_AE_MODE_OFF = 0 22_AE_MODE_ON_AUTO_FLASH = 2 23_AE_PRECAPTURE_TRIGGER_START = 1 24_AE_PRECAPTURE_TRIGGER_IDLE = 0 25_CAPTURE_INTENT_STILL_CAPTURE = 2 26_CAPTURE_INTENT_PREVIEW = 1 27_COMMON_IMG_ARS = (4/3, 16/9) 28_COMMON_IMG_ARS_ATOL = 0.01 29_FLASH_MODE_SINGLE = 1 30FMT_CODE_JPEG = 0x100 31FMT_CODE_JPEG_R = 0x1005 32FMT_CODE_HEIC_ULTRAHDR = 0x1006 33FMT_CODE_PRIV = 0x22 34FMT_CODE_RAW = 0x20 35FMT_CODE_RAW10 = 0x25 36FMT_CODE_RAW12 = 0x26 37FMT_CODE_YUV = 0x23 # YUV_420_888 38FMT_CODE_Y8 = 0x20203859 39_MAX_YUV_SIZE = (1920, 1080) 40_MIN_YUV_SIZE = (640, 360) 41_STATIONARY_LENS_NUM_TRIES = 2 # num of tries to wait for stationary lens 42_STATIONARY_LENS_NUM_FRAMES = 4 # num of frames to capture for stationay lens 43_STATIONARY_LENS_STATE = 0 44_VGA_W, _VGA_H = (640, 480) 45 46 47def stationary_lens_capture( 48 cam, req, fmt, 49 num_frames=_STATIONARY_LENS_NUM_FRAMES, 50 num_tries=_STATIONARY_LENS_NUM_TRIES): 51 """Take up to num_tries caps with num_frames & save when lens stationary. 52 53 Args: 54 cam: open device session. 55 req: capture request. 56 fmt: format dictionary for capture. 57 num_frames: int; number of frames per capture. 58 num_tries: int; number of tries to get lens stationary capture. 59 60 Returns: 61 capture 62 """ 63 tries = 0 64 done = False 65 while not done: 66 logging.debug('Waiting for lens to move to correct location.') 67 cap = cam.do_capture([req] * num_frames, fmt) 68 done = (cap[num_frames - 1]['metadata']['android.lens.state'] == 69 _STATIONARY_LENS_STATE) 70 logging.debug('lens stationary status: %s', done) 71 if tries == num_tries: 72 raise error_util.CameraItsError('Cannot settle lens after %d tries!' % 73 tries) 74 tries += 1 75 return cap[num_frames - 1] 76 77 78def is_common_aspect_ratio(size): 79 """Returns if aspect ratio is a 4:3 or 16:9. 80 81 Args: 82 size: tuple of image (w, h) 83 84 Returns: 85 Boolean 86 """ 87 for aspect_ratio in _COMMON_IMG_ARS: 88 if math.isclose(size[0]/size[1], aspect_ratio, 89 abs_tol=_COMMON_IMG_ARS_ATOL): 90 return True 91 return False 92 93 94def auto_capture_request(linear_tonemap=False, props=None, do_af=True, 95 do_autoframing=False, zoom_ratio=None): 96 """Returns a capture request with everything set to auto. 97 98 Args: 99 linear_tonemap: [Optional] boolean whether linear tonemap should be used. 100 props: [Optional] object from its_session_utils.get_camera_properties(). 101 Must present when linear_tonemap is True. 102 do_af: [Optional] boolean whether af mode should be active. 103 do_autoframing: [Optional] boolean whether autoframing should be active. 104 zoom_ratio: [Optional] zoom ratio to be set in the capture request. 105 106 Returns: 107 Auto capture request, ready to be passed to the 108 its_session_utils.device.do_capture() 109 """ 110 req = { 111 'android.control.mode': 1, 112 'android.control.aeMode': 1, 113 'android.control.awbMode': 1, 114 'android.control.afMode': 1 if do_af else 0, 115 'android.colorCorrection.mode': 1, 116 'android.shading.mode': 1, 117 'android.tonemap.mode': 1, 118 'android.lens.opticalStabilizationMode': 0, 119 'android.control.videoStabilizationMode': 0, 120 } 121 if do_autoframing: 122 req['android.control.autoframing'] = 1 123 if not do_af: 124 req['android.lens.focusDistance'] = 0.0 125 if zoom_ratio: 126 req['android.control.zoomRatio'] = zoom_ratio 127 if linear_tonemap: 128 if props is None: 129 raise AssertionError('props is None with linear_tonemap.') 130 # CONTRAST_CURVE mode 131 if 0 in props['android.tonemap.availableToneMapModes']: 132 logging.debug('CONTRAST_CURVE tonemap mode') 133 req['android.tonemap.mode'] = 0 134 req['android.tonemap.curve'] = { 135 'red': [0.0, 0.0, 1.0, 1.0], # coordinate pairs: x0, y0, x1, y1 136 'green': [0.0, 0.0, 1.0, 1.0], 137 'blue': [0.0, 0.0, 1.0, 1.0] 138 } 139 # GAMMA_VALUE mode 140 elif 3 in props['android.tonemap.availableToneMapModes']: 141 logging.debug('GAMMA_VALUE tonemap mode') 142 req['android.tonemap.mode'] = 3 143 req['android.tonemap.gamma'] = 1.0 144 else: 145 raise AssertionError('Linear tonemap is not supported') 146 return req 147 148 149def manual_capture_request(sensitivity, 150 exp_time, 151 f_distance=0.0, 152 linear_tonemap=False, 153 props=None): 154 """Returns a capture request with everything set to manual. 155 156 Uses identity/unit color correction, and the default tonemap curve. 157 Optionally, the tonemap can be specified as being linear. 158 159 Args: 160 sensitivity: The sensitivity value to populate the request with. 161 exp_time: The exposure time, in nanoseconds, to populate the request with. 162 f_distance: The focus distance to populate the request with. 163 linear_tonemap: [Optional] whether a linear tonemap should be used in this 164 request. 165 props: [Optional] the object returned from 166 its_session_utils.get_camera_properties(). Must present when linear_tonemap 167 is True. 168 169 Returns: 170 The default manual capture request, ready to be passed to the 171 its_session_utils.device.do_capture function. 172 """ 173 req = { 174 'android.control.captureIntent': 6, 175 'android.control.mode': 0, 176 'android.control.aeMode': 0, 177 'android.control.awbMode': 0, 178 'android.control.afMode': 0, 179 'android.control.effectMode': 0, 180 'android.sensor.sensitivity': sensitivity, 181 'android.sensor.exposureTime': exp_time, 182 'android.colorCorrection.mode': 0, 183 'android.colorCorrection.transform': 184 int_to_rational([1, 0, 0, 0, 1, 0, 0, 0, 1]), 185 'android.colorCorrection.gains': [1, 1, 1, 1], 186 'android.lens.focusDistance': f_distance, 187 'android.tonemap.mode': 1, 188 'android.shading.mode': 1, 189 'android.lens.opticalStabilizationMode': 0, 190 'android.control.videoStabilizationMode': 0, 191 } 192 if linear_tonemap: 193 if props is None: 194 raise AssertionError('props is None.') 195 # CONTRAST_CURVE mode 196 if 0 in props['android.tonemap.availableToneMapModes']: 197 logging.debug('CONTRAST_CURVE tonemap mode') 198 req['android.tonemap.mode'] = 0 199 req['android.tonemap.curve'] = { 200 'red': [0.0, 0.0, 1.0, 1.0], 201 'green': [0.0, 0.0, 1.0, 1.0], 202 'blue': [0.0, 0.0, 1.0, 1.0] 203 } 204 # GAMMA_VALUE mode 205 elif 3 in props['android.tonemap.availableToneMapModes']: 206 logging.debug('GAMMA_VALUE tonemap mode') 207 req['android.tonemap.mode'] = 3 208 req['android.tonemap.gamma'] = 1.0 209 else: 210 raise AssertionError('Linear tonemap is not supported') 211 return req 212 213 214def get_available_output_sizes(fmt, props, max_size=None, match_ar_size=None): 215 """Return a sorted list of available output sizes for a given format. 216 217 Args: 218 fmt: the output format, as a string in ['jpg', 'yuv', 'raw', 'raw10', 219 'raw12', 'y8']. 220 props: the object returned from its_session_utils.get_camera_properties(). 221 max_size: (Optional) A (w,h) tuple.Sizes larger than max_size (either w or h) 222 will be discarded. 223 match_ar_size: (Optional) A (w,h) tuple.Sizes not matching the aspect ratio 224 of match_ar_size will be discarded. 225 226 Returns: 227 A sorted list of (w,h) tuples (sorted large-to-small). 228 """ 229 ar_tolerance = 0.03 230 fmt_codes = { 231 'raw': FMT_CODE_RAW, 232 'raw10': FMT_CODE_RAW10, 233 'raw12': FMT_CODE_RAW12, 234 'yuv': FMT_CODE_YUV, 235 'jpg': FMT_CODE_JPEG, 236 'jpeg': FMT_CODE_JPEG, 237 'jpeg_r': FMT_CODE_JPEG_R, 238 'heic_ultrahdr': FMT_CODE_HEIC_ULTRAHDR, 239 'priv': FMT_CODE_PRIV, 240 'y8': FMT_CODE_Y8 241 } 242 configs = props[ 243 'android.scaler.streamConfigurationMap']['availableStreamConfigurations'] 244 fmt_configs = [cfg for cfg in configs if cfg['format'] == fmt_codes[fmt]] 245 out_configs = [cfg for cfg in fmt_configs if not cfg['input']] 246 out_sizes = [(cfg['width'], cfg['height']) for cfg in out_configs] 247 if max_size: 248 max_size = [int(i) for i in max_size] 249 out_sizes = [ 250 s for s in out_sizes if s[0] <= max_size[0] and s[1] <= max_size[1] 251 ] 252 if match_ar_size: 253 ar = match_ar_size[0] / match_ar_size[1] 254 out_sizes = [ 255 s for s in out_sizes if abs(ar - s[0] / float(s[1])) <= ar_tolerance 256 ] 257 out_sizes.sort(reverse=True, key=lambda s: s[0]) # 1st pass, sort by width 258 out_sizes.sort(reverse=True, key=lambda s: s[0] * s[1]) # sort by area 259 logging.debug('Available %s output sizes: %s', fmt, out_sizes) 260 return out_sizes 261 262 263def float_to_rational(f, denom=128): 264 """Function to convert Python floats to Camera2 rationals. 265 266 Args: 267 f: python float or list of floats. 268 denom: (Optional) the denominator to use in the output rationals. 269 270 Returns: 271 Python dictionary or list of dictionaries representing the given 272 float(s) as rationals. 273 """ 274 if isinstance(f, list): 275 return [{'numerator': math.floor(val*denom+0.5), 'denominator': denom} 276 for val in f] 277 else: 278 return {'numerator': math.floor(f*denom+0.5), 'denominator': denom} 279 280 281def rational_to_float(r): 282 """Function to convert Camera2 rational objects to Python floats. 283 284 Args: 285 r: Rational or list of rationals, as Python dictionaries. 286 287 Returns: 288 Float or list of floats. 289 """ 290 if isinstance(r, list): 291 return [float(val['numerator']) / float(val['denominator']) for val in r] 292 else: 293 return float(r['numerator']) / float(r['denominator']) 294 295 296def get_fastest_manual_capture_settings(props): 297 """Returns a capture request and format spec for the fastest manual capture. 298 299 Args: 300 props: the object returned from its_session_utils.get_camera_properties(). 301 302 Returns: 303 Two values, the first is a capture request, and the second is an output 304 format specification, for the fastest possible (legal) capture that 305 can be performed on this device (with the smallest output size). 306 """ 307 fmt = 'yuv' 308 size = get_available_output_sizes(fmt, props)[-1] 309 out_spec = {'format': fmt, 'width': size[0], 'height': size[1]} 310 s = min(props['android.sensor.info.sensitivityRange']) 311 e = min(props['android.sensor.info.exposureTimeRange']) 312 req = manual_capture_request(s, e) 313 314 turn_slow_filters_off(props, req) 315 316 return req, out_spec 317 318 319def get_fastest_auto_capture_settings(props): 320 """Returns a capture request and format spec for the fastest auto capture. 321 322 Args: 323 props: the object returned from its_session_utils.get_camera_properties(). 324 325 Returns: 326 Two values, the first is a capture request, and the second is an output 327 format specification, for the fastest possible (legal) capture that 328 can be performed on this device (with the smallest output size). 329 """ 330 fmt = 'yuv' 331 size = get_available_output_sizes(fmt, props)[-1] 332 out_spec = {'format': fmt, 'width': size[0], 'height': size[1]} 333 req = auto_capture_request() 334 335 turn_slow_filters_off(props, req) 336 337 return req, out_spec 338 339 340def fastest_auto_capture_request(props): 341 """Return an auto capture request for the fastest capture. 342 343 Args: 344 props: the object returned from its.device.get_camera_properties(). 345 346 Returns: 347 A capture request with everything set to auto and all filters that 348 may slow down capture set to OFF or FAST if possible 349 """ 350 req = auto_capture_request() 351 turn_slow_filters_off(props, req) 352 return req 353 354 355def turn_slow_filters_off(props, req): 356 """Turn filters that may slow FPS down to OFF or FAST in input request. 357 358 This function modifies the request argument, such that filters that may 359 reduce the frames-per-second throughput of the camera device will be set to 360 OFF or FAST if possible. 361 362 Args: 363 props: the object returned from its_session_utils.get_camera_properties(). 364 req: the input request. 365 366 Returns: 367 Nothing. 368 """ 369 set_filter_off_or_fast_if_possible( 370 props, req, 'android.noiseReduction.availableNoiseReductionModes', 371 'android.noiseReduction.mode') 372 set_filter_off_or_fast_if_possible( 373 props, req, 'android.colorCorrection.availableAberrationModes', 374 'android.colorCorrection.aberrationMode') 375 if 'camera.characteristics.keys' in props: 376 chars_keys = props['camera.characteristics.keys'] 377 hot_pixel_modes = 'android.hotPixel.availableHotPixelModes' in chars_keys 378 edge_modes = 'android.edge.availableEdgeModes' in chars_keys 379 if 'camera.characteristics.requestKeys' in props: 380 req_keys = props['camera.characteristics.requestKeys'] 381 hot_pixel_mode = 'android.hotPixel.mode' in req_keys 382 edge_mode = 'android.edge.mode' in req_keys 383 if hot_pixel_modes and hot_pixel_mode: 384 set_filter_off_or_fast_if_possible( 385 props, req, 'android.hotPixel.availableHotPixelModes', 386 'android.hotPixel.mode') 387 if edge_modes and edge_mode: 388 set_filter_off_or_fast_if_possible(props, req, 389 'android.edge.availableEdgeModes', 390 'android.edge.mode') 391 392 393def set_filter_off_or_fast_if_possible(props, req, available_modes, filter_key): 394 """Check and set controlKey to off or fast in req. 395 396 Args: 397 props: the object returned from its.device.get_camera_properties(). 398 req: the input request. filter will be set to OFF or FAST if possible. 399 available_modes: the key to check available modes. 400 filter_key: the filter key 401 402 Returns: 403 Nothing. 404 """ 405 if available_modes in props: 406 if 0 in props[available_modes]: 407 req[filter_key] = 0 408 elif 1 in props[available_modes]: 409 req[filter_key] = 1 410 411 412def int_to_rational(i): 413 """Function to convert Python integers to Camera2 rationals. 414 415 Args: 416 i: Python integer or list of integers. 417 418 Returns: 419 Python dictionary or list of dictionaries representing the given int(s) 420 as rationals with denominator=1. 421 """ 422 if isinstance(i, list): 423 return [{'numerator': val, 'denominator': 1} for val in i] 424 else: 425 return {'numerator': i, 'denominator': 1} 426 427 428def get_smallest_yuv_format(props, match_ar=None): 429 """Return a capture request and format spec for the smallest yuv size. 430 431 Args: 432 props: object returned from camera_properties_utils.get_camera_properties(). 433 match_ar: (Optional) a (w, h) tuple. Aspect ratio to match during search. 434 435 Returns: 436 fmt: an output format specification for the smallest possible yuv format 437 for this device. 438 """ 439 size = get_available_output_sizes('yuv', props, match_ar_size=match_ar)[-1] 440 fmt = {'format': 'yuv', 'width': size[0], 'height': size[1]} 441 442 return fmt 443 444 445def get_near_vga_yuv_format(props, match_ar=None): 446 """Return a capture request and format spec for the smallest yuv size. 447 448 Args: 449 props: object returned from camera_properties_utils.get_camera_properties(). 450 match_ar: (Optional) a (w, h) tuple. Aspect ratio to match during search. 451 452 Returns: 453 fmt: an output format specification for the smallest possible yuv format 454 for this device. 455 """ 456 sizes = get_available_output_sizes('yuv', props, match_ar_size=match_ar) 457 logging.debug('Available YUV sizes: %s', sizes) 458 max_area = _MAX_YUV_SIZE[1] * _MAX_YUV_SIZE[0] 459 min_area = _MIN_YUV_SIZE[1] * _MIN_YUV_SIZE[0] 460 461 fmt = {'format': 'yuv', 'width': _VGA_W, 'height': _VGA_H} 462 for size in sizes: 463 fmt_area = size[0]*size[1] 464 if fmt_area < min_area or fmt_area > max_area: 465 continue 466 fmt['width'], fmt['height'] = size[0], size[1] 467 logging.debug('YUV format selected: %s', fmt) 468 469 return fmt 470 471 472def get_largest_format(match_fmt, props, match_ar=None): 473 """Return a capture request and format spec for the largest match_fmt size. 474 475 Args: 476 match_fmt: str; 'yuv', 'jpeg', or 'raw'. 477 props: object returned from camera_properties_utils.get_camera_properties(). 478 match_ar: (Optional) a (w, h) tuple. Aspect ratio to match during search. 479 480 Returns: 481 fmt: an output format specification for the largest possible format 482 for this device of the type match_fmt. 483 """ 484 size = get_available_output_sizes(match_fmt, props, match_ar_size=match_ar)[0] 485 fmt = {'format': match_fmt, 'width': size[0], 'height': size[1]} 486 logging.debug('format selected: %s', fmt) 487 488 return fmt 489 490 491def get_max_digital_zoom(props): 492 """Returns the maximum amount of zooming possible by the camera device. 493 494 Args: 495 props: the object returned from its.device.get_camera_properties(). 496 497 Return: 498 A float indicating the maximum amount of zoom possible by the camera device. 499 """ 500 501 max_z = 1.0 502 if 'android.scaler.availableMaxDigitalZoom' in props: 503 max_z = props['android.scaler.availableMaxDigitalZoom'] 504 505 return max_z 506 507 508def take_captures_with_flash(cam, out_surface): 509 """Takes capture with auto flash ON. 510 511 Runs precapture sequence by setting the aePrecapture trigger to 512 START and capture intent set to Preview and then take the capture 513 with flash. 514 Args: 515 cam: ItsSession object 516 out_surface: Specifications of the output image format and 517 size to use for the capture. 518 519 Returns: 520 cap: An object which contains following fields: 521 * data: the image data as a numpy array of bytes. 522 * width: the width of the captured image. 523 * height: the height of the captured image. 524 * format: image format 525 * metadata: the capture result object 526 """ 527 528 preview_req_start = auto_capture_request() 529 preview_req_start[ 530 'android.control.aeMode'] = _AE_MODE_ON_AUTO_FLASH 531 preview_req_start[ 532 'android.control.captureIntent'] = _CAPTURE_INTENT_PREVIEW 533 preview_req_start[ 534 'android.control.aePrecaptureTrigger'] = _AE_PRECAPTURE_TRIGGER_START 535 # Repeat preview requests with aePrecapture set to IDLE 536 # until AE is converged. 537 preview_req_idle = auto_capture_request() 538 preview_req_idle[ 539 'android.control.aeMode'] = _AE_MODE_ON_AUTO_FLASH 540 preview_req_idle[ 541 'android.control.captureIntent'] = _CAPTURE_INTENT_PREVIEW 542 preview_req_idle[ 543 'android.control.aePrecaptureTrigger'] = _AE_PRECAPTURE_TRIGGER_IDLE 544 # Single still capture request. 545 still_capture_req = auto_capture_request() 546 still_capture_req[ 547 'android.control.aeMode'] = _AE_MODE_ON_AUTO_FLASH 548 still_capture_req[ 549 'android.control.captureIntent'] = _CAPTURE_INTENT_STILL_CAPTURE 550 still_capture_req[ 551 'android.control.aePrecaptureTrigger'] = _AE_PRECAPTURE_TRIGGER_IDLE 552 cap = cam.do_capture_with_flash(preview_req_start, 553 preview_req_idle, 554 still_capture_req, out_surface) 555 return cap 556 557 558def take_captures_with_flash_strength(cam, out_surface, ae_mode, strength): 559 """Takes capture with desired flash strength. 560 561 Runs precapture sequence by setting the aePrecapture trigger to 562 START and capture intent set to Preview. 563 Then, take the capture with set flash strength. 564 Args: 565 cam: ItsSession object 566 out_surface: Specifications of the output image format and 567 size to use for the capture. 568 ae_mode: AE_mode 569 strength: flash strength 570 571 Returns: 572 cap: An object which contains following fields: 573 * data: the image data as a numpy array of bytes. 574 * width: the width of the captured image. 575 * height: the height of the captured image. 576 * format: image format 577 * metadata: the capture result object 578 """ 579 preview_req_start = auto_capture_request() 580 preview_req_start['android.control.aeMode'] = ( 581 _AE_MODE_ON_AUTO_FLASH if ae_mode == _AE_MODE_OFF else ae_mode 582 ) 583 preview_req_start[ 584 'android.control.captureIntent'] = _CAPTURE_INTENT_PREVIEW 585 preview_req_start[ 586 'android.control.aePrecaptureTrigger'] = _AE_PRECAPTURE_TRIGGER_START 587 preview_req_start[ 588 'android.flash.mode'] = _FLASH_MODE_SINGLE 589 preview_req_start[ 590 'android.flash.strengthLevel'] = strength 591 # Repeat preview requests with aePrecapture set to IDLE 592 # until AE is converged. 593 preview_req_idle = auto_capture_request() 594 preview_req_idle['android.control.aeMode'] = ( 595 _AE_MODE_ON_AUTO_FLASH if ae_mode == _AE_MODE_OFF else ae_mode 596 ) 597 preview_req_idle[ 598 'android.control.captureIntent'] = _CAPTURE_INTENT_PREVIEW 599 preview_req_idle[ 600 'android.control.aePrecaptureTrigger'] = _AE_PRECAPTURE_TRIGGER_IDLE 601 preview_req_idle[ 602 'android.flash.strengthLevel'] = strength 603 # Single still capture request. 604 still_capture_req = auto_capture_request() 605 still_capture_req[ 606 'android.control.aeMode'] = ae_mode 607 still_capture_req[ 608 'android.flash.mode'] = _FLASH_MODE_SINGLE 609 still_capture_req[ 610 'android.control.captureIntent'] = _CAPTURE_INTENT_STILL_CAPTURE 611 still_capture_req[ 612 'android.control.aePrecaptureTrigger'] = _AE_PRECAPTURE_TRIGGER_IDLE 613 still_capture_req[ 614 'android.flash.strengthLevel'] = strength 615 cap = cam.do_capture_with_flash(preview_req_start, 616 preview_req_idle, 617 still_capture_req, out_surface) 618 return cap 619 620