summaryrefslogtreecommitdiff
path: root/apps/CameraITS/tests/scene1_1/test_ev_compensation_basic.py
blob: 7d8b9f2c11ee000f8f48740a07ae2625f4b3cdfe (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
# Copyright 2014 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.
"""Verifies EV compensation is applied."""


import logging
import math
import os.path
import matplotlib
from matplotlib import pylab
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

LOCKED = 3
LUMA_LOCKED_RTOL_EV_SM = 0.05
LUMA_LOCKED_RTOL_EV_LG = 0.10
NAME = os.path.splitext(os.path.basename(__file__))[0]
NUM_UNSATURATED_EVS = 3
PATCH_H = 0.1  # center 10%
PATCH_W = 0.1
PATCH_X = 0.5 - PATCH_W/2
PATCH_Y = 0.5 - PATCH_H/2
THRESH_CONVERGE_FOR_EV = 8  # AE must converge within this num
YUV_FULL_SCALE = 255.0
YUV_SAT_MIN = 250.0
YUV_SAT_TOL = 3.0


def create_request_with_ev(ev):
  req = capture_request_utils.auto_capture_request()
  req['android.control.aeExposureCompensation'] = ev
  req['android.control.aeLock'] = True
  return req


def extract_luma_from_capture(cap):
  """Extract luma from capture."""
  y = image_processing_utils.convert_capture_to_planes(cap)[0]
  patch = image_processing_utils.get_image_patch(
      y, PATCH_X, PATCH_Y, PATCH_W, PATCH_H)
  luma = image_processing_utils.compute_image_means(patch)[0]
  return luma


class EvCompensationBasicTest(its_base_test.ItsBaseTest):
  """Tests that EV compensation is applied."""

  def test_ev_compensation_basic(self):
    logging.debug('Starting %s', 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()
      props = cam.override_with_hidden_physical_camera_props(props)
      log_path = self.log_path
      debug = self.debug_mode
      test_name_w_path = os.path.join(log_path, NAME)

      # check SKIP conditions
      camera_properties_utils.skip_unless(
          camera_properties_utils.ev_compensation(props) and
          camera_properties_utils.ae_lock(props))

      # Load chart for scene
      its_session_utils.load_scene(
          cam, props, self.scene, self.tablet, self.chart_distance)

      # Create ev compensation changes
      ev_per_step = capture_request_utils.rational_to_float(
          props['android.control.aeCompensationStep'])
      steps_per_ev = int(1.0 / ev_per_step)
      evs = range(-2 * steps_per_ev, 2 * steps_per_ev + 1, steps_per_ev)
      luma_locked_rtols = [LUMA_LOCKED_RTOL_EV_LG,
                           LUMA_LOCKED_RTOL_EV_SM,
                           LUMA_LOCKED_RTOL_EV_SM,
                           LUMA_LOCKED_RTOL_EV_SM,
                           LUMA_LOCKED_RTOL_EV_LG]

      # Converge 3A, and lock AE once converged. skip AF trigger as
      # dark/bright scene could make AF convergence fail and this test
      # doesn't care the image sharpness.
      mono_camera = camera_properties_utils.mono_camera(props)
      cam.do_3a(ev_comp=0, lock_ae=True, do_af=False, mono_camera=mono_camera)

      # Do captures and extract information
      largest_yuv = capture_request_utils.get_largest_yuv_format(props)
      match_ar = (largest_yuv['width'], largest_yuv['height'])
      fmt = capture_request_utils.get_smallest_yuv_format(
          props, match_ar=match_ar)
      lumas = []
      for j, ev in enumerate(evs):
        luma_locked_rtol = luma_locked_rtols[j]
        # Capture a single shot with the same EV comp and locked AE.
        req = create_request_with_ev(ev)
        caps = cam.do_capture([req]*THRESH_CONVERGE_FOR_EV, fmt)
        luma_locked = []
        for i, cap in enumerate(caps):
          if debug:
            img = image_processing_utils.convert_capture_to_rgb_image(
                cap, props)
            image_processing_utils.write_image(
                img, f'{test_name_w_path}_ev{ev}_frame{i}.jpg')
          if cap['metadata']['android.control.aeState'] == LOCKED:
            ev_meta = cap['metadata']['android.control.aeExposureCompensation']
            logging.debug('cap EV compensation: %d', ev_meta)
            if ev != ev_meta:
              raise AssertionError(
                  f'EV compensation cap != req! cap: {ev_meta}, req: {ev}')
            luma = extract_luma_from_capture(cap)
            luma_locked.append(luma)
            if i == THRESH_CONVERGE_FOR_EV-1:
              lumas.append(luma)
              if not math.isclose(min(luma_locked), max(luma_locked),
                                  rel_tol=luma_locked_rtol):
                raise AssertionError(f'AE locked lumas: {luma_locked}, '
                                     f'RTOL: {luma_locked_rtol}')
      logging.debug('lumas in AE locked captures: %s', str(lumas))
      if caps[THRESH_CONVERGE_FOR_EV-1]['metadata'][
          'android.control.aeState'] != LOCKED:
        raise AssertionError(f'No AE lock by {THRESH_CONVERGE_FOR_EV} frame.')

    # Create plot
    pylab.figure(NAME)
    pylab.plot(evs, lumas, '-ro')
    pylab.title(NAME)
    pylab.xlabel('EV Compensation')
    pylab.ylabel('Mean Luma (Normalized)')
    matplotlib.pyplot.savefig(f'{test_name_w_path}_plot_means.png')

    # Trim extra saturated images
    while (lumas[-2] >= YUV_SAT_MIN/YUV_FULL_SCALE and
           lumas[-1] >= YUV_SAT_MIN/YUV_FULL_SCALE and
           len(lumas) > 2):
      lumas.pop(-1)
      logging.debug('Removed saturated image.')

    # Only allow positive EVs to give saturated image
    if len(lumas) < NUM_UNSATURATED_EVS:
      raise AssertionError(
          f'>{NUM_UNSATURATED_EVS-1} unsaturated images needed.')
    min_luma_diffs = min(np.diff(lumas))
    logging.debug('Min of luma value difference between adjacent ev comp: %.3f',
                  min_luma_diffs)

    # Assert unsaturated lumas increasing with increasing ev comp.
    if min_luma_diffs <= 0:
      raise AssertionError('Lumas not increasing with ev comp! '
                           f'EVs: {list(evs)}, lumas: {lumas}')


if __name__ == '__main__':
  test_runner.main()