blob: 01d3c5f14b490b9a61d054dd4f530b22fd88ae80 [file] [log] [blame]
Ruben Brunk370e2432014-10-14 18:33:23 -07001# 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 its.device
16import its.image
17import its.objects
18import os
19import os.path
20import sys
21import json
22import unittest
23import json
24
25CACHE_FILENAME = "its.target.cfg"
26
27def __do_target_exposure_measurement(its_session):
28 """Use device 3A and captured shots to determine scene exposure.
29
30 Creates a new ITS device session (so this function should not be called
31 while another session to the device is open).
32
33 Assumes that the camera is pointed at a scene that is reasonably uniform
34 and reasonably lit -- that is, an appropriate target for running the ITS
35 tests that assume such uniformity.
36
37 Measures the scene using device 3A and then by taking a shot to hone in on
38 the exact exposure level that will result in a center 10% by 10% patch of
39 the scene having a intensity level of 0.5 (in the pixel range of [0,1])
40 when a linear tonemap is used. That is, the pixels coming off the sensor
41 should be at approximately 50% intensity (however note that it's actually
42 the luma value in the YUV image that is being targeted to 50%).
43
44 The computed exposure value is the product of the sensitivity (ISO) and
45 exposure time (ns) to achieve that sensor exposure level.
46
47 Args:
48 its_session: Holds an open device session.
49
50 Returns:
51 The measured product of sensitivity and exposure time that results in
52 the luma channel of captured shots having an intensity of 0.5.
53 """
54 print "Measuring target exposure"
55
56 # Get AE+AWB lock first, so the auto values in the capture result are
57 # populated properly.
58 r = [[0.45, 0.45, 0.1, 0.1, 1]]
59 sens, exp_time, gains, xform, _ \
60 = its_session.do_3a(r,r,r,do_af=False,get_results=True)
61
62 # Convert the transform to rational.
63 xform_rat = [{"numerator":int(100*x),"denominator":100} for x in xform]
64
65 # Linear tonemap
66 tmap = sum([[i/63.0,i/63.0] for i in range(64)], [])
67
68 # Capture a manual shot with this exposure, using a linear tonemap.
69 # Use the gains+transform returned by the AWB pass.
70 req = its.objects.manual_capture_request(sens, exp_time)
71 req["android.tonemap.mode"] = 0
Yin-Chia Yeh43c7b6c2018-05-07 14:09:14 -070072 req["android.tonemap.curve"] = {
73 "red": tmap, "green": tmap, "blue": tmap}
Ruben Brunk370e2432014-10-14 18:33:23 -070074 req["android.colorCorrection.transform"] = xform_rat
75 req["android.colorCorrection.gains"] = gains
76 cap = its_session.do_capture(req)
77
78 # Compute the mean luma of a center patch.
79 yimg,uimg,vimg = its.image.convert_capture_to_planes(cap)
80 tile = its.image.get_image_patch(yimg, 0.45, 0.45, 0.1, 0.1)
81 luma_mean = its.image.compute_image_means(tile)
82
83 # Compute the exposure value that would result in a luma of 0.5.
84 return sens * exp_time * 0.5 / luma_mean[0]
85
86def __set_cached_target_exposure(exposure):
87 """Saves the given exposure value to a cached location.
88
89 Once a value is cached, a call to __get_cached_target_exposure will return
90 the value, even from a subsequent test/script run. That is, the value is
91 persisted.
92
93 The value is persisted in a JSON file in the current directory (from which
94 the script calling this function is run).
95
96 Args:
97 exposure: The value to cache.
98 """
99 print "Setting cached target exposure"
100 with open(CACHE_FILENAME, "w") as f:
101 f.write(json.dumps({"exposure":exposure}))
102
103def __get_cached_target_exposure():
104 """Get the cached exposure value.
105
106 Returns:
107 The cached exposure value, or None if there is no valid cached value.
108 """
109 try:
110 with open(CACHE_FILENAME, "r") as f:
111 o = json.load(f)
112 return o["exposure"]
113 except:
114 return None
115
116def clear_cached_target_exposure():
117 """If there is a cached exposure value, clear it.
118 """
119 if os.path.isfile(CACHE_FILENAME):
120 os.remove(CACHE_FILENAME)
121
122def set_hardcoded_exposure(exposure):
123 """Set a hard-coded exposure value, rather than relying on measurements.
124
125 The exposure value is the product of sensitivity (ISO) and eposure time
126 (ns) that will result in a center-patch luma value of 0.5 (using a linear
127 tonemap) for the scene that the camera is pointing at.
128
129 If bringing up a new HAL implementation and the ability use the device to
130 measure the scene isn't there yet (e.g. device 3A doesn't work), then a
131 cache file of the appropriate name can be manually created and populated
132 with a hard-coded value using this function.
133
134 Args:
135 exposure: The hard-coded exposure value to set.
136 """
137 __set_cached_target_exposure(exposure)
138
139def get_target_exposure(its_session=None):
140 """Get the target exposure to use.
141
142 If there is a cached value and if the "target" command line parameter is
143 present, then return the cached value. Otherwise, measure a new value from
144 the scene, cache it, then return it.
145
146 Args:
147 its_session: Optional, holding an open device session.
148
149 Returns:
150 The target exposure value.
151 """
152 cached_exposure = None
153 for s in sys.argv[1:]:
154 if s == "target":
155 cached_exposure = __get_cached_target_exposure()
156 if cached_exposure is not None:
157 print "Using cached target exposure"
158 return cached_exposure
159 if its_session is None:
160 with its.device.ItsSession() as cam:
161 measured_exposure = __do_target_exposure_measurement(cam)
162 else:
163 measured_exposure = __do_target_exposure_measurement(its_session)
164 __set_cached_target_exposure(measured_exposure)
165 return measured_exposure
166
167def get_target_exposure_combos(its_session=None):
168 """Get a set of legal combinations of target (exposure time, sensitivity).
169
170 Gets the target exposure value, which is a product of sensitivity (ISO) and
171 exposure time, and returns equivalent tuples of (exposure time,sensitivity)
172 that are all legal and that correspond to the four extrema in this 2D param
173 space, as well as to two "middle" points.
174
175 Will open a device session if its_session is None.
176
177 Args:
178 its_session: Optional, holding an open device session.
179
180 Returns:
181 Object containing six legal (exposure time, sensitivity) tuples, keyed
182 by the following strings:
183 "minExposureTime"
184 "midExposureTime"
185 "maxExposureTime"
186 "minSensitivity"
187 "midSensitivity"
188 "maxSensitivity
189 """
190 if its_session is None:
191 with its.device.ItsSession() as cam:
192 exposure = get_target_exposure(cam)
193 props = cam.get_camera_properties()
194 else:
195 exposure = get_target_exposure(its_session)
196 props = its_session.get_camera_properties()
197
198 sens_range = props['android.sensor.info.sensitivityRange']
199 exp_time_range = props['android.sensor.info.exposureTimeRange']
200
201 # Combo 1: smallest legal exposure time.
202 e1_expt = exp_time_range[0]
203 e1_sens = exposure / e1_expt
204 if e1_sens > sens_range[1]:
205 e1_sens = sens_range[1]
206 e1_expt = exposure / e1_sens
207
208 # Combo 2: largest legal exposure time.
209 e2_expt = exp_time_range[1]
210 e2_sens = exposure / e2_expt
211 if e2_sens < sens_range[0]:
212 e2_sens = sens_range[0]
213 e2_expt = exposure / e2_sens
214
215 # Combo 3: smallest legal sensitivity.
216 e3_sens = sens_range[0]
217 e3_expt = exposure / e3_sens
218 if e3_expt > exp_time_range[1]:
219 e3_expt = exp_time_range[1]
220 e3_sens = exposure / e3_expt
221
222 # Combo 4: largest legal sensitivity.
223 e4_sens = sens_range[1]
224 e4_expt = exposure / e4_sens
225 if e4_expt < exp_time_range[0]:
226 e4_expt = exp_time_range[0]
227 e4_sens = exposure / e4_expt
228
229 # Combo 5: middle exposure time.
230 e5_expt = (exp_time_range[0] + exp_time_range[1]) / 2.0
231 e5_sens = exposure / e5_expt
232 if e5_sens > sens_range[1]:
233 e5_sens = sens_range[1]
234 e5_expt = exposure / e5_sens
235 if e5_sens < sens_range[0]:
236 e5_sens = sens_range[0]
237 e5_expt = exposure / e5_sens
238
239 # Combo 6: middle sensitivity.
240 e6_sens = (sens_range[0] + sens_range[1]) / 2.0
241 e6_expt = exposure / e6_sens
242 if e6_expt > exp_time_range[1]:
243 e6_expt = exp_time_range[1]
244 e6_sens = exposure / e6_expt
245 if e6_expt < exp_time_range[0]:
246 e6_expt = exp_time_range[0]
247 e6_sens = exposure / e6_expt
248
249 return {
250 "minExposureTime" : (int(e1_expt), int(e1_sens)),
251 "maxExposureTime" : (int(e2_expt), int(e2_sens)),
252 "minSensitivity" : (int(e3_expt), int(e3_sens)),
253 "maxSensitivity" : (int(e4_expt), int(e4_sens)),
254 "midExposureTime" : (int(e5_expt), int(e5_sens)),
255 "midSensitivity" : (int(e6_expt), int(e6_sens))
256 }
257
258class __UnitTest(unittest.TestCase):
259 """Run a suite of unit tests on this module.
260 """
261 # TODO: Add some unit tests.
262
263if __name__ == '__main__':
264 unittest.main()
265