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