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