1# Copyright 2015 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.edge.mode works properly."""
15
16
17import logging
18import os
19import matplotlib
20from matplotlib import pylab
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 opencv_processing_utils
30
31_EDGE_MODES = {'OFF': 0, 'FAST': 1, 'HQ': 2}
32_NAME = os.path.splitext(os.path.basename(__file__))[0]
33_NUM_SAMPLES = 4
34_SHARPNESS_RTOL = 0.1
35
36
37def plot_results(modes, sharpness_values, name_with_log_path):
38  """Plot the results.
39
40  Args:
41    modes: integer edge mode values
42    sharpness_values: float values of sharpness
43    name_with_log_path: file name with log_path for save location
44  """
45  pylab.figure(_NAME)
46  pylab.suptitle(_NAME)
47  pylab.title(str(_EDGE_MODES))
48  pylab.xlabel('Edge Enhancement Mode')
49  pylab.ylabel('Image Sharpness')
50  pylab.xticks(modes)
51  pylab.plot(modes, sharpness_values, '-ro')
52  matplotlib.pyplot.savefig(f'{name_with_log_path}_plot.png')
53
54
55def do_capture_and_determine_sharpness(
56    cam, edge_mode, sensitivity, exp, fd, out_surface, chart,
57    name_with_log_path):
58  """Return sharpness of the output image and the capture result metadata.
59
60     Processes a capture request with a given edge mode, sensitivity, exposure
61     time, focus distance, output surface parameter.
62
63  Args:
64    cam: An open device session.
65    edge_mode: Edge mode for the request as defined in android.edge.mode
66    sensitivity: Sensitivity for the request as defined in
67                 android.sensor.sensitivity
68    exp: Exposure time for the request as defined in
69         android.sensor.exposureTime.
70    fd: Focus distance for the request as defined in
71        android.lens.focusDistance
72    out_surface: Specifications of the output image format and size.
73    chart: object that contains chart information
74    name_with_log_path: file name with log_path to write result images
75
76  Returns:
77    Object containing reported edge mode and the sharpness of the output
78    image, keyed by the following strings:
79        edge_mode
80        sharpness
81  """
82
83  req = capture_request_utils.manual_capture_request(sensitivity, exp)
84  req['android.lens.focusDistance'] = fd
85  req['android.edge.mode'] = edge_mode
86
87  sharpness_list = []
88  caps = cam.do_capture([req]*_NUM_SAMPLES, [out_surface], repeat_request=req)
89  for n, cap in enumerate(caps):
90    y, _, _ = image_processing_utils.convert_capture_to_planes(cap)
91    chart.img = image_processing_utils.get_image_patch(
92        y, chart.xnorm, chart.ynorm, chart.wnorm, chart.hnorm)
93    if n == 0:
94      image_processing_utils.write_image(
95          chart.img, f'{name_with_log_path}_edge={edge_mode}.jpg')
96      edge_mode_res = cap['metadata']['android.edge.mode']
97    sharpness_list.append(
98        image_processing_utils.compute_image_sharpness(chart.img)*255)
99  logging.debug('edge mode: %d, sharpness values: %s',
100                edge_mode_res, sharpness_list)
101  return {'edge_mode': edge_mode_res, 'sharpness': np.mean(sharpness_list)}
102
103
104class EdgeEnhancementTest(its_base_test.ItsBaseTest):
105  """Test that the android.edge.mode param is applied correctly.
106
107  Capture non-reprocess images for each edge mode and calculate their
108  sharpness as a baseline.
109  """
110
111  def test_edge_enhancement(self):
112    with its_session_utils.ItsSession(
113        device_id=self.dut.serial,
114        camera_id=self.camera_id,
115        hidden_physical_id=self.hidden_physical_id) as cam:
116      props = cam.get_camera_properties()
117      props = cam.override_with_hidden_physical_camera_props(props)
118      name_with_log_path = os.path.join(self.log_path, _NAME)
119
120      # Check skip conditions
121      camera_properties_utils.skip_unless(
122          camera_properties_utils.read_3a(props) and
123          camera_properties_utils.per_frame_control(props) and
124          camera_properties_utils.edge_mode(props, 0))
125
126      # Load chart for scene
127      its_session_utils.load_scene(
128          cam, props, self.scene, self.tablet, self.chart_distance)
129
130      # Initialize chart class and locate chart in scene
131      chart = opencv_processing_utils.Chart(
132          cam, props, self.log_path, distance=self.chart_distance)
133
134      # Define format
135      fmt = 'yuv'
136      size = capture_request_utils.get_available_output_sizes(fmt, props)[0]
137      out_surface = {'width': size[0], 'height': size[1], 'format': fmt}
138      logging.debug('%s: %dx%d', fmt, size[0], size[1])
139
140      # Get proper sensitivity, exposure time, and focus distance.
141      mono_camera = camera_properties_utils.mono_camera(props)
142      s, e, _, _, fd = cam.do_3a(get_results=True, mono_camera=mono_camera)
143      logging.debug('iso: %d, exp: %d, fd: %.3f', s, e, fd)
144
145      # Get the sharpness for each edge mode for regular requests
146      sharpness_regular = []
147      edge_mode_reported_regular = []
148      for edge_mode in _EDGE_MODES.values():
149        # Skip unavailable modes
150        if not camera_properties_utils.edge_mode(props, edge_mode):
151          edge_mode_reported_regular.append(edge_mode)
152          sharpness_regular.append(0)
153          continue
154
155        ret = do_capture_and_determine_sharpness(
156            cam, edge_mode, s, e, fd, out_surface, chart, name_with_log_path)
157        edge_mode_reported_regular.append(ret['edge_mode'])
158        sharpness_regular.append(ret['sharpness'])
159
160      logging.debug('Reported edge modes: %s', edge_mode_reported_regular)
161      logging.debug('Sharpness with EE mode [0,1,2,3]: %s',
162                    str(sharpness_regular))
163      plot_results(
164          edge_mode_reported_regular, sharpness_regular, name_with_log_path)
165
166      logging.debug('Verify HQ is sharper than OFF')
167      if (sharpness_regular[_EDGE_MODES['HQ']] <=
168          sharpness_regular[_EDGE_MODES['OFF']]):
169        raise AssertionError(
170            f"HQ: {sharpness_regular[_EDGE_MODES['HQ']]:.3f}, "
171            f"OFF: {sharpness_regular[_EDGE_MODES['OFF']]:.3f}")
172
173      logging.debug('Verify OFF is not sharper than FAST')
174      if (sharpness_regular[_EDGE_MODES['FAST']] <=
175          sharpness_regular[_EDGE_MODES['OFF']]*(1.0-_SHARPNESS_RTOL)):
176        raise AssertionError(
177            f"FAST: {sharpness_regular[_EDGE_MODES['FAST']]:.3f}, "
178            f"OFF: {sharpness_regular[_EDGE_MODES['OFF']]:.3f}, "
179            f"RTOL: {_SHARPNESS_RTOL}")
180
181      logging.debug('Verify FAST is not sharper than HQ')
182      if (sharpness_regular[_EDGE_MODES['HQ']] <=
183          sharpness_regular[_EDGE_MODES['FAST']]*(1.0-_SHARPNESS_RTOL)):
184        raise AssertionError(
185            f"HQ: {sharpness_regular[_EDGE_MODES['HQ']]:.3f}, "
186            f"FAST: {sharpness_regular[_EDGE_MODES['FAST']]:.3f}, "
187            f"RTOL: {_SHARPNESS_RTOL}")
188
189if __name__ == '__main__':
190  test_runner.main()
191