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 shots with different tonemap curves.""" 15 16 17import logging 18import os.path 19from mobly import test_runner 20import numpy as np 21 22import its_base_test 23import camera_properties_utils 24import capture_request_utils 25import image_processing_utils 26import its_session_utils 27 28_MAX_DELTA_SAME = 0.03 # match number in test_burst_sameness_manual 29_MIN_DELTA_DIFF = 0.10 30_NAME = os.path.splitext(os.path.basename(__file__))[0] 31_NUM_CAPTURES = 3 32_NUM_FRAMES_PER_CAPTURE = 8 33_PATCH_H = 0.1 # center 10% 34_PATCH_W = 0.1 35_PATCH_X = 0.5 - _PATCH_W/2 36_PATCH_Y = 0.5 - _PATCH_H/2 37_RGB_G_CH = 1 38_TMAP_NO_DELTA_FRAMES = list(range(_NUM_CAPTURES-1)) + list( 39 range(_NUM_CAPTURES, 2*_NUM_CAPTURES-1)) 40 41 42def do_captures_and_extract_means( 43 cam, req, fmt, num_frames_per_cap, tonemap, log_path): 44 """Do captures, save image and extract means from center patch. 45 46 Args: 47 cam: camera object. 48 req: camera request. 49 fmt: capture format. 50 num_frames_per_cap: int; number of frames per capture 51 tonemap: string to determine 'linear' or 'default' tonemap. 52 log_path: location to save images. 53 54 Returns: 55 appended means list. 56 """ 57 green_means = [] 58 59 for i in range(_NUM_CAPTURES): 60 cap = cam.do_capture([req]*num_frames_per_cap, fmt) 61 img = image_processing_utils.convert_capture_to_rgb_image(cap[-1]) 62 image_processing_utils.write_image( 63 img, '%s_%s_%d.jpg' % (os.path.join(log_path, _NAME), tonemap, i)) 64 patch = image_processing_utils.get_image_patch( 65 img, _PATCH_X, _PATCH_Y, _PATCH_W, _PATCH_H) 66 rgb_means = image_processing_utils.compute_image_means(patch) 67 logging.debug('%s frame %d means: %s', tonemap, i, str(rgb_means)) 68 green_means.append(rgb_means[_RGB_G_CH]) # G, note python 2 version used R 69 return green_means 70 71 72class TonemapSequenceTest(its_base_test.ItsBaseTest): 73 """Tests a sequence of shots with different tonemap curves. 74 75 There should be _NUM_CAPTURES with a linear tonemap followed by a second set 76 of _NUM_CAPTURES with the default tonemap. 77 78 asserts the frames in each _NUM_CAPTURES bunch are similar 79 asserts the frames in the 2 _NUM_CAPTURES bunches are different by >10% 80 """ 81 82 def test_tonemap_sequence(self): 83 logging.debug('Starting %s', _NAME) 84 with its_session_utils.ItsSession( 85 device_id=self.dut.serial, 86 camera_id=self.camera_id, 87 hidden_physical_id=self.hidden_physical_id) as cam: 88 props = cam.get_camera_properties() 89 props = cam.override_with_hidden_physical_camera_props(props) 90 camera_properties_utils.skip_unless( 91 not camera_properties_utils.mono_camera(props) and 92 camera_properties_utils.linear_tonemap(props)) 93 log_path = self.log_path 94 95 # Load chart for scene 96 its_session_utils.load_scene( 97 cam, props, self.scene, self.tablet, self.chart_distance) 98 99 # define formats 100 largest_yuv = capture_request_utils.get_largest_yuv_format(props) 101 match_ar = (largest_yuv['width'], largest_yuv['height']) 102 fmt = capture_request_utils.get_smallest_yuv_format( 103 props, match_ar=match_ar) 104 means = [] 105 106 # set params based on per_frame_control & read_3a 107 manual_and_per_frame_control = ( 108 camera_properties_utils.per_frame_control(props) and 109 camera_properties_utils.read_3a(props)) 110 if manual_and_per_frame_control: 111 logging.debug('PER_FRAME_CONTROL supported.') 112 num_frames_per_cap = 1 113 sens, exp, _, _, f_dist = cam.do_3a(do_af=True, get_results=True) 114 else: 115 logging.debug('PER_FRAME_CONTROL not supported.') 116 num_frames_per_cap = _NUM_FRAMES_PER_CAPTURE 117 cam.do_3a() 118 119 for tonemap in ['linear', 'default']: 120 if tonemap == 'linear': 121 if manual_and_per_frame_control: 122 req = capture_request_utils.manual_capture_request( 123 sens, exp, f_dist, True, props) 124 else: 125 req = capture_request_utils.auto_capture_request( 126 linear_tonemap=True, props=props) 127 else: 128 if manual_and_per_frame_control: 129 req = capture_request_utils.manual_capture_request( 130 sens, exp, f_dist, False) 131 else: 132 req = capture_request_utils.auto_capture_request() 133 134 means.extend(do_captures_and_extract_means( 135 cam, req, fmt, num_frames_per_cap, tonemap, log_path)) 136 137 # Compute the delta between each consecutive frame pair 138 deltas = [np.fabs(means[i+1]-means[i]) for i in range(2*_NUM_CAPTURES-1)] 139 logging.debug('Deltas between consecutive frames: %s', str(deltas)) 140 141 # assert frames similar with same tonemap 142 if not all([deltas[i] < _MAX_DELTA_SAME for i in _TMAP_NO_DELTA_FRAMES]): 143 raise AssertionError('Captures too different with same tonemap! ' 144 f'deltas: {deltas}, ATOL: {_MAX_DELTA_SAME}') 145 146 # assert frames different with tonemap change 147 if deltas[_NUM_CAPTURES-1] <= _MIN_DELTA_DIFF: 148 raise AssertionError('Captures too similar with different tonemaps! ' 149 f'deltas: {deltas}, THRESH: {_MIN_DELTA_DIFF}') 150 151if __name__ == '__main__': 152 test_runner.main() 153