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