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.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.curve"] = { 119 "red": [0.0,0.0, 1.0,1.0], 120 "green": [0.0,0.0, 1.0,1.0], 121 "blue": [0.0,0.0, 1.0,1.0]} 122 #GAMMA_VALUE mode 123 elif 3 in props["android.tonemap.availableToneMapModes"]: 124 req["android.tonemap.mode"] = 3 125 req["android.tonemap.gamma"] = 1.0 126 else: 127 print "Linear tonemap is not supported" 128 assert(False) 129 return req 130 131def auto_capture_request(): 132 """Return a capture request with everything set to auto. 133 """ 134 return { 135 "android.control.mode": 1, 136 "android.control.aeMode": 1, 137 "android.control.awbMode": 1, 138 "android.control.afMode": 1, 139 "android.colorCorrection.mode": 1, 140 "android.tonemap.mode": 1, 141 "android.lens.opticalStabilizationMode": 0 142 } 143 144def fastest_auto_capture_request(props): 145 """Return an auto capture request for the fastest capture. 146 147 Args: 148 props: the object returned from its.device.get_camera_properties(). 149 150 Returns: 151 A capture request with everything set to auto and all filters that 152 may slow down capture set to OFF or FAST if possible 153 """ 154 req = auto_capture_request() 155 turn_slow_filters_off(props, req) 156 157 return req 158 159def get_available_output_sizes(fmt, props, max_size=None, match_ar_size=None): 160 """Return a sorted list of available output sizes for a given format. 161 162 Args: 163 fmt: the output format, as a string in 164 ["jpg", "yuv", "raw", "raw10", "raw12", "y8"]. 165 props: the object returned from its.device.get_camera_properties(). 166 max_size: (Optional) A (w,h) tuple. 167 Sizes larger than max_size (either w or h) will be discarded. 168 match_ar_size: (Optional) A (w,h) tuple. 169 Sizes not matching the aspect ratio of match_ar_size will be 170 discarded. 171 172 Returns: 173 A sorted list of (w,h) tuples (sorted large-to-small). 174 """ 175 AR_TOLERANCE = 0.03 176 fmt_codes = {"raw":0x20, "raw10":0x25, "raw12":0x26,"yuv":0x23, 177 "jpg":0x100, "jpeg":0x100, "y8":0x20203859} 178 configs = props['android.scaler.streamConfigurationMap']\ 179 ['availableStreamConfigurations'] 180 fmt_configs = [cfg for cfg in configs if cfg['format'] == fmt_codes[fmt]] 181 out_configs = [cfg for cfg in fmt_configs if cfg['input'] == False] 182 out_sizes = [(cfg['width'],cfg['height']) for cfg in out_configs] 183 if max_size: 184 out_sizes = [s for s in out_sizes if 185 s[0] <= max_size[0] and s[1] <= max_size[1]] 186 if match_ar_size: 187 ar = match_ar_size[0] / float(match_ar_size[1]) 188 out_sizes = [s for s in out_sizes if 189 abs(ar - s[0] / float(s[1])) <= AR_TOLERANCE] 190 out_sizes.sort(reverse=True, key=lambda s: s[0]) # 1st pass, sort by width 191 out_sizes.sort(reverse=True, key=lambda s: s[0]*s[1]) # sort by area 192 return out_sizes 193 194def set_filter_off_or_fast_if_possible(props, req, available_modes, filter): 195 """Check and set controlKey to off or fast in req. 196 197 Args: 198 props: the object returned from its.device.get_camera_properties(). 199 req: the input request. filter will be set to OFF or FAST if possible. 200 available_modes: the key to check available modes. 201 filter: the filter key 202 203 Returns: 204 Nothing. 205 """ 206 if props.has_key(available_modes): 207 if 0 in props[available_modes]: 208 req[filter] = 0 209 elif 1 in props[available_modes]: 210 req[filter] = 1 211 212def turn_slow_filters_off(props, req): 213 """Turn filters that may slow FPS down to OFF or FAST in input request. 214 215 This function modifies the request argument, such that filters that may 216 reduce the frames-per-second throughput of the camera device will be set to 217 OFF or FAST if possible. 218 219 Args: 220 props: the object returned from its.device.get_camera_properties(). 221 req: the input request. 222 223 Returns: 224 Nothing. 225 """ 226 set_filter_off_or_fast_if_possible(props, req, 227 "android.noiseReduction.availableNoiseReductionModes", 228 "android.noiseReduction.mode") 229 set_filter_off_or_fast_if_possible(props, req, 230 "android.colorCorrection.availableAberrationModes", 231 "android.colorCorrection.aberrationMode") 232 if props.has_key("camera.characteristics.keys"): 233 chars_keys = props["camera.characteristics.keys"] 234 hot_pixel_modes = \ 235 "android.hotPixel.availableHotPixelModes" in chars_keys 236 edge_modes = "android.edge.availableEdgeModes" in chars_keys 237 if props.has_key("camera.characteristics.requestKeys"): 238 req_keys = props["camera.characteristics.requestKeys"] 239 hot_pixel_mode = "android.hotPixel.mode" in req_keys 240 edge_mode = "android.edge.mode" in req_keys 241 if hot_pixel_modes and hot_pixel_mode: 242 set_filter_off_or_fast_if_possible(props, req, 243 "android.hotPixel.availableHotPixelModes", 244 "android.hotPixel.mode") 245 if edge_modes and edge_mode: 246 set_filter_off_or_fast_if_possible(props, req, 247 "android.edge.availableEdgeModes", 248 "android.edge.mode") 249 250def get_fastest_manual_capture_settings(props): 251 """Return a capture request and format spec for the fastest manual capture. 252 253 Args: 254 props: the object returned from its.device.get_camera_properties(). 255 256 Returns: 257 Two values, the first is a capture request, and the second is an output 258 format specification, for the fastest possible (legal) capture that 259 can be performed on this device (with the smallest output size). 260 """ 261 fmt = "yuv" 262 size = get_available_output_sizes(fmt, props)[-1] 263 out_spec = {"format":fmt, "width":size[0], "height":size[1]} 264 s = min(props['android.sensor.info.sensitivityRange']) 265 e = min(props['android.sensor.info.exposureTimeRange']) 266 req = manual_capture_request(s,e) 267 268 turn_slow_filters_off(props, req) 269 270 return req, out_spec 271 272def get_fastest_auto_capture_settings(props): 273 """Return a capture request and format spec for the fastest auto capture. 274 275 Args: 276 props: the object returned from its.device.get_camera_properties(). 277 278 Returns: 279 Two values, the first is a capture request, and the second is an output 280 format specification, for the fastest possible (legal) capture that 281 can be performed on this device (with the smallest output size). 282 """ 283 fmt = "yuv" 284 size = get_available_output_sizes(fmt, props)[-1] 285 out_spec = {"format":fmt, "width":size[0], "height":size[1]} 286 req = auto_capture_request() 287 288 turn_slow_filters_off(props, req) 289 290 return req, out_spec 291 292 293def get_smallest_yuv_format(props, match_ar=None): 294 """Return a capture request and format spec for the smallest yuv size. 295 296 Args: 297 props: the object returned from its.device.get_camera_properties(). 298 299 Returns: 300 fmt: an output format specification, for the smallest possible yuv 301 format for this device. 302 """ 303 size = get_available_output_sizes("yuv", props, match_ar_size=match_ar)[-1] 304 fmt = {"format":"yuv", "width":size[0], "height":size[1]} 305 306 return fmt 307 308 309def get_largest_yuv_format(props, match_ar=None): 310 """Return a capture request and format spec for the largest yuv size. 311 312 Args: 313 props: the object returned from its.device.get_camera_properties(). 314 315 Returns: 316 fmt: an output format specification, for the largest possible yuv 317 format for this device. 318 """ 319 size = get_available_output_sizes("yuv", props, match_ar_size=match_ar)[0] 320 fmt = {"format":"yuv", "width":size[0], "height":size[1]} 321 322 return fmt 323 324 325def get_max_digital_zoom(props): 326 """Returns the maximum amount of zooming possible by the camera device. 327 328 Args: 329 props: the object returned from its.device.get_camera_properties(). 330 331 Return: 332 A float indicating the maximum amount of zooming possible by the 333 camera device. 334 """ 335 336 maxz = 1.0 337 338 if props.has_key("android.scaler.availableMaxDigitalZoom"): 339 maxz = props["android.scaler.availableMaxDigitalZoom"] 340 341 return maxz 342 343 344class __UnitTest(unittest.TestCase): 345 """Run a suite of unit tests on this module. 346 """ 347 348 def test_int_to_rational(self): 349 """Unit test for int_to_rational. 350 """ 351 self.assertEqual(int_to_rational(10), 352 {"numerator":10,"denominator":1}) 353 self.assertEqual(int_to_rational([1,2]), 354 [{"numerator":1,"denominator":1}, 355 {"numerator":2,"denominator":1}]) 356 357 def test_float_to_rational(self): 358 """Unit test for float_to_rational. 359 """ 360 self.assertEqual(float_to_rational(0.5001, 64), 361 {"numerator":32, "denominator":64}) 362 363 def test_rational_to_float(self): 364 """Unit test for rational_to_float. 365 """ 366 self.assertTrue( 367 abs(rational_to_float({"numerator":32,"denominator":64})-0.5) 368 < 0.0001) 369 370if __name__ == '__main__': 371 unittest.main() 372 373