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