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 manual burst capture consistency."""
15
16
17import logging
18import os.path
19from matplotlib import pylab
20import matplotlib.pyplot
21from mobly import test_runner
22import numpy as np
23
24import its_base_test
25import camera_properties_utils
26import capture_request_utils
27import image_processing_utils
28import its_session_utils
29import target_exposure_utils
30
31_API_LEVEL_30 = 30
32_BURST_LEN = 50
33_COLORS = ('R', 'G', 'B')
34_NAME = os.path.splitext(os.path.basename(__file__))[0]
35_NUM_BURSTS = 2
36_PATCH_H = 0.1  # center 10%
37_PATCH_W = 0.1
38_PATCH_X = 0.5 - _PATCH_W/2
39_PATCH_Y = 0.5 - _PATCH_H/2
40_SPREAD_THRESH = 0.03
41_SPREAD_THRESH_API_LEVEL_30 = 0.02
42
43_NUM_FRAMES = _BURST_LEN * _NUM_BURSTS
44
45
46class BurstSamenessManualTest(its_base_test.ItsBaseTest):
47  """Take long bursts of images and check that they're all identical.
48
49  Assumes a static scene. Can be used to idenfity if there are sporadic
50  frames that are processed differently or have artifacts. Uses manual
51  capture settings.
52  """
53
54  def test_burst_sameness_manual(self):
55    with its_session_utils.ItsSession(
56        device_id=self.dut.serial,
57        camera_id=self.camera_id,
58        hidden_physical_id=self.hidden_physical_id) as cam:
59      props = cam.get_camera_properties()
60      props = cam.override_with_hidden_physical_camera_props(props)
61      log_path = self.log_path
62      name_with_path = os.path.join(log_path, _NAME)
63
64      # check SKIP conditions
65      camera_properties_utils.skip_unless(
66          camera_properties_utils.compute_target_exposure(props) and
67          camera_properties_utils.per_frame_control(props))
68
69      # Load chart for scene
70      its_session_utils.load_scene(
71          cam, props, self.scene, self.tablet,
72          its_session_utils.CHART_DISTANCE_NO_SCALING)
73
74      # Capture at the smallest resolution
75      _, fmt = capture_request_utils.get_fastest_manual_capture_settings(props)
76      e, s = target_exposure_utils.get_target_exposure_combos(
77          log_path, cam)['minSensitivity']
78      req = capture_request_utils.manual_capture_request(s, e)
79      w, h = fmt['width'], fmt['height']
80
81      # Capture bursts of YUV shots.
82      # Get the mean values of a center patch for each.
83      # Also build a 4D array, imgs, which is an array of all RGB images.
84      r_means = []
85      g_means = []
86      b_means = []
87      imgs = np.empty([_NUM_FRAMES, h, w, 3])
88      for j in range(_NUM_BURSTS):
89        caps = cam.do_capture([req]*_BURST_LEN, [fmt])
90        for i, cap in enumerate(caps):
91          n = j*_BURST_LEN + i
92          imgs[n] = image_processing_utils.convert_capture_to_rgb_image(cap)
93          patch = image_processing_utils.get_image_patch(
94              imgs[n], _PATCH_X, _PATCH_Y, _PATCH_W, _PATCH_H)
95          means = image_processing_utils.compute_image_means(patch)
96          r_means.append(means[0])
97          g_means.append(means[1])
98          b_means.append(means[2])
99
100      # Save first frame for setup debug
101      image_processing_utils.write_image(
102          imgs[0], f'{name_with_path}_frame000.jpg')
103
104      # Plot RGB means vs frames
105      frames = range(_NUM_FRAMES)
106      pylab.figure(_NAME)
107      pylab.title(_NAME)
108      pylab.plot(frames, r_means, '-ro')
109      pylab.plot(frames, g_means, '-go')
110      pylab.plot(frames, b_means, '-bo')
111      pylab.ylim([0, 1])
112      pylab.xlabel('frame number')
113      pylab.ylabel('RGB avg [0, 1]')
114      matplotlib.pyplot.savefig(f'{name_with_path}_plot_means.png')
115
116      # determine spread_thresh
117      spread_thresh = _SPREAD_THRESH
118      if its_session_utils.get_first_api_level(
119          self.dut.serial) >= _API_LEVEL_30:
120        spread_thresh = _SPREAD_THRESH_API_LEVEL_30
121
122      # PASS/FAIL based on center patch similarity
123      for plane, means in enumerate([r_means, g_means, b_means]):
124        spread = max(means) - min(means)
125        logging.debug('%s spread: %.5f', _COLORS[plane], spread)
126        if spread > spread_thresh:
127          # Save all frames if FAIL
128          logging.debug('Dumping all images')
129          for i in range(1, _NUM_FRAMES):
130            image_processing_utils.write_image(
131                imgs[i], f'{name_with_path}_frame{i:03d}.jpg')
132          raise AssertionError(f'{_COLORS[plane]} spread > THRESH. spread: '
133                               f'{spread:.4f}, THRESH: {spread_thresh:.2f}')
134
135if __name__ == '__main__':
136  test_runner.main()
137