• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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"""Verifies sub-cameras have similar RGB values for gray patch."""
15
16
17import logging
18import os.path
19
20from mobly import test_runner
21import numpy as np
22
23import its_base_test
24import camera_properties_utils
25import capture_request_utils
26import image_processing_utils
27import its_session_utils
28
29_NAME = os.path.splitext(os.path.basename(__file__))[0]
30_PATCH_H = 0.0625  # 1/16 x 1/16 in center of image
31_PATCH_W = 0.0625
32_PATCH_X = 0.5 - _PATCH_W/2
33_PATCH_Y = 0.5 - _PATCH_H/2
34_THRESH_DIFF = 0.06
35_THRESH_GAIN = 0.1
36_THRESH_EXP = 0.05
37
38
39class MultiCameraMatchTest(its_base_test.ItsBaseTest):
40  """Test both cameras give similar RGB values for gray patch.
41
42  This test uses android.lens.info.availableFocalLengths to determine
43  subcameras. The test will take images of the gray chart for each cameras,
44  crop the center patch, and compare the Y (of YUV) means of the two images.
45  Y means must be within _THRESH_DIFF for the test to pass.
46
47  Cameras that use android.control.zoomRatioRange will have only 1 focal
48  length and will need separate test.
49  """
50
51  def test_multi_camera_match(self):
52    logging.debug('Starting %s', _NAME)
53    yuv_sizes = {}
54    with its_session_utils.ItsSession(
55        device_id=self.dut.serial,
56        camera_id=self.camera_id,
57        hidden_physical_id=self.hidden_physical_id) as cam:
58      props = cam.get_camera_properties()
59      props = cam.override_with_hidden_physical_camera_props(props)
60      log_path = self.log_path
61
62      # check SKIP conditions
63      camera_properties_utils.skip_unless(
64          camera_properties_utils.per_frame_control(props) and
65          camera_properties_utils.logical_multi_camera(props))
66
67      # Load chart for scene
68      its_session_utils.load_scene(
69          cam, props, self.scene, self.tablet, self.chart_distance)
70
71      ids = camera_properties_utils.logical_multi_camera_physical_ids(props)
72      for i in ids:
73        physical_props = cam.get_camera_properties_by_id(i)
74        camera_properties_utils.skip_unless(
75            not camera_properties_utils.mono_camera(physical_props) and
76            camera_properties_utils.backward_compatible(physical_props))
77        yuv_sizes[i] = capture_request_utils.get_available_output_sizes(
78            'yuv', physical_props)
79        if i == ids[0]:  # get_available_output_sizes returns sorted list
80          yuv_match_sizes = yuv_sizes[i]
81        else:
82          yuv_match_sizes = list(
83              set(yuv_sizes[i]).intersection(yuv_match_sizes))
84
85      # find matched size for captures
86      yuv_match_sizes.sort()
87      w = yuv_match_sizes[-1][0]
88      h = yuv_match_sizes[-1][1]
89      logging.debug('Matched YUV size: (%d, %d)', w, h)
90
91      # do 3a and create requests
92      cam.do_3a()
93      reqs = []
94      avail_fls = sorted(props['android.lens.info.availableFocalLengths'],
95                         reverse=True)
96      # SKIP test if only 1 focal length
97      camera_properties_utils.skip_unless(len(avail_fls) > 1)
98
99      for i, fl in enumerate(avail_fls):
100        reqs.append(capture_request_utils.auto_capture_request())
101        reqs[i]['android.lens.focalLength'] = fl
102        if i > 0:
103          # Calculate the active sensor region for a non-cropped image
104          zoom = avail_fls[0] / fl
105          aa = props['android.sensor.info.activeArraySize']
106          aa_w, aa_h = aa['right'] - aa['left'], aa['bottom'] - aa['top']
107
108          # Calculate a center crop region.
109          assert zoom >= 1
110          crop_w = aa_w // zoom
111          crop_h = aa_h // zoom
112          crop_region = {'left': aa_w // 2 - crop_w // 2,
113                         'top': aa_h // 2 - crop_h // 2,
114                         'right': aa_w // 2 + crop_w // 2,
115                         'bottom': aa_h // 2 + crop_h // 2}
116          reqs[i]['android.scaler.cropRegion'] = crop_region
117
118      # capture YUVs
119      y_means = {}
120      e_msg = ''
121      fmt = [{'format': 'yuv', 'width': w, 'height': h}]
122      caps = cam.do_capture(reqs, fmt)
123      for i, fl in enumerate(avail_fls):
124        img = image_processing_utils.convert_capture_to_rgb_image(
125            caps[i], props=props)
126        image_processing_utils.write_image(img, '%s_yuv_fl=%s.jpg' % (
127            os.path.join(log_path, _NAME), fl))
128        y, _, _ = image_processing_utils.convert_capture_to_planes(
129            caps[i], props=props)
130        y_mean = image_processing_utils.compute_image_means(
131            image_processing_utils.get_image_patch(
132                y, _PATCH_X, _PATCH_Y, _PATCH_W, _PATCH_H))[0]
133        msg = 'y[%s]: %.3f, ' % (fl, y_mean)
134        logging.debug(msg)
135        e_msg += msg
136        y_means[fl] = y_mean
137
138      # compare Y means
139      e_msg += 'TOL=%.5f' % _THRESH_DIFF
140      assert np.isclose(max(y_means.values()), min(y_means.values()),
141                        rtol=_THRESH_DIFF), e_msg
142
143if __name__ == '__main__':
144  test_runner.main()
145