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
15import os.path
16import its.caps
17import its.device
18import its.image
19import its.objects
20import matplotlib.pyplot
21import mpl_toolkits.mplot3d  # Required for 3d plot to work
22import numpy
23
24
25def main():
26    """Test that valid data comes back in CaptureResult objects.
27    """
28    global NAME, auto_req, manual_req, w_map, h_map
29    global manual_tonemap, manual_transform, manual_gains, manual_region
30    global manual_exp_time, manual_sensitivity, manual_gains_ok
31
32    NAME = os.path.basename(__file__).split(".")[0]
33
34    with its.device.ItsSession() as cam:
35        props = cam.get_camera_properties()
36        its.caps.skip_unless(its.caps.manual_sensor(props) and
37                             its.caps.manual_post_proc(props) and
38                             its.caps.per_frame_control(props))
39
40        manual_tonemap = [0,0, 1,1]  # Linear
41        manual_transform = its.objects.float_to_rational(
42                [-1.5,-1.0,-0.5, 0.0,0.5,1.0, 1.5,2.0,3.0])
43        manual_gains = [1,1.5,2.0,3.0]
44        manual_region = [{"x":8,"y":8,"width":128,"height":128,"weight":1}]
45        manual_exp_time = min(props["android.sensor.info.exposureTimeRange"])
46        manual_sensitivity = min(props["android.sensor.info.sensitivityRange"])
47
48        # The camera HAL may not support different gains for two G channels.
49        manual_gains_ok = [[1,1.5,2.0,3.0],[1,1.5,1.5,3.0],[1,2.0,2.0,3.0]]
50
51        auto_req = its.objects.auto_capture_request()
52        auto_req["android.statistics.lensShadingMapMode"] = 1
53
54        manual_req = {
55                "android.control.mode": 0,
56                "android.control.aeMode": 0,
57                "android.control.awbMode": 0,
58                "android.control.afMode": 0,
59                "android.sensor.sensitivity": manual_sensitivity,
60                "android.sensor.exposureTime": manual_exp_time,
61                "android.colorCorrection.mode": 0,
62                "android.colorCorrection.transform": manual_transform,
63                "android.colorCorrection.gains": manual_gains,
64                "android.tonemap.mode": 0,
65                "android.tonemap.curve": {"red": manual_tonemap,
66                                          "green": manual_tonemap,
67                                          "blue": manual_tonemap},
68                "android.control.aeRegions": manual_region,
69                "android.control.afRegions": manual_region,
70                "android.control.awbRegions": manual_region,
71                "android.statistics.lensShadingMapMode": 1
72                }
73
74        sync_latency = its.caps.sync_latency(props)
75        print "Testing auto capture results"
76        lsc_map_auto = test_auto(cam, props, sync_latency)
77        print "Testing manual capture results"
78        test_manual(cam, lsc_map_auto, props, sync_latency)
79        print "Testing auto capture results again"
80        test_auto(cam, props, sync_latency)
81
82
83def is_close_float(n1, n2):
84    """A very loose definition for two floats being close to each other.
85
86    there may be different interpolation and rounding used to get the
87    two values, and all this test is looking at is whether there is
88    something obviously broken; it's not looking for a perfect match.
89
90    Args:
91        n1:     float 1
92        n2:     float 2
93    Returns:
94        Boolean
95    """
96    return abs(n1 - n2) < 0.05
97
98
99def is_close_rational(n1, n2):
100    return is_close_float(its.objects.rational_to_float(n1),
101                          its.objects.rational_to_float(n2))
102
103
104def draw_lsc_plot(w_map, h_map, lsc_map, name):
105    for ch in range(4):
106        fig = matplotlib.pyplot.figure()
107        ax = fig.gca(projection="3d")
108        xs = numpy.array([range(w_map)] * h_map).reshape(h_map, w_map)
109        ys = numpy.array([[i]*w_map for i in range(h_map)]).reshape(
110                h_map, w_map)
111        zs = numpy.array(lsc_map[ch::4]).reshape(h_map, w_map)
112        ax.plot_wireframe(xs, ys, zs)
113        matplotlib.pyplot.savefig("%s_plot_lsc_%s_ch%d.png"%(NAME, name, ch))
114
115
116def test_auto(cam, props, sync_latency):
117    # Get 3A lock first, so the auto values in the capture result are
118    # populated properly.
119    rect = [[0, 0, 1, 1, 1]]
120    mono_camera = its.caps.mono_camera(props)
121    cam.do_3a(rect, rect, rect, do_af=False, mono_camera=mono_camera)
122
123    cap = its.device.do_capture_with_latency(cam, auto_req, sync_latency)
124    cap_res = cap["metadata"]
125
126    gains = cap_res["android.colorCorrection.gains"]
127    transform = cap_res["android.colorCorrection.transform"]
128    exp_time = cap_res["android.sensor.exposureTime"]
129    lsc_obj = cap_res["android.statistics.lensShadingCorrectionMap"]
130    lsc_map = lsc_obj["map"]
131    w_map = lsc_obj["width"]
132    h_map = lsc_obj["height"]
133    ctrl_mode = cap_res["android.control.mode"]
134
135    print "Control mode:", ctrl_mode
136    print "Gains:", gains
137    print "Transform:", [its.objects.rational_to_float(t)
138                         for t in transform]
139    if props["android.control.maxRegionsAe"] > 0:
140        print "AE region:", cap_res["android.control.aeRegions"]
141    if props["android.control.maxRegionsAf"] > 0:
142        print "AF region:", cap_res["android.control.afRegions"]
143    if props["android.control.maxRegionsAwb"] > 0:
144        print "AWB region:", cap_res["android.control.awbRegions"]
145    print "LSC map:", w_map, h_map, lsc_map[:8]
146
147    assert(ctrl_mode == 1)
148
149    # Color correction gain and transform must be valid.
150    assert(len(gains) == 4)
151    assert(len(transform) == 9)
152    assert(all([g > 0 for g in gains]))
153    assert(all([t["denominator"] != 0 for t in transform]))
154
155    # Color correction should not match the manual settings.
156    assert(any([not is_close_float(gains[i], manual_gains[i])
157                for i in xrange(4)]))
158    assert(any([not is_close_rational(transform[i], manual_transform[i])
159                for i in xrange(9)]))
160
161    # Exposure time must be valid.
162    assert(exp_time > 0)
163
164    # Lens shading map must be valid.
165    assert(w_map > 0 and h_map > 0 and w_map * h_map * 4 == len(lsc_map))
166    assert(all([m >= 1 for m in lsc_map]))
167
168    draw_lsc_plot(w_map, h_map, lsc_map, "auto")
169
170    return lsc_map
171
172
173def test_manual(cam, lsc_map_auto, props, sync_latency):
174    cap = its.device.do_capture_with_latency(cam, manual_req, sync_latency)
175    cap_res = cap["metadata"]
176
177    gains = cap_res["android.colorCorrection.gains"]
178    transform = cap_res["android.colorCorrection.transform"]
179    curves = [cap_res["android.tonemap.curve"]["red"],
180              cap_res["android.tonemap.curve"]["green"],
181              cap_res["android.tonemap.curve"]["blue"]]
182    exp_time = cap_res["android.sensor.exposureTime"]
183    lsc_obj = cap_res["android.statistics.lensShadingCorrectionMap"]
184    lsc_map = lsc_obj["map"]
185    w_map = lsc_obj["width"]
186    h_map = lsc_obj["height"]
187    ctrl_mode = cap_res["android.control.mode"]
188
189    print "Control mode:", ctrl_mode
190    print "Gains:", gains
191    print "Transform:", [its.objects.rational_to_float(t)
192                         for t in transform]
193    print "Tonemap:", curves[0][1::16]
194    if props["android.control.maxRegionsAe"] > 0:
195        print "AE region:", cap_res["android.control.aeRegions"]
196    if props["android.control.maxRegionsAf"] > 0:
197        print "AF region:", cap_res["android.control.afRegions"]
198    if props["android.control.maxRegionsAwb"] > 0:
199        print "AWB region:", cap_res["android.control.awbRegions"]
200    print "LSC map:", w_map, h_map, lsc_map[:8]
201
202    assert(ctrl_mode == 0)
203
204    # Color correction gain and transform must be valid.
205    # Color correction gains and transform should be the same size and
206    # values as the manually set values.
207    assert(len(gains) == 4)
208    assert(len(transform) == 9)
209    assert( all([is_close_float(gains[i], manual_gains_ok[0][i])
210                 for i in xrange(4)]) or
211            all([is_close_float(gains[i], manual_gains_ok[1][i])
212                 for i in xrange(4)]) or
213            all([is_close_float(gains[i], manual_gains_ok[2][i])
214                 for i in xrange(4)]))
215    assert(all([is_close_rational(transform[i], manual_transform[i])
216                for i in xrange(9)]))
217
218    # Tonemap must be valid.
219    # The returned tonemap must be linear.
220    for c in curves:
221        assert(len(c) > 0)
222        assert(all([is_close_float(c[i], c[i+1])
223                    for i in xrange(0,len(c),2)]))
224
225    # Exposure time must be close to the requested exposure time.
226    assert(is_close_float(exp_time/1000000.0, manual_exp_time/1000000.0))
227
228    # Lens shading map must be valid.
229    assert(w_map > 0 and h_map > 0 and w_map * h_map * 4 == len(lsc_map))
230    assert(all([m >= 1 for m in lsc_map]))
231
232    draw_lsc_plot(w_map, h_map, lsc_map, "manual")
233
234
235if __name__ == "__main__":
236    main()
237
238