• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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