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