xref: /aosp_15_r20/cts/apps/CameraITS/tests/scene2_a/test_effects.py (revision b7c941bb3fa97aba169d73cee0bed2de8ac964bf)
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"""Verifies android.control.availableEffects that are supported."""
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# android.control.availableEffects possible values
29_EFFECTS = {0: 'OFF',
30            1: 'MONO',
31            2: 'NEGATIVE',
32            3: 'SOLARIZE',
33            4: 'SEPIA',
34            5: 'POSTERIZE',
35            6: 'WHITEBOARD',
36            7: 'BLACKBOARD',
37            8: 'AQUA'}
38_MONO_UV_SPREAD_MAX = 2  # max spread for U & V channels [0:255] for mono image
39_NAME = os.path.splitext(os.path.basename(__file__))[0]
40_VGA_W, _VGA_H = 640, 480
41_YUV_MAX = 255  # normalization number for YUV images [0:1] --> [0:255]
42_YUV_UV_SPREAD_ATOL = 10  # min spread for U & V channels [0:255] for color img
43_YUV_Y_SPREAD_ATOL = 50  # min spread for Y channel [0:255] for color image
44
45
46class EffectsTest(its_base_test.ItsBaseTest):
47  """Test effects.
48
49  Test: capture frame for supported camera effects and check if generated
50  correctly. Note we only check effects OFF and MONO currently. Other effects
51  do not have standardized definitions, so they are not tested.
52  However, the test saves images for all supported effects.
53  """
54
55  def test_effects(self):
56    logging.debug('Starting %s', _NAME)
57    with its_session_utils.ItsSession(
58        device_id=self.dut.serial,
59        camera_id=self.camera_id,
60        hidden_physical_id=self.hidden_physical_id) as cam:
61      props = cam.get_camera_properties()
62      props = cam.override_with_hidden_physical_camera_props(props)
63      mono_camera = camera_properties_utils.mono_camera(props)
64
65      # Load chart for scene.
66      its_session_utils.load_scene(
67          cam, props, self.scene, self.tablet, self.chart_distance)
68
69      # Determine available effects and run test(s)
70      effects = props['android.control.availableEffects']
71      camera_properties_utils.skip_unless(effects != [0])
72      cam.do_3a(mono_camera=mono_camera)
73      logging.debug('Supported effects: %s', str(effects))
74      failed = []
75      for effect in effects:
76        req = capture_request_utils.auto_capture_request()
77        req['android.control.effectMode'] = effect
78        fmt = {'format': 'yuv', 'width': _VGA_W, 'height': _VGA_H}
79        cap = cam.do_capture(req, fmt)
80
81        # Save image of each effect
82        img = image_processing_utils.convert_capture_to_rgb_image(
83            cap, props=props)
84        img_name = (f'{os.path.join(self.log_path,_NAME)}_'
85                    f'{_EFFECTS[effect]}.jpg')
86        image_processing_utils.write_image(img, img_name)
87
88        # Simple checks
89        if effect == 0:
90          logging.debug('Checking effects OFF...')
91          y, u, v = image_processing_utils.convert_capture_to_planes(
92              cap, props)
93          y_min, y_max = np.amin(y)*_YUV_MAX, np.amax(y)*_YUV_MAX
94          e_msg = (f'Y_range: {y_min:.2f},{y_max:.2f} '
95                   f'THRESH: {_YUV_Y_SPREAD_ATOL}; ')
96          if (y_max-y_min) < _YUV_Y_SPREAD_ATOL:
97            failed.append({'effect': _EFFECTS[effect], 'error': e_msg})
98          if not mono_camera:
99            u_min, u_max = np.amin(u) * _YUV_MAX, np.amax(u) * _YUV_MAX
100            v_min, v_max = np.amin(v) * _YUV_MAX, np.amax(v) * _YUV_MAX
101            e_msg += (f'U_range: {u_min:.2f},{u_max:.2f} '
102                      f'THRESH: {_YUV_UV_SPREAD_ATOL}; ')
103            e_msg += (f'V_range: {v_min:.2f},{v_max:.2f} '
104                      f'THRESH: {_YUV_UV_SPREAD_ATOL}')
105            if ((u_max - u_min) < _YUV_UV_SPREAD_ATOL or
106                (v_max - v_min) < _YUV_UV_SPREAD_ATOL):
107              failed.append({'effect': _EFFECTS[effect], 'error': e_msg})
108        elif effect == 1:
109          logging.debug('Checking MONO effect...')
110          _, u, v = image_processing_utils.convert_capture_to_planes(
111              cap, props)
112          u_min, u_max = np.amin(u)*_YUV_MAX, np.amax(u)*_YUV_MAX
113          v_min, v_max = np.amin(v)*_YUV_MAX, np.amax(v)*_YUV_MAX
114          e_msg = (f'U_range: {u_min:.2f},{u_max:.2f}; '
115                   f'V_range: {v_min:.2f},{v_max:.2f}; '
116                   f'ATOL: {_MONO_UV_SPREAD_MAX}')
117          if ((u_max - u_min) > _MONO_UV_SPREAD_MAX or
118              (v_max - v_min) > _MONO_UV_SPREAD_MAX):
119            failed.append({'effect': _EFFECTS[effect], 'error': e_msg})
120      if failed:
121        logging.debug('Failed effects:')
122        for fail in failed:
123          logging.debug(' %s: %s', fail['effect'], fail['error'])
124        raise AssertionError(f'{_NAME} failed. See test_log.DEBUG for errors.')
125
126if __name__ == '__main__':
127  test_runner.main()
128