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