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