• 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 zoom_capture_utils
26
27from mobly import test_runner
28import numpy as np
29
30_NUM_STEPS = 10
31_ZOOM_MIN_THRESH = 2.0
32_THRESHOLD_MAX_RMS_DIFF_CROPPED_RAW_USE_CASE = 0.03
33_NAME = os.path.splitext(os.path.basename(__file__))[0]
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      props = cam.get_camera_properties()
46      props = cam.override_with_hidden_physical_camera_props(props)
47      name_with_log_path = os.path.join(self.log_path, _NAME)
48      # Skip the test if CROPPED_RAW is not present in stream use cases
49      camera_properties_utils.skip_unless(
50          camera_properties_utils.cropped_raw_stream_use_case(props))
51
52      # Load chart for scene
53      its_session_utils.load_scene(
54          cam, props, self.scene, self.tablet, self.chart_distance)
55
56      z_range = props['android.control.zoomRatioRange']
57      logging.debug('In sensor zoom: testing zoomRatioRange: %s', str(z_range))
58
59      z_min, z_max = float(z_range[0]), float(z_range[1])
60      camera_properties_utils.skip_unless(z_max >= z_min * _ZOOM_MIN_THRESH)
61      z_list = np.arange(z_min, z_max, float(z_max - z_min) / (_NUM_STEPS - 1))
62      z_list = np.append(z_list, z_max)
63
64      a = props['android.sensor.info.activeArraySize']
65      aw, ah = a['right'] - a['left'], a['bottom'] - a['top']
66
67      # Capture a RAW frame without any zoom
68      imgs = {}
69      cam.do_3a()
70      req = capture_request_utils.auto_capture_request()
71      cap_raw_full = cam.do_capture(req, cam.CAP_RAW)
72      rgb_full_img = image_processing_utils.convert_capture_to_rgb_image(
73          cap_raw_full, props=props)
74      image_processing_utils.write_image(
75          rgb_full_img, f'{name_with_log_path}_raw_full.jpg')
76      imgs['raw_full'] = rgb_full_img
77
78      # Capture RAW images with different zoom ratios with stream use case
79      # CROPPED_RAW set
80      for _, z in enumerate(z_list):
81        req['android.control.zoomRatio'] = z
82        cap_zoomed_raw = cam.do_capture(req, cam.CAP_CROPPED_RAW)
83        rgb_zoomed_raw = image_processing_utils.convert_capture_to_rgb_image(
84            cap_zoomed_raw, props=props)
85        # Dump zoomed in RAW image
86        img_name = f'{name_with_log_path}_zoomed_raw_{z:.2f}.jpg'
87        image_processing_utils.write_image(rgb_zoomed_raw, img_name)
88        size_raw = [cap_zoomed_raw['width'], cap_zoomed_raw['height']]
89        logging.debug('Finding center circle for zoom %f: size [%d x %d],'
90                      ' (min zoom %f)', z, cap_zoomed_raw['width'],
91                      cap_zoomed_raw['height'], z_list[0])
92        meta = cap_zoomed_raw['metadata']
93        result_raw_crop_region = meta['android.scaler.rawCropRegion']
94        rl = result_raw_crop_region['left']
95        rt = result_raw_crop_region['top']
96        # Make sure that scale factor for width and height scaling is the same.
97        rw = result_raw_crop_region['right'] - rl
98        rh = result_raw_crop_region['bottom'] - rt
99        logging.debug('RAW_CROP_REGION reported for zoom %f: [%d %d %d %d]',
100                      z, rl, rt, rw, rh)
101        # Effective zoom ratio. May not be == z since its possible the HAL
102        # wasn't able to crop RAW.
103        effective_zoom_ratio = aw / rw
104        inv_scale_factor = rw / aw
105        if aw / rw != ah / rh:
106          raise AssertionError('RAW_CROP_REGION width and height aspect ratio'
107                               f' != active array AR, region size: {rw} x {rh} '
108                               f' active array size: {aw} x {ah}')
109       # Find the center circle in img
110        circle = zoom_capture_utils.get_center_circle(
111            rgb_zoomed_raw, img_name, size_raw, effective_zoom_ratio,
112            z_list[0], debug=True)
113        # Zoom is too large to find center circle, break out
114        if circle is None:
115          break
116
117        xnorm = rl / aw
118        ynorm = rt / ah
119        wnorm = rw / aw
120        hnorm = rh / ah
121        logging.debug('Image patch norm for zoom %.2f: [%.2f %.2f %.2f %.2f]',
122                      z, xnorm, ynorm, wnorm, hnorm)
123        # Crop the full FoV RAW to result_raw_crop_region
124        rgb_full_cropped = image_processing_utils.get_image_patch(
125            rgb_full_img, xnorm, ynorm, wnorm, hnorm)
126
127        # Downscale the zoomed-in RAW image returned by the camera sub-system
128        rgb_zoomed_downscale = cv2.resize(
129            rgb_zoomed_raw, None, fx=inv_scale_factor, fy=inv_scale_factor)
130
131        # Debug dump images being rms compared
132        img_name_downscaled = f'{name_with_log_path}_downscale_raw_{z:.2f}.jpg'
133        image_processing_utils.write_image(
134            rgb_zoomed_downscale, img_name_downscaled)
135
136        img_name_cropped = f'{name_with_log_path}_full_cropped_raw_{z:.2f}.jpg'
137        image_processing_utils.write_image(rgb_full_cropped, img_name_cropped)
138
139        rms_diff = image_processing_utils.compute_image_rms_difference_3d(
140            rgb_zoomed_downscale, rgb_full_cropped)
141        msg = f'RMS diff for CROPPED_RAW use case: {rms_diff:.4f}'
142        logging.debug('%s', msg)
143        if rms_diff >= _THRESHOLD_MAX_RMS_DIFF_CROPPED_RAW_USE_CASE:
144          raise AssertionError(f'{_NAME} failed! test_log.DEBUG has errors')
145
146
147if __name__ == '__main__':
148  test_runner.main()
149