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