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.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 130def 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 142def 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 157def 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, key=lambda s: s[0]) # 1st pass, sort by width 189 out_sizes.sort(reverse=True, key=lambda s: s[0]*s[1]) # sort by area 190 return out_sizes 191 192def set_filter_off_or_fast_if_possible(props, req, available_modes, filter): 193 """Check and set controlKey to off or fast in req. 194 195 Args: 196 props: the object returned from its.device.get_camera_properties(). 197 req: the input request. filter will be set to OFF or FAST if possible. 198 available_modes: the key to check available modes. 199 filter: the filter key 200 201 Returns: 202 Nothing. 203 """ 204 if props.has_key(available_modes): 205 if 0 in props[available_modes]: 206 req[filter] = 0 207 elif 1 in props[available_modes]: 208 req[filter] = 1 209 210def turn_slow_filters_off(props, req): 211 """Turn filters that may slow FPS down to OFF or FAST in input request. 212 213 This function modifies the request argument, such that filters that may 214 reduce the frames-per-second throughput of the camera device will be set to 215 OFF or FAST if possible. 216 217 Args: 218 props: the object returned from its.device.get_camera_properties(). 219 req: the input request. 220 221 Returns: 222 Nothing. 223 """ 224 set_filter_off_or_fast_if_possible(props, req, 225 "android.noiseReduction.availableNoiseReductionModes", 226 "android.noiseReduction.mode") 227 set_filter_off_or_fast_if_possible(props, req, 228 "android.colorCorrection.availableAberrationModes", 229 "android.colorCorrection.aberrationMode") 230 if props.has_key("android.request.availableCharacteristicsKeys"): 231 hot_pixel_modes = 393217 in props["android.request.availableCharacteristicsKeys"] 232 edge_modes = 196610 in props["android.request.availableCharacteristicsKeys"] 233 if props.has_key("android.request.availableRequestKeys"): 234 hot_pixel_mode = 393216 in props["android.request.availableRequestKeys"] 235 edge_mode = 196608 in props["android.request.availableRequestKeys"] 236 if hot_pixel_modes and hot_pixel_mode: 237 set_filter_off_or_fast_if_possible(props, req, 238 "android.hotPixel.availableHotPixelModes", 239 "android.hotPixel.mode") 240 if edge_modes and edge_mode: 241 set_filter_off_or_fast_if_possible(props, req, 242 "android.edge.availableEdgeModes", 243 "android.edge.mode") 244 245def get_fastest_manual_capture_settings(props): 246 """Return a capture request and format spec for the fastest capture. 247 248 Args: 249 props: the object returned from its.device.get_camera_properties(). 250 251 Returns: 252 Two values, the first is a capture request, and the second is an output 253 format specification, for the fastest possible (legal) capture that 254 can be performed on this device (with the smallest output size). 255 """ 256 fmt = "yuv" 257 size = get_available_output_sizes(fmt, props)[-1] 258 out_spec = {"format":fmt, "width":size[0], "height":size[1]} 259 s = min(props['android.sensor.info.sensitivityRange']) 260 e = min(props['android.sensor.info.exposureTimeRange']) 261 req = manual_capture_request(s,e) 262 263 turn_slow_filters_off(props, req) 264 265 return req, out_spec 266 267 268def get_smallest_yuv_format(props): 269 """Return a capture request and format spec for the smallest yuv size. 270 271 Args: 272 props: the object returned from its.device.get_camera_properties(). 273 274 Returns: 275 fmt: an output format specification, for the smallest possible yuv 276 format for this device. 277 """ 278 size = get_available_output_sizes("yuv", props)[-1] 279 fmt = {"format":"yuv", "width":size[0], "height":size[1]} 280 281 return fmt 282 283 284def get_largest_yuv_format(props): 285 """Return a capture request and format spec for the smallest yuv size. 286 287 Args: 288 props: the object returned from its.device.get_camera_properties(). 289 290 Returns: 291 fmt: an output format specification, for the smallest possible yuv 292 format for this device. 293 """ 294 size = get_available_output_sizes("yuv", props)[0] 295 fmt = {"format":"yuv", "width":size[0], "height":size[1]} 296 297 return fmt 298 299 300def get_max_digital_zoom(props): 301 """Returns the maximum amount of zooming possible by the camera device. 302 303 Args: 304 props: the object returned from its.device.get_camera_properties(). 305 306 Return: 307 A float indicating the maximum amount of zooming possible by the 308 camera device. 309 """ 310 311 maxz = 1.0 312 313 if props.has_key("android.scaler.availableMaxDigitalZoom"): 314 maxz = props["android.scaler.availableMaxDigitalZoom"] 315 316 return maxz 317 318 319class __UnitTest(unittest.TestCase): 320 """Run a suite of unit tests on this module. 321 """ 322 323 def test_int_to_rational(self): 324 """Unit test for int_to_rational. 325 """ 326 self.assertEqual(int_to_rational(10), 327 {"numerator":10,"denominator":1}) 328 self.assertEqual(int_to_rational([1,2]), 329 [{"numerator":1,"denominator":1}, 330 {"numerator":2,"denominator":1}]) 331 332 def test_float_to_rational(self): 333 """Unit test for float_to_rational. 334 """ 335 self.assertEqual(float_to_rational(0.5001, 64), 336 {"numerator":32, "denominator":64}) 337 338 def test_rational_to_float(self): 339 """Unit test for rational_to_float. 340 """ 341 self.assertTrue( 342 abs(rational_to_float({"numerator":32,"denominator":64})-0.5) 343 < 0.0001) 344 345if __name__ == '__main__': 346 unittest.main() 347 348