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.curveRed"] = tmap
     73     req["android.tonemap.curveGreen"] = tmap
     74     req["android.tonemap.curveBlue"] = tmap
     75     req["android.colorCorrection.transform"] = xform_rat
     76     req["android.colorCorrection.gains"] = gains
     77     cap = its_session.do_capture(req)
     78 
     79     # Compute the mean luma of a center patch.
     80     yimg,uimg,vimg = its.image.convert_capture_to_planes(cap)
     81     tile = its.image.get_image_patch(yimg, 0.45, 0.45, 0.1, 0.1)
     82     luma_mean = its.image.compute_image_means(tile)
     83 
     84     # Compute the exposure value that would result in a luma of 0.5.
     85     return sens * exp_time * 0.5 / luma_mean[0]
     86 
     87 def __set_cached_target_exposure(exposure):
     88     """Saves the given exposure value to a cached location.
     89 
     90     Once a value is cached, a call to __get_cached_target_exposure will return
     91     the value, even from a subsequent test/script run. That is, the value is
     92     persisted.
     93 
     94     The value is persisted in a JSON file in the current directory (from which
     95     the script calling this function is run).
     96 
     97     Args:
     98         exposure: The value to cache.
     99     """
    100     print "Setting cached target exposure"
    101     with open(CACHE_FILENAME, "w") as f:
    102         f.write(json.dumps({"exposure":exposure}))
    103 
    104 def __get_cached_target_exposure():
    105     """Get the cached exposure value.
    106 
    107     Returns:
    108         The cached exposure value, or None if there is no valid cached value.
    109     """
    110     try:
    111         with open(CACHE_FILENAME, "r") as f:
    112             o = json.load(f)
    113             return o["exposure"]
    114     except:
    115         return None
    116 
    117 def clear_cached_target_exposure():
    118     """If there is a cached exposure value, clear it.
    119     """
    120     if os.path.isfile(CACHE_FILENAME):
    121         os.remove(CACHE_FILENAME)
    122 
    123 def set_hardcoded_exposure(exposure):
    124     """Set a hard-coded exposure value, rather than relying on measurements.
    125 
    126     The exposure value is the product of sensitivity (ISO) and eposure time
    127     (ns) that will result in a center-patch luma value of 0.5 (using a linear
    128     tonemap) for the scene that the camera is pointing at.
    129 
    130     If bringing up a new HAL implementation and the ability use the device to
    131     measure the scene isn't there yet (e.g. device 3A doesn't work), then a
    132     cache file of the appropriate name can be manually created and populated
    133     with a hard-coded value using this function.
    134 
    135     Args:
    136         exposure: The hard-coded exposure value to set.
    137     """
    138     __set_cached_target_exposure(exposure)
    139 
    140 def get_target_exposure(its_session=None):
    141     """Get the target exposure to use.
    142 
    143     If there is a cached value and if the "target" command line parameter is
    144     present, then return the cached value. Otherwise, measure a new value from
    145     the scene, cache it, then return it.
    146 
    147     Args:
    148         its_session: Optional, holding an open device session.
    149 
    150     Returns:
    151         The target exposure value.
    152     """
    153     cached_exposure = None
    154     for s in sys.argv[1:]:
    155         if s == "target":
    156             cached_exposure = __get_cached_target_exposure()
    157     if cached_exposure is not None:
    158         print "Using cached target exposure"
    159         return cached_exposure
    160     if its_session is None:
    161         with its.device.ItsSession() as cam:
    162             measured_exposure = __do_target_exposure_measurement(cam)
    163     else:
    164         measured_exposure = __do_target_exposure_measurement(its_session)
    165     __set_cached_target_exposure(measured_exposure)
    166     return measured_exposure
    167 
    168 def get_target_exposure_combos(its_session=None):
    169     """Get a set of legal combinations of target (exposure time, sensitivity).
    170 
    171     Gets the target exposure value, which is a product of sensitivity (ISO) and
    172     exposure time, and returns equivalent tuples of (exposure time,sensitivity)
    173     that are all legal and that correspond to the four extrema in this 2D param
    174     space, as well as to two "middle" points.
    175 
    176     Will open a device session if its_session is None.
    177 
    178     Args:
    179         its_session: Optional, holding an open device session.
    180 
    181     Returns:
    182         Object containing six legal (exposure time, sensitivity) tuples, keyed
    183         by the following strings:
    184             "minExposureTime"
    185             "midExposureTime"
    186             "maxExposureTime"
    187             "minSensitivity"
    188             "midSensitivity"
    189             "maxSensitivity
    190     """
    191     if its_session is None:
    192         with its.device.ItsSession() as cam:
    193             exposure = get_target_exposure(cam)
    194             props = cam.get_camera_properties()
    195     else:
    196         exposure = get_target_exposure(its_session)
    197         props = its_session.get_camera_properties()
    198 
    199     sens_range = props['android.sensor.info.sensitivityRange']
    200     exp_time_range = props['android.sensor.info.exposureTimeRange']
    201 
    202     # Combo 1: smallest legal exposure time.
    203     e1_expt = exp_time_range[0]
    204     e1_sens = exposure / e1_expt
    205     if e1_sens > sens_range[1]:
    206         e1_sens = sens_range[1]
    207         e1_expt = exposure / e1_sens
    208 
    209     # Combo 2: largest legal exposure time.
    210     e2_expt = exp_time_range[1]
    211     e2_sens = exposure / e2_expt
    212     if e2_sens < sens_range[0]:
    213         e2_sens = sens_range[0]
    214         e2_expt = exposure / e2_sens
    215 
    216     # Combo 3: smallest legal sensitivity.
    217     e3_sens = sens_range[0]
    218     e3_expt = exposure / e3_sens
    219     if e3_expt > exp_time_range[1]:
    220         e3_expt = exp_time_range[1]
    221         e3_sens = exposure / e3_expt
    222 
    223     # Combo 4: largest legal sensitivity.
    224     e4_sens = sens_range[1]
    225     e4_expt = exposure / e4_sens
    226     if e4_expt < exp_time_range[0]:
    227         e4_expt = exp_time_range[0]
    228         e4_sens = exposure / e4_expt
    229 
    230     # Combo 5: middle exposure time.
    231     e5_expt = (exp_time_range[0] + exp_time_range[1]) / 2.0
    232     e5_sens = exposure / e5_expt
    233     if e5_sens > sens_range[1]:
    234         e5_sens = sens_range[1]
    235         e5_expt = exposure / e5_sens
    236     if e5_sens < sens_range[0]:
    237         e5_sens = sens_range[0]
    238         e5_expt = exposure / e5_sens
    239 
    240     # Combo 6: middle sensitivity.
    241     e6_sens = (sens_range[0] + sens_range[1]) / 2.0
    242     e6_expt = exposure / e6_sens
    243     if e6_expt > exp_time_range[1]:
    244         e6_expt = exp_time_range[1]
    245         e6_sens = exposure / e6_expt
    246     if e6_expt < exp_time_range[0]:
    247         e6_expt = exp_time_range[0]
    248         e6_sens = exposure / e6_expt
    249 
    250     return {
    251         "minExposureTime" : (int(e1_expt), int(e1_sens)),
    252         "maxExposureTime" : (int(e2_expt), int(e2_sens)),
    253         "minSensitivity" : (int(e3_expt), int(e3_sens)),
    254         "maxSensitivity" : (int(e4_expt), int(e4_sens)),
    255         "midExposureTime" : (int(e5_expt), int(e5_sens)),
    256         "midSensitivity" : (int(e6_expt), int(e6_sens))
    257         }
    258 
    259 class __UnitTest(unittest.TestCase):
    260     """Run a suite of unit tests on this module.
    261     """
    262     # TODO: Add some unit tests.
    263 
    264 if __name__ == '__main__':
    265     unittest.main()
    266 
    267