xref: /aosp_15_r20/cts/apps/CameraITS/tests/tutorial.py (revision b7c941bb3fa97aba169d73cee0bed2de8ac964bf)
1*b7c941bbSAndroid Build Coastguard Worker# Copyright 2014 The Android Open Source Project
2*b7c941bbSAndroid Build Coastguard Worker#
3*b7c941bbSAndroid Build Coastguard Worker# Licensed under the Apache License, Version 2.0 (the "License");
4*b7c941bbSAndroid Build Coastguard Worker# you may not use this file except in compliance with the License.
5*b7c941bbSAndroid Build Coastguard Worker# You may obtain a copy of the License at
6*b7c941bbSAndroid Build Coastguard Worker#
7*b7c941bbSAndroid Build Coastguard Worker#      http://www.apache.org/licenses/LICENSE-2.0
8*b7c941bbSAndroid Build Coastguard Worker#
9*b7c941bbSAndroid Build Coastguard Worker# Unless required by applicable law or agreed to in writing, software
10*b7c941bbSAndroid Build Coastguard Worker# distributed under the License is distributed on an "AS IS" BASIS,
11*b7c941bbSAndroid Build Coastguard Worker# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12*b7c941bbSAndroid Build Coastguard Worker# See the License for the specific language governing permissions and
13*b7c941bbSAndroid Build Coastguard Worker# limitations under the License.
14*b7c941bbSAndroid Build Coastguard Worker
15*b7c941bbSAndroid Build Coastguard Worker# --------------------------------------------------------------------------- #
16*b7c941bbSAndroid Build Coastguard Worker# The Google Python style guide should be used for scripts:                   #
17*b7c941bbSAndroid Build Coastguard Worker# http://google-styleguide.googlecode.com/svn/trunk/pyguide.html              #
18*b7c941bbSAndroid Build Coastguard Worker# --------------------------------------------------------------------------- #
19*b7c941bbSAndroid Build Coastguard Worker
20*b7c941bbSAndroid Build Coastguard Worker# The ITS modules that are in the utils directory. To see formatted
21*b7c941bbSAndroid Build Coastguard Worker# docs, use the "pydoc" command:
22*b7c941bbSAndroid Build Coastguard Worker#
23*b7c941bbSAndroid Build Coastguard Worker# > pydoc image_processing_utils
24*b7c941bbSAndroid Build Coastguard Worker#
25*b7c941bbSAndroid Build Coastguard Worker"""Tutorial script for CameraITS tests."""
26*b7c941bbSAndroid Build Coastguard Workerimport capture_request_utils
27*b7c941bbSAndroid Build Coastguard Workerimport image_processing_utils
28*b7c941bbSAndroid Build Coastguard Workerimport its_base_test
29*b7c941bbSAndroid Build Coastguard Workerimport its_session_utils
30*b7c941bbSAndroid Build Coastguard Worker
31*b7c941bbSAndroid Build Coastguard Worker# Standard Python modules.
32*b7c941bbSAndroid Build Coastguard Workerimport logging
33*b7c941bbSAndroid Build Coastguard Workerimport os.path
34*b7c941bbSAndroid Build Coastguard Worker
35*b7c941bbSAndroid Build Coastguard Worker# Modules from the numpy, scipy, and matplotlib libraries. These are used for
36*b7c941bbSAndroid Build Coastguard Worker# the image processing code, and images are represented as numpy arrays.
37*b7c941bbSAndroid Build Coastguard Workerfrom matplotlib import pyplot as plt
38*b7c941bbSAndroid Build Coastguard Workerimport numpy
39*b7c941bbSAndroid Build Coastguard Worker
40*b7c941bbSAndroid Build Coastguard Worker# Module for Mobly
41*b7c941bbSAndroid Build Coastguard Workerfrom mobly import test_runner
42*b7c941bbSAndroid Build Coastguard Worker
43*b7c941bbSAndroid Build Coastguard Worker# A convention in each script is to use the filename (without the extension)
44*b7c941bbSAndroid Build Coastguard Worker# as the name of the test, when printing results to the screen or dumping files.
45*b7c941bbSAndroid Build Coastguard Worker_NAME = os.path.basename(__file__).split('.')[0]
46*b7c941bbSAndroid Build Coastguard Worker
47*b7c941bbSAndroid Build Coastguard Worker
48*b7c941bbSAndroid Build Coastguard Worker# Each script has a class definition
49*b7c941bbSAndroid Build Coastguard Workerclass TutorialTest(its_base_test.ItsBaseTest):
50*b7c941bbSAndroid Build Coastguard Worker  """Test the validity of some metadata entries.
51*b7c941bbSAndroid Build Coastguard Worker
52*b7c941bbSAndroid Build Coastguard Worker  Looks at the capture results and at the camera characteristics objects.
53*b7c941bbSAndroid Build Coastguard Worker  Script uses a config.yml file in the CameraITS directory.
54*b7c941bbSAndroid Build Coastguard Worker  A sample config.yml file:
55*b7c941bbSAndroid Build Coastguard Worker    TestBeds:
56*b7c941bbSAndroid Build Coastguard Worker    - Name: TEST_BED_TUTORIAL
57*b7c941bbSAndroid Build Coastguard Worker      Controllers:
58*b7c941bbSAndroid Build Coastguard Worker          AndroidDevice:
59*b7c941bbSAndroid Build Coastguard Worker            - serial: 03281FDD40008Y
60*b7c941bbSAndroid Build Coastguard Worker              label: dut
61*b7c941bbSAndroid Build Coastguard Worker      TestParams:
62*b7c941bbSAndroid Build Coastguard Worker        camera: "1"
63*b7c941bbSAndroid Build Coastguard Worker        scene: "0"
64*b7c941bbSAndroid Build Coastguard Worker
65*b7c941bbSAndroid Build Coastguard Worker  A sample script call:
66*b7c941bbSAndroid Build Coastguard Worker    python tests/tutorial.py --config config.yml
67*b7c941bbSAndroid Build Coastguard Worker
68*b7c941bbSAndroid Build Coastguard Worker  """
69*b7c941bbSAndroid Build Coastguard Worker
70*b7c941bbSAndroid Build Coastguard Worker  def test_tutorial(self):
71*b7c941bbSAndroid Build Coastguard Worker    # Each script has a string description of what it does. This is the first
72*b7c941bbSAndroid Build Coastguard Worker    # entry inside the main function.
73*b7c941bbSAndroid Build Coastguard Worker    """Tutorial script to show how to use the ITS infrastructure."""
74*b7c941bbSAndroid Build Coastguard Worker
75*b7c941bbSAndroid Build Coastguard Worker    # The standard way to open a session with a connected camera device. This
76*b7c941bbSAndroid Build Coastguard Worker    # creates a cam object which encapsulates the session and which is active
77*b7c941bbSAndroid Build Coastguard Worker    # within the scope of the 'with' block; when the block exits, the camera
78*b7c941bbSAndroid Build Coastguard Worker    # session is closed. The device and camera are defined in the config.yml
79*b7c941bbSAndroid Build Coastguard Worker    # file.
80*b7c941bbSAndroid Build Coastguard Worker    with its_session_utils.ItsSession(
81*b7c941bbSAndroid Build Coastguard Worker        device_id=self.dut.serial,
82*b7c941bbSAndroid Build Coastguard Worker        camera_id=self.camera_id,
83*b7c941bbSAndroid Build Coastguard Worker        hidden_physical_id=self.hidden_physical_id) as cam:
84*b7c941bbSAndroid Build Coastguard Worker
85*b7c941bbSAndroid Build Coastguard Worker      # Append the log_path to store images in the proper location.
86*b7c941bbSAndroid Build Coastguard Worker      # Images will be stored in the test output folder:
87*b7c941bbSAndroid Build Coastguard Worker      # /tmp/logs/mobly/$TEST_BED_NAME/$DATE/TutorialTest
88*b7c941bbSAndroid Build Coastguard Worker      file_name = os.path.join(self.log_path, _NAME)
89*b7c941bbSAndroid Build Coastguard Worker
90*b7c941bbSAndroid Build Coastguard Worker      # Get the static properties of the camera device. Returns a Python
91*b7c941bbSAndroid Build Coastguard Worker      # associative array object; print it to the console.
92*b7c941bbSAndroid Build Coastguard Worker      props = cam.get_camera_properties()
93*b7c941bbSAndroid Build Coastguard Worker      logging.debug('props\n%s', str(props))
94*b7c941bbSAndroid Build Coastguard Worker
95*b7c941bbSAndroid Build Coastguard Worker      # Grab a YUV frame with manual exposure of sensitivity = 200, exposure
96*b7c941bbSAndroid Build Coastguard Worker      # duration = 50ms.
97*b7c941bbSAndroid Build Coastguard Worker      req = capture_request_utils.manual_capture_request(200, 50*1000*1000)
98*b7c941bbSAndroid Build Coastguard Worker      cap = cam.do_capture(req)
99*b7c941bbSAndroid Build Coastguard Worker
100*b7c941bbSAndroid Build Coastguard Worker      # Print the properties of the captured frame; width and height are
101*b7c941bbSAndroid Build Coastguard Worker      # integers, and the metadata is a Python associative array object.
102*b7c941bbSAndroid Build Coastguard Worker      # logging.info will be printed to screen & test_log.INFO
103*b7c941bbSAndroid Build Coastguard Worker      # logging.debug to test_log.DEBUG in /tmp/logs/mobly/... directory
104*b7c941bbSAndroid Build Coastguard Worker      logging.info('Captured image width: %d, height: %d',
105*b7c941bbSAndroid Build Coastguard Worker                   cap['width'], cap['height'])
106*b7c941bbSAndroid Build Coastguard Worker      logging.debug('metadata\n%s', str(cap['metadata']))
107*b7c941bbSAndroid Build Coastguard Worker
108*b7c941bbSAndroid Build Coastguard Worker      # The captured image is YUV420. Convert to RGB, and save as a file.
109*b7c941bbSAndroid Build Coastguard Worker      rgbimg = image_processing_utils.convert_capture_to_rgb_image(cap)
110*b7c941bbSAndroid Build Coastguard Worker      image_processing_utils.write_image(rgbimg, f'{file_name}_rgb.jpg')
111*b7c941bbSAndroid Build Coastguard Worker
112*b7c941bbSAndroid Build Coastguard Worker      # Can also get the Y,U,V planes separately; save these to greyscale
113*b7c941bbSAndroid Build Coastguard Worker      # files.
114*b7c941bbSAndroid Build Coastguard Worker      yimg, uimg, vimg = image_processing_utils.convert_capture_to_planes(cap)
115*b7c941bbSAndroid Build Coastguard Worker      image_processing_utils.write_image(yimg, f'{file_name}_y_plane.jpg')
116*b7c941bbSAndroid Build Coastguard Worker      image_processing_utils.write_image(uimg, f'{file_name}_u_plane.jpg')
117*b7c941bbSAndroid Build Coastguard Worker      image_processing_utils.write_image(vimg, f'{file_name}_v_plane.jpg')
118*b7c941bbSAndroid Build Coastguard Worker
119*b7c941bbSAndroid Build Coastguard Worker      # Run 3A on the device. In this case, just use the entire image as the
120*b7c941bbSAndroid Build Coastguard Worker      # 3A region, and run each of AWB,AE,AF. Can also change the region and
121*b7c941bbSAndroid Build Coastguard Worker      # specify independently for each of AE,AWB,AF whether it should run.
122*b7c941bbSAndroid Build Coastguard Worker      #
123*b7c941bbSAndroid Build Coastguard Worker      # NOTE: This may fail, if the camera isn't pointed at a reasonable
124*b7c941bbSAndroid Build Coastguard Worker      # target scene. If it fails, the script will end. The logcat messages
125*b7c941bbSAndroid Build Coastguard Worker      # can be inspected to see the status of 3A running on the device.
126*b7c941bbSAndroid Build Coastguard Worker      #
127*b7c941bbSAndroid Build Coastguard Worker      # If this keeps on failing, try also rebooting the device before
128*b7c941bbSAndroid Build Coastguard Worker      # running the test.
129*b7c941bbSAndroid Build Coastguard Worker      sens, exp, gains, xform, focus = cam.do_3a(get_results=True)
130*b7c941bbSAndroid Build Coastguard Worker      logging.info('AE: sensitivity %d, exposure %dms', sens, exp/1000000.0)
131*b7c941bbSAndroid Build Coastguard Worker      logging.info('AWB: gains %s', str(gains))
132*b7c941bbSAndroid Build Coastguard Worker      logging.info('AWB: transform %s', str(xform))
133*b7c941bbSAndroid Build Coastguard Worker      logging.info('AF: distance %.4f', focus)
134*b7c941bbSAndroid Build Coastguard Worker
135*b7c941bbSAndroid Build Coastguard Worker      # Grab a new manual frame, using the 3A values, and convert it to RGB
136*b7c941bbSAndroid Build Coastguard Worker      # and save it to a file too. Note that the 'req' object is just a
137*b7c941bbSAndroid Build Coastguard Worker      # Python dictionary that is pre-populated by the capture_request_utils
138*b7c941bbSAndroid Build Coastguard Worker      # functions (in this case a default manual capture), and the key/value
139*b7c941bbSAndroid Build Coastguard Worker      # pairs in the object can be used to set any field of the capture
140*b7c941bbSAndroid Build Coastguard Worker      # request. Here, the AWB gains and transform (CCM) are being used.
141*b7c941bbSAndroid Build Coastguard Worker      # Note that the CCM transform is in a rational format in capture
142*b7c941bbSAndroid Build Coastguard Worker      # requests, meaning it is an object with integer numerators and
143*b7c941bbSAndroid Build Coastguard Worker      # denominators. The 3A routine returns simple floats instead, however,
144*b7c941bbSAndroid Build Coastguard Worker      # so a conversion from float to rational must be performed.
145*b7c941bbSAndroid Build Coastguard Worker      req = capture_request_utils.manual_capture_request(sens, exp)
146*b7c941bbSAndroid Build Coastguard Worker      xform_rat = capture_request_utils.float_to_rational(xform)
147*b7c941bbSAndroid Build Coastguard Worker
148*b7c941bbSAndroid Build Coastguard Worker      req['android.colorCorrection.transform'] = xform_rat
149*b7c941bbSAndroid Build Coastguard Worker      req['android.colorCorrection.gains'] = gains
150*b7c941bbSAndroid Build Coastguard Worker      cap = cam.do_capture(req)
151*b7c941bbSAndroid Build Coastguard Worker      rgbimg = image_processing_utils.convert_capture_to_rgb_image(cap)
152*b7c941bbSAndroid Build Coastguard Worker      image_processing_utils.write_image(rgbimg, f'{file_name}_rgb_2.jpg')
153*b7c941bbSAndroid Build Coastguard Worker
154*b7c941bbSAndroid Build Coastguard Worker      # log the actual capture request object that was used.
155*b7c941bbSAndroid Build Coastguard Worker      logging.debug('req: %s', str(req))
156*b7c941bbSAndroid Build Coastguard Worker
157*b7c941bbSAndroid Build Coastguard Worker      # Images are numpy arrays. The dimensions are (h,w,3) when indexing,
158*b7c941bbSAndroid Build Coastguard Worker      # in the case of RGB images. Greyscale images are (h,w,1). Pixels are
159*b7c941bbSAndroid Build Coastguard Worker      # generally float32 values in the [0,1] range, however some of the
160*b7c941bbSAndroid Build Coastguard Worker      # helper functions in image_processing_utils deal with the packed YUV420
161*b7c941bbSAndroid Build Coastguard Worker      # and other formats of images that come from the device (and convert
162*b7c941bbSAndroid Build Coastguard Worker      # them to float32).
163*b7c941bbSAndroid Build Coastguard Worker      # Print the dimensions of the image, and the top-left pixel value,
164*b7c941bbSAndroid Build Coastguard Worker      # which is an array of 3 floats.
165*b7c941bbSAndroid Build Coastguard Worker      logging.info('RGB image dimensions: %s', str(rgbimg.shape))
166*b7c941bbSAndroid Build Coastguard Worker      logging.info('RGB image top-left pixel: %s', str(rgbimg[0, 0]))
167*b7c941bbSAndroid Build Coastguard Worker
168*b7c941bbSAndroid Build Coastguard Worker      # Grab a center tile from the image; this returns a new image. Save
169*b7c941bbSAndroid Build Coastguard Worker      # this tile image. In this case, the tile is the middle 10% x 10%
170*b7c941bbSAndroid Build Coastguard Worker      # rectangle.
171*b7c941bbSAndroid Build Coastguard Worker      tile = image_processing_utils.get_image_patch(
172*b7c941bbSAndroid Build Coastguard Worker          rgbimg, 0.45, 0.45, 0.1, 0.1)
173*b7c941bbSAndroid Build Coastguard Worker      image_processing_utils.write_image(tile, f'{file_name}_rgb_2_tile.jpg')
174*b7c941bbSAndroid Build Coastguard Worker
175*b7c941bbSAndroid Build Coastguard Worker      # Compute the mean values of the center tile image.
176*b7c941bbSAndroid Build Coastguard Worker      rgb_means = image_processing_utils.compute_image_means(tile)
177*b7c941bbSAndroid Build Coastguard Worker      logging.info('RGB means: %s', str(rgb_means))
178*b7c941bbSAndroid Build Coastguard Worker
179*b7c941bbSAndroid Build Coastguard Worker      # Apply a lookup table to the image, and save the new version. The LUT
180*b7c941bbSAndroid Build Coastguard Worker      # is basically a tonemap, and can be used to implement a gamma curve.
181*b7c941bbSAndroid Build Coastguard Worker      # In this case, the LUT is used to double the value of each pixel.
182*b7c941bbSAndroid Build Coastguard Worker      lut = numpy.array([2*i for i in range(65536)])
183*b7c941bbSAndroid Build Coastguard Worker      rgbimg_lut = image_processing_utils.apply_lut_to_image(rgbimg, lut)
184*b7c941bbSAndroid Build Coastguard Worker      image_processing_utils.write_image(
185*b7c941bbSAndroid Build Coastguard Worker          rgbimg_lut, f'{file_name}_rgb_2_lut.jpg')
186*b7c941bbSAndroid Build Coastguard Worker
187*b7c941bbSAndroid Build Coastguard Worker      # Compute a histogram of the luma image, in 256 buckets.
188*b7c941bbSAndroid Build Coastguard Worker      yimg, _, _ = image_processing_utils.convert_capture_to_planes(cap)
189*b7c941bbSAndroid Build Coastguard Worker      hist, _ = numpy.histogram(yimg*255, 256, (0, 256))
190*b7c941bbSAndroid Build Coastguard Worker
191*b7c941bbSAndroid Build Coastguard Worker      # Plot the histogram using matplotlib, and save as a PNG image.
192*b7c941bbSAndroid Build Coastguard Worker      plt.plot(range(256), hist.tolist())
193*b7c941bbSAndroid Build Coastguard Worker      plt.xlabel('Luma DN')
194*b7c941bbSAndroid Build Coastguard Worker      plt.ylabel('Pixel count')
195*b7c941bbSAndroid Build Coastguard Worker      plt.title('Histogram of luma channel of captured image')
196*b7c941bbSAndroid Build Coastguard Worker      plt.savefig(f'{file_name}_histogram.png')
197*b7c941bbSAndroid Build Coastguard Worker
198*b7c941bbSAndroid Build Coastguard Worker      # Capture a frame to be returned as a JPEG. Load it as an RGB image,
199*b7c941bbSAndroid Build Coastguard Worker      # then save it back as a JPEG.
200*b7c941bbSAndroid Build Coastguard Worker      cap = cam.do_capture(req, cam.CAP_JPEG)
201*b7c941bbSAndroid Build Coastguard Worker      rgbimg = image_processing_utils.convert_capture_to_rgb_image(cap)
202*b7c941bbSAndroid Build Coastguard Worker      image_processing_utils.write_image(rgbimg, f'{file_name}_jpg.jpg')
203*b7c941bbSAndroid Build Coastguard Worker      r, _, _ = image_processing_utils.convert_capture_to_planes(cap)
204*b7c941bbSAndroid Build Coastguard Worker      image_processing_utils.write_image(r, f'{file_name}_r.jpg')
205*b7c941bbSAndroid Build Coastguard Worker
206*b7c941bbSAndroid Build Coastguard Worker# This is the standard boilerplate in each test that allows the script to both
207*b7c941bbSAndroid Build Coastguard Worker# be executed directly and imported as a module.
208*b7c941bbSAndroid Build Coastguard Workerif __name__ == '__main__':
209*b7c941bbSAndroid Build Coastguard Worker  test_runner.main()
210*b7c941bbSAndroid Build Coastguard Worker
211