1# Copyright 2018 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
15import os.path
16
17import its.caps
18import its.device
19import its.image
20import its.objects
21
22import matplotlib
23from matplotlib import pylab
24import numpy as np
25
26AE_STATE_CONVERGED = 2
27CONTROL_AE_STATE_FLASH_REQUIRED = 4
28NAME = os.path.basename(__file__).split('.')[0]
29NUM_CAPTURE = 30
30VALID_STABLE_LUMA_MIN = 0.1
31VALID_STABLE_LUMA_MAX = 0.9
32
33
34def is_awb_af_stable(prev_cap, cap):
35    awb_gains_0 = prev_cap['metadata']['android.colorCorrection.gains']
36    awb_gains_1 = cap['metadata']['android.colorCorrection.gains']
37    ccm_0 = prev_cap['metadata']['android.colorCorrection.transform']
38    ccm_1 = cap['metadata']['android.colorCorrection.transform']
39    focus_distance_0 = prev_cap['metadata']['android.lens.focusDistance']
40    focus_distance_1 = cap['metadata']['android.lens.focusDistance']
41
42    return (np.allclose(awb_gains_0, awb_gains_1, rtol=0.01) and
43            ccm_0 == ccm_1 and
44            np.isclose(focus_distance_0, focus_distance_1, rtol=0.01))
45
46
47def main():
48    """Tests PER_FRAME_CONTROL properties for auto capture requests.
49
50    If debug is required, MANUAL_POSTPROCESSING capability is implied
51    since its.caps.read_3a is valid for test. Debug can performed with
52    a defined tonemap curve:
53    req['android.tonemap.mode'] = 0
54    gamma = sum([[i/63.0,math.pow(i/63.0,1/2.2)] for i in xrange(64)],[])
55    req['android.tonemap.curve'] = {
56            'red': gamma, 'green': gamma, 'blue': gamma}
57    """
58
59    with its.device.ItsSession() as cam:
60        props = cam.get_camera_properties()
61        its.caps.skip_unless(its.caps.per_frame_control(props) and
62                             its.caps.read_3a(props))
63
64        debug = its.caps.debug_mode()
65        largest_yuv = its.objects.get_largest_yuv_format(props)
66        if debug:
67            fmt = largest_yuv
68        else:
69            match_ar = (largest_yuv['width'], largest_yuv['height'])
70            fmt = its.objects.get_smallest_yuv_format(props, match_ar=match_ar)
71
72        req = its.objects.auto_capture_request()
73        caps = cam.do_capture([req]*NUM_CAPTURE, fmt)
74
75        total_gains = []
76        lumas = []
77        ae_states = []
78        for i, cap in enumerate(caps):
79            print '=========== frame %d ==========' % i
80            y = its.image.convert_capture_to_planes(cap)[0]
81            tile = its.image.get_image_patch(y, 0.45, 0.45, 0.1, 0.1)
82            luma = its.image.compute_image_means(tile)[0]
83
84            ae_state = cap['metadata']['android.control.aeState']
85            iso = cap['metadata']['android.sensor.sensitivity']
86            isp_gain = cap['metadata']['android.control.postRawSensitivityBoost']
87            exp_time = cap['metadata']['android.sensor.exposureTime']
88            total_gain = iso*isp_gain/100.0*exp_time/1000000.0
89            awb_state = cap['metadata']['android.control.awbState']
90            awb_gains = cap['metadata']['android.colorCorrection.gains']
91            ccm = cap['metadata']['android.colorCorrection.transform']
92            focus_distance = cap['metadata']['android.lens.focusDistance']
93
94            # Convert CCM from rational to float, as numpy arrays.
95            awb_ccm = np.array(its.objects.rational_to_float(ccm)).reshape(3, 3)
96
97            print 'AE: %d ISO: %d ISP_sen: %d exp(ms): %d tot_gain: %f' % (
98                    ae_state, iso, isp_gain, exp_time, total_gain),
99            print 'luma: %f' % luma
100            print 'fd: %f' % focus_distance
101            print 'AWB: %d, AWB gains: %s\n AWB matrix: %s' % (
102                    awb_state, str(awb_gains), str(awb_ccm))
103            print 'Tonemap curve:', cap['metadata']['android.tonemap.curve']
104
105            lumas.append(luma)
106            total_gains.append(total_gain)
107            ae_states.append(ae_state)
108            img = its.image.convert_capture_to_rgb_image(cap)
109            its.image.write_image(img, '%s_frame_%d.jpg'% (NAME, i))
110
111        norm_gains = [x / max(total_gains) * max(lumas) for x in total_gains]
112        pylab.plot(range(len(lumas)), lumas, '-g.',
113                   label='Center patch brightness')
114        pylab.plot(range(len(norm_gains)), norm_gains, '-r.',
115                   label='Metadata AE setting product')
116        pylab.title(NAME)
117        pylab.xlabel('frame index')
118        pylab.legend()
119        matplotlib.pyplot.savefig('%s_plot.png' % (NAME))
120
121        for i in range(1, len(caps)):
122            if is_awb_af_stable(caps[i-1], caps[i]):
123                prev_total_gain = total_gains[i-1]
124                total_gain = total_gains[i]
125                delta_gain = total_gain - prev_total_gain
126                prev_luma = lumas[i-1]
127                luma = lumas[i]
128                delta_luma = luma - prev_luma
129                # luma and total_gain should change in same direction
130                msg = 'Frame %d to frame %d:' % (i-1, i)
131                msg += ' metadata gain %f->%f (%s), luma %f->%f (%s)' % (
132                        prev_total_gain, total_gain,
133                        'increasing' if delta_gain > 0.0 else 'decreasing',
134                        prev_luma, luma,
135                        'increasing' if delta_luma > 0.0 else 'decreasing')
136                assert delta_gain * delta_luma >= 0.0, msg
137            else:
138                print 'Frame %d->%d AWB/AF changed' % (i-1, i)
139
140        for i in range(len(lumas)):
141            luma = lumas[i]
142            ae_state = ae_states[i]
143            if (ae_state == AE_STATE_CONVERGED or
144                        ae_state == CONTROL_AE_STATE_FLASH_REQUIRED):
145                msg = 'Frame %d AE converged luma %f. valid range: (%f, %f)' % (
146                        i, luma, VALID_STABLE_LUMA_MIN, VALID_STABLE_LUMA_MAX)
147                assert VALID_STABLE_LUMA_MIN < luma < VALID_STABLE_LUMA_MAX, msg
148
149if __name__ == '__main__':
150    main()
151