Home | History | Annotate | Download | only in its
      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 
     15 import os
     16 import os.path
     17 import sys
     18 import re
     19 import json
     20 import tempfile
     21 import time
     22 import unittest
     23 import subprocess
     24 import math
     25 
     26 def 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 
     41 def 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 
     58 def 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 
     73 def 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.frameDuration": 0,
    103         "android.sensor.sensitivity": sensitivity,
    104         "android.sensor.exposureTime": exp_time,
    105         "android.colorCorrection.mode": 0,
    106         "android.colorCorrection.transform":
    107                 int_to_rational([1,0,0, 0,1,0, 0,0,1]),
    108         "android.colorCorrection.gains": [1,1,1,1],
    109         "android.lens.focusDistance" : f_distance,
    110         "android.tonemap.mode": 1,
    111         "android.shading.mode": 1
    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.curveRed"] = [0.0,0.0, 1.0,1.0]
    119             req["android.tonemap.curveGreen"] = [0.0,0.0, 1.0,1.0]
    120             req["android.tonemap.curveBlue"] = [0.0,0.0, 1.0,1.0]
    121         #GAMMA_VALUE mode
    122         elif 3 in props["android.tonemap.availableToneMapModes"]:
    123             req["android.tonemap.mode"] = 3
    124             req["android.tonemap.gamma"] = 1.0
    125         else:
    126             print "Linear tonemap is not supported"
    127             assert(False)
    128     return req
    129 
    130 def auto_capture_request():
    131     """Return a capture request with everything set to auto.
    132     """
    133     return {
    134         "android.control.mode": 1,
    135         "android.control.aeMode": 1,
    136         "android.control.awbMode": 1,
    137         "android.control.afMode": 1,
    138         "android.colorCorrection.mode": 1,
    139         "android.tonemap.mode": 1,
    140         }
    141 
    142 def fastest_auto_capture_request(props):
    143     """Return an auto capture request for the fastest capture.
    144 
    145     Args:
    146         props: the object returned from its.device.get_camera_properties().
    147 
    148     Returns:
    149         A capture request with everything set to auto and all filters that
    150             may slow down capture set to OFF or FAST if possible
    151     """
    152     req = auto_capture_request()
    153     turn_slow_filters_off(props, req)
    154 
    155     return req
    156 
    157 def get_available_output_sizes(fmt, props, max_size=None, match_ar_size=None):
    158     """Return a sorted list of available output sizes for a given format.
    159 
    160     Args:
    161         fmt: the output format, as a string in
    162             ["jpg", "yuv", "raw", "raw10", "raw12"].
    163         props: the object returned from its.device.get_camera_properties().
    164         max_size: (Optional) A (w,h) tuple.
    165             Sizes larger than max_size (either w or h)  will be discarded.
    166         match_ar_size: (Optional) A (w,h) tuple.
    167             Sizes not matching the aspect ratio of match_ar_size will be
    168             discarded.
    169 
    170     Returns:
    171         A sorted list of (w,h) tuples (sorted large-to-small).
    172     """
    173     AR_TOLERANCE = 0.03
    174     fmt_codes = {"raw":0x20, "raw10":0x25, "raw12":0x26,"yuv":0x23,
    175                  "jpg":0x100, "jpeg":0x100}
    176     configs = props['android.scaler.streamConfigurationMap']\
    177                    ['availableStreamConfigurations']
    178     fmt_configs = [cfg for cfg in configs if cfg['format'] == fmt_codes[fmt]]
    179     out_configs = [cfg for cfg in fmt_configs if cfg['input'] == False]
    180     out_sizes = [(cfg['width'],cfg['height']) for cfg in out_configs]
    181     if max_size:
    182         out_sizes = [s for s in out_sizes if
    183                 s[0] <= max_size[0] and s[1] <= max_size[1]]
    184     if match_ar_size:
    185         ar = match_ar_size[0] / float(match_ar_size[1])
    186         out_sizes = [s for s in out_sizes if
    187                 abs(ar - s[0] / float(s[1])) <= AR_TOLERANCE]
    188     out_sizes.sort(reverse=True)
    189     return out_sizes
    190 
    191 def set_filter_off_or_fast_if_possible(props, req, available_modes, filter):
    192     """Check and set controlKey to off or fast in req.
    193 
    194     Args:
    195         props: the object returned from its.device.get_camera_properties().
    196         req: the input request. filter will be set to OFF or FAST if possible.
    197         available_modes: the key to check available modes.
    198         filter: the filter key
    199 
    200     Returns:
    201         Nothing.
    202     """
    203     if props.has_key(available_modes):
    204         if 0 in props[available_modes]:
    205             req[filter] = 0
    206         elif 1 in props[available_modes]:
    207             req[filter] = 1
    208 
    209 def turn_slow_filters_off(props, req):
    210     """Turn filters that may slow FPS down to OFF or FAST in input request.
    211 
    212     This function modifies the request argument, such that filters that may
    213     reduce the frames-per-second throughput of the camera device will be set to
    214     OFF or FAST if possible.
    215 
    216     Args:
    217         props: the object returned from its.device.get_camera_properties().
    218         req: the input request.
    219 
    220     Returns:
    221         Nothing.
    222     """
    223     set_filter_off_or_fast_if_possible(props, req,
    224         "android.noiseReduction.availableNoiseReductionModes",
    225         "android.noiseReduction.mode")
    226     set_filter_off_or_fast_if_possible(props, req,
    227         "android.colorCorrection.availableAberrationModes",
    228         "android.colorCorrection.aberrationMode")
    229     if props.has_key("android.request.availableCharacteristicsKeys"):
    230         hot_pixel_modes = 393217 in props["android.request.availableCharacteristicsKeys"]
    231         edge_modes = 196610 in props["android.request.availableCharacteristicsKeys"]
    232     if props.has_key("android.request.availableRequestKeys"):
    233         hot_pixel_mode = 393216 in props["android.request.availableRequestKeys"]
    234         edge_mode = 196608 in props["android.request.availableRequestKeys"]
    235     if hot_pixel_modes and hot_pixel_mode:
    236         set_filter_off_or_fast_if_possible(props, req,
    237             "android.hotPixel.availableHotPixelModes",
    238             "android.hotPixel.mode")
    239     if edge_modes and edge_mode:
    240         set_filter_off_or_fast_if_possible(props, req,
    241             "android.edge.availableEdgeModes",
    242             "android.edge.mode")
    243 
    244 def get_fastest_manual_capture_settings(props):
    245     """Return a capture request and format spec for the fastest capture.
    246 
    247     Args:
    248         props: the object returned from its.device.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     s = min(props['android.sensor.info.sensitivityRange'])
    259     e = min(props['android.sensor.info.exposureTimeRange'])
    260     req = manual_capture_request(s,e)
    261 
    262     turn_slow_filters_off(props, req)
    263 
    264     return req, out_spec
    265 
    266 def get_max_digital_zoom(props):
    267     """Returns the maximum amount of zooming possible by the camera device.
    268 
    269     Args:
    270         props: the object returned from its.device.get_camera_properties().
    271 
    272     Return:
    273         A float indicating the maximum amount of zooming possible by the
    274         camera device.
    275     """
    276 
    277     maxz = 1.0
    278 
    279     if props.has_key("android.scaler.availableMaxDigitalZoom"):
    280         maxz = props["android.scaler.availableMaxDigitalZoom"]
    281 
    282     return maxz
    283 
    284 
    285 class __UnitTest(unittest.TestCase):
    286     """Run a suite of unit tests on this module.
    287     """
    288 
    289     def test_int_to_rational(self):
    290         """Unit test for int_to_rational.
    291         """
    292         self.assertEqual(int_to_rational(10),
    293                          {"numerator":10,"denominator":1})
    294         self.assertEqual(int_to_rational([1,2]),
    295                          [{"numerator":1,"denominator":1},
    296                           {"numerator":2,"denominator":1}])
    297 
    298     def test_float_to_rational(self):
    299         """Unit test for float_to_rational.
    300         """
    301         self.assertEqual(float_to_rational(0.5001, 64),
    302                         {"numerator":32, "denominator":64})
    303 
    304     def test_rational_to_float(self):
    305         """Unit test for rational_to_float.
    306         """
    307         self.assertTrue(
    308                 abs(rational_to_float({"numerator":32,"denominator":64})-0.5)
    309                 < 0.0001)
    310 
    311 if __name__ == '__main__':
    312     unittest.main()
    313 
    314