• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright 2023 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"""Test the camera in-sensor zoom behavior."""
15
16import logging
17import os.path
18
19import camera_properties_utils
20import capture_request_utils
21import cv2
22import image_processing_utils
23import its_base_test
24import its_session_utils
25import opencv_processing_utils
26import zoom_capture_utils
27
28from mobly import test_runner
29import numpy as np
30
31_NAME = os.path.splitext(os.path.basename(__file__))[0]
32_NUM_STEPS = 10
33_THRESHOLD_MAX_RMS_DIFF_CROPPED_RAW_USE_CASE = 0.06
34
35
36class InSensorZoomTest(its_base_test.ItsBaseTest):
37
38  """Use case CROPPED_RAW: verify that CaptureResult.RAW_CROP_REGION matches cropped RAW image."""
39
40  def test_in_sensor_zoom(self):
41    with its_session_utils.ItsSession(
42        device_id=self.dut.serial,
43        camera_id=self.camera_id,
44        hidden_physical_id=self.hidden_physical_id) as cam:
45      logical_props = cam.get_camera_properties()
46      props = cam.override_with_hidden_physical_camera_props(logical_props)
47      name_with_log_path = os.path.join(self.log_path, _NAME)
48
49      # Skip the test if CROPPED_RAW is not present in stream use cases
50      camera_properties_utils.skip_unless(
51          camera_properties_utils.cropped_raw_stream_use_case(props))
52
53      # Load chart for scene
54      its_session_utils.load_scene(
55          cam, props, self.scene, self.tablet, self.chart_distance)
56
57      z_range = props['android.control.zoomRatioRange']
58      logging.debug('In sensor zoom: testing zoomRatioRange: %s', str(z_range))
59
60      z_min, z_max = float(z_range[0]), float(z_range[1])
61      camera_properties_utils.skip_unless(
62          z_max >= z_min * zoom_capture_utils.ZOOM_MIN_THRESH)
63      z_list = np.arange(z_min, z_max, float(z_max - z_min) / (_NUM_STEPS - 1))
64      z_list = np.append(z_list, z_max)
65
66      a = props['android.sensor.info.activeArraySize']
67      aw, ah = a['right'] - a['left'], a['bottom'] - a['top']
68
69      # Capture a RAW frame without any zoom
70      raw_size = capture_request_utils.get_available_output_sizes(
71          'raw', props)[0]
72      output_surfaces = [{'format': 'raw',
73                          'width': raw_size[0],
74                          'height': raw_size[1]}]
75      if self.hidden_physical_id:
76        output_surfaces[0].update({'physicalCamera': self.hidden_physical_id})
77      imgs = {}
78      cam.do_3a(out_surfaces=output_surfaces)
79      req = capture_request_utils.auto_capture_request()
80      req['android.statistics.lensShadingMapMode'] = (
81          image_processing_utils.LENS_SHADING_MAP_ON)
82      cap_raw_full = cam.do_capture(
83          req,
84          output_surfaces,
85          reuse_session=True)
86      rgb_full_img = image_processing_utils.convert_raw_capture_to_rgb_image(
87          cap_raw_full, props, 'raw', name_with_log_path)
88      image_processing_utils.write_image(
89          rgb_full_img, f'{name_with_log_path}_raw_full.jpg')
90      imgs['raw_full'] = rgb_full_img
91      output_surfaces[0].update(
92          {'useCase': its_session_utils.USE_CASE_CROPPED_RAW}
93      )
94      first_api_level = its_session_utils.get_first_api_level(self.dut.serial)
95      reuse_session = False
96      # Capture RAW images with different zoom ratios with stream use case
97      # CROPPED_RAW set
98      for _, z in enumerate(z_list):
99        req['android.control.zoomRatio'] = z
100        if first_api_level >= its_session_utils.ANDROID15_API_LEVEL:
101          cam.do_3a(out_surfaces=output_surfaces)
102          reuse_session = True
103        cap_zoomed_raw = cam.do_capture(
104            req,
105            output_surfaces,
106            reuse_session=reuse_session)
107        rgb_zoomed_raw = (
108            image_processing_utils.convert_raw_capture_to_rgb_image(
109                cap_zoomed_raw, props, 'raw', name_with_log_path))
110        # Dump zoomed in RAW image
111        img_name = f'{name_with_log_path}_zoomed_raw_{z:.2f}.jpg'
112        image_processing_utils.write_image(rgb_zoomed_raw, img_name)
113        logging.debug('Finding ArUco markers for zoom %f: size [%d x %d],'
114                      ' (min zoom %f)', z, cap_zoomed_raw['width'],
115                      cap_zoomed_raw['height'], z_list[0])
116        meta = cap_zoomed_raw['metadata']
117        result_raw_crop_region = meta['android.scaler.rawCropRegion']
118        rl = result_raw_crop_region['left']
119        rt = result_raw_crop_region['top']
120        # Make sure that scale factor for width and height scaling is the same.
121        rw = result_raw_crop_region['right'] - rl
122        rh = result_raw_crop_region['bottom'] - rt
123        logging.debug('RAW_CROP_REGION reported for zoom %f: [%d %d %d %d]',
124                      z, rl, rt, rw, rh)
125        inv_scale_factor = rw / aw
126        if aw / rw != ah / rh:
127          raise AssertionError('RAW_CROP_REGION width and height aspect ratio'
128                               f' != active array AR, region size: {rw} x {rh}'
129                               f' active array size: {aw} x {ah}')
130        # Find any ArUco marker in img
131        try:
132          opencv_processing_utils.find_aruco_markers(
133              image_processing_utils.convert_image_to_uint8(rgb_zoomed_raw),
134              f'{name_with_log_path}_zoomed_raw_{z:.2f}_ArUco.jpg',
135              aruco_marker_count=1
136          )
137        except AssertionError as e:
138          logging.debug('Could not find ArUco marker at zoom ratio %.2f: %s',
139                        z, e)
140          break
141
142        xnorm = rl / aw
143        ynorm = rt / ah
144        wnorm = rw / aw
145        hnorm = rh / ah
146        logging.debug('Image patch norm for zoom %.2f: [%.2f %.2f %.2f %.2f]',
147                      z, xnorm, ynorm, wnorm, hnorm)
148        # Crop the full FoV RAW to result_raw_crop_region
149        rgb_full_cropped = image_processing_utils.get_image_patch(
150            rgb_full_img, xnorm, ynorm, wnorm, hnorm)
151
152        # Downscale the zoomed-in RAW image returned by the camera sub-system
153        rgb_zoomed_downscale = cv2.resize(
154            rgb_zoomed_raw, None, fx=inv_scale_factor, fy=inv_scale_factor)
155
156        # Debug dump images being rms compared
157        img_name_downscaled = f'{name_with_log_path}_downscale_raw_{z:.2f}.jpg'
158        image_processing_utils.write_image(
159            rgb_zoomed_downscale, img_name_downscaled)
160
161        img_name_cropped = f'{name_with_log_path}_full_cropped_raw_{z:.2f}.jpg'
162        image_processing_utils.write_image(rgb_full_cropped, img_name_cropped)
163
164        rms_diff = image_processing_utils.compute_image_rms_difference_3d(
165            rgb_zoomed_downscale, rgb_full_cropped)
166        msg = f'RMS diff for CROPPED_RAW use case: {rms_diff:.4f}'
167        logging.debug('%s', msg)
168        if rms_diff >= _THRESHOLD_MAX_RMS_DIFF_CROPPED_RAW_USE_CASE:
169          raise AssertionError(
170              f'RMS diff {rms_diff:.4f} of downscaled cropped RAW & full, '
171              f'ATOL: {_THRESHOLD_MAX_RMS_DIFF_CROPPED_RAW_USE_CASE}')
172
173
174if __name__ == '__main__':
175  test_runner.main()
176