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