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