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