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