• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright 2018 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"""CameraITS test for tonemap curve with sensor test pattern."""
15
16import logging
17import os
18
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
29NAME = os.path.basename(__file__).split('.')[0]
30COLOR_BAR_PATTERN = 2  # Note scene0/test_test_patterns must PASS
31COLOR_BARS = ['WHITE', 'YELLOW', 'CYAN', 'GREEN', 'MAGENTA', 'RED',
32              'BLUE', 'BLACK']
33N_BARS = len(COLOR_BARS)
34COLOR_CHECKER = {'BLACK': [0, 0, 0], 'RED': [1, 0, 0], 'GREEN': [0, 1, 0],
35                 'BLUE': [0, 0, 1], 'MAGENTA': [1, 0, 1], 'CYAN': [0, 1, 1],
36                 'YELLOW': [1, 1, 0], 'WHITE': [1, 1, 1]}
37DELTA = 0.005  # crop on each edge of color bars
38RAW_TOL = 0.001  # 1 DN in [0:1] (1/(1023-64)
39RGB_VAR_TOL = 0.0039  # 1/255
40RGB_MEAN_TOL = 0.1
41TONEMAP_MAX = 0.5
42YUV_H = 480
43YUV_W = 640
44# Normalized co-ordinates for the color bar patch.
45Y_NORM = 0.0
46W_NORM = 1.0 / N_BARS - 2 * DELTA
47H_NORM = 1.0
48
49# Linear tonemap with maximum of 0.5
50LINEAR_TONEMAP = sum([[i/63.0, i/126.0] for i in range(64)], [])
51
52
53def get_yuv_patch_coordinates(num, w_orig, w_crop):
54  """Returns the normalized x co-ordinate for the title.
55
56  Args:
57   num: int; position on color in the color bar.
58   w_orig: float; original RAW image W
59   w_crop: float; cropped RAW image W
60
61  Returns:
62    normalized x, w values for color patch.
63  """
64  if w_crop == w_orig:  # uncropped image
65    x_norm = num / N_BARS + DELTA
66    w_norm = 1 / N_BARS - 2 * DELTA
67    logging.debug('x_norm: %.5f, w_norm: %.5f', x_norm, w_norm)
68  elif w_crop < w_orig:  # adjust patch width to match vertical RAW crop
69    w_delta_edge = (w_orig - w_crop) / 2
70    w_bar_orig = w_orig / N_BARS
71    if num == 0:  # left-most bar
72      x_norm = DELTA
73      w_norm = (w_bar_orig - w_delta_edge) / w_crop - 2 * DELTA
74    elif num == N_BARS:  # right-most bar
75      x_norm = (w_bar_orig * num - w_delta_edge) / w_crop + DELTA
76      w_norm = (w_bar_orig - w_delta_edge) / w_crop - 2 * DELTA
77    else:  # middle bars
78      x_norm = (w_bar_orig * num - w_delta_edge) / w_crop + DELTA
79      w_norm = w_bar_orig / w_crop - 2 * DELTA
80    logging.debug('x_norm: %.5f, w_norm: %.5f (crop-corrected)', x_norm, w_norm)
81  else:
82    raise AssertionError('Cropped image is larger than original!')
83  return x_norm, w_norm
84
85
86def get_x_norm(num):
87  """Returns the normalized x co-ordinate for the title.
88
89  Args:
90   num: int; position on color in the color bar.
91
92  Returns:
93    normalized x co-ordinate.
94  """
95  return float(num) / N_BARS + DELTA
96
97
98def check_raw_pattern(img_raw):
99  """Checks for RAW capture matches color bar pattern.
100
101  Args:
102    img_raw: RAW image
103  """
104  logging.debug('Checking RAW/PATTERN match')
105  color_match = []
106  for n in range(N_BARS):
107    x_norm = get_x_norm(n)
108    raw_patch = image_processing_utils.get_image_patch(img_raw, x_norm, Y_NORM,
109                                                       W_NORM, H_NORM)
110    raw_means = image_processing_utils.compute_image_means(raw_patch)
111    logging.debug('patch: %d, x_norm: %.3f, RAW means: %s',
112                  n, x_norm, str(raw_means))
113    for color in COLOR_BARS:
114      if np.allclose(COLOR_CHECKER[color], raw_means, atol=RAW_TOL):
115        color_match.append(color)
116        logging.debug('%s match', color)
117        break
118      else:
119        logging.debug('No match w/ %s: %s, ATOL: %.3f',
120                      color, str(COLOR_CHECKER[color]), RAW_TOL)
121  if set(color_match) != set(COLOR_BARS):
122    raise AssertionError('RAW COLOR_BARS test pattern does not have all colors')
123
124
125def check_yuv_vs_raw(img_raw, img_yuv, name, debug):
126  """Checks for YUV vs RAW match in 8 patches.
127
128  Check for correct values and color consistency
129
130  Args:
131    img_raw: RAW image
132    img_yuv: YUV image
133    name: string for test name with path
134    debug: boolean to log additional information
135  """
136  logging.debug('Checking YUV/RAW match')
137  raw_w = img_raw.shape[1]
138  raw_h = img_raw.shape[0]
139  raw_aspect_ratio = raw_w/raw_h
140  yuv_aspect_ratio = YUV_W/YUV_H
141  logging.debug('raw_img: W, H, AR: %d, %d, %.3f',
142                raw_w, raw_h, raw_aspect_ratio)
143
144  # Crop RAW to match YUV 4:3 format
145  raw_w_cropped = raw_w
146  if raw_aspect_ratio > yuv_aspect_ratio:  # vertical crop sensor
147    logging.debug('Cropping RAW to match YUV aspect ratio.')
148    w_norm_raw = yuv_aspect_ratio / raw_aspect_ratio
149    x_norm_raw = (1 - w_norm_raw) / 2
150    img_raw = image_processing_utils.get_image_patch(
151        img_raw, x_norm_raw, 0, w_norm_raw, 1)
152    raw_w_cropped = img_raw.shape[1]
153    logging.debug('New RAW W, H: %d, %d', raw_w_cropped, img_raw.shape[0])
154    image_processing_utils.write_image(
155        img_raw, f'{name}_raw_cropped_COLOR_BARS.jpg', True)
156
157  # Compare YUV and RAW color patches
158  color_match_errs = []
159  color_variance_errs = []
160  for n in range(N_BARS):
161    x_norm, w_norm = get_yuv_patch_coordinates(n, raw_w, raw_w_cropped)
162    raw_patch = image_processing_utils.get_image_patch(img_raw, x_norm, Y_NORM,
163                                                       w_norm, H_NORM)
164    yuv_patch = image_processing_utils.get_image_patch(img_yuv, x_norm, Y_NORM,
165                                                       w_norm, H_NORM)
166    if debug:
167      image_processing_utils.write_image(
168          raw_patch, f'{name}_raw_patch_{n}.jpg', True)
169      image_processing_utils.write_image(
170          yuv_patch, f'{name}_yuv_patch_{n}.jpg', True)
171    raw_means = np.array(image_processing_utils.compute_image_means(raw_patch))
172    raw_vars = np.array(
173        image_processing_utils.compute_image_variances(raw_patch))
174    yuv_means = np.array(image_processing_utils.compute_image_means(yuv_patch))
175    yuv_means /= TONEMAP_MAX  # Normalize to tonemap max
176    yuv_vars = np.array(
177        image_processing_utils.compute_image_variances(yuv_patch))
178    if not np.allclose(raw_means, yuv_means, atol=RGB_MEAN_TOL):
179      color_match_errs.append(
180          'means RAW: %s, RGB(norm): %s, ATOL: %.2f' %
181          (str(raw_means), str(np.round(yuv_means, 3)), RGB_MEAN_TOL))
182    if not np.allclose(raw_vars, yuv_vars, atol=RGB_VAR_TOL):
183      color_variance_errs.append('variances RAW: %s, RGB: %s, ATOL: %.4f' %
184                                 (str(raw_vars), str(yuv_vars), RGB_VAR_TOL))
185
186  # Print all errors before assertion
187  if color_match_errs:
188    for err in color_match_errs:
189      logging.debug(err)
190    for err in color_variance_errs:
191      logging.error(err)
192    raise AssertionError('Color match errors. See test_log.DEBUG')
193  if color_variance_errs:
194    for err in color_variance_errs:
195      logging.error(err)
196    raise AssertionError('Color variance errors. See test_log.DEBUG')
197
198
199def test_tonemap_curve_impl(name, cam, props, debug):
200  """Test tonemap curve with sensor test pattern.
201
202  Args:
203   name: Path to save the captured image.
204   cam: An open device session.
205   props: Properties of cam.
206   debug: boolean for debug mode
207  """
208
209  avail_patterns = props['android.sensor.availableTestPatternModes']
210  logging.debug('Available Patterns: %s', avail_patterns)
211  sens_min, _ = props['android.sensor.info.sensitivityRange']
212  min_exposure = min(props['android.sensor.info.exposureTimeRange'])
213
214  # RAW image
215  req_raw = capture_request_utils.manual_capture_request(
216      int(sens_min), min_exposure)
217  req_raw['android.sensor.testPatternMode'] = COLOR_BAR_PATTERN
218  fmt_raw = {'format': 'raw'}
219  cap_raw = cam.do_capture(req_raw, fmt_raw)
220  img_raw = image_processing_utils.convert_capture_to_rgb_image(
221      cap_raw, props=props)
222
223  # Save RAW pattern
224  image_processing_utils.write_image(
225      img_raw, f'{name}_raw_COLOR_BARS.jpg', True)
226
227  # Check pattern for correctness
228  check_raw_pattern(img_raw)
229
230  # YUV image
231  req_yuv = capture_request_utils.manual_capture_request(
232      int(sens_min), min_exposure)
233  req_yuv['android.sensor.testPatternMode'] = COLOR_BAR_PATTERN
234  req_yuv['android.distortionCorrection.mode'] = 0
235  req_yuv['android.tonemap.mode'] = 0
236  req_yuv['android.tonemap.curve'] = {
237      'red': LINEAR_TONEMAP,
238      'green': LINEAR_TONEMAP,
239      'blue': LINEAR_TONEMAP
240  }
241  fmt_yuv = {'format': 'yuv', 'width': YUV_W, 'height': YUV_H}
242  cap_yuv = cam.do_capture(req_yuv, fmt_yuv)
243  img_yuv = image_processing_utils.convert_capture_to_rgb_image(cap_yuv, True)
244
245  # Save YUV pattern
246  image_processing_utils.write_image(
247      img_yuv, f'{name}_yuv_COLOR_BARS.jpg', True)
248
249  # Check pattern for correctness
250  check_yuv_vs_raw(img_raw, img_yuv, name, debug)
251
252
253class TonemapCurveTest(its_base_test.ItsBaseTest):
254  """Test conversion of test pattern from RAW to YUV with linear tonemap.
255
256  Test makes use of android.sensor.testPatternMode 2 (COLOR_BARS).
257  """
258
259  def test_tonemap_curve(self):
260    logging.debug('Starting %s', NAME)
261    name = os.path.join(self.log_path, NAME)
262    with its_session_utils.ItsSession(
263        device_id=self.dut.serial,
264        camera_id=self.camera_id,
265        hidden_physical_id=self.hidden_physical_id) as cam:
266      props = cam.get_camera_properties()
267      camera_properties_utils.skip_unless(
268          camera_properties_utils.raw16(props) and
269          camera_properties_utils.manual_sensor(props) and
270          camera_properties_utils.per_frame_control(props) and
271          camera_properties_utils.manual_post_proc(props) and
272          camera_properties_utils.color_bars_test_pattern(props))
273
274      test_tonemap_curve_impl(name, cam, props, self.debug_mode)
275
276
277if __name__ == '__main__':
278  test_runner.main()
279