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