blob: 63ddbdd13031af5ab0ff639391c2399d73503fa6 [file] [log] [blame]
Will Guedes0f11cfb2018-04-09 08:47:31 -05001# 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 its.caps
16from its.cv2image import get_angle
17import its.device
18import its.image
19import its.objects
20import its.target
21
22import cv2
23import matplotlib
24from matplotlib import pylab
25import numpy
26import os
27
28ANGLE_MASK = 10 # degrees
Will Guedes0c535312018-07-12 09:11:02 -050029ANGULAR_DIFF_THRESHOLD = 10 # degrees
30ANGULAR_MOVEMENT_THRESHOLD = 35 # degrees
Will Guedes0f11cfb2018-04-09 08:47:31 -050031NAME = os.path.basename(__file__).split(".")[0]
32NUM_CAPTURES = 100
33W = 640
34H = 480
Will Guedes0c535312018-07-12 09:11:02 -050035CHART_DISTANCE = 25 # cm
36CM_TO_M = 1/100.0
Will Guedes0f11cfb2018-04-09 08:47:31 -050037
38
39def _check_available_capabilities(props):
40 """Returns True if all required test capabilities are present."""
41 return all([
42 its.caps.compute_target_exposure(props),
43 its.caps.per_frame_control(props),
44 its.caps.logical_multi_camera(props),
45 its.caps.raw16(props),
46 its.caps.manual_sensor(props),
47 its.caps.sensor_fusion(props)])
48
49
50def _assert_camera_movement(frame_pairs_angles):
51 """Assert the angles between each frame pair are sufficiently different.
52
53 Different angles is an indication of camera movement.
54 """
55 angles = [i for i, j in frame_pairs_angles]
56 max_angle = numpy.amax(angles)
57 min_angle = numpy.amin(angles)
58 emsg = "Not enough phone movement!\n"
59 emsg += "min angle: %.2f, max angle: %.2f deg, THRESH: %d deg" % (
60 min_angle, max_angle, ANGULAR_MOVEMENT_THRESHOLD)
61 assert max_angle - min_angle > ANGULAR_MOVEMENT_THRESHOLD, emsg
62
63
64def _assert_angular_difference(angle_1, angle_2):
65 """Assert angular difference is within threshold."""
66 diff = abs(angle_2 - angle_1)
67
68 # Assert difference is less than threshold
69 emsg = "Diff between frame pair: %.1f. Threshold: %d deg." % (
70 diff, ANGULAR_DIFF_THRESHOLD)
71 assert diff < ANGULAR_DIFF_THRESHOLD, emsg
72
73
74def _mask_angles_near_extremes(frame_pairs_angles):
75 """Mask out the data near the top and bottom of angle range."""
76 masked_pairs_angles = [[i, j] for i, j in frame_pairs_angles
Will Guedes0c535312018-07-12 09:11:02 -050077 if ANGLE_MASK <= abs(i) <= 90-ANGLE_MASK and
78 ANGLE_MASK <= abs(j) <= 90-ANGLE_MASK]
Will Guedes0f11cfb2018-04-09 08:47:31 -050079 return masked_pairs_angles
80
81
82def _plot_frame_pairs_angles(frame_pairs_angles, ids):
83 """Plot the extracted angles."""
84 matplotlib.pyplot.figure("Camera Rotation Angle")
85 cam0_angles = [i for i, j in frame_pairs_angles]
86 cam1_angles = [j for i, j in frame_pairs_angles]
87 pylab.plot(range(len(cam0_angles)), cam0_angles, "r", label="%s" % ids[0])
88 pylab.plot(range(len(cam1_angles)), cam1_angles, "g", label="%s" % ids[1])
89 pylab.legend()
90 pylab.xlabel("Camera frame number")
91 pylab.ylabel("Rotation angle (degrees)")
92 matplotlib.pyplot.savefig("%s_angles_plot.png" % (NAME))
93
94 matplotlib.pyplot.figure("Angle Diffs")
95 angle_diffs = [j-i for i, j in frame_pairs_angles]
96 pylab.plot(range(len(angle_diffs)), angle_diffs, "b",
97 label="cam%s-%s" % (ids[1], ids[0]))
98 pylab.legend()
99 pylab.xlabel("Camera frame number")
100 pylab.ylabel("Rotation angle difference (degrees)")
101 matplotlib.pyplot.savefig("%s_angle_diffs_plot.png" % (NAME))
102
103def _collect_data():
104 """Returns list of pair of gray frames and camera ids used for captures."""
105 yuv_sizes = {}
106 with its.device.ItsSession() as cam:
107 props = cam.get_camera_properties()
108
109 # If capabilities not present, skip.
110 its.caps.skip_unless(_check_available_capabilities(props))
111
112 # Determine return parameters
113 debug = its.caps.debug_mode()
114 ids = its.caps.logical_multi_camera_physical_ids(props)
115
116 # Define capture request
Will Guedes0c535312018-07-12 09:11:02 -0500117 s, e, _, _, _ = cam.do_3a(get_results=True, do_af=False)
118 req = its.objects.manual_capture_request(s, e)
119 req["android.lens.focusDistance"] = 1 / (CHART_DISTANCE * CM_TO_M)
Will Guedes0f11cfb2018-04-09 08:47:31 -0500120
121 # capture YUVs
122 out_surfaces = [{"format": "yuv", "width": W, "height": H,
123 "physicalCamera": ids[0]},
124 {"format": "yuv", "width": W, "height": H,
125 "physicalCamera": ids[1]}]
126
127 capture_1_list, capture_2_list = cam.do_capture(
128 [req]*NUM_CAPTURES, out_surfaces)
129
130 # Create list of capture pairs. [[cap1A, cap1B], [cap2A, cap2B], ...]
131 frame_pairs = zip(capture_1_list, capture_2_list)
132
133 # Convert captures to grayscale
134 frame_pairs_gray = [
135 [
136 cv2.cvtColor(its.image.convert_capture_to_rgb_image(f, props=props), cv2.COLOR_RGB2GRAY) for f in pair
137 ] for pair in frame_pairs]
138
139 # Save images for debugging
140 if debug:
141 for i, imgs in enumerate(frame_pairs_gray):
142 for j in [0, 1]:
143 file_name = "%s_%s_%03d.png" % (NAME, ids[j], i)
144 cv2.imwrite(file_name, imgs[j]*255)
145
146 return frame_pairs_gray, ids
147
148def main():
149 """Test frame timestamps captured by logical camera are within 10ms."""
150 frame_pairs_gray, ids = _collect_data()
151
152 # Compute angles in frame pairs
153 frame_pairs_angles = [
154 [get_angle(p[0]), get_angle(p[1])] for p in frame_pairs_gray]
155
156 # Remove frames where not enough squares were detected.
157 filtered_pairs_angles = []
158 for angle_1, angle_2 in frame_pairs_angles:
159 if angle_1 == None or angle_2 == None:
160 continue
161 filtered_pairs_angles.append([angle_1, angle_2])
162
163 print 'Using {} image pairs to compute angular difference.'.format(
164 len(filtered_pairs_angles))
165
166 assert len(filtered_pairs_angles) > 20, (
167 "Unable to identify enough frames with detected squares.")
168
169 # Mask out data near 90 degrees.
170 # The chessboard angles we compute go from 0 to 89. Meaning,
171 # 90 degrees equals to 0 degrees.
172 # In order to avoid this jump, we ignore any frames at these extremeties.
173 masked_pairs_angles = _mask_angles_near_extremes(filtered_pairs_angles)
174
175 # Plot angles and differences
176 _plot_frame_pairs_angles(filtered_pairs_angles, ids)
177
178 # Ensure camera moved
179 _assert_camera_movement(filtered_pairs_angles)
180
181 # Ensure angle between images from each camera does not change appreciably
182 for cam_1_angle, cam_2_angle in masked_pairs_angles:
183 _assert_angular_difference(cam_1_angle, cam_2_angle)
184
185if __name__ == "__main__":
186 main()