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.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.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         "android.lens.opticalStabilizationMode": 0
    141         }
    142 
    143 def fastest_auto_capture_request(props):
    144     """Return an auto capture request for the fastest capture.
    145 
    146     Args:
    147         props: the object returned from its.device.get_camera_properties().
    148 
    149     Returns:
    150         A capture request with everything set to auto and all filters that
    151             may slow down capture set to OFF or FAST if possible
    152     """
    153     req = auto_capture_request()
    154     turn_slow_filters_off(props, req)
    155 
    156     return req
    157 
    158 def get_available_output_sizes(fmt, props, max_size=None, match_ar_size=None):
    159     """Return a sorted list of available output sizes for a given format.
    160 
    161     Args:
    162         fmt: the output format, as a string in
    163             ["jpg", "yuv", "raw", "raw10", "raw12"].
    164         props: the object returned from its.device.get_camera_properties().
    165         max_size: (Optional) A (w,h) tuple.
    166             Sizes larger than max_size (either w or h)  will be discarded.
    167         match_ar_size: (Optional) A (w,h) tuple.
    168             Sizes not matching the aspect ratio of match_ar_size will be
    169             discarded.
    170 
    171     Returns:
    172         A sorted list of (w,h) tuples (sorted large-to-small).
    173     """
    174     AR_TOLERANCE = 0.03
    175     fmt_codes = {"raw":0x20, "raw10":0x25, "raw12":0x26,"yuv":0x23,
    176                  "jpg":0x100, "jpeg":0x100}
    177     configs = props['android.scaler.streamConfigurationMap']\
    178                    ['availableStreamConfigurations']
    179     fmt_configs = [cfg for cfg in configs if cfg['format'] == fmt_codes[fmt]]
    180     out_configs = [cfg for cfg in fmt_configs if cfg['input'] == False]
    181     out_sizes = [(cfg['width'],cfg['height']) for cfg in out_configs]
    182     if max_size:
    183         out_sizes = [s for s in out_sizes if
    184                 s[0] <= max_size[0] and s[1] <= max_size[1]]
    185     if match_ar_size:
    186         ar = match_ar_size[0] / float(match_ar_size[1])
    187         out_sizes = [s for s in out_sizes if
    188                 abs(ar - s[0] / float(s[1])) <= AR_TOLERANCE]
    189     out_sizes.sort(reverse=True, key=lambda s: s[0]) # 1st pass, sort by width
    190     out_sizes.sort(reverse=True, key=lambda s: s[0]*s[1]) # sort by area
    191     return out_sizes
    192 
    193 def set_filter_off_or_fast_if_possible(props, req, available_modes, filter):
    194     """Check and set controlKey to off or fast in req.
    195 
    196     Args:
    197         props: the object returned from its.device.get_camera_properties().
    198         req: the input request. filter will be set to OFF or FAST if possible.
    199         available_modes: the key to check available modes.
    200         filter: the filter key
    201 
    202     Returns:
    203         Nothing.
    204     """
    205     if props.has_key(available_modes):
    206         if 0 in props[available_modes]:
    207             req[filter] = 0
    208         elif 1 in props[available_modes]:
    209             req[filter] = 1
    210 
    211 def turn_slow_filters_off(props, req):
    212     """Turn filters that may slow FPS down to OFF or FAST in input request.
    213 
    214     This function modifies the request argument, such that filters that may
    215     reduce the frames-per-second throughput of the camera device will be set to
    216     OFF or FAST if possible.
    217 
    218     Args:
    219         props: the object returned from its.device.get_camera_properties().
    220         req: the input request.
    221 
    222     Returns:
    223         Nothing.
    224     """
    225     set_filter_off_or_fast_if_possible(props, req,
    226         "android.noiseReduction.availableNoiseReductionModes",
    227         "android.noiseReduction.mode")
    228     set_filter_off_or_fast_if_possible(props, req,
    229         "android.colorCorrection.availableAberrationModes",
    230         "android.colorCorrection.aberrationMode")
    231     if props.has_key("android.request.availableCharacteristicsKeys"):
    232         hot_pixel_modes = 393217 in props["android.request.availableCharacteristicsKeys"]
    233         edge_modes = 196610 in props["android.request.availableCharacteristicsKeys"]
    234     if props.has_key("android.request.availableRequestKeys"):
    235         hot_pixel_mode = 393216 in props["android.request.availableRequestKeys"]
    236         edge_mode = 196608 in props["android.request.availableRequestKeys"]
    237     if hot_pixel_modes and hot_pixel_mode:
    238         set_filter_off_or_fast_if_possible(props, req,
    239             "android.hotPixel.availableHotPixelModes",
    240             "android.hotPixel.mode")
    241     if edge_modes and edge_mode:
    242         set_filter_off_or_fast_if_possible(props, req,
    243             "android.edge.availableEdgeModes",
    244             "android.edge.mode")
    245 
    246 def get_fastest_manual_capture_settings(props):
    247     """Return a capture request and format spec for the fastest capture.
    248 
    249     Args:
    250         props: the object returned from its.device.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 
    269 def get_smallest_yuv_format(props, match_ar=None):
    270     """Return a capture request and format spec for the smallest yuv size.
    271 
    272     Args:
    273         props: the object returned from its.device.get_camera_properties().
    274 
    275     Returns:
    276         fmt:    an output format specification, for the smallest possible yuv
    277         format for this device.
    278     """
    279     size = get_available_output_sizes("yuv", props, match_ar_size=match_ar)[-1]
    280     fmt = {"format":"yuv", "width":size[0], "height":size[1]}
    281 
    282     return fmt
    283 
    284 
    285 def get_largest_yuv_format(props):
    286     """Return a capture request and format spec for the smallest yuv size.
    287 
    288     Args:
    289         props: the object returned from its.device.get_camera_properties().
    290 
    291     Returns:
    292         fmt:    an output format specification, for the smallest possible yuv
    293         format for this device.
    294     """
    295     size = get_available_output_sizes("yuv", props)[0]
    296     fmt = {"format":"yuv", "width":size[0], "height":size[1]}
    297 
    298     return fmt
    299 
    300 
    301 def get_max_digital_zoom(props):
    302     """Returns the maximum amount of zooming possible by the camera device.
    303 
    304     Args:
    305         props: the object returned from its.device.get_camera_properties().
    306 
    307     Return:
    308         A float indicating the maximum amount of zooming possible by the
    309         camera device.
    310     """
    311 
    312     maxz = 1.0
    313 
    314     if props.has_key("android.scaler.availableMaxDigitalZoom"):
    315         maxz = props["android.scaler.availableMaxDigitalZoom"]
    316 
    317     return maxz
    318 
    319 
    320 class __UnitTest(unittest.TestCase):
    321     """Run a suite of unit tests on this module.
    322     """
    323 
    324     def test_int_to_rational(self):
    325         """Unit test for int_to_rational.
    326         """
    327         self.assertEqual(int_to_rational(10),
    328                          {"numerator":10,"denominator":1})
    329         self.assertEqual(int_to_rational([1,2]),
    330                          [{"numerator":1,"denominator":1},
    331                           {"numerator":2,"denominator":1}])
    332 
    333     def test_float_to_rational(self):
    334         """Unit test for float_to_rational.
    335         """
    336         self.assertEqual(float_to_rational(0.5001, 64),
    337                         {"numerator":32, "denominator":64})
    338 
    339     def test_rational_to_float(self):
    340         """Unit test for rational_to_float.
    341         """
    342         self.assertTrue(
    343                 abs(rational_to_float({"numerator":32,"denominator":64})-0.5)
    344                 < 0.0001)
    345 
    346 if __name__ == '__main__':
    347     unittest.main()
    348 
    349