1# Copyright 2022 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 JPEG and YUV still capture images are pixel-wise matching.""" 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_TEST_REQUIRED_MPC = 33 30_THRESHOLD_MAX_RMS_DIFF_YUV_JPEG = 0.01 # YUV/JPEG bit exactness threshold 31_THRESHOLD_MAX_RMS_DIFF_USE_CASE = 0.1 # Catch swapped color channels 32_USE_CASE_PREVIEW = 1 33_USE_CASE_STILL_CAPTURE = 2 34_USE_CASE_VIDEO_RECORD = 3 35_USE_CASE_PREVIEW_VIDEO_STILL = 4 36_USE_CASE_VIDEO_CALL = 5 37_USE_CASE_NAME_MAP = { 38 _USE_CASE_PREVIEW: 'preview', 39 _USE_CASE_STILL_CAPTURE: 'still_capture', 40 _USE_CASE_VIDEO_RECORD: 'video_record', 41 _USE_CASE_PREVIEW_VIDEO_STILL: 'preview_video_still', 42 _USE_CASE_VIDEO_CALL: 'video_call' 43} 44 45 46class YuvJpegCaptureSamenessTest(its_base_test.ItsBaseTest): 47 """Test capturing a single frame as both YUV and JPEG outputs.""" 48 49 def test_yuv_jpeg_capture_sameness(self): 50 logging.debug('Starting %s', _NAME) 51 with its_session_utils.ItsSession( 52 device_id=self.dut.serial, 53 camera_id=self.camera_id, 54 hidden_physical_id=self.hidden_physical_id) as cam: 55 props = cam.get_camera_properties() 56 props = cam.override_with_hidden_physical_camera_props(props) 57 log_path = self.log_path 58 59 # check media performance class 60 should_run = camera_properties_utils.stream_use_case(props) 61 media_performance_class = its_session_utils.get_media_performance_class( 62 self.dut.serial) 63 if media_performance_class >= _TEST_REQUIRED_MPC and not should_run: 64 its_session_utils.raise_mpc_assertion_error( 65 _TEST_REQUIRED_MPC, _NAME, media_performance_class) 66 67 # check SKIP conditions 68 camera_properties_utils.skip_unless(should_run) 69 70 # Load chart for scene 71 its_session_utils.load_scene( 72 cam, props, self.scene, self.tablet, self.chart_distance) 73 74 # Find the maximum mandatory size supported by all use cases 75 display_size = cam.get_display_size() 76 max_camcorder_profile_size = cam.get_max_camcorder_profile_size( 77 self.camera_id) 78 size_bound = min([_MAX_IMG_SIZE, display_size, max_camcorder_profile_size], 79 key=lambda t: int(t[0])*int(t[1])) 80 81 logging.debug('display_size %s, max_camcorder_profile_size %s, size_bound %s', 82 display_size, max_camcorder_profile_size, size_bound) 83 w, h = capture_request_utils.get_available_output_sizes( 84 'yuv', props, max_size=size_bound)[0] 85 86 # Create requests 87 fmt_yuv = {'format': 'yuv', 'width': w, 'height': h, 88 'useCase': _USE_CASE_STILL_CAPTURE} 89 fmt_jpg = {'format': 'jpeg', 'width': w, 'height': h, 90 'useCase': _USE_CASE_STILL_CAPTURE} 91 logging.debug('YUV & JPEG stream width: %d, height: %d', w, h) 92 93 cam.do_3a() 94 req = capture_request_utils.auto_capture_request() 95 req['android.jpeg.quality'] = 100 96 97 cap_yuv, cap_jpg = cam.do_capture(req, [fmt_yuv, fmt_jpg]) 98 rgb_yuv = image_processing_utils.convert_capture_to_rgb_image( 99 cap_yuv, True) 100 file_stem = os.path.join(log_path, _NAME) 101 image_processing_utils.write_image(rgb_yuv, f'{file_stem}_yuv.jpg') 102 rgb_jpg = image_processing_utils.convert_capture_to_rgb_image( 103 cap_jpg, True) 104 image_processing_utils.write_image(rgb_jpg, f'{file_stem}_jpg.jpg') 105 106 rms_diff = image_processing_utils.compute_image_rms_difference_3d( 107 rgb_yuv, rgb_jpg) 108 msg = f'RMS diff: {rms_diff:.4f}' 109 logging.debug('%s', msg) 110 if rms_diff >= _THRESHOLD_MAX_RMS_DIFF_YUV_JPEG: 111 raise AssertionError(msg + f', TOL: {_THRESHOLD_MAX_RMS_DIFF_YUV_JPEG}') 112 113 # Create requests for all use cases, and make sure they are at least 114 # similar enough with the STILL_CAPTURE YUV. For example, the color 115 # channels must be valid. 116 num_tests = 0 117 num_fail = 0 118 for use_case in _USE_CASE_NAME_MAP: 119 num_tests += 1 120 cam.do_3a() 121 fmt_yuv_use_case = {'format': 'yuv', 'width': w, 'height': h, 122 'useCase': use_case} 123 cap_yuv_use_case = cam.do_capture(req, [fmt_yuv_use_case]) 124 rgb_yuv_use_case = image_processing_utils.convert_capture_to_rgb_image( 125 cap_yuv_use_case, True) 126 use_case_name = _USE_CASE_NAME_MAP[use_case] 127 image_processing_utils.write_image( 128 rgb_yuv_use_case, f'{file_stem}_yuv_{use_case_name}.jpg') 129 rms_diff = image_processing_utils.compute_image_rms_difference_3d( 130 rgb_yuv, rgb_yuv_use_case) 131 msg = f'RMS diff for single {use_case_name} use case & still capture YUV: {rms_diff:.4f}' 132 logging.debug('%s', msg) 133 if rms_diff >= _THRESHOLD_MAX_RMS_DIFF_USE_CASE: 134 logging.error(msg + f', TOL: {_THRESHOLD_MAX_RMS_DIFF_USE_CASE}') 135 num_fail += 1 136 137 if num_fail > 0: 138 raise AssertionError(f'Number of fails: {num_fail} / {num_tests}') 139 140if __name__ == '__main__': 141 test_runner.main() 142