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