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