• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright 2014 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 RAW and YUV images are similar."""
15
16
17import logging
18import os.path
19from mobly import test_runner
20
21import its_base_test
22import camera_properties_utils
23import capture_request_utils
24import image_processing_utils
25import its_session_utils
26import opencv_processing_utils
27
28_MAX_IMG_SIZE = (1920, 1080)
29_NAME = os.path.splitext(os.path.basename(__file__))[0]
30_NUM_RAW_CHANNELS = 4  # r, gr, gb, b
31_PATCH_H = 0.1  # center 10%
32_PATCH_W = 0.1
33_PATCH_X = 0.5 - _PATCH_W/2
34_PATCH_Y = 0.5 - _PATCH_H/2
35_POST_RAW_BOOST_REF = 100  # numbers larger than 100 increase YUV brightness
36_THRESHOLD_MAX_RMS_DIFF = 0.035
37
38
39def convert_and_compare_captures(cap_raw, cap_yuv, props,
40                                 log_path_with_name, raw_fmt):
41  """Helper function to convert and compare RAW and YUV captures.
42
43  Args:
44   cap_raw: capture request object with RAW/RAW10/RAW12 format specified
45   cap_yuv: capture capture request object with YUV format specified
46   props: object from its_session_utils.get_camera_properties().
47   log_path_with_name: logging path where artifacts should be stored.
48   raw_fmt: string 'raw', 'raw10', or 'raw12' to include in file name
49
50  Returns:
51    string "PASS" if test passed, else message for AssertionError.
52  """
53  shading_mode = cap_raw['metadata']['android.shading.mode']
54
55  # YUV
56  img = image_processing_utils.convert_capture_to_rgb_image(cap_yuv)
57  image_processing_utils.write_image(
58      img, f'{log_path_with_name}_shading={shading_mode}_yuv.jpg', True)
59  patch = image_processing_utils.get_image_patch(
60      img, _PATCH_X, _PATCH_Y, _PATCH_W, _PATCH_H)
61  rgb_means_yuv = image_processing_utils.compute_image_means(patch)
62  logging.debug('%s YUV RGB means: %s', raw_fmt, rgb_means_yuv)
63
64  # RAW
65  img = image_processing_utils.convert_raw_capture_to_rgb_image(
66      cap_raw, props, raw_fmt, log_path_with_name
67  )
68  image_processing_utils.write_image(
69      img, f'{log_path_with_name}_shading={shading_mode}_{raw_fmt}.jpg', True)
70
71  # Shots are 1/2 x 1/2 smaller after conversion to RGB, but patch
72  # cropping is relative.
73  patch = image_processing_utils.get_image_patch(
74      img, _PATCH_X, _PATCH_Y, _PATCH_W, _PATCH_H)
75  rgb_means_raw = image_processing_utils.compute_image_means(patch)
76  logging.debug('%s RAW RGB means: %s', raw_fmt, rgb_means_raw)
77
78  # Compensate for postRawSensitivityBoost in YUV.
79  if cap_yuv['metadata'].get('android.control.postRawSensitivityBoost'):
80    boost = cap_yuv['metadata']['android.control.postRawSensitivityBoost']
81    logging.debug('postRawSensitivityBoost: %d', boost)
82    if boost != _POST_RAW_BOOST_REF:
83      rgb_means_raw = [m * boost / _POST_RAW_BOOST_REF for m in rgb_means_raw]
84      logging.debug('Post-boost %s RAW RGB means: %s', raw_fmt, rgb_means_raw)
85
86  rms_diff = image_processing_utils.compute_image_rms_difference_1d(
87      rgb_means_yuv, rgb_means_raw)
88  msg = f'{raw_fmt} diff: {rms_diff:.4f}'
89  # Log rms-diff, so that it can be written to the report log.
90  print(f'test_yuv_plus_{raw_fmt}_rms_diff: {rms_diff:.4f}')
91  logging.debug('%s', msg)
92  if rms_diff >= _THRESHOLD_MAX_RMS_DIFF:
93    return f'{msg}, spec: {_THRESHOLD_MAX_RMS_DIFF}'
94  else:
95    return 'PASS'
96
97
98class YuvPlusRawTest(its_base_test.ItsBaseTest):
99  """Test capturing a single frame as both YUV and various RAW formats.
100
101  Tests RAW, RAW10 and RAW12 as available.
102  """
103
104  def test_yuv_plus_raw(self):
105    failure_messages = []
106    with its_session_utils.ItsSession(
107        device_id=self.dut.serial,
108        camera_id=self.camera_id,
109        hidden_physical_id=self.hidden_physical_id) as cam:
110      props = cam.get_camera_properties()
111      logical_fov = float(cam.calc_camera_fov(props))
112      minimum_zoom_ratio = float(props['android.control.zoomRatioRange'][0])
113      props = cam.override_with_hidden_physical_camera_props(props)
114      physical_fov = float(cam.calc_camera_fov(props))
115      is_tele = physical_fov < opencv_processing_utils.FOV_THRESH_TELE
116      log_path = os.path.join(self.log_path, _NAME)
117
118      # check SKIP conditions
119      camera_properties_utils.skip_unless(
120          camera_properties_utils.raw_output(props) and
121          camera_properties_utils.linear_tonemap(props) and
122          not camera_properties_utils.mono_camera(props) and
123          not is_tele)
124
125      # Load chart for scene
126      its_session_utils.load_scene(
127          cam, props, self.scene, self.tablet,
128          its_session_utils.CHART_DISTANCE_NO_SCALING)
129
130      # determine compatible RAW formats
131      raw_formats = []
132      if camera_properties_utils.raw16(props):
133        raw_formats.append('raw')
134      else:
135        logging.debug('Skipping test_yuv_plus_raw')
136      if camera_properties_utils.raw10(props):
137        raw_formats.append('raw10')
138      else:
139        logging.debug('Skipping test_yuv_plus_raw10')
140      if camera_properties_utils.raw12(props):
141        raw_formats.append('raw12')
142      else:
143        logging.debug('Skipping test_yuv_plus_raw12')
144
145      for raw_fmt in raw_formats:
146        req = capture_request_utils.auto_capture_request(
147            linear_tonemap=True, props=props, do_af=False)
148        max_raw_size = capture_request_utils.get_available_output_sizes(
149            raw_fmt, props)[0]
150        if capture_request_utils.is_common_aspect_ratio(max_raw_size):
151          w, h = capture_request_utils.get_available_output_sizes(
152              'yuv', props, _MAX_IMG_SIZE, max_raw_size)[0]
153        else:
154          w, h = capture_request_utils.get_available_output_sizes(
155              'yuv', props, max_size=_MAX_IMG_SIZE)[0]
156        out_surfaces = [{'format': raw_fmt},
157                        {'format': 'yuv', 'width': w, 'height': h}]
158        cam.do_3a(do_af=False)
159        req['android.statistics.lensShadingMapMode'] = (
160            image_processing_utils.LENS_SHADING_MAP_ON)
161        # Override zoom ratio to min for UW camera to avoid cropping
162        logging.debug('Logical FOV: %.2f, Physical FOV: %.2f',
163                      logical_fov, physical_fov)
164        if logical_fov < physical_fov:
165          logging.debug('Overriding zoom ratio to min for UW camera')
166          req['android.control.zoomRatio'] = minimum_zoom_ratio
167        cap_raw, cap_yuv = cam.do_capture(req, out_surfaces)
168        msg = convert_and_compare_captures(cap_raw, cap_yuv, props,
169                                           log_path, raw_fmt)
170        if msg != 'PASS':
171          failure_messages.append(msg)
172
173      if failure_messages:
174        raise AssertionError('\n'.join(failure_messages))
175
176
177if __name__ == '__main__':
178  test_runner.main()
179
180