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