Home | History | Annotate | Download | only in scene1
      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 os.path
     16 
     17 import its.caps
     18 import its.device
     19 import its.image
     20 import its.objects
     21 import its.target
     22 import matplotlib
     23 from matplotlib import pylab
     24 import numpy
     25 
     26 IMG_STATS_GRID = 9  # find used to find the center 11.11%
     27 NAME = os.path.basename(__file__).split('.')[0]
     28 THRESHOLD_MAX_OUTLIER_DIFF = 0.1
     29 THRESHOLD_MIN_LEVEL = 0.1
     30 THRESHOLD_MAX_LEVEL = 0.9
     31 THRESHOLD_MAX_LEVEL_DIFF = 0.045
     32 THRESHOLD_MAX_LEVEL_DIFF_WIDE_RANGE = 0.06
     33 THRESH_ROUND_DOWN_GAIN = 0.1
     34 THRESH_ROUND_DOWN_EXP = 0.03
     35 THRESH_ROUND_DOWN_EXP0 = 1.00  # tol at 0ms exp; theoretical limit @ 4-line exp
     36 THRESH_EXP_KNEE = 6E6  # exposures less than knee have relaxed tol
     37 
     38 
     39 def get_raw_active_array_size(props):
     40     """Return the active array w, h from props."""
     41     aaw = (props['android.sensor.info.preCorrectionActiveArraySize']['right'] -
     42            props['android.sensor.info.preCorrectionActiveArraySize']['left'])
     43     aah = (props['android.sensor.info.preCorrectionActiveArraySize']['bottom'] -
     44            props['android.sensor.info.preCorrectionActiveArraySize']['top'])
     45     return aaw, aah
     46 
     47 
     48 def main():
     49     """Test that a constant exposure is seen as ISO and exposure time vary.
     50 
     51     Take a series of shots that have ISO and exposure time chosen to balance
     52     each other; result should be the same brightness, but over the sequence
     53     the images should get noisier.
     54     """
     55     mults = []
     56     r_means = []
     57     g_means = []
     58     b_means = []
     59     raw_r_means = []
     60     raw_gr_means = []
     61     raw_gb_means = []
     62     raw_b_means = []
     63     threshold_max_level_diff = THRESHOLD_MAX_LEVEL_DIFF
     64 
     65     with its.device.ItsSession() as cam:
     66         props = cam.get_camera_properties()
     67         props = cam.override_with_hidden_physical_camera_props(props)
     68         its.caps.skip_unless(its.caps.compute_target_exposure(props))
     69         sync_latency = its.caps.sync_latency(props)
     70         process_raw = its.caps.raw16(props) and its.caps.manual_sensor(props)
     71         debug = its.caps.debug_mode()
     72         largest_yuv = its.objects.get_largest_yuv_format(props)
     73         if debug:
     74             fmt = largest_yuv
     75         else:
     76             match_ar = (largest_yuv['width'], largest_yuv['height'])
     77             fmt = its.objects.get_smallest_yuv_format(props, match_ar=match_ar)
     78 
     79         e, s = its.target.get_target_exposure_combos(cam)['minSensitivity']
     80         s_e_product = s*e
     81         expt_range = props['android.sensor.info.exposureTimeRange']
     82         sens_range = props['android.sensor.info.sensitivityRange']
     83 
     84         m = 1.0
     85         while s*m < sens_range[1] and e/m > expt_range[0]:
     86             mults.append(m)
     87             s_test = round(s*m)
     88             e_test = s_e_product / s_test
     89             print 'Testing s:', s_test, 'e:', e_test
     90             req = its.objects.manual_capture_request(
     91                     s_test, e_test, 0.0, True, props)
     92             cap = its.device.do_capture_with_latency(
     93                     cam, req, sync_latency, fmt)
     94             s_res = cap['metadata']['android.sensor.sensitivity']
     95             e_res = cap['metadata']['android.sensor.exposureTime']
     96             # determine exposure tolerance based on exposure time
     97             if e_test >= THRESH_EXP_KNEE:
     98                 thresh_round_down_exp = THRESH_ROUND_DOWN_EXP
     99             else:
    100                 thresh_round_down_exp = (
    101                         THRESH_ROUND_DOWN_EXP +
    102                         (THRESH_ROUND_DOWN_EXP0 - THRESH_ROUND_DOWN_EXP) *
    103                         (THRESH_EXP_KNEE - e_test) / THRESH_EXP_KNEE)
    104             s_msg = 's_write: %d, s_read: %d, TOL=%.f%%' % (
    105                     s_test, s_res, THRESH_ROUND_DOWN_GAIN*100)
    106             e_msg = 'e_write: %.3fms, e_read: %.3fms, TOL=%.f%%' % (
    107                     e_test/1.0E6, e_res/1.0E6, thresh_round_down_exp*100)
    108             assert 0 <= s_test - s_res < s_test * THRESH_ROUND_DOWN_GAIN, s_msg
    109             assert 0 <= e_test - e_res < e_test * thresh_round_down_exp, e_msg
    110             s_e_product_res = s_res * e_res
    111             request_result_ratio = s_e_product / s_e_product_res
    112             print 'Capture result s:', s_res, 'e:', e_res
    113             img = its.image.convert_capture_to_rgb_image(cap)
    114             its.image.write_image(img, '%s_mult=%3.2f.jpg' % (NAME, m))
    115             tile = its.image.get_image_patch(img, 0.45, 0.45, 0.1, 0.1)
    116             rgb_means = its.image.compute_image_means(tile)
    117             # Adjust for the difference between request and result
    118             r_means.append(rgb_means[0] * request_result_ratio)
    119             g_means.append(rgb_means[1] * request_result_ratio)
    120             b_means.append(rgb_means[2] * request_result_ratio)
    121             # do same in RAW space if possible
    122             if process_raw and debug:
    123                 aaw, aah = get_raw_active_array_size(props)
    124                 fmt_raw = {'format': 'rawStats',
    125                            'gridWidth': aaw/IMG_STATS_GRID,
    126                            'gridHeight': aah/IMG_STATS_GRID}
    127                 raw_cap = its.device.do_capture_with_latency(
    128                         cam, req, sync_latency, fmt_raw)
    129                 r, gr, gb, b = its.image.convert_capture_to_planes(
    130                         raw_cap, props)
    131                 raw_r_means.append(r[IMG_STATS_GRID/2, IMG_STATS_GRID/2]
    132                                    * request_result_ratio)
    133                 raw_gr_means.append(gr[IMG_STATS_GRID/2, IMG_STATS_GRID/2]
    134                                     * request_result_ratio)
    135                 raw_gb_means.append(gb[IMG_STATS_GRID/2, IMG_STATS_GRID/2]
    136                                     * request_result_ratio)
    137                 raw_b_means.append(b[IMG_STATS_GRID/2, IMG_STATS_GRID/2]
    138                                    * request_result_ratio)
    139             # Test 3 steps per 2x gain
    140             m *= pow(2, 1.0 / 3)
    141 
    142         # Allow more threshold for devices with wider exposure range
    143         if m >= 64.0:
    144             threshold_max_level_diff = THRESHOLD_MAX_LEVEL_DIFF_WIDE_RANGE
    145 
    146     # Draw plots
    147     pylab.figure('rgb data')
    148     pylab.plot(mults, r_means, 'ro-')
    149     pylab.plot(mults, g_means, 'go-')
    150     pylab.plot(mults, b_means, 'bo-')
    151     pylab.title(NAME + 'RGB Data')
    152     pylab.xlabel('Gain Multiplier')
    153     pylab.ylabel('Normalized RGB Plane Avg')
    154     pylab.ylim([0, 1])
    155     matplotlib.pyplot.savefig('%s_plot_means.png' % (NAME))
    156 
    157     if process_raw and debug:
    158         pylab.figure('raw data')
    159         pylab.plot(mults, raw_r_means, 'ro-', label='R')
    160         pylab.plot(mults, raw_gr_means, 'go-', label='GR')
    161         pylab.plot(mults, raw_gb_means, 'ko-', label='GB')
    162         pylab.plot(mults, raw_b_means, 'bo-', label='B')
    163         pylab.title(NAME + 'RAW Data')
    164         pylab.xlabel('Gain Multiplier')
    165         pylab.ylabel('Normalized RAW Plane Avg')
    166         pylab.ylim([0, 1])
    167         pylab.legend(numpoints=1)
    168         matplotlib.pyplot.savefig('%s_plot_raw_means.png' % (NAME))
    169 
    170     # Check for linearity. Verify sample pixel mean values are close to each
    171     # other. Also ensure that the images aren't clamped to 0 or 1
    172     # (which would make them look like flat lines).
    173     for chan in xrange(3):
    174         values = [r_means, g_means, b_means][chan]
    175         m, b = numpy.polyfit(mults, values, 1).tolist()
    176         max_val = max(values)
    177         min_val = min(values)
    178         max_diff = max_val - min_val
    179         print 'Channel %d line fit (y = mx+b): m = %f, b = %f' % (chan, m, b)
    180         print 'Channel max %f min %f diff %f' % (max_val, min_val, max_diff)
    181         assert max_diff < threshold_max_level_diff
    182         assert b > THRESHOLD_MIN_LEVEL and b < THRESHOLD_MAX_LEVEL
    183         for v in values:
    184             assert v > THRESHOLD_MIN_LEVEL and v < THRESHOLD_MAX_LEVEL
    185             assert abs(v - b) < THRESHOLD_MAX_OUTLIER_DIFF
    186     if process_raw and debug:
    187         for chan in xrange(4):
    188             values = [raw_r_means, raw_gr_means, raw_gb_means,
    189                       raw_b_means][chan]
    190             m, b = numpy.polyfit(mults, values, 1).tolist()
    191             max_val = max(values)
    192             min_val = min(values)
    193             max_diff = max_val - min_val
    194             print 'Channel %d line fit (y = mx+b): m = %f, b = %f' % (chan,
    195                                                                       m, b)
    196             print 'Channel max %f min %f diff %f' % (max_val, min_val, max_diff)
    197             assert max_diff < threshold_max_level_diff
    198             assert b > THRESHOLD_MIN_LEVEL and b < THRESHOLD_MAX_LEVEL
    199             for v in values:
    200                 assert v > THRESHOLD_MIN_LEVEL and v < THRESHOLD_MAX_LEVEL
    201                 assert abs(v - b) < THRESHOLD_MAX_OUTLIER_DIFF
    202 
    203 if __name__ == '__main__':
    204     main()
    205