| # Copyright 2018 The Android Open Source Project |
| # |
| # Licensed under the Apache License, Version 2.0 (the "License"); |
| # you may not use this file except in compliance with the License. |
| # You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| # See the License for the specific language governing permissions and |
| # limitations under the License. |
| """CameraITS test for tonemap curve with sensor test pattern.""" |
| |
| import logging |
| import os |
| |
| from mobly import test_runner |
| import numpy as np |
| |
| import its_base_test |
| import camera_properties_utils |
| import capture_request_utils |
| import image_processing_utils |
| import its_session_utils |
| |
| |
| NAME = os.path.basename(__file__).split('.')[0] |
| COLOR_BAR_PATTERN = 2 # Note scene0/test_test_patterns must PASS |
| COLOR_BARS = ['WHITE', 'YELLOW', 'CYAN', 'GREEN', 'MAGENTA', 'RED', |
| 'BLUE', 'BLACK'] |
| N_BARS = len(COLOR_BARS) |
| COLOR_CHECKER = {'BLACK': [0, 0, 0], 'RED': [1, 0, 0], 'GREEN': [0, 1, 0], |
| 'BLUE': [0, 0, 1], 'MAGENTA': [1, 0, 1], 'CYAN': [0, 1, 1], |
| 'YELLOW': [1, 1, 0], 'WHITE': [1, 1, 1]} |
| DELTA = 0.0005 # crop on edge of color bars |
| RAW_TOL = 0.001 # 1 DN in [0:1] (1/(1023-64) |
| RGB_VAR_TOL = 0.0039 # 1/255 |
| RGB_MEAN_TOL = 0.1 |
| TONEMAP_MAX = 0.5 |
| YUV_H = 480 |
| YUV_W = 640 |
| # Normalized co-ordinates for the color bar patch. |
| Y_NORM = 0.0 |
| W_NORM = 1.0 / N_BARS - 2 * DELTA |
| H_NORM = 1.0 |
| |
| # Linear tonemap with maximum of 0.5 |
| LINEAR_TONEMAP = sum([[i/63.0, i/126.0] for i in range(64)], []) |
| |
| |
| def get_x_norm(num): |
| """Returns the normalized x co-ordinate for the title. |
| |
| Args: |
| num: int; position on color in the color bar. |
| |
| Returns: |
| normalized x co-ordinate. |
| """ |
| return float(num) / N_BARS + DELTA |
| |
| |
| def check_raw_pattern(img_raw): |
| """Checks for RAW capture matches color bar pattern. |
| |
| Args: |
| img_raw: RAW image |
| """ |
| logging.debug('Checking RAW/PATTERN match') |
| color_match = [] |
| for n in range(N_BARS): |
| logging.debug('patch: %d', n) |
| x_norm = get_x_norm(n) |
| logging.debug('x_norm: %.3f', x_norm) |
| raw_patch = image_processing_utils.get_image_patch(img_raw, x_norm, Y_NORM, |
| W_NORM, H_NORM) |
| raw_means = image_processing_utils.compute_image_means(raw_patch) |
| for color in COLOR_BARS: |
| if np.allclose(COLOR_CHECKER[color], raw_means, atol=RAW_TOL): |
| color_match.append(color) |
| logging.debug('%s', color) |
| assert set(color_match) == set(COLOR_BARS), 'RAW does not have all colors' |
| |
| |
| def check_yuv_vs_raw(img_raw, img_yuv): |
| """Checks for YUV vs RAW match in 8 patches. |
| |
| Check for correct values and color consistency |
| |
| Args: |
| img_raw: RAW image |
| img_yuv: YUV image |
| """ |
| logging.debug('Checking YUV/RAW match') |
| color_match_errs = [] |
| color_variance_errs = [] |
| for n in range(N_BARS): |
| x_norm = get_x_norm(n) |
| logging.debug('x_norm: %.3f', x_norm) |
| raw_patch = image_processing_utils.get_image_patch(img_raw, x_norm, Y_NORM, |
| W_NORM, H_NORM) |
| yuv_patch = image_processing_utils.get_image_patch(img_yuv, x_norm, Y_NORM, |
| W_NORM, H_NORM) |
| raw_means = np.array(image_processing_utils.compute_image_means(raw_patch)) |
| raw_vars = np.array( |
| image_processing_utils.compute_image_variances(raw_patch)) |
| yuv_means = np.array(image_processing_utils.compute_image_means(yuv_patch)) |
| yuv_means /= TONEMAP_MAX # Normalize to tonemap max |
| yuv_vars = np.array( |
| image_processing_utils.compute_image_variances(yuv_patch)) |
| if not np.allclose(raw_means, yuv_means, atol=RGB_MEAN_TOL): |
| color_match_errs.append( |
| 'RAW: %s, RGB(norm): %s, ATOL: %.2f' % |
| (str(raw_means), str(np.round(yuv_means, 3)), RGB_MEAN_TOL)) |
| if not np.allclose(raw_vars, yuv_vars, atol=RGB_VAR_TOL): |
| color_variance_errs.append('RAW: %s, RGB: %s, ATOL: %.4f' % |
| (str(raw_vars), str(yuv_vars), RGB_VAR_TOL)) |
| if color_match_errs: |
| logging.error('Color match errors:') |
| for err in color_match_errs: |
| logging.debug(err) |
| if color_variance_errs: |
| logging.error('Color variance errors:') |
| for err in color_variance_errs: |
| logging.error(err) |
| assert not color_match_errs, 'Color match errors.' |
| assert not color_variance_errs, 'Color variance errors.' |
| |
| |
| def test_tonemap_curve_impl(name, cam, props): |
| """Test tonemap curve with sensor test pattern. |
| |
| Args: |
| name: Path to save the captured image. |
| cam: An open device session. |
| props: Properties of cam. |
| """ |
| |
| avail_patterns = props['android.sensor.availableTestPatternModes'] |
| logging.debug('Available Patterns: %s', avail_patterns) |
| sens_min, _ = props['android.sensor.info.sensitivityRange'] |
| min_exposure = min(props['android.sensor.info.exposureTimeRange']) |
| |
| if COLOR_BAR_PATTERN in avail_patterns: |
| # RAW image |
| req_raw = capture_request_utils.manual_capture_request( |
| int(sens_min), min_exposure) |
| req_raw['android.sensor.testPatternMode'] = COLOR_BAR_PATTERN |
| fmt_raw = {'format': 'raw'} |
| cap_raw = cam.do_capture(req_raw, fmt_raw) |
| img_raw = image_processing_utils.convert_capture_to_rgb_image( |
| cap_raw, props=props) |
| |
| # Save RAW pattern |
| image_processing_utils.write_image( |
| img_raw, '%s_raw_%d.jpg' % (name, COLOR_BAR_PATTERN), True) |
| check_raw_pattern(img_raw) |
| |
| # YUV image |
| req_yuv = capture_request_utils.manual_capture_request( |
| int(sens_min), min_exposure) |
| req_yuv['android.sensor.testPatternMode'] = COLOR_BAR_PATTERN |
| req_yuv['android.distortionCorrection.mode'] = 0 |
| req_yuv['android.tonemap.mode'] = 0 |
| req_yuv['android.tonemap.curve'] = { |
| 'red': LINEAR_TONEMAP, |
| 'green': LINEAR_TONEMAP, |
| 'blue': LINEAR_TONEMAP |
| } |
| fmt_yuv = {'format': 'yuv', 'width': YUV_W, 'height': YUV_H} |
| cap_yuv = cam.do_capture(req_yuv, fmt_yuv) |
| img_yuv = image_processing_utils.convert_capture_to_rgb_image(cap_yuv, True) |
| |
| # Save YUV pattern |
| image_processing_utils.write_image( |
| img_yuv, '%s_yuv_%d.jpg' % (name, COLOR_BAR_PATTERN), True) |
| |
| # Check pattern for correctness |
| check_yuv_vs_raw(img_raw, img_yuv) |
| else: |
| logging.debug('Pattern not in android.sensor.availableTestPatternModes.') |
| assert 0 |
| |
| |
| class TonemapCurveTest(its_base_test.ItsBaseTest): |
| """Test conversion of test pattern from RAW to YUV with linear tonemap. |
| |
| Test makes use of android.sensor.testPatternMode 2 (COLOR_BARS). |
| """ |
| |
| def test_tonemap_curve(self): |
| logging.debug('Starting %s', NAME) |
| name = os.path.join(self.log_path, NAME) |
| with its_session_utils.ItsSession( |
| device_id=self.dut.serial, |
| camera_id=self.camera_id, |
| hidden_physical_id=self.hidden_physical_id) as cam: |
| props = cam.get_camera_properties() |
| camera_properties_utils.skip_unless( |
| camera_properties_utils.raw16(props) and |
| camera_properties_utils.manual_sensor(props) and |
| camera_properties_utils.per_frame_control(props) and |
| camera_properties_utils.manual_post_proc(props)) |
| |
| test_tonemap_curve_impl(name, cam, props) |
| |
| |
| if __name__ == '__main__': |
| test_runner.main() |