Home | History | Annotate | Download | only in its
      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 its.device
     16 import its.image
     17 import its.objects
     18 import os
     19 import os.path
     20 import sys
     21 import json
     22 import unittest
     23 import json
     24 
     25 CACHE_FILENAME = "its.target.cfg"
     26 
     27 def __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
     72     req["android.tonemap.curve"] = {
     73         "red": tmap, "green": tmap, "blue": tmap}
     74     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 
     86 def __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 
    103 def __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 
    116 def 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 
    122 def 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 
    139 def 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 
    167 def 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 
    258 class __UnitTest(unittest.TestCase):
    259     """Run a suite of unit tests on this module.
    260     """
    261     # TODO: Add some unit tests.
    262 
    263 if __name__ == '__main__':
    264     unittest.main()
    265 
    266