• 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 math
18import unittest
19
20COMMON_IMG_ARS = (1.333, 1.778)
21COMMON_IMG_ARS_ATOL = 0.01
22
23
24def is_common_aspect_ratio(size):
25  """Returns if aspect ratio is a 4:3 or 16:9.
26
27  Args:
28    size: tuple of image (w, h)
29
30  Returns:
31    Boolean
32  """
33  for aspect_ratio in COMMON_IMG_ARS:
34    if math.isclose(size[0]/size[1], aspect_ratio, abs_tol=COMMON_IMG_ARS_ATOL):
35      return True
36  return False
37
38
39def auto_capture_request(linear_tonemap=False, props=None):
40  """Returns a capture request with everything set to auto.
41
42  Args:
43   linear_tonemap: [Optional] boolean whether linear tonemap should be used.
44   props: [Optional] object from its_session_utils.get_camera_properties().
45          Must present when linear_tonemap is True.
46
47  Returns:
48    Auto capture request, ready to be passed to the
49    its_session_utils.device.do_capture()
50  """
51  req = {
52      'android.control.mode': 1,
53      'android.control.aeMode': 1,
54      'android.control.awbMode': 1,
55      'android.control.afMode': 1,
56      'android.colorCorrection.mode': 1,
57      'android.tonemap.mode': 1,
58      'android.lens.opticalStabilizationMode': 0,
59      'android.control.videoStabilizationMode': 0
60  }
61  if linear_tonemap:
62    if props is None:
63      raise AssertionError('props is None with linear_tonemap.')
64    # CONTRAST_CURVE mode
65    if 0 in props['android.tonemap.availableToneMapModes']:
66      req['android.tonemap.mode'] = 0
67      req['android.tonemap.curve'] = {
68          'red': [0.0, 0.0, 1.0, 1.0],  # coordinate pairs: x0, y0, x1, y1
69          'green': [0.0, 0.0, 1.0, 1.0],
70          'blue': [0.0, 0.0, 1.0, 1.0]
71      }
72    # GAMMA_VALUE mode
73    elif 3 in props['android.tonemap.availableToneMapModes']:
74      req['android.tonemap.mode'] = 3
75      req['android.tonemap.gamma'] = 1.0
76    else:
77      raise AssertionError('Linear tonemap is not supported')
78  return req
79
80
81def manual_capture_request(sensitivity,
82                           exp_time,
83                           f_distance=0.0,
84                           linear_tonemap=False,
85                           props=None):
86  """Returns a capture request with everything set to manual.
87
88  Uses identity/unit color correction, and the default tonemap curve.
89  Optionally, the tonemap can be specified as being linear.
90
91  Args:
92   sensitivity: The sensitivity value to populate the request with.
93   exp_time: The exposure time, in nanoseconds, to populate the request with.
94   f_distance: The focus distance to populate the request with.
95   linear_tonemap: [Optional] whether a linear tonemap should be used in this
96     request.
97   props: [Optional] the object returned from
98     its_session_utils.get_camera_properties(). Must present when linear_tonemap
99     is True.
100
101  Returns:
102    The default manual capture request, ready to be passed to the
103    its_session_utils.device.do_capture function.
104  """
105  req = {
106      'android.control.captureIntent': 6,
107      'android.control.mode': 0,
108      'android.control.aeMode': 0,
109      'android.control.awbMode': 0,
110      'android.control.afMode': 0,
111      'android.control.effectMode': 0,
112      'android.sensor.sensitivity': sensitivity,
113      'android.sensor.exposureTime': exp_time,
114      'android.colorCorrection.mode': 0,
115      'android.colorCorrection.transform':
116          int_to_rational([1, 0, 0, 0, 1, 0, 0, 0, 1]),
117      'android.colorCorrection.gains': [1, 1, 1, 1],
118      'android.lens.focusDistance': f_distance,
119      'android.tonemap.mode': 1,
120      'android.shading.mode': 1,
121      'android.lens.opticalStabilizationMode': 0,
122      'android.control.videoStabilizationMode': 0,
123  }
124  if linear_tonemap:
125    if props is None:
126      raise AssertionError('props is None.')
127    # CONTRAST_CURVE mode
128    if 0 in props['android.tonemap.availableToneMapModes']:
129      req['android.tonemap.mode'] = 0
130      req['android.tonemap.curve'] = {
131          'red': [0.0, 0.0, 1.0, 1.0],
132          'green': [0.0, 0.0, 1.0, 1.0],
133          'blue': [0.0, 0.0, 1.0, 1.0]
134      }
135    # GAMMA_VALUE mode
136    elif 3 in props['android.tonemap.availableToneMapModes']:
137      req['android.tonemap.mode'] = 3
138      req['android.tonemap.gamma'] = 1.0
139    else:
140      raise AssertionError('Linear tonemap is not supported')
141  return req
142
143
144def get_available_output_sizes(fmt, props, max_size=None, match_ar_size=None):
145  """Return a sorted list of available output sizes for a given format.
146
147  Args:
148   fmt: the output format, as a string in ['jpg', 'yuv', 'raw', 'raw10',
149     'raw12', 'y8'].
150   props: the object returned from its_session_utils.get_camera_properties().
151   max_size: (Optional) A (w,h) tuple.Sizes larger than max_size (either w or h)
152     will be discarded.
153   match_ar_size: (Optional) A (w,h) tuple.Sizes not matching the aspect ratio
154     of match_ar_size will be discarded.
155
156  Returns:
157    A sorted list of (w,h) tuples (sorted large-to-small).
158  """
159  ar_tolerance = 0.03
160  fmt_codes = {
161      'raw': 0x20,
162      'raw10': 0x25,
163      'raw12': 0x26,
164      'yuv': 0x23,
165      'jpg': 0x100,
166      'jpeg': 0x100,
167      'y8': 0x20203859
168  }
169  configs = props[
170      'android.scaler.streamConfigurationMap']['availableStreamConfigurations']
171  fmt_configs = [cfg for cfg in configs if cfg['format'] == fmt_codes[fmt]]
172  out_configs = [cfg for cfg in fmt_configs if not cfg['input']]
173  out_sizes = [(cfg['width'], cfg['height']) for cfg in out_configs]
174  if max_size:
175    out_sizes = [
176        s for s in out_sizes if s[0] <= int(max_size[0]) and s[1] <= int(max_size[1])
177    ]
178  if match_ar_size:
179    ar = match_ar_size[0] / float(match_ar_size[1])
180    out_sizes = [
181        s for s in out_sizes if abs(ar - s[0] / float(s[1])) <= ar_tolerance
182    ]
183  out_sizes.sort(reverse=True, key=lambda s: s[0])  # 1st pass, sort by width
184  out_sizes.sort(reverse=True, key=lambda s: s[0] * s[1])  # sort by area
185  return out_sizes
186
187
188def float_to_rational(f, denom=128):
189  """Function to convert Python floats to Camera2 rationals.
190
191  Args:
192    f: python float or list of floats.
193    denom: (Optional) the denominator to use in the output rationals.
194
195  Returns:
196    Python dictionary or list of dictionaries representing the given
197    float(s) as rationals.
198  """
199  if isinstance(f, list):
200    return [{'numerator': math.floor(val*denom+0.5), 'denominator': denom}
201            for val in f]
202  else:
203    return {'numerator': math.floor(f*denom+0.5), 'denominator': denom}
204
205
206def rational_to_float(r):
207  """Function to convert Camera2 rational objects to Python floats.
208
209  Args:
210   r: Rational or list of rationals, as Python dictionaries.
211
212  Returns:
213   Float or list of floats.
214  """
215  if isinstance(r, list):
216    return [float(val['numerator']) / float(val['denominator']) for val in r]
217  else:
218    return float(r['numerator']) / float(r['denominator'])
219
220
221def get_fastest_manual_capture_settings(props):
222  """Returns a capture request and format spec for the fastest manual capture.
223
224  Args:
225     props: the object returned from its_session_utils.get_camera_properties().
226
227  Returns:
228    Two values, the first is a capture request, and the second is an output
229    format specification, for the fastest possible (legal) capture that
230    can be performed on this device (with the smallest output size).
231  """
232  fmt = 'yuv'
233  size = get_available_output_sizes(fmt, props)[-1]
234  out_spec = {'format': fmt, 'width': size[0], 'height': size[1]}
235  s = min(props['android.sensor.info.sensitivityRange'])
236  e = min(props['android.sensor.info.exposureTimeRange'])
237  req = manual_capture_request(s, e)
238
239  turn_slow_filters_off(props, req)
240
241  return req, out_spec
242
243
244def get_fastest_auto_capture_settings(props):
245  """Returns a capture request and format spec for the fastest auto capture.
246
247  Args:
248     props: the object returned from its_session_utils.get_camera_properties().
249
250  Returns:
251      Two values, the first is a capture request, and the second is an output
252      format specification, for the fastest possible (legal) capture that
253      can be performed on this device (with the smallest output size).
254  """
255  fmt = 'yuv'
256  size = get_available_output_sizes(fmt, props)[-1]
257  out_spec = {'format': fmt, 'width': size[0], 'height': size[1]}
258  req = auto_capture_request()
259
260  turn_slow_filters_off(props, req)
261
262  return req, out_spec
263
264
265def fastest_auto_capture_request(props):
266  """Return an auto capture request for the fastest capture.
267
268  Args:
269    props: the object returned from its.device.get_camera_properties().
270
271  Returns:
272    A capture request with everything set to auto and all filters that
273    may slow down capture set to OFF or FAST if possible
274  """
275  req = auto_capture_request()
276  turn_slow_filters_off(props, req)
277  return req
278
279
280def turn_slow_filters_off(props, req):
281  """Turn filters that may slow FPS down to OFF or FAST in input request.
282
283   This function modifies the request argument, such that filters that may
284   reduce the frames-per-second throughput of the camera device will be set to
285   OFF or FAST if possible.
286
287  Args:
288    props: the object returned from its_session_utils.get_camera_properties().
289    req: the input request.
290
291  Returns:
292    Nothing.
293  """
294  set_filter_off_or_fast_if_possible(
295      props, req, 'android.noiseReduction.availableNoiseReductionModes',
296      'android.noiseReduction.mode')
297  set_filter_off_or_fast_if_possible(
298      props, req, 'android.colorCorrection.availableAberrationModes',
299      'android.colorCorrection.aberrationMode')
300  if 'camera.characteristics.keys' in props:
301    chars_keys = props['camera.characteristics.keys']
302    hot_pixel_modes = 'android.hotPixel.availableHotPixelModes' in chars_keys
303    edge_modes = 'android.edge.availableEdgeModes' in chars_keys
304  if 'camera.characteristics.requestKeys' in props:
305    req_keys = props['camera.characteristics.requestKeys']
306    hot_pixel_mode = 'android.hotPixel.mode' in req_keys
307    edge_mode = 'android.edge.mode' in req_keys
308  if hot_pixel_modes and hot_pixel_mode:
309    set_filter_off_or_fast_if_possible(
310        props, req, 'android.hotPixel.availableHotPixelModes',
311        'android.hotPixel.mode')
312  if edge_modes and edge_mode:
313    set_filter_off_or_fast_if_possible(props, req,
314                                       'android.edge.availableEdgeModes',
315                                       'android.edge.mode')
316
317
318def set_filter_off_or_fast_if_possible(props, req, available_modes, filter_key):
319  """Check and set controlKey to off or fast in req.
320
321  Args:
322    props: the object returned from its.device.get_camera_properties().
323    req: the input request. filter will be set to OFF or FAST if possible.
324    available_modes: the key to check available modes.
325    filter_key: the filter key
326
327  Returns:
328    Nothing.
329  """
330  if available_modes in props:
331    if 0 in props[available_modes]:
332      req[filter_key] = 0
333    elif 1 in props[available_modes]:
334      req[filter_key] = 1
335
336
337def int_to_rational(i):
338  """Function to convert Python integers to Camera2 rationals.
339
340  Args:
341   i: Python integer or list of integers.
342
343  Returns:
344    Python dictionary or list of dictionaries representing the given int(s)
345    as rationals with denominator=1.
346  """
347  if isinstance(i, list):
348    return [{'numerator': val, 'denominator': 1} for val in i]
349  else:
350    return {'numerator': i, 'denominator': 1}
351
352
353def get_largest_yuv_format(props, match_ar=None):
354  """Return a capture request and format spec for the largest yuv size.
355
356  Args:
357    props: object returned from camera_properties_utils.get_camera_properties().
358    match_ar: (Optional) a (w, h) tuple. Aspect ratio to match during search.
359
360  Returns:
361    fmt:   an output format specification for the largest possible yuv format
362           for this device.
363  """
364  size = get_available_output_sizes('yuv', props, match_ar_size=match_ar)[0]
365  fmt = {'format': 'yuv', 'width': size[0], 'height': size[1]}
366
367  return fmt
368
369
370def get_smallest_yuv_format(props, match_ar=None):
371  """Return a capture request and format spec for the smallest yuv size.
372
373  Args:
374    props: object returned from camera_properties_utils.get_camera_properties().
375    match_ar: (Optional) a (w, h) tuple. Aspect ratio to match during search.
376
377  Returns:
378    fmt:   an output format specification for the smallest possible yuv format
379           for this device.
380  """
381  size = get_available_output_sizes('yuv', props, match_ar_size=match_ar)[-1]
382  fmt = {'format': 'yuv', 'width': size[0], 'height': size[1]}
383
384  return fmt
385
386
387def get_largest_jpeg_format(props, match_ar=None):
388  """Return a capture request and format spec for the largest jpeg size.
389
390  Args:
391    props: object returned from camera_properties_utils.get_camera_properties().
392    match_ar: (Optional) a (w, h) tuple. Aspect ratio to match during search.
393
394  Returns:
395    fmt:   an output format specification for the largest possible jpeg format
396           for this device.
397  """
398  size = get_available_output_sizes('jpeg', props, match_ar_size=match_ar)[0]
399  fmt = {'format': 'jpeg', 'width': size[0], 'height': size[1]}
400
401  return fmt
402
403
404def get_max_digital_zoom(props):
405  """Returns the maximum amount of zooming possible by the camera device.
406
407  Args:
408    props: the object returned from its.device.get_camera_properties().
409
410  Return:
411    A float indicating the maximum amount of zoom possible by the camera device.
412  """
413
414  max_z = 1.0
415  if 'android.scaler.availableMaxDigitalZoom' in props:
416    max_z = props['android.scaler.availableMaxDigitalZoom']
417
418  return max_z
419
420
421class CaptureRequestUtilsTest(unittest.TestCase):
422  """Unit tests for this module.
423
424  Ensures rational number conversion dicts are created properly.
425  """
426  _FLOAT_HALF = 0.5
427  # No immutable container: frozendict requires package install on partner host
428  _RATIONAL_HALF = {'numerator': 32, 'denominator': 64}
429
430  def test_float_to_rational(self):
431    """Unit test for float_to_rational."""
432    self.assertEqual(
433        float_to_rational(self._FLOAT_HALF, 64), self._RATIONAL_HALF)
434
435  def test_rational_to_float(self):
436    """Unit test for rational_to_float."""
437    self.assertTrue(
438        math.isclose(rational_to_float(self._RATIONAL_HALF),
439                     self._FLOAT_HALF, abs_tol=0.0001))
440
441  def test_int_to_rational(self):
442    """Unit test for int_to_rational."""
443    rational_10 = {'numerator': 10, 'denominator': 1}
444    rational_1 = {'numerator': 1, 'denominator': 1}
445    rational_2 = {'numerator': 2, 'denominator': 1}
446    # Simple test
447    self.assertEqual(int_to_rational(10), rational_10)
448    # Handle list entries
449    self.assertEqual(
450        int_to_rational([1, 2]), [rational_1, rational_2])
451
452if __name__ == '__main__':
453  unittest.main()
454