• 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 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