1*b7c941bbSAndroid Build Coastguard Worker# Copyright 2013 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"""Image processing utility functions.""" 15*b7c941bbSAndroid Build Coastguard Worker 16*b7c941bbSAndroid Build Coastguard Worker 17*b7c941bbSAndroid Build Coastguard Workerimport copy 18*b7c941bbSAndroid Build Coastguard Workerimport io 19*b7c941bbSAndroid Build Coastguard Workerimport logging 20*b7c941bbSAndroid Build Coastguard Workerimport math 21*b7c941bbSAndroid Build Coastguard Workerimport matplotlib 22*b7c941bbSAndroid Build Coastguard Workerfrom matplotlib import pyplot as plt 23*b7c941bbSAndroid Build Coastguard Workerimport os 24*b7c941bbSAndroid Build Coastguard Workerimport sys 25*b7c941bbSAndroid Build Coastguard Worker 26*b7c941bbSAndroid Build Coastguard Workerimport capture_request_utils 27*b7c941bbSAndroid Build Coastguard Workerimport error_util 28*b7c941bbSAndroid Build Coastguard Workerimport noise_model_constants 29*b7c941bbSAndroid Build Coastguard Workerimport numpy 30*b7c941bbSAndroid Build Coastguard Workerfrom PIL import Image 31*b7c941bbSAndroid Build Coastguard Workerfrom PIL import ImageCms 32*b7c941bbSAndroid Build Coastguard Worker 33*b7c941bbSAndroid Build Coastguard Worker 34*b7c941bbSAndroid Build Coastguard Worker_CMAP_BLUE = ('black', 'blue', 'lightblue') 35*b7c941bbSAndroid Build Coastguard Worker_CMAP_GREEN = ('black', 'green', 'lightgreen') 36*b7c941bbSAndroid Build Coastguard Worker_CMAP_RED = ('black', 'red', 'lightcoral') 37*b7c941bbSAndroid Build Coastguard Worker_CMAP_SIZE = 6 # 6 inches 38*b7c941bbSAndroid Build Coastguard Worker_NUM_RAW_CHANNELS = 4 # R, Gr, Gb, B 39*b7c941bbSAndroid Build Coastguard Worker 40*b7c941bbSAndroid Build Coastguard WorkerLENS_SHADING_MAP_ON = 1 41*b7c941bbSAndroid Build Coastguard Worker 42*b7c941bbSAndroid Build Coastguard Worker# The matrix is from JFIF spec 43*b7c941bbSAndroid Build Coastguard WorkerDEFAULT_YUV_TO_RGB_CCM = numpy.matrix([[1.000, 0.000, 1.402], 44*b7c941bbSAndroid Build Coastguard Worker [1.000, -0.344, -0.714], 45*b7c941bbSAndroid Build Coastguard Worker [1.000, 1.772, 0.000]]) 46*b7c941bbSAndroid Build Coastguard Worker 47*b7c941bbSAndroid Build Coastguard WorkerDEFAULT_YUV_OFFSETS = numpy.array([0, 128, 128], dtype=numpy.uint8) 48*b7c941bbSAndroid Build Coastguard WorkerMAX_LUT_SIZE = 65536 49*b7c941bbSAndroid Build Coastguard WorkerDEFAULT_GAMMA_LUT = numpy.array([ 50*b7c941bbSAndroid Build Coastguard Worker math.floor((MAX_LUT_SIZE-1) * math.pow(i/(MAX_LUT_SIZE-1), 1/2.2) + 0.5) 51*b7c941bbSAndroid Build Coastguard Worker for i in range(MAX_LUT_SIZE)]) 52*b7c941bbSAndroid Build Coastguard WorkerRGB2GRAY_WEIGHTS = (0.299, 0.587, 0.114) 53*b7c941bbSAndroid Build Coastguard WorkerTEST_IMG_DIR = os.path.join(os.environ['CAMERA_ITS_TOP'], 'test_images') 54*b7c941bbSAndroid Build Coastguard Worker 55*b7c941bbSAndroid Build Coastguard Worker# Expected adapted primaries in ICC profile per color space 56*b7c941bbSAndroid Build Coastguard WorkerEXPECTED_RX_P3 = 0.682 57*b7c941bbSAndroid Build Coastguard WorkerEXPECTED_RY_P3 = 0.319 58*b7c941bbSAndroid Build Coastguard WorkerEXPECTED_GX_P3 = 0.285 59*b7c941bbSAndroid Build Coastguard WorkerEXPECTED_GY_P3 = 0.675 60*b7c941bbSAndroid Build Coastguard WorkerEXPECTED_BX_P3 = 0.156 61*b7c941bbSAndroid Build Coastguard WorkerEXPECTED_BY_P3 = 0.066 62*b7c941bbSAndroid Build Coastguard Worker 63*b7c941bbSAndroid Build Coastguard WorkerEXPECTED_RX_SRGB = 0.648 64*b7c941bbSAndroid Build Coastguard WorkerEXPECTED_RY_SRGB = 0.331 65*b7c941bbSAndroid Build Coastguard WorkerEXPECTED_GX_SRGB = 0.321 66*b7c941bbSAndroid Build Coastguard WorkerEXPECTED_GY_SRGB = 0.598 67*b7c941bbSAndroid Build Coastguard WorkerEXPECTED_BX_SRGB = 0.156 68*b7c941bbSAndroid Build Coastguard WorkerEXPECTED_BY_SRGB = 0.066 69*b7c941bbSAndroid Build Coastguard Worker 70*b7c941bbSAndroid Build Coastguard Worker# Color conversion matrix for DISPLAY P3 to CIEXYZ 71*b7c941bbSAndroid Build Coastguard WorkerP3_TO_XYZ = numpy.array([ 72*b7c941bbSAndroid Build Coastguard Worker [0.5151187, 0.2919778, 0.1571035], 73*b7c941bbSAndroid Build Coastguard Worker [0.2411892, 0.6922441, 0.0665668], 74*b7c941bbSAndroid Build Coastguard Worker [-0.0010505, 0.0418791, 0.7840713] 75*b7c941bbSAndroid Build Coastguard Worker]).transpose() 76*b7c941bbSAndroid Build Coastguard Worker 77*b7c941bbSAndroid Build Coastguard Worker# Chosen empirically - tolerance for the point in triangle test for colorspace 78*b7c941bbSAndroid Build Coastguard Worker# chromaticities 79*b7c941bbSAndroid Build Coastguard WorkerCOLORSPACE_TRIANGLE_AREA_TOL = 0.00039 80*b7c941bbSAndroid Build Coastguard Worker 81*b7c941bbSAndroid Build Coastguard Worker 82*b7c941bbSAndroid Build Coastguard Workerdef plot_lsc_maps(lsc_maps, plot_name, test_name_with_log_path): 83*b7c941bbSAndroid Build Coastguard Worker """Plot the lens shading correction maps. 84*b7c941bbSAndroid Build Coastguard Worker 85*b7c941bbSAndroid Build Coastguard Worker Args: 86*b7c941bbSAndroid Build Coastguard Worker lsc_maps: 4D np array; r, gr, gb, b lens shading correction maps. 87*b7c941bbSAndroid Build Coastguard Worker plot_name: str; identifier for maps ('full_scale' or 'metadata'). 88*b7c941bbSAndroid Build Coastguard Worker test_name_with_log_path: str; test name with log_path location. 89*b7c941bbSAndroid Build Coastguard Worker 90*b7c941bbSAndroid Build Coastguard Worker Returns: 91*b7c941bbSAndroid Build Coastguard Worker None, but generates and saves plots. 92*b7c941bbSAndroid Build Coastguard Worker """ 93*b7c941bbSAndroid Build Coastguard Worker aspect_ratio = lsc_maps[:, :, 0].shape[1] / lsc_maps[:, :, 0].shape[0] 94*b7c941bbSAndroid Build Coastguard Worker plot_w = 1 + aspect_ratio * _CMAP_SIZE # add 1 for heatmap legend 95*b7c941bbSAndroid Build Coastguard Worker plt.figure(plot_name, figsize=(plot_w, _CMAP_SIZE)) 96*b7c941bbSAndroid Build Coastguard Worker plt.suptitle(plot_name) 97*b7c941bbSAndroid Build Coastguard Worker 98*b7c941bbSAndroid Build Coastguard Worker plt.subplot(2, 2, 1) # 2x2 top left 99*b7c941bbSAndroid Build Coastguard Worker plt.title('R') 100*b7c941bbSAndroid Build Coastguard Worker cmap = matplotlib.colors.LinearSegmentedColormap.from_list('', _CMAP_RED) 101*b7c941bbSAndroid Build Coastguard Worker plt.pcolormesh(lsc_maps[:, :, 0], cmap=cmap) 102*b7c941bbSAndroid Build Coastguard Worker plt.colorbar() 103*b7c941bbSAndroid Build Coastguard Worker 104*b7c941bbSAndroid Build Coastguard Worker plt.subplot(2, 2, 2) # 2x2 top right 105*b7c941bbSAndroid Build Coastguard Worker plt.title('Gr') 106*b7c941bbSAndroid Build Coastguard Worker cmap = matplotlib.colors.LinearSegmentedColormap.from_list('', _CMAP_GREEN) 107*b7c941bbSAndroid Build Coastguard Worker plt.pcolormesh(lsc_maps[:, :, 1], cmap=cmap) 108*b7c941bbSAndroid Build Coastguard Worker plt.colorbar() 109*b7c941bbSAndroid Build Coastguard Worker 110*b7c941bbSAndroid Build Coastguard Worker plt.subplot(2, 2, 3) # 2x2 bottom left 111*b7c941bbSAndroid Build Coastguard Worker plt.title('Gb') 112*b7c941bbSAndroid Build Coastguard Worker cmap = matplotlib.colors.LinearSegmentedColormap.from_list('', _CMAP_GREEN) 113*b7c941bbSAndroid Build Coastguard Worker plt.pcolormesh(lsc_maps[:, :, 2], cmap=cmap) 114*b7c941bbSAndroid Build Coastguard Worker plt.colorbar() 115*b7c941bbSAndroid Build Coastguard Worker 116*b7c941bbSAndroid Build Coastguard Worker plt.subplot(2, 2, 4) # 2x2 bottom right 117*b7c941bbSAndroid Build Coastguard Worker plt.title('B') 118*b7c941bbSAndroid Build Coastguard Worker cmap = matplotlib.colors.LinearSegmentedColormap.from_list('', _CMAP_BLUE) 119*b7c941bbSAndroid Build Coastguard Worker plt.pcolormesh(lsc_maps[:, :, 3], cmap=cmap) 120*b7c941bbSAndroid Build Coastguard Worker plt.colorbar() 121*b7c941bbSAndroid Build Coastguard Worker 122*b7c941bbSAndroid Build Coastguard Worker plt.savefig(f'{test_name_with_log_path}_{plot_name}_cmaps.png') 123*b7c941bbSAndroid Build Coastguard Worker 124*b7c941bbSAndroid Build Coastguard Worker 125*b7c941bbSAndroid Build Coastguard Workerdef capture_scene_image(cam, props, name_with_log_path): 126*b7c941bbSAndroid Build Coastguard Worker """Take a picture of the scene on test FAIL.""" 127*b7c941bbSAndroid Build Coastguard Worker req = capture_request_utils.auto_capture_request() 128*b7c941bbSAndroid Build Coastguard Worker img = convert_capture_to_rgb_image( 129*b7c941bbSAndroid Build Coastguard Worker cam.do_capture(req, cam.CAP_YUV), props=props) 130*b7c941bbSAndroid Build Coastguard Worker write_image(img, f'{name_with_log_path}_scene.jpg', True) 131*b7c941bbSAndroid Build Coastguard Worker 132*b7c941bbSAndroid Build Coastguard Worker 133*b7c941bbSAndroid Build Coastguard Workerdef convert_image_to_uint8(image): 134*b7c941bbSAndroid Build Coastguard Worker image = image*255 135*b7c941bbSAndroid Build Coastguard Worker return image.astype(numpy.uint8) 136*b7c941bbSAndroid Build Coastguard Worker 137*b7c941bbSAndroid Build Coastguard Worker 138*b7c941bbSAndroid Build Coastguard Workerdef assert_props_is_not_none(props): 139*b7c941bbSAndroid Build Coastguard Worker if not props: 140*b7c941bbSAndroid Build Coastguard Worker raise AssertionError('props is None') 141*b7c941bbSAndroid Build Coastguard Worker 142*b7c941bbSAndroid Build Coastguard Worker 143*b7c941bbSAndroid Build Coastguard Workerdef assert_capture_width_and_height(cap, width, height): 144*b7c941bbSAndroid Build Coastguard Worker if cap['width'] != width or cap['height'] != height: 145*b7c941bbSAndroid Build Coastguard Worker raise AssertionError( 146*b7c941bbSAndroid Build Coastguard Worker 'Unexpected capture WxH size, expected [{}x{}], actual [{}x{}]'.format( 147*b7c941bbSAndroid Build Coastguard Worker width, height, cap['width'], cap['height'] 148*b7c941bbSAndroid Build Coastguard Worker ) 149*b7c941bbSAndroid Build Coastguard Worker ) 150*b7c941bbSAndroid Build Coastguard Worker 151*b7c941bbSAndroid Build Coastguard Worker 152*b7c941bbSAndroid Build Coastguard Workerdef apply_lens_shading_map(color_plane, black_level, white_level, lsc_map): 153*b7c941bbSAndroid Build Coastguard Worker """Apply the lens shading map to the color plane. 154*b7c941bbSAndroid Build Coastguard Worker 155*b7c941bbSAndroid Build Coastguard Worker Args: 156*b7c941bbSAndroid Build Coastguard Worker color_plane: 2D np array for color plane with values [0.0, 1.0]. 157*b7c941bbSAndroid Build Coastguard Worker black_level: float; black level for the color plane. 158*b7c941bbSAndroid Build Coastguard Worker white_level: int; full scale for the color plane. 159*b7c941bbSAndroid Build Coastguard Worker lsc_map: 2D np array lens shading matching size of color_plane. 160*b7c941bbSAndroid Build Coastguard Worker 161*b7c941bbSAndroid Build Coastguard Worker Returns: 162*b7c941bbSAndroid Build Coastguard Worker color_plane with lsc applied. 163*b7c941bbSAndroid Build Coastguard Worker """ 164*b7c941bbSAndroid Build Coastguard Worker logging.debug('color plane pre-lsc min, max: %.4f, %.4f', 165*b7c941bbSAndroid Build Coastguard Worker numpy.min(color_plane), numpy.max(color_plane)) 166*b7c941bbSAndroid Build Coastguard Worker color_plane = (numpy.multiply((color_plane * white_level - black_level), 167*b7c941bbSAndroid Build Coastguard Worker lsc_map) 168*b7c941bbSAndroid Build Coastguard Worker + black_level) / white_level 169*b7c941bbSAndroid Build Coastguard Worker logging.debug('color plane post-lsc min, max: %.4f, %.4f', 170*b7c941bbSAndroid Build Coastguard Worker numpy.min(color_plane), numpy.max(color_plane)) 171*b7c941bbSAndroid Build Coastguard Worker return color_plane 172*b7c941bbSAndroid Build Coastguard Worker 173*b7c941bbSAndroid Build Coastguard Worker 174*b7c941bbSAndroid Build Coastguard Workerdef populate_lens_shading_map(img_shape, lsc_map): 175*b7c941bbSAndroid Build Coastguard Worker """Helper function to create LSC coeifficients for RAW image. 176*b7c941bbSAndroid Build Coastguard Worker 177*b7c941bbSAndroid Build Coastguard Worker Args: 178*b7c941bbSAndroid Build Coastguard Worker img_shape: tuple; RAW image shape. 179*b7c941bbSAndroid Build Coastguard Worker lsc_map: 2D low resolution array with lens shading map values. 180*b7c941bbSAndroid Build Coastguard Worker 181*b7c941bbSAndroid Build Coastguard Worker Returns: 182*b7c941bbSAndroid Build Coastguard Worker value for lens shading map at point (x, y) in the image. 183*b7c941bbSAndroid Build Coastguard Worker """ 184*b7c941bbSAndroid Build Coastguard Worker img_w, img_h = img_shape[1], img_shape[0] 185*b7c941bbSAndroid Build Coastguard Worker map_w, map_h = lsc_map.shape[1], lsc_map.shape[0] 186*b7c941bbSAndroid Build Coastguard Worker 187*b7c941bbSAndroid Build Coastguard Worker x, y = numpy.meshgrid(numpy.arange(img_w), numpy.arange(img_h)) 188*b7c941bbSAndroid Build Coastguard Worker 189*b7c941bbSAndroid Build Coastguard Worker # (u,v) is lsc map location, values [0, map_w-1], [0, map_h-1] 190*b7c941bbSAndroid Build Coastguard Worker # Vectorized calculations 191*b7c941bbSAndroid Build Coastguard Worker u = x * (map_w - 1) / (img_w - 1) 192*b7c941bbSAndroid Build Coastguard Worker v = y * (map_h - 1) / (img_h - 1) 193*b7c941bbSAndroid Build Coastguard Worker u_min = numpy.floor(u).astype(int) 194*b7c941bbSAndroid Build Coastguard Worker v_min = numpy.floor(v).astype(int) 195*b7c941bbSAndroid Build Coastguard Worker u_frac = u - u_min 196*b7c941bbSAndroid Build Coastguard Worker v_frac = v - v_min 197*b7c941bbSAndroid Build Coastguard Worker u_max = numpy.where(u_frac > 0, u_min + 1, u_min) 198*b7c941bbSAndroid Build Coastguard Worker v_max = numpy.where(v_frac > 0, v_min + 1, v_min) 199*b7c941bbSAndroid Build Coastguard Worker 200*b7c941bbSAndroid Build Coastguard Worker # Gather LSC values, handling edge cases (optional) 201*b7c941bbSAndroid Build Coastguard Worker lsc_tl = lsc_map[(v_min, u_min)] 202*b7c941bbSAndroid Build Coastguard Worker lsc_tr = lsc_map[(v_min, u_max)] 203*b7c941bbSAndroid Build Coastguard Worker lsc_bl = lsc_map[(v_max, u_min)] 204*b7c941bbSAndroid Build Coastguard Worker lsc_br = lsc_map[(v_max, u_max)] 205*b7c941bbSAndroid Build Coastguard Worker 206*b7c941bbSAndroid Build Coastguard Worker # Bilinear interpolation (vectorized) 207*b7c941bbSAndroid Build Coastguard Worker lsc_t = lsc_tl * (1 - u_frac) + lsc_tr * u_frac 208*b7c941bbSAndroid Build Coastguard Worker lsc_b = lsc_bl * (1 - u_frac) + lsc_br * u_frac 209*b7c941bbSAndroid Build Coastguard Worker 210*b7c941bbSAndroid Build Coastguard Worker return lsc_t * (1 - v_frac) + lsc_b * v_frac 211*b7c941bbSAndroid Build Coastguard Worker 212*b7c941bbSAndroid Build Coastguard Worker 213*b7c941bbSAndroid Build Coastguard Workerdef unpack_lsc_map_from_metadata(metadata): 214*b7c941bbSAndroid Build Coastguard Worker """Get lens shading correction map from metadata and turn into 3D array. 215*b7c941bbSAndroid Build Coastguard Worker 216*b7c941bbSAndroid Build Coastguard Worker Args: 217*b7c941bbSAndroid Build Coastguard Worker metadata: dict; metadata from RAW capture. 218*b7c941bbSAndroid Build Coastguard Worker 219*b7c941bbSAndroid Build Coastguard Worker Returns: 220*b7c941bbSAndroid Build Coastguard Worker 3D numpy array of lens shading maps. 221*b7c941bbSAndroid Build Coastguard Worker """ 222*b7c941bbSAndroid Build Coastguard Worker lsc_metadata = metadata['android.statistics.lensShadingCorrectionMap'] 223*b7c941bbSAndroid Build Coastguard Worker lsc_map_w, lsc_map_h = lsc_metadata['width'], lsc_metadata['height'] 224*b7c941bbSAndroid Build Coastguard Worker lsc_map = lsc_metadata['map'] 225*b7c941bbSAndroid Build Coastguard Worker logging.debug( 226*b7c941bbSAndroid Build Coastguard Worker 'lensShadingCorrectionMap (H, W): (%d, %d)', lsc_map_h, lsc_map_w 227*b7c941bbSAndroid Build Coastguard Worker ) 228*b7c941bbSAndroid Build Coastguard Worker return numpy.array(lsc_map).reshape(lsc_map_h, lsc_map_w, _NUM_RAW_CHANNELS) 229*b7c941bbSAndroid Build Coastguard Worker 230*b7c941bbSAndroid Build Coastguard Worker 231*b7c941bbSAndroid Build Coastguard Workerdef convert_raw_capture_to_rgb_image(cap_raw, props, raw_fmt, 232*b7c941bbSAndroid Build Coastguard Worker log_path_with_name): 233*b7c941bbSAndroid Build Coastguard Worker """Convert a RAW captured image object to a RGB image. 234*b7c941bbSAndroid Build Coastguard Worker 235*b7c941bbSAndroid Build Coastguard Worker Args: 236*b7c941bbSAndroid Build Coastguard Worker cap_raw: A RAW capture object as returned by its_session_utils.do_capture. 237*b7c941bbSAndroid Build Coastguard Worker props: camera properties object (of static values). 238*b7c941bbSAndroid Build Coastguard Worker raw_fmt: string of type 'raw', 'raw10', 'raw12'. 239*b7c941bbSAndroid Build Coastguard Worker log_path_with_name: string with test name and save location. 240*b7c941bbSAndroid Build Coastguard Worker 241*b7c941bbSAndroid Build Coastguard Worker Returns: 242*b7c941bbSAndroid Build Coastguard Worker RGB float-3 image array, with pixel values in [0.0, 1.0]. 243*b7c941bbSAndroid Build Coastguard Worker """ 244*b7c941bbSAndroid Build Coastguard Worker shading_mode = cap_raw['metadata']['android.shading.mode'] 245*b7c941bbSAndroid Build Coastguard Worker lens_shading_map_mode = cap_raw[ 246*b7c941bbSAndroid Build Coastguard Worker 'metadata'].get('android.statistics.lensShadingMapMode') 247*b7c941bbSAndroid Build Coastguard Worker lens_shading_applied = props['android.sensor.info.lensShadingApplied'] 248*b7c941bbSAndroid Build Coastguard Worker control_af_mode = cap_raw['metadata']['android.control.afMode'] 249*b7c941bbSAndroid Build Coastguard Worker focus_distance = cap_raw['metadata']['android.lens.focusDistance'] 250*b7c941bbSAndroid Build Coastguard Worker logging.debug('%s capture AF mode: %s', raw_fmt, control_af_mode) 251*b7c941bbSAndroid Build Coastguard Worker logging.debug('%s capture focus distance: %s', raw_fmt, focus_distance) 252*b7c941bbSAndroid Build Coastguard Worker logging.debug('%s capture shading mode: %d', raw_fmt, shading_mode) 253*b7c941bbSAndroid Build Coastguard Worker logging.debug('lensShadingMapApplied: %r', lens_shading_applied) 254*b7c941bbSAndroid Build Coastguard Worker logging.debug('lensShadingMapMode: %s', lens_shading_map_mode) 255*b7c941bbSAndroid Build Coastguard Worker 256*b7c941bbSAndroid Build Coastguard Worker # Split RAW to RGB conversion in 2 to allow LSC application (if needed). 257*b7c941bbSAndroid Build Coastguard Worker r, gr, gb, b = convert_capture_to_planes(cap_raw, props=props) 258*b7c941bbSAndroid Build Coastguard Worker 259*b7c941bbSAndroid Build Coastguard Worker # get from metadata, upsample, and apply 260*b7c941bbSAndroid Build Coastguard Worker if lens_shading_map_mode == LENS_SHADING_MAP_ON: 261*b7c941bbSAndroid Build Coastguard Worker logging.debug('Applying lens shading map') 262*b7c941bbSAndroid Build Coastguard Worker plot_name_stem_with_log_path = f'{log_path_with_name}_{raw_fmt}' 263*b7c941bbSAndroid Build Coastguard Worker black_levels = get_black_levels(props, cap_raw) 264*b7c941bbSAndroid Build Coastguard Worker white_level = int(props['android.sensor.info.whiteLevel']) 265*b7c941bbSAndroid Build Coastguard Worker lsc_maps = unpack_lsc_map_from_metadata(cap_raw['metadata']) 266*b7c941bbSAndroid Build Coastguard Worker plot_lsc_maps(lsc_maps, 'metadata', plot_name_stem_with_log_path) 267*b7c941bbSAndroid Build Coastguard Worker lsc_map_fs_r = populate_lens_shading_map(r.shape, lsc_maps[:, :, 0]) 268*b7c941bbSAndroid Build Coastguard Worker lsc_map_fs_gr = populate_lens_shading_map(gr.shape, lsc_maps[:, :, 1]) 269*b7c941bbSAndroid Build Coastguard Worker lsc_map_fs_gb = populate_lens_shading_map(gb.shape, lsc_maps[:, :, 2]) 270*b7c941bbSAndroid Build Coastguard Worker lsc_map_fs_b = populate_lens_shading_map(b.shape, lsc_maps[:, :, 3]) 271*b7c941bbSAndroid Build Coastguard Worker plot_lsc_maps( 272*b7c941bbSAndroid Build Coastguard Worker numpy.dstack((lsc_map_fs_r, lsc_map_fs_gr, lsc_map_fs_gb, 273*b7c941bbSAndroid Build Coastguard Worker lsc_map_fs_b)), 274*b7c941bbSAndroid Build Coastguard Worker 'fullscale', plot_name_stem_with_log_path 275*b7c941bbSAndroid Build Coastguard Worker ) 276*b7c941bbSAndroid Build Coastguard Worker r = apply_lens_shading_map( 277*b7c941bbSAndroid Build Coastguard Worker r[:, :, 0], black_levels[0], white_level, lsc_map_fs_r 278*b7c941bbSAndroid Build Coastguard Worker ) 279*b7c941bbSAndroid Build Coastguard Worker gr = apply_lens_shading_map( 280*b7c941bbSAndroid Build Coastguard Worker gr[:, :, 0], black_levels[1], white_level, lsc_map_fs_gr 281*b7c941bbSAndroid Build Coastguard Worker ) 282*b7c941bbSAndroid Build Coastguard Worker gb = apply_lens_shading_map( 283*b7c941bbSAndroid Build Coastguard Worker gb[:, :, 0], black_levels[2], white_level, lsc_map_fs_gb 284*b7c941bbSAndroid Build Coastguard Worker ) 285*b7c941bbSAndroid Build Coastguard Worker b = apply_lens_shading_map( 286*b7c941bbSAndroid Build Coastguard Worker b[:, :, 0], black_levels[3], white_level, lsc_map_fs_b 287*b7c941bbSAndroid Build Coastguard Worker ) 288*b7c941bbSAndroid Build Coastguard Worker img = convert_raw_to_rgb_image(r, gr, gb, b, props, cap_raw['metadata']) 289*b7c941bbSAndroid Build Coastguard Worker return img 290*b7c941bbSAndroid Build Coastguard Worker 291*b7c941bbSAndroid Build Coastguard Worker 292*b7c941bbSAndroid Build Coastguard Workerdef convert_capture_to_rgb_image(cap, 293*b7c941bbSAndroid Build Coastguard Worker props=None, 294*b7c941bbSAndroid Build Coastguard Worker apply_ccm_raw_to_rgb=True): 295*b7c941bbSAndroid Build Coastguard Worker """Convert a captured image object to a RGB image. 296*b7c941bbSAndroid Build Coastguard Worker 297*b7c941bbSAndroid Build Coastguard Worker Args: 298*b7c941bbSAndroid Build Coastguard Worker cap: A capture object as returned by its_session_utils.do_capture. 299*b7c941bbSAndroid Build Coastguard Worker props: (Optional) camera properties object (of static values); 300*b7c941bbSAndroid Build Coastguard Worker required for processing raw images. 301*b7c941bbSAndroid Build Coastguard Worker apply_ccm_raw_to_rgb: (Optional) boolean to apply color correction matrix. 302*b7c941bbSAndroid Build Coastguard Worker 303*b7c941bbSAndroid Build Coastguard Worker Returns: 304*b7c941bbSAndroid Build Coastguard Worker RGB float-3 image array, with pixel values in [0.0, 1.0]. 305*b7c941bbSAndroid Build Coastguard Worker """ 306*b7c941bbSAndroid Build Coastguard Worker w = cap['width'] 307*b7c941bbSAndroid Build Coastguard Worker h = cap['height'] 308*b7c941bbSAndroid Build Coastguard Worker if cap['format'] == 'raw10' or cap['format'] == 'raw10QuadBayer': 309*b7c941bbSAndroid Build Coastguard Worker assert_props_is_not_none(props) 310*b7c941bbSAndroid Build Coastguard Worker is_quad_bayer = cap['format'] == 'raw10QuadBayer' 311*b7c941bbSAndroid Build Coastguard Worker cap = unpack_raw10_capture(cap, is_quad_bayer) 312*b7c941bbSAndroid Build Coastguard Worker 313*b7c941bbSAndroid Build Coastguard Worker if cap['format'] == 'raw12': 314*b7c941bbSAndroid Build Coastguard Worker assert_props_is_not_none(props) 315*b7c941bbSAndroid Build Coastguard Worker cap = unpack_raw12_capture(cap) 316*b7c941bbSAndroid Build Coastguard Worker 317*b7c941bbSAndroid Build Coastguard Worker if cap['format'] == 'yuv': 318*b7c941bbSAndroid Build Coastguard Worker y = cap['data'][0: w * h] 319*b7c941bbSAndroid Build Coastguard Worker u = cap['data'][w * h: w * h * 5//4] 320*b7c941bbSAndroid Build Coastguard Worker v = cap['data'][w * h * 5//4: w * h * 6//4] 321*b7c941bbSAndroid Build Coastguard Worker return convert_yuv420_planar_to_rgb_image(y, u, v, w, h) 322*b7c941bbSAndroid Build Coastguard Worker elif cap['format'] == 'jpeg' or cap['format'] == 'jpeg_r': 323*b7c941bbSAndroid Build Coastguard Worker return decompress_jpeg_to_rgb_image(cap['data']) 324*b7c941bbSAndroid Build Coastguard Worker elif (cap['format'] in ('raw', 'rawQuadBayer') or 325*b7c941bbSAndroid Build Coastguard Worker cap['format'] in noise_model_constants.VALID_RAW_STATS_FORMATS): 326*b7c941bbSAndroid Build Coastguard Worker assert_props_is_not_none(props) 327*b7c941bbSAndroid Build Coastguard Worker r, gr, gb, b = convert_capture_to_planes(cap, props) 328*b7c941bbSAndroid Build Coastguard Worker return convert_raw_to_rgb_image( 329*b7c941bbSAndroid Build Coastguard Worker r, gr, gb, b, props, cap['metadata'], apply_ccm_raw_to_rgb) 330*b7c941bbSAndroid Build Coastguard Worker elif cap['format'] == 'y8': 331*b7c941bbSAndroid Build Coastguard Worker y = cap['data'][0: w * h] 332*b7c941bbSAndroid Build Coastguard Worker return convert_y8_to_rgb_image(y, w, h) 333*b7c941bbSAndroid Build Coastguard Worker else: 334*b7c941bbSAndroid Build Coastguard Worker raise error_util.CameraItsError(f"Invalid format {cap['format']}") 335*b7c941bbSAndroid Build Coastguard Worker 336*b7c941bbSAndroid Build Coastguard Worker 337*b7c941bbSAndroid Build Coastguard Workerdef unpack_raw10_capture(cap, is_quad_bayer=False): 338*b7c941bbSAndroid Build Coastguard Worker """Unpack a raw-10 capture to a raw-16 capture. 339*b7c941bbSAndroid Build Coastguard Worker 340*b7c941bbSAndroid Build Coastguard Worker Args: 341*b7c941bbSAndroid Build Coastguard Worker cap: A raw-10 capture object. 342*b7c941bbSAndroid Build Coastguard Worker is_quad_bayer: Boolean flag for Bayer or Quad Bayer capture. 343*b7c941bbSAndroid Build Coastguard Worker 344*b7c941bbSAndroid Build Coastguard Worker Returns: 345*b7c941bbSAndroid Build Coastguard Worker New capture object with raw-16 data. 346*b7c941bbSAndroid Build Coastguard Worker """ 347*b7c941bbSAndroid Build Coastguard Worker # Data is packed as 4x10b pixels in 5 bytes, with the first 4 bytes holding 348*b7c941bbSAndroid Build Coastguard Worker # the MSBs of the pixels, and the 5th byte holding 4x2b LSBs. 349*b7c941bbSAndroid Build Coastguard Worker w, h = cap['width'], cap['height'] 350*b7c941bbSAndroid Build Coastguard Worker if w % 4 != 0: 351*b7c941bbSAndroid Build Coastguard Worker raise error_util.CameraItsError('Invalid raw-10 buffer width') 352*b7c941bbSAndroid Build Coastguard Worker cap = copy.deepcopy(cap) 353*b7c941bbSAndroid Build Coastguard Worker cap['data'] = unpack_raw10_image(cap['data'].reshape(h, w * 5 // 4)) 354*b7c941bbSAndroid Build Coastguard Worker cap['format'] = 'rawQuadBayer' if is_quad_bayer else 'raw' 355*b7c941bbSAndroid Build Coastguard Worker return cap 356*b7c941bbSAndroid Build Coastguard Worker 357*b7c941bbSAndroid Build Coastguard Worker 358*b7c941bbSAndroid Build Coastguard Workerdef unpack_raw10_image(img): 359*b7c941bbSAndroid Build Coastguard Worker """Unpack a raw-10 image to a raw-16 image. 360*b7c941bbSAndroid Build Coastguard Worker 361*b7c941bbSAndroid Build Coastguard Worker Output image will have the 10 LSBs filled in each 16b word, and the 6 MSBs 362*b7c941bbSAndroid Build Coastguard Worker will be set to zero. 363*b7c941bbSAndroid Build Coastguard Worker 364*b7c941bbSAndroid Build Coastguard Worker Args: 365*b7c941bbSAndroid Build Coastguard Worker img: A raw-10 image, as a uint8 numpy array. 366*b7c941bbSAndroid Build Coastguard Worker 367*b7c941bbSAndroid Build Coastguard Worker Returns: 368*b7c941bbSAndroid Build Coastguard Worker Image as a uint16 numpy array, with all row padding stripped. 369*b7c941bbSAndroid Build Coastguard Worker """ 370*b7c941bbSAndroid Build Coastguard Worker if img.shape[1] % 5 != 0: 371*b7c941bbSAndroid Build Coastguard Worker raise error_util.CameraItsError('Invalid raw-10 buffer width') 372*b7c941bbSAndroid Build Coastguard Worker w = img.shape[1] * 4 // 5 373*b7c941bbSAndroid Build Coastguard Worker h = img.shape[0] 374*b7c941bbSAndroid Build Coastguard Worker # Cut out the 4x8b MSBs and shift to bits [9:2] in 16b words. 375*b7c941bbSAndroid Build Coastguard Worker msbs = numpy.delete(img, numpy.s_[4::5], 1) 376*b7c941bbSAndroid Build Coastguard Worker msbs = msbs.astype(numpy.uint16) 377*b7c941bbSAndroid Build Coastguard Worker msbs = numpy.left_shift(msbs, 2) 378*b7c941bbSAndroid Build Coastguard Worker msbs = msbs.reshape(h, w) 379*b7c941bbSAndroid Build Coastguard Worker # Cut out the 4x2b LSBs and put each in bits [1:0] of their own 8b words. 380*b7c941bbSAndroid Build Coastguard Worker lsbs = img[::, 4::5].reshape(h, w // 4) 381*b7c941bbSAndroid Build Coastguard Worker lsbs = numpy.right_shift( 382*b7c941bbSAndroid Build Coastguard Worker numpy.packbits(numpy.unpackbits(lsbs).reshape((h, w // 4, 4, 2)), 3), 6) 383*b7c941bbSAndroid Build Coastguard Worker # Pair the LSB bits group to 0th pixel instead of 3rd pixel 384*b7c941bbSAndroid Build Coastguard Worker lsbs = lsbs.reshape(h, w // 4, 4)[:, :, ::-1] 385*b7c941bbSAndroid Build Coastguard Worker lsbs = lsbs.reshape(h, w) 386*b7c941bbSAndroid Build Coastguard Worker # Fuse the MSBs and LSBs back together 387*b7c941bbSAndroid Build Coastguard Worker img16 = numpy.bitwise_or(msbs, lsbs).reshape(h, w) 388*b7c941bbSAndroid Build Coastguard Worker return img16 389*b7c941bbSAndroid Build Coastguard Worker 390*b7c941bbSAndroid Build Coastguard Worker 391*b7c941bbSAndroid Build Coastguard Workerdef unpack_raw12_capture(cap): 392*b7c941bbSAndroid Build Coastguard Worker """Unpack a raw-12 capture to a raw-16 capture. 393*b7c941bbSAndroid Build Coastguard Worker 394*b7c941bbSAndroid Build Coastguard Worker Args: 395*b7c941bbSAndroid Build Coastguard Worker cap: A raw-12 capture object. 396*b7c941bbSAndroid Build Coastguard Worker 397*b7c941bbSAndroid Build Coastguard Worker Returns: 398*b7c941bbSAndroid Build Coastguard Worker New capture object with raw-16 data. 399*b7c941bbSAndroid Build Coastguard Worker """ 400*b7c941bbSAndroid Build Coastguard Worker # Data is packed as 4x10b pixels in 5 bytes, with the first 4 bytes holding 401*b7c941bbSAndroid Build Coastguard Worker # the MSBs of the pixels, and the 5th byte holding 4x2b LSBs. 402*b7c941bbSAndroid Build Coastguard Worker w, h = cap['width'], cap['height'] 403*b7c941bbSAndroid Build Coastguard Worker if w % 2 != 0: 404*b7c941bbSAndroid Build Coastguard Worker raise error_util.CameraItsError('Invalid raw-12 buffer width') 405*b7c941bbSAndroid Build Coastguard Worker cap = copy.deepcopy(cap) 406*b7c941bbSAndroid Build Coastguard Worker cap['data'] = unpack_raw12_image(cap['data'].reshape(h, w * 3 // 2)) 407*b7c941bbSAndroid Build Coastguard Worker cap['format'] = 'raw' 408*b7c941bbSAndroid Build Coastguard Worker return cap 409*b7c941bbSAndroid Build Coastguard Worker 410*b7c941bbSAndroid Build Coastguard Worker 411*b7c941bbSAndroid Build Coastguard Workerdef unpack_raw12_image(img): 412*b7c941bbSAndroid Build Coastguard Worker """Unpack a raw-12 image to a raw-16 image. 413*b7c941bbSAndroid Build Coastguard Worker 414*b7c941bbSAndroid Build Coastguard Worker Output image will have the 12 LSBs filled in each 16b word, and the 4 MSBs 415*b7c941bbSAndroid Build Coastguard Worker will be set to zero. 416*b7c941bbSAndroid Build Coastguard Worker 417*b7c941bbSAndroid Build Coastguard Worker Args: 418*b7c941bbSAndroid Build Coastguard Worker img: A raw-12 image, as a uint8 numpy array. 419*b7c941bbSAndroid Build Coastguard Worker 420*b7c941bbSAndroid Build Coastguard Worker Returns: 421*b7c941bbSAndroid Build Coastguard Worker Image as a uint16 numpy array, with all row padding stripped. 422*b7c941bbSAndroid Build Coastguard Worker """ 423*b7c941bbSAndroid Build Coastguard Worker if img.shape[1] % 3 != 0: 424*b7c941bbSAndroid Build Coastguard Worker raise error_util.CameraItsError('Invalid raw-12 buffer width') 425*b7c941bbSAndroid Build Coastguard Worker w = img.shape[1] * 2 // 3 426*b7c941bbSAndroid Build Coastguard Worker h = img.shape[0] 427*b7c941bbSAndroid Build Coastguard Worker # Cut out the 2x8b MSBs and shift to bits [11:4] in 16b words. 428*b7c941bbSAndroid Build Coastguard Worker msbs = numpy.delete(img, numpy.s_[2::3], 1) 429*b7c941bbSAndroid Build Coastguard Worker msbs = msbs.astype(numpy.uint16) 430*b7c941bbSAndroid Build Coastguard Worker msbs = numpy.left_shift(msbs, 4) 431*b7c941bbSAndroid Build Coastguard Worker msbs = msbs.reshape(h, w) 432*b7c941bbSAndroid Build Coastguard Worker # Cut out the 2x4b LSBs and put each in bits [3:0] of their own 8b words. 433*b7c941bbSAndroid Build Coastguard Worker lsbs = img[::, 2::3].reshape(h, w // 2) 434*b7c941bbSAndroid Build Coastguard Worker lsbs = numpy.right_shift( 435*b7c941bbSAndroid Build Coastguard Worker numpy.packbits(numpy.unpackbits(lsbs).reshape((h, w // 2, 2, 4)), 3), 4) 436*b7c941bbSAndroid Build Coastguard Worker # Pair the LSB bits group to pixel 0 instead of pixel 1 437*b7c941bbSAndroid Build Coastguard Worker lsbs = lsbs.reshape(h, w // 2, 2)[:, :, ::-1] 438*b7c941bbSAndroid Build Coastguard Worker lsbs = lsbs.reshape(h, w) 439*b7c941bbSAndroid Build Coastguard Worker # Fuse the MSBs and LSBs back together 440*b7c941bbSAndroid Build Coastguard Worker img16 = numpy.bitwise_or(msbs, lsbs).reshape(h, w) 441*b7c941bbSAndroid Build Coastguard Worker return img16 442*b7c941bbSAndroid Build Coastguard Worker 443*b7c941bbSAndroid Build Coastguard Worker 444*b7c941bbSAndroid Build Coastguard Workerdef convert_yuv420_planar_to_rgb_image(y_plane, u_plane, v_plane, 445*b7c941bbSAndroid Build Coastguard Worker w, h, 446*b7c941bbSAndroid Build Coastguard Worker ccm_yuv_to_rgb=DEFAULT_YUV_TO_RGB_CCM, 447*b7c941bbSAndroid Build Coastguard Worker yuv_off=DEFAULT_YUV_OFFSETS): 448*b7c941bbSAndroid Build Coastguard Worker """Convert a YUV420 8-bit planar image to an RGB image. 449*b7c941bbSAndroid Build Coastguard Worker 450*b7c941bbSAndroid Build Coastguard Worker Args: 451*b7c941bbSAndroid Build Coastguard Worker y_plane: The packed 8-bit Y plane. 452*b7c941bbSAndroid Build Coastguard Worker u_plane: The packed 8-bit U plane. 453*b7c941bbSAndroid Build Coastguard Worker v_plane: The packed 8-bit V plane. 454*b7c941bbSAndroid Build Coastguard Worker w: The width of the image. 455*b7c941bbSAndroid Build Coastguard Worker h: The height of the image. 456*b7c941bbSAndroid Build Coastguard Worker ccm_yuv_to_rgb: (Optional) the 3x3 CCM to convert from YUV to RGB. 457*b7c941bbSAndroid Build Coastguard Worker yuv_off: (Optional) offsets to subtract from each of Y,U,V values. 458*b7c941bbSAndroid Build Coastguard Worker 459*b7c941bbSAndroid Build Coastguard Worker Returns: 460*b7c941bbSAndroid Build Coastguard Worker RGB float-3 image array, with pixel values in [0.0, 1.0]. 461*b7c941bbSAndroid Build Coastguard Worker """ 462*b7c941bbSAndroid Build Coastguard Worker y = numpy.subtract(y_plane, yuv_off[0]) 463*b7c941bbSAndroid Build Coastguard Worker u = numpy.subtract(u_plane, yuv_off[1]).view(numpy.int8) 464*b7c941bbSAndroid Build Coastguard Worker v = numpy.subtract(v_plane, yuv_off[2]).view(numpy.int8) 465*b7c941bbSAndroid Build Coastguard Worker u = u.reshape(h // 2, w // 2).repeat(2, axis=1).repeat(2, axis=0) 466*b7c941bbSAndroid Build Coastguard Worker v = v.reshape(h // 2, w // 2).repeat(2, axis=1).repeat(2, axis=0) 467*b7c941bbSAndroid Build Coastguard Worker yuv = numpy.dstack([y, u.reshape(w * h), v.reshape(w * h)]) 468*b7c941bbSAndroid Build Coastguard Worker flt = numpy.empty([h, w, 3], dtype=numpy.float32) 469*b7c941bbSAndroid Build Coastguard Worker flt.reshape(w * h * 3)[:] = yuv.reshape(h * w * 3)[:] 470*b7c941bbSAndroid Build Coastguard Worker flt = numpy.dot(flt.reshape(w * h, 3), ccm_yuv_to_rgb.T).clip(0, 255) 471*b7c941bbSAndroid Build Coastguard Worker rgb = numpy.empty([h, w, 3], dtype=numpy.uint8) 472*b7c941bbSAndroid Build Coastguard Worker rgb.reshape(w * h * 3)[:] = flt.reshape(w * h * 3)[:] 473*b7c941bbSAndroid Build Coastguard Worker return rgb.astype(numpy.float32) / 255.0 474*b7c941bbSAndroid Build Coastguard Worker 475*b7c941bbSAndroid Build Coastguard Worker 476*b7c941bbSAndroid Build Coastguard Workerdef decompress_jpeg_to_rgb_image(jpeg_buffer): 477*b7c941bbSAndroid Build Coastguard Worker """Decompress a JPEG-compressed image, returning as an RGB image. 478*b7c941bbSAndroid Build Coastguard Worker 479*b7c941bbSAndroid Build Coastguard Worker Args: 480*b7c941bbSAndroid Build Coastguard Worker jpeg_buffer: The JPEG stream. 481*b7c941bbSAndroid Build Coastguard Worker 482*b7c941bbSAndroid Build Coastguard Worker Returns: 483*b7c941bbSAndroid Build Coastguard Worker A numpy array for the RGB image, with pixels in [0,1]. 484*b7c941bbSAndroid Build Coastguard Worker """ 485*b7c941bbSAndroid Build Coastguard Worker img = Image.open(io.BytesIO(jpeg_buffer)) 486*b7c941bbSAndroid Build Coastguard Worker w = img.size[0] 487*b7c941bbSAndroid Build Coastguard Worker h = img.size[1] 488*b7c941bbSAndroid Build Coastguard Worker return numpy.array(img).reshape((h, w, 3)) / 255.0 489*b7c941bbSAndroid Build Coastguard Worker 490*b7c941bbSAndroid Build Coastguard Worker 491*b7c941bbSAndroid Build Coastguard Workerdef decompress_jpeg_to_yuv_image(jpeg_buffer): 492*b7c941bbSAndroid Build Coastguard Worker """Decompress a JPEG-compressed image, returning as a YUV image. 493*b7c941bbSAndroid Build Coastguard Worker 494*b7c941bbSAndroid Build Coastguard Worker Args: 495*b7c941bbSAndroid Build Coastguard Worker jpeg_buffer: The JPEG stream. 496*b7c941bbSAndroid Build Coastguard Worker 497*b7c941bbSAndroid Build Coastguard Worker Returns: 498*b7c941bbSAndroid Build Coastguard Worker A numpy array for the YUV image, with pixels in [0,1]. 499*b7c941bbSAndroid Build Coastguard Worker """ 500*b7c941bbSAndroid Build Coastguard Worker img = Image.open(io.BytesIO(jpeg_buffer)) 501*b7c941bbSAndroid Build Coastguard Worker img = img.convert('YCbCr') 502*b7c941bbSAndroid Build Coastguard Worker w = img.size[0] 503*b7c941bbSAndroid Build Coastguard Worker h = img.size[1] 504*b7c941bbSAndroid Build Coastguard Worker return numpy.array(img).reshape((h, w, 3)) / 255.0 505*b7c941bbSAndroid Build Coastguard Worker 506*b7c941bbSAndroid Build Coastguard Worker 507*b7c941bbSAndroid Build Coastguard Workerdef extract_luma_from_patch(cap, patch_x, patch_y, patch_w, patch_h): 508*b7c941bbSAndroid Build Coastguard Worker """Extract luma from capture.""" 509*b7c941bbSAndroid Build Coastguard Worker y, _, _ = convert_capture_to_planes(cap) 510*b7c941bbSAndroid Build Coastguard Worker patch = get_image_patch(y, patch_x, patch_y, patch_w, patch_h) 511*b7c941bbSAndroid Build Coastguard Worker luma = compute_image_means(patch)[0] 512*b7c941bbSAndroid Build Coastguard Worker return luma 513*b7c941bbSAndroid Build Coastguard Worker 514*b7c941bbSAndroid Build Coastguard Worker 515*b7c941bbSAndroid Build Coastguard Workerdef convert_image_to_numpy_array(image_path): 516*b7c941bbSAndroid Build Coastguard Worker """Converts image at image_path to numpy array and returns the array. 517*b7c941bbSAndroid Build Coastguard Worker 518*b7c941bbSAndroid Build Coastguard Worker Args: 519*b7c941bbSAndroid Build Coastguard Worker image_path: file path 520*b7c941bbSAndroid Build Coastguard Worker 521*b7c941bbSAndroid Build Coastguard Worker Returns: 522*b7c941bbSAndroid Build Coastguard Worker numpy array 523*b7c941bbSAndroid Build Coastguard Worker """ 524*b7c941bbSAndroid Build Coastguard Worker if not os.path.exists(image_path): 525*b7c941bbSAndroid Build Coastguard Worker raise AssertionError(f'{image_path} does not exist.') 526*b7c941bbSAndroid Build Coastguard Worker image = Image.open(image_path) 527*b7c941bbSAndroid Build Coastguard Worker return numpy.array(image) 528*b7c941bbSAndroid Build Coastguard Worker 529*b7c941bbSAndroid Build Coastguard Worker 530*b7c941bbSAndroid Build Coastguard Workerdef _convert_quad_bayer_img_to_bayer_channels(quad_bayer_img, props=None): 531*b7c941bbSAndroid Build Coastguard Worker """Convert a quad Bayer image to the Bayer image channels. 532*b7c941bbSAndroid Build Coastguard Worker 533*b7c941bbSAndroid Build Coastguard Worker Args: 534*b7c941bbSAndroid Build Coastguard Worker quad_bayer_img: The quad Bayer image. 535*b7c941bbSAndroid Build Coastguard Worker props: The camera properties. 536*b7c941bbSAndroid Build Coastguard Worker 537*b7c941bbSAndroid Build Coastguard Worker Returns: 538*b7c941bbSAndroid Build Coastguard Worker A list of reordered standard Bayer channels of the Bayer image. 539*b7c941bbSAndroid Build Coastguard Worker """ 540*b7c941bbSAndroid Build Coastguard Worker height, width, num_channels = quad_bayer_img.shape 541*b7c941bbSAndroid Build Coastguard Worker 542*b7c941bbSAndroid Build Coastguard Worker if num_channels != noise_model_constants.NUM_QUAD_BAYER_CHANNELS: 543*b7c941bbSAndroid Build Coastguard Worker raise AssertionError( 544*b7c941bbSAndroid Build Coastguard Worker 'The number of channels in the quad Bayer image must be ' 545*b7c941bbSAndroid Build Coastguard Worker f'{noise_model_constants.NUM_QUAD_BAYER_CHANNELS}.' 546*b7c941bbSAndroid Build Coastguard Worker ) 547*b7c941bbSAndroid Build Coastguard Worker quad_bayer_cfa_order = get_canonical_cfa_order(props, is_quad_bayer=True) 548*b7c941bbSAndroid Build Coastguard Worker 549*b7c941bbSAndroid Build Coastguard Worker # Bayer channels are in the order of R, Gr, Gb and B. 550*b7c941bbSAndroid Build Coastguard Worker bayer_channels = [] 551*b7c941bbSAndroid Build Coastguard Worker for ch in range(4): 552*b7c941bbSAndroid Build Coastguard Worker channel_img = numpy.zeros(shape=(height, width), dtype='<f') 553*b7c941bbSAndroid Build Coastguard Worker # Average every four quad Bayer channels into a standard Bayer channel. 554*b7c941bbSAndroid Build Coastguard Worker for i in quad_bayer_cfa_order[4 * ch: 4 * (ch + 1)]: 555*b7c941bbSAndroid Build Coastguard Worker channel_img[:, :] += quad_bayer_img[:, :, i] 556*b7c941bbSAndroid Build Coastguard Worker bayer_channels.append(channel_img / 4) 557*b7c941bbSAndroid Build Coastguard Worker return bayer_channels 558*b7c941bbSAndroid Build Coastguard Worker 559*b7c941bbSAndroid Build Coastguard Worker 560*b7c941bbSAndroid Build Coastguard Workerdef subsample(image, num_channels=4): 561*b7c941bbSAndroid Build Coastguard Worker """Subsamples the image to separate its color channels. 562*b7c941bbSAndroid Build Coastguard Worker 563*b7c941bbSAndroid Build Coastguard Worker Args: 564*b7c941bbSAndroid Build Coastguard Worker image: 2-D numpy array of raw image. 565*b7c941bbSAndroid Build Coastguard Worker num_channels: The number of channels in the image. 566*b7c941bbSAndroid Build Coastguard Worker 567*b7c941bbSAndroid Build Coastguard Worker Returns: 568*b7c941bbSAndroid Build Coastguard Worker 3-D numpy image with each channel separated. 569*b7c941bbSAndroid Build Coastguard Worker """ 570*b7c941bbSAndroid Build Coastguard Worker if num_channels not in noise_model_constants.VALID_NUM_CHANNELS: 571*b7c941bbSAndroid Build Coastguard Worker raise error_util.CameraItsError( 572*b7c941bbSAndroid Build Coastguard Worker f'Invalid number of channels {num_channels}, which should be in ' 573*b7c941bbSAndroid Build Coastguard Worker f'{noise_model_constants.VALID_NUM_CHANNELS}.' 574*b7c941bbSAndroid Build Coastguard Worker ) 575*b7c941bbSAndroid Build Coastguard Worker 576*b7c941bbSAndroid Build Coastguard Worker size_h, size_v = image.shape[1], image.shape[0] 577*b7c941bbSAndroid Build Coastguard Worker 578*b7c941bbSAndroid Build Coastguard Worker # Subsample step size, which is the horizontal or vertical pixel interval 579*b7c941bbSAndroid Build Coastguard Worker # between two adjacent pixels of the same channel. 580*b7c941bbSAndroid Build Coastguard Worker stride = int(numpy.sqrt(num_channels)) 581*b7c941bbSAndroid Build Coastguard Worker subsample_img = lambda img, i, h, v, s: img[i // s: v: s, i % s: h: s] 582*b7c941bbSAndroid Build Coastguard Worker channel_img = numpy.empty(( 583*b7c941bbSAndroid Build Coastguard Worker image.shape[0] // stride, 584*b7c941bbSAndroid Build Coastguard Worker image.shape[1] // stride, 585*b7c941bbSAndroid Build Coastguard Worker num_channels, 586*b7c941bbSAndroid Build Coastguard Worker )) 587*b7c941bbSAndroid Build Coastguard Worker 588*b7c941bbSAndroid Build Coastguard Worker for i in range(num_channels): 589*b7c941bbSAndroid Build Coastguard Worker sub_img = subsample_img(image, i, size_h, size_v, stride) 590*b7c941bbSAndroid Build Coastguard Worker channel_img[:, :, i] = sub_img 591*b7c941bbSAndroid Build Coastguard Worker 592*b7c941bbSAndroid Build Coastguard Worker return channel_img 593*b7c941bbSAndroid Build Coastguard Worker 594*b7c941bbSAndroid Build Coastguard Worker 595*b7c941bbSAndroid Build Coastguard Workerdef convert_capture_to_planes(cap, props=None): 596*b7c941bbSAndroid Build Coastguard Worker """Convert a captured image object to separate image planes. 597*b7c941bbSAndroid Build Coastguard Worker 598*b7c941bbSAndroid Build Coastguard Worker Decompose an image into multiple images, corresponding to different planes. 599*b7c941bbSAndroid Build Coastguard Worker 600*b7c941bbSAndroid Build Coastguard Worker For YUV420 captures ("yuv"): 601*b7c941bbSAndroid Build Coastguard Worker Returns Y,U,V planes, where the Y plane is full-res and the U,V planes 602*b7c941bbSAndroid Build Coastguard Worker are each 1/2 x 1/2 of the full res. 603*b7c941bbSAndroid Build Coastguard Worker 604*b7c941bbSAndroid Build Coastguard Worker For standard Bayer or quad Bayer captures ("raw", "raw10", "raw12", 605*b7c941bbSAndroid Build Coastguard Worker "rawQuadBayer", "rawStats", "rawQuadBayerStats", "raw10QuadBayer", 606*b7c941bbSAndroid Build Coastguard Worker "raw10Stats", "raw10QuadBayerStats"): 607*b7c941bbSAndroid Build Coastguard Worker Returns planes in the order R, Gr, Gb, B, regardless of the Bayer 608*b7c941bbSAndroid Build Coastguard Worker pattern layout. 609*b7c941bbSAndroid Build Coastguard Worker For full-res raw images ("raw", "rawQuadBayer", "raw10", 610*b7c941bbSAndroid Build Coastguard Worker "raw10QuadBayer", "raw12"), each plane is 1/2 x 1/2 of the full res. 611*b7c941bbSAndroid Build Coastguard Worker For standard Bayer stats images, the mean image is returned. 612*b7c941bbSAndroid Build Coastguard Worker For quad Bayer stats images, the average mean image is returned. 613*b7c941bbSAndroid Build Coastguard Worker 614*b7c941bbSAndroid Build Coastguard Worker For JPEG captures ("jpeg"): 615*b7c941bbSAndroid Build Coastguard Worker Returns R,G,B full-res planes. 616*b7c941bbSAndroid Build Coastguard Worker 617*b7c941bbSAndroid Build Coastguard Worker Args: 618*b7c941bbSAndroid Build Coastguard Worker cap: A capture object as returned by its_session_utils.do_capture. 619*b7c941bbSAndroid Build Coastguard Worker props: (Optional) camera properties object (of static values); 620*b7c941bbSAndroid Build Coastguard Worker required for processing raw images. 621*b7c941bbSAndroid Build Coastguard Worker 622*b7c941bbSAndroid Build Coastguard Worker Returns: 623*b7c941bbSAndroid Build Coastguard Worker A tuple of float numpy arrays (one per plane), consisting of pixel values 624*b7c941bbSAndroid Build Coastguard Worker in the range [0.0, 1.0]. 625*b7c941bbSAndroid Build Coastguard Worker """ 626*b7c941bbSAndroid Build Coastguard Worker w = cap['width'] 627*b7c941bbSAndroid Build Coastguard Worker h = cap['height'] 628*b7c941bbSAndroid Build Coastguard Worker if cap['format'] in ('raw10', 'raw10QuadBayer'): 629*b7c941bbSAndroid Build Coastguard Worker assert_props_is_not_none(props) 630*b7c941bbSAndroid Build Coastguard Worker is_quad_bayer = cap['format'] == 'raw10QuadBayer' 631*b7c941bbSAndroid Build Coastguard Worker cap = unpack_raw10_capture(cap, is_quad_bayer) 632*b7c941bbSAndroid Build Coastguard Worker 633*b7c941bbSAndroid Build Coastguard Worker if cap['format'] == 'raw12': 634*b7c941bbSAndroid Build Coastguard Worker assert_props_is_not_none(props) 635*b7c941bbSAndroid Build Coastguard Worker cap = unpack_raw12_capture(cap) 636*b7c941bbSAndroid Build Coastguard Worker if cap['format'] == 'yuv': 637*b7c941bbSAndroid Build Coastguard Worker y = cap['data'][0:w * h] 638*b7c941bbSAndroid Build Coastguard Worker u = cap['data'][w * h:w * h * 5 // 4] 639*b7c941bbSAndroid Build Coastguard Worker v = cap['data'][w * h * 5 // 4:w * h * 6 // 4] 640*b7c941bbSAndroid Build Coastguard Worker return ((y.astype(numpy.float32) / 255.0).reshape(h, w, 1), 641*b7c941bbSAndroid Build Coastguard Worker (u.astype(numpy.float32) / 255.0).reshape(h // 2, w // 2, 1), 642*b7c941bbSAndroid Build Coastguard Worker (v.astype(numpy.float32) / 255.0).reshape(h // 2, w // 2, 1)) 643*b7c941bbSAndroid Build Coastguard Worker elif cap['format'] == 'jpeg': 644*b7c941bbSAndroid Build Coastguard Worker rgb = decompress_jpeg_to_rgb_image(cap['data']).reshape(w * h * 3) 645*b7c941bbSAndroid Build Coastguard Worker return (rgb[::3].reshape(h, w, 1), rgb[1::3].reshape(h, w, 1), 646*b7c941bbSAndroid Build Coastguard Worker rgb[2::3].reshape(h, w, 1)) 647*b7c941bbSAndroid Build Coastguard Worker elif cap['format'] in ('raw', 'rawQuadBayer'): 648*b7c941bbSAndroid Build Coastguard Worker assert_props_is_not_none(props) 649*b7c941bbSAndroid Build Coastguard Worker is_quad_bayer = 'QuadBayer' in cap['format'] 650*b7c941bbSAndroid Build Coastguard Worker white_level = get_white_level(props, cap['metadata']) 651*b7c941bbSAndroid Build Coastguard Worker img = numpy.ndarray( 652*b7c941bbSAndroid Build Coastguard Worker shape=(h * w,), dtype='<u2', buffer=cap['data'][0:w * h * 2]) 653*b7c941bbSAndroid Build Coastguard Worker img = img.astype(numpy.float32).reshape(h, w) / white_level 654*b7c941bbSAndroid Build Coastguard Worker if is_quad_bayer: 655*b7c941bbSAndroid Build Coastguard Worker pixel_array_size = props.get( 656*b7c941bbSAndroid Build Coastguard Worker 'android.sensor.info.pixelArraySizeMaximumResolution' 657*b7c941bbSAndroid Build Coastguard Worker ) 658*b7c941bbSAndroid Build Coastguard Worker active_array_size = props.get( 659*b7c941bbSAndroid Build Coastguard Worker 'android.sensor.info.preCorrectionActiveArraySizeMaximumResolution' 660*b7c941bbSAndroid Build Coastguard Worker ) 661*b7c941bbSAndroid Build Coastguard Worker else: 662*b7c941bbSAndroid Build Coastguard Worker pixel_array_size = props.get('android.sensor.info.pixelArraySize') 663*b7c941bbSAndroid Build Coastguard Worker active_array_size = props.get( 664*b7c941bbSAndroid Build Coastguard Worker 'android.sensor.info.preCorrectionActiveArraySize' 665*b7c941bbSAndroid Build Coastguard Worker ) 666*b7c941bbSAndroid Build Coastguard Worker # Crop the raw image to the active array region. 667*b7c941bbSAndroid Build Coastguard Worker if pixel_array_size and active_array_size: 668*b7c941bbSAndroid Build Coastguard Worker # Note that the Rect class is defined such that the left,top values 669*b7c941bbSAndroid Build Coastguard Worker # are "inside" while the right,bottom values are "outside"; that is, 670*b7c941bbSAndroid Build Coastguard Worker # it's inclusive of the top,left sides only. So, the width is 671*b7c941bbSAndroid Build Coastguard Worker # computed as right-left, rather than right-left+1, etc. 672*b7c941bbSAndroid Build Coastguard Worker wfull = pixel_array_size['width'] 673*b7c941bbSAndroid Build Coastguard Worker hfull = pixel_array_size['height'] 674*b7c941bbSAndroid Build Coastguard Worker xcrop = active_array_size['left'] 675*b7c941bbSAndroid Build Coastguard Worker ycrop = active_array_size['top'] 676*b7c941bbSAndroid Build Coastguard Worker wcrop = active_array_size['right'] - xcrop 677*b7c941bbSAndroid Build Coastguard Worker hcrop = active_array_size['bottom'] - ycrop 678*b7c941bbSAndroid Build Coastguard Worker if not wfull >= wcrop >= 0: 679*b7c941bbSAndroid Build Coastguard Worker raise AssertionError(f'wcrop: {wcrop} not in wfull: {wfull}') 680*b7c941bbSAndroid Build Coastguard Worker if not hfull >= hcrop >= 0: 681*b7c941bbSAndroid Build Coastguard Worker raise AssertionError(f'hcrop: {hcrop} not in hfull: {hfull}') 682*b7c941bbSAndroid Build Coastguard Worker if not wfull - wcrop >= xcrop >= 0: 683*b7c941bbSAndroid Build Coastguard Worker raise AssertionError(f'xcrop: {xcrop} not in wfull-crop: {wfull-wcrop}') 684*b7c941bbSAndroid Build Coastguard Worker if not hfull - hcrop >= ycrop >= 0: 685*b7c941bbSAndroid Build Coastguard Worker raise AssertionError(f'ycrop: {ycrop} not in hfull-crop: {hfull-hcrop}') 686*b7c941bbSAndroid Build Coastguard Worker if w == wfull and h == hfull: 687*b7c941bbSAndroid Build Coastguard Worker # Crop needed; extract the center region. 688*b7c941bbSAndroid Build Coastguard Worker img = img[ycrop:ycrop + hcrop, xcrop:xcrop + wcrop] 689*b7c941bbSAndroid Build Coastguard Worker w = wcrop 690*b7c941bbSAndroid Build Coastguard Worker h = hcrop 691*b7c941bbSAndroid Build Coastguard Worker elif w == wcrop and h == hcrop: 692*b7c941bbSAndroid Build Coastguard Worker logging.debug('Image is already cropped. No cropping needed.') 693*b7c941bbSAndroid Build Coastguard Worker else: 694*b7c941bbSAndroid Build Coastguard Worker raise error_util.CameraItsError('Invalid image size metadata') 695*b7c941bbSAndroid Build Coastguard Worker 696*b7c941bbSAndroid Build Coastguard Worker idxs = get_canonical_cfa_order(props, is_quad_bayer) 697*b7c941bbSAndroid Build Coastguard Worker if is_quad_bayer: 698*b7c941bbSAndroid Build Coastguard Worker # Subsample image array based on the color map. 699*b7c941bbSAndroid Build Coastguard Worker quad_bayer_img = subsample( 700*b7c941bbSAndroid Build Coastguard Worker img, noise_model_constants.NUM_QUAD_BAYER_CHANNELS 701*b7c941bbSAndroid Build Coastguard Worker ) 702*b7c941bbSAndroid Build Coastguard Worker bayer_channels = _convert_quad_bayer_img_to_bayer_channels( 703*b7c941bbSAndroid Build Coastguard Worker quad_bayer_img, props 704*b7c941bbSAndroid Build Coastguard Worker ) 705*b7c941bbSAndroid Build Coastguard Worker return bayer_channels 706*b7c941bbSAndroid Build Coastguard Worker else: 707*b7c941bbSAndroid Build Coastguard Worker # Separate the image planes. 708*b7c941bbSAndroid Build Coastguard Worker imgs = [ 709*b7c941bbSAndroid Build Coastguard Worker img[::2].reshape(w * h // 2)[::2].reshape(h // 2, w // 2, 1), 710*b7c941bbSAndroid Build Coastguard Worker img[::2].reshape(w * h // 2)[1::2].reshape(h // 2, w // 2, 1), 711*b7c941bbSAndroid Build Coastguard Worker img[1::2].reshape(w * h // 2)[::2].reshape(h // 2, w // 2, 1), 712*b7c941bbSAndroid Build Coastguard Worker img[1::2].reshape(w * h // 2)[1::2].reshape(h // 2, w // 2, 1), 713*b7c941bbSAndroid Build Coastguard Worker ] 714*b7c941bbSAndroid Build Coastguard Worker return [imgs[i] for i in idxs] 715*b7c941bbSAndroid Build Coastguard Worker elif cap['format'] in ( 716*b7c941bbSAndroid Build Coastguard Worker 'rawStats', 717*b7c941bbSAndroid Build Coastguard Worker 'raw10Stats', 718*b7c941bbSAndroid Build Coastguard Worker 'rawQuadBayerStats', 719*b7c941bbSAndroid Build Coastguard Worker 'raw10QuadBayerStats', 720*b7c941bbSAndroid Build Coastguard Worker ): 721*b7c941bbSAndroid Build Coastguard Worker assert_props_is_not_none(props) 722*b7c941bbSAndroid Build Coastguard Worker is_quad_bayer = 'QuadBayer' in cap['format'] 723*b7c941bbSAndroid Build Coastguard Worker white_level = get_white_level(props, cap['metadata']) 724*b7c941bbSAndroid Build Coastguard Worker if is_quad_bayer: 725*b7c941bbSAndroid Build Coastguard Worker num_channels = noise_model_constants.NUM_QUAD_BAYER_CHANNELS 726*b7c941bbSAndroid Build Coastguard Worker else: 727*b7c941bbSAndroid Build Coastguard Worker num_channels = noise_model_constants.NUM_BAYER_CHANNELS 728*b7c941bbSAndroid Build Coastguard Worker mean_image, _ = unpack_rawstats_capture(cap, num_channels) 729*b7c941bbSAndroid Build Coastguard Worker if is_quad_bayer: 730*b7c941bbSAndroid Build Coastguard Worker bayer_channels = _convert_quad_bayer_img_to_bayer_channels( 731*b7c941bbSAndroid Build Coastguard Worker mean_image, props 732*b7c941bbSAndroid Build Coastguard Worker ) 733*b7c941bbSAndroid Build Coastguard Worker bayer_channels = [ 734*b7c941bbSAndroid Build Coastguard Worker bayer_channels[i] / white_level for i in range(len(bayer_channels)) 735*b7c941bbSAndroid Build Coastguard Worker ] 736*b7c941bbSAndroid Build Coastguard Worker return bayer_channels 737*b7c941bbSAndroid Build Coastguard Worker else: 738*b7c941bbSAndroid Build Coastguard Worker # Standard Bayer canonical color channel indices. 739*b7c941bbSAndroid Build Coastguard Worker idxs = get_canonical_cfa_order(props, is_quad_bayer=False) 740*b7c941bbSAndroid Build Coastguard Worker # Normalizes the range to [0, 1] without subtracting the black level. 741*b7c941bbSAndroid Build Coastguard Worker return [mean_image[:, :, i] / white_level for i in idxs] 742*b7c941bbSAndroid Build Coastguard Worker else: 743*b7c941bbSAndroid Build Coastguard Worker raise error_util.CameraItsError(f"Invalid format {cap['format']}") 744*b7c941bbSAndroid Build Coastguard Worker 745*b7c941bbSAndroid Build Coastguard Worker 746*b7c941bbSAndroid Build Coastguard Workerdef downscale_image(img, f): 747*b7c941bbSAndroid Build Coastguard Worker """Shrink an image by a given integer factor. 748*b7c941bbSAndroid Build Coastguard Worker 749*b7c941bbSAndroid Build Coastguard Worker This function computes output pixel values by averaging over rectangular 750*b7c941bbSAndroid Build Coastguard Worker regions of the input image; it doesn't skip or sample pixels, and all input 751*b7c941bbSAndroid Build Coastguard Worker image pixels are evenly weighted. 752*b7c941bbSAndroid Build Coastguard Worker 753*b7c941bbSAndroid Build Coastguard Worker If the downscaling factor doesn't cleanly divide the width and/or height, 754*b7c941bbSAndroid Build Coastguard Worker then the remaining pixels on the right or bottom edge are discarded prior 755*b7c941bbSAndroid Build Coastguard Worker to the downscaling. 756*b7c941bbSAndroid Build Coastguard Worker 757*b7c941bbSAndroid Build Coastguard Worker Args: 758*b7c941bbSAndroid Build Coastguard Worker img: The input image as an ndarray. 759*b7c941bbSAndroid Build Coastguard Worker f: The downscaling factor, which should be an integer. 760*b7c941bbSAndroid Build Coastguard Worker 761*b7c941bbSAndroid Build Coastguard Worker Returns: 762*b7c941bbSAndroid Build Coastguard Worker The new (downscaled) image, as an ndarray. 763*b7c941bbSAndroid Build Coastguard Worker """ 764*b7c941bbSAndroid Build Coastguard Worker h, w, chans = img.shape 765*b7c941bbSAndroid Build Coastguard Worker f = int(f) 766*b7c941bbSAndroid Build Coastguard Worker assert f >= 1 767*b7c941bbSAndroid Build Coastguard Worker h = (h//f)*f 768*b7c941bbSAndroid Build Coastguard Worker w = (w//f)*f 769*b7c941bbSAndroid Build Coastguard Worker img = img[0:h:, 0:w:, ::] 770*b7c941bbSAndroid Build Coastguard Worker chs = [] 771*b7c941bbSAndroid Build Coastguard Worker for i in range(chans): 772*b7c941bbSAndroid Build Coastguard Worker ch = img.reshape(h*w*chans)[i::chans].reshape(h, w) 773*b7c941bbSAndroid Build Coastguard Worker ch = ch.reshape(h, w//f, f).mean(2).reshape(h, w//f) 774*b7c941bbSAndroid Build Coastguard Worker ch = ch.T.reshape(w//f, h//f, f).mean(2).T.reshape(h//f, w//f) 775*b7c941bbSAndroid Build Coastguard Worker chs.append(ch.reshape(h*w//(f*f))) 776*b7c941bbSAndroid Build Coastguard Worker img = numpy.vstack(chs).T.reshape(h//f, w//f, chans) 777*b7c941bbSAndroid Build Coastguard Worker return img 778*b7c941bbSAndroid Build Coastguard Worker 779*b7c941bbSAndroid Build Coastguard Worker 780*b7c941bbSAndroid Build Coastguard Workerdef convert_raw_to_rgb_image(r_plane, gr_plane, gb_plane, b_plane, props, 781*b7c941bbSAndroid Build Coastguard Worker cap_res, apply_ccm_raw_to_rgb=True): 782*b7c941bbSAndroid Build Coastguard Worker """Convert a Bayer raw-16 image to an RGB image. 783*b7c941bbSAndroid Build Coastguard Worker 784*b7c941bbSAndroid Build Coastguard Worker Includes some extremely rudimentary demosaicking and color processing 785*b7c941bbSAndroid Build Coastguard Worker operations; the output of this function shouldn't be used for any image 786*b7c941bbSAndroid Build Coastguard Worker quality analysis. 787*b7c941bbSAndroid Build Coastguard Worker 788*b7c941bbSAndroid Build Coastguard Worker Args: 789*b7c941bbSAndroid Build Coastguard Worker r_plane: 790*b7c941bbSAndroid Build Coastguard Worker gr_plane: 791*b7c941bbSAndroid Build Coastguard Worker gb_plane: 792*b7c941bbSAndroid Build Coastguard Worker b_plane: Numpy arrays for each color plane 793*b7c941bbSAndroid Build Coastguard Worker in the Bayer image, with pixels in the [0.0, 1.0] range. 794*b7c941bbSAndroid Build Coastguard Worker props: Camera properties object. 795*b7c941bbSAndroid Build Coastguard Worker cap_res: Capture result (metadata) object. 796*b7c941bbSAndroid Build Coastguard Worker apply_ccm_raw_to_rgb: (Optional) boolean to apply color correction matrix. 797*b7c941bbSAndroid Build Coastguard Worker 798*b7c941bbSAndroid Build Coastguard Worker Returns: 799*b7c941bbSAndroid Build Coastguard Worker RGB float-3 image array, with pixel values in [0.0, 1.0] 800*b7c941bbSAndroid Build Coastguard Worker """ 801*b7c941bbSAndroid Build Coastguard Worker # Values required for the RAW to RGB conversion. 802*b7c941bbSAndroid Build Coastguard Worker assert_props_is_not_none(props) 803*b7c941bbSAndroid Build Coastguard Worker white_level = get_white_level(props, cap_res) 804*b7c941bbSAndroid Build Coastguard Worker gains = cap_res['android.colorCorrection.gains'] 805*b7c941bbSAndroid Build Coastguard Worker ccm = cap_res['android.colorCorrection.transform'] 806*b7c941bbSAndroid Build Coastguard Worker 807*b7c941bbSAndroid Build Coastguard Worker # Reorder black levels and gains to R,Gr,Gb,B, to match the order 808*b7c941bbSAndroid Build Coastguard Worker # of the planes. 809*b7c941bbSAndroid Build Coastguard Worker black_levels = get_black_levels(props, cap_res, is_quad_bayer=False) 810*b7c941bbSAndroid Build Coastguard Worker logging.debug('dynamic black levels: %s', black_levels) 811*b7c941bbSAndroid Build Coastguard Worker gains = get_gains_in_canonical_order(props, gains) 812*b7c941bbSAndroid Build Coastguard Worker 813*b7c941bbSAndroid Build Coastguard Worker # Convert CCM from rational to float, as numpy arrays. 814*b7c941bbSAndroid Build Coastguard Worker ccm = numpy.array(capture_request_utils.rational_to_float(ccm)).reshape(3, 3) 815*b7c941bbSAndroid Build Coastguard Worker 816*b7c941bbSAndroid Build Coastguard Worker # Need to scale the image back to the full [0,1] range after subtracting 817*b7c941bbSAndroid Build Coastguard Worker # the black level from each pixel. 818*b7c941bbSAndroid Build Coastguard Worker scale = white_level / (white_level - max(black_levels)) 819*b7c941bbSAndroid Build Coastguard Worker 820*b7c941bbSAndroid Build Coastguard Worker # Three-channel black levels, normalized to [0,1] by white_level. 821*b7c941bbSAndroid Build Coastguard Worker black_levels = numpy.array( 822*b7c941bbSAndroid Build Coastguard Worker [b / white_level for b in [black_levels[i] for i in [0, 1, 3]]]) 823*b7c941bbSAndroid Build Coastguard Worker 824*b7c941bbSAndroid Build Coastguard Worker # Three-channel gains. 825*b7c941bbSAndroid Build Coastguard Worker gains = numpy.array([gains[i] for i in [0, 1, 3]]) 826*b7c941bbSAndroid Build Coastguard Worker 827*b7c941bbSAndroid Build Coastguard Worker h, w = r_plane.shape[:2] 828*b7c941bbSAndroid Build Coastguard Worker img = numpy.dstack([r_plane, (gr_plane + gb_plane) / 2.0, b_plane]) 829*b7c941bbSAndroid Build Coastguard Worker img = (((img.reshape(h, w, 3) - black_levels) * scale) * gains).clip(0.0, 1.0) 830*b7c941bbSAndroid Build Coastguard Worker if apply_ccm_raw_to_rgb: 831*b7c941bbSAndroid Build Coastguard Worker img = numpy.dot( 832*b7c941bbSAndroid Build Coastguard Worker img.reshape(w * h, 3), ccm.T).reshape((h, w, 3)).clip(0.0, 1.0) 833*b7c941bbSAndroid Build Coastguard Worker return img 834*b7c941bbSAndroid Build Coastguard Worker 835*b7c941bbSAndroid Build Coastguard Worker 836*b7c941bbSAndroid Build Coastguard Workerdef convert_y8_to_rgb_image(y_plane, w, h): 837*b7c941bbSAndroid Build Coastguard Worker """Convert a Y 8-bit image to an RGB image. 838*b7c941bbSAndroid Build Coastguard Worker 839*b7c941bbSAndroid Build Coastguard Worker Args: 840*b7c941bbSAndroid Build Coastguard Worker y_plane: The packed 8-bit Y plane. 841*b7c941bbSAndroid Build Coastguard Worker w: The width of the image. 842*b7c941bbSAndroid Build Coastguard Worker h: The height of the image. 843*b7c941bbSAndroid Build Coastguard Worker 844*b7c941bbSAndroid Build Coastguard Worker Returns: 845*b7c941bbSAndroid Build Coastguard Worker RGB float-3 image array, with pixel values in [0.0, 1.0]. 846*b7c941bbSAndroid Build Coastguard Worker """ 847*b7c941bbSAndroid Build Coastguard Worker y3 = numpy.dstack([y_plane, y_plane, y_plane]) 848*b7c941bbSAndroid Build Coastguard Worker rgb = numpy.empty([h, w, 3], dtype=numpy.uint8) 849*b7c941bbSAndroid Build Coastguard Worker rgb.reshape(w * h * 3)[:] = y3.reshape(w * h * 3)[:] 850*b7c941bbSAndroid Build Coastguard Worker return rgb.astype(numpy.float32) / 255.0 851*b7c941bbSAndroid Build Coastguard Worker 852*b7c941bbSAndroid Build Coastguard Worker 853*b7c941bbSAndroid Build Coastguard Workerdef write_rgb_uint8_image(img, file_name): 854*b7c941bbSAndroid Build Coastguard Worker """Save a uint8 numpy array image to a file. 855*b7c941bbSAndroid Build Coastguard Worker 856*b7c941bbSAndroid Build Coastguard Worker Supported formats: PNG, JPEG, and others; see PIL docs for more. 857*b7c941bbSAndroid Build Coastguard Worker 858*b7c941bbSAndroid Build Coastguard Worker Args: 859*b7c941bbSAndroid Build Coastguard Worker img: numpy image array data. 860*b7c941bbSAndroid Build Coastguard Worker file_name: path of file to save to; the extension specifies the format. 861*b7c941bbSAndroid Build Coastguard Worker """ 862*b7c941bbSAndroid Build Coastguard Worker if img.dtype != 'uint8': 863*b7c941bbSAndroid Build Coastguard Worker raise AssertionError(f'Incorrect input type: {img.dtype}! Expected: uint8') 864*b7c941bbSAndroid Build Coastguard Worker else: 865*b7c941bbSAndroid Build Coastguard Worker Image.fromarray(img, 'RGB').save(file_name) 866*b7c941bbSAndroid Build Coastguard Worker 867*b7c941bbSAndroid Build Coastguard Worker 868*b7c941bbSAndroid Build Coastguard Workerdef write_image(img, fname, apply_gamma=False, is_yuv=False): 869*b7c941bbSAndroid Build Coastguard Worker """Save a float-3 numpy array image to a file. 870*b7c941bbSAndroid Build Coastguard Worker 871*b7c941bbSAndroid Build Coastguard Worker Supported formats: PNG, JPEG, and others; see PIL docs for more. 872*b7c941bbSAndroid Build Coastguard Worker 873*b7c941bbSAndroid Build Coastguard Worker Image can be 3-channel, which is interpreted as RGB or YUV, or can be 874*b7c941bbSAndroid Build Coastguard Worker 1-channel, which is greyscale. 875*b7c941bbSAndroid Build Coastguard Worker 876*b7c941bbSAndroid Build Coastguard Worker Can optionally specify that the image should be gamma-encoded prior to 877*b7c941bbSAndroid Build Coastguard Worker writing it out; this should be done if the image contains linear pixel 878*b7c941bbSAndroid Build Coastguard Worker values, to make the image look "normal". 879*b7c941bbSAndroid Build Coastguard Worker 880*b7c941bbSAndroid Build Coastguard Worker Args: 881*b7c941bbSAndroid Build Coastguard Worker img: Numpy image array data. 882*b7c941bbSAndroid Build Coastguard Worker fname: Path of file to save to; the extension specifies the format. 883*b7c941bbSAndroid Build Coastguard Worker apply_gamma: (Optional) apply gamma to the image prior to writing it. 884*b7c941bbSAndroid Build Coastguard Worker is_yuv: Whether the image is in YUV format. 885*b7c941bbSAndroid Build Coastguard Worker """ 886*b7c941bbSAndroid Build Coastguard Worker if apply_gamma: 887*b7c941bbSAndroid Build Coastguard Worker img = apply_lut_to_image(img, DEFAULT_GAMMA_LUT) 888*b7c941bbSAndroid Build Coastguard Worker (h, w, chans) = img.shape 889*b7c941bbSAndroid Build Coastguard Worker if chans == 3: 890*b7c941bbSAndroid Build Coastguard Worker if not is_yuv: 891*b7c941bbSAndroid Build Coastguard Worker Image.fromarray((img * 255.0).astype(numpy.uint8), 'RGB').save(fname) 892*b7c941bbSAndroid Build Coastguard Worker else: 893*b7c941bbSAndroid Build Coastguard Worker Image.fromarray((img * 255.0).astype(numpy.uint8), 'YCbCr').save(fname) 894*b7c941bbSAndroid Build Coastguard Worker elif chans == 1: 895*b7c941bbSAndroid Build Coastguard Worker img3 = (img * 255.0).astype(numpy.uint8).repeat(3).reshape(h, w, 3) 896*b7c941bbSAndroid Build Coastguard Worker Image.fromarray(img3, 'RGB').save(fname) 897*b7c941bbSAndroid Build Coastguard Worker else: 898*b7c941bbSAndroid Build Coastguard Worker raise error_util.CameraItsError('Unsupported image type') 899*b7c941bbSAndroid Build Coastguard Worker 900*b7c941bbSAndroid Build Coastguard Worker 901*b7c941bbSAndroid Build Coastguard Workerdef read_image(fname): 902*b7c941bbSAndroid Build Coastguard Worker """Read image function to match write_image() above.""" 903*b7c941bbSAndroid Build Coastguard Worker return Image.open(fname) 904*b7c941bbSAndroid Build Coastguard Worker 905*b7c941bbSAndroid Build Coastguard Worker 906*b7c941bbSAndroid Build Coastguard Workerdef apply_lut_to_image(img, lut): 907*b7c941bbSAndroid Build Coastguard Worker """Applies a LUT to every pixel in a float image array. 908*b7c941bbSAndroid Build Coastguard Worker 909*b7c941bbSAndroid Build Coastguard Worker Internally converts to a 16b integer image, since the LUT can work with up 910*b7c941bbSAndroid Build Coastguard Worker to 16b->16b mappings (i.e. values in the range [0,65535]). The lut can also 911*b7c941bbSAndroid Build Coastguard Worker have fewer than 65536 entries, however it must be sized as a power of 2 912*b7c941bbSAndroid Build Coastguard Worker (and for smaller luts, the scale must match the bitdepth). 913*b7c941bbSAndroid Build Coastguard Worker 914*b7c941bbSAndroid Build Coastguard Worker For a 16b lut of 65536 entries, the operation performed is: 915*b7c941bbSAndroid Build Coastguard Worker 916*b7c941bbSAndroid Build Coastguard Worker lut[r * 65535] / 65535 -> r' 917*b7c941bbSAndroid Build Coastguard Worker lut[g * 65535] / 65535 -> g' 918*b7c941bbSAndroid Build Coastguard Worker lut[b * 65535] / 65535 -> b' 919*b7c941bbSAndroid Build Coastguard Worker 920*b7c941bbSAndroid Build Coastguard Worker For a 10b lut of 1024 entries, the operation becomes: 921*b7c941bbSAndroid Build Coastguard Worker 922*b7c941bbSAndroid Build Coastguard Worker lut[r * 1023] / 1023 -> r' 923*b7c941bbSAndroid Build Coastguard Worker lut[g * 1023] / 1023 -> g' 924*b7c941bbSAndroid Build Coastguard Worker lut[b * 1023] / 1023 -> b' 925*b7c941bbSAndroid Build Coastguard Worker 926*b7c941bbSAndroid Build Coastguard Worker Args: 927*b7c941bbSAndroid Build Coastguard Worker img: Numpy float image array, with pixel values in [0,1]. 928*b7c941bbSAndroid Build Coastguard Worker lut: Numpy table encoding a LUT, mapping 16b integer values. 929*b7c941bbSAndroid Build Coastguard Worker 930*b7c941bbSAndroid Build Coastguard Worker Returns: 931*b7c941bbSAndroid Build Coastguard Worker Float image array after applying LUT to each pixel. 932*b7c941bbSAndroid Build Coastguard Worker """ 933*b7c941bbSAndroid Build Coastguard Worker n = len(lut) 934*b7c941bbSAndroid Build Coastguard Worker if n <= 0 or n > MAX_LUT_SIZE or (n & (n - 1)) != 0: 935*b7c941bbSAndroid Build Coastguard Worker raise error_util.CameraItsError(f'Invalid arg LUT size: {n}') 936*b7c941bbSAndroid Build Coastguard Worker m = float(n - 1) 937*b7c941bbSAndroid Build Coastguard Worker return (lut[(img * m).astype(numpy.uint16)] / m).astype(numpy.float32) 938*b7c941bbSAndroid Build Coastguard Worker 939*b7c941bbSAndroid Build Coastguard Worker 940*b7c941bbSAndroid Build Coastguard Workerdef get_gains_in_canonical_order(props, gains): 941*b7c941bbSAndroid Build Coastguard Worker """Reorders the gains tuple to the canonical R,Gr,Gb,B order. 942*b7c941bbSAndroid Build Coastguard Worker 943*b7c941bbSAndroid Build Coastguard Worker Args: 944*b7c941bbSAndroid Build Coastguard Worker props: Camera properties object. 945*b7c941bbSAndroid Build Coastguard Worker gains: List of 4 values, in R,G_even,G_odd,B order. 946*b7c941bbSAndroid Build Coastguard Worker 947*b7c941bbSAndroid Build Coastguard Worker Returns: 948*b7c941bbSAndroid Build Coastguard Worker List of gains values, in R,Gr,Gb,B order. 949*b7c941bbSAndroid Build Coastguard Worker """ 950*b7c941bbSAndroid Build Coastguard Worker cfa_pat = props['android.sensor.info.colorFilterArrangement'] 951*b7c941bbSAndroid Build Coastguard Worker if cfa_pat in [0, 1]: 952*b7c941bbSAndroid Build Coastguard Worker # RGGB or GRBG, so G_even is Gr 953*b7c941bbSAndroid Build Coastguard Worker return gains 954*b7c941bbSAndroid Build Coastguard Worker elif cfa_pat in [2, 3]: 955*b7c941bbSAndroid Build Coastguard Worker # GBRG or BGGR, so G_even is Gb 956*b7c941bbSAndroid Build Coastguard Worker return [gains[0], gains[2], gains[1], gains[3]] 957*b7c941bbSAndroid Build Coastguard Worker else: 958*b7c941bbSAndroid Build Coastguard Worker raise error_util.CameraItsError('Not supported') 959*b7c941bbSAndroid Build Coastguard Worker 960*b7c941bbSAndroid Build Coastguard Worker 961*b7c941bbSAndroid Build Coastguard Workerdef get_white_level(props, cap_metadata=None): 962*b7c941bbSAndroid Build Coastguard Worker """Gets white level to use for a given capture. 963*b7c941bbSAndroid Build Coastguard Worker 964*b7c941bbSAndroid Build Coastguard Worker Uses a dynamic value from the capture result if available, else falls back 965*b7c941bbSAndroid Build Coastguard Worker to the static global value in the camera characteristics. 966*b7c941bbSAndroid Build Coastguard Worker 967*b7c941bbSAndroid Build Coastguard Worker Args: 968*b7c941bbSAndroid Build Coastguard Worker props: The camera properties object. 969*b7c941bbSAndroid Build Coastguard Worker cap_metadata: A capture results metadata object. 970*b7c941bbSAndroid Build Coastguard Worker 971*b7c941bbSAndroid Build Coastguard Worker Returns: 972*b7c941bbSAndroid Build Coastguard Worker Float white level value. 973*b7c941bbSAndroid Build Coastguard Worker """ 974*b7c941bbSAndroid Build Coastguard Worker if (cap_metadata is not None and 975*b7c941bbSAndroid Build Coastguard Worker 'android.sensor.dynamicWhiteLevel' in cap_metadata and 976*b7c941bbSAndroid Build Coastguard Worker cap_metadata['android.sensor.dynamicWhiteLevel'] is not None): 977*b7c941bbSAndroid Build Coastguard Worker white_level = cap_metadata['android.sensor.dynamicWhiteLevel'] 978*b7c941bbSAndroid Build Coastguard Worker logging.debug('dynamic white level: %.2f', white_level) 979*b7c941bbSAndroid Build Coastguard Worker else: 980*b7c941bbSAndroid Build Coastguard Worker white_level = props['android.sensor.info.whiteLevel'] 981*b7c941bbSAndroid Build Coastguard Worker logging.debug('white level: %.2f', white_level) 982*b7c941bbSAndroid Build Coastguard Worker return float(white_level) 983*b7c941bbSAndroid Build Coastguard Worker 984*b7c941bbSAndroid Build Coastguard Worker 985*b7c941bbSAndroid Build Coastguard Workerdef get_black_levels(props, cap=None, is_quad_bayer=False): 986*b7c941bbSAndroid Build Coastguard Worker """Gets black levels to use for a given capture. 987*b7c941bbSAndroid Build Coastguard Worker 988*b7c941bbSAndroid Build Coastguard Worker Uses a dynamic value from the capture result if available, else falls back 989*b7c941bbSAndroid Build Coastguard Worker to the static global value in the camera characteristics. 990*b7c941bbSAndroid Build Coastguard Worker 991*b7c941bbSAndroid Build Coastguard Worker Args: 992*b7c941bbSAndroid Build Coastguard Worker props: The camera properties object. 993*b7c941bbSAndroid Build Coastguard Worker cap: A capture object. 994*b7c941bbSAndroid Build Coastguard Worker is_quad_bayer: Boolean flag for Bayer or Quad Bayer capture. 995*b7c941bbSAndroid Build Coastguard Worker 996*b7c941bbSAndroid Build Coastguard Worker Returns: 997*b7c941bbSAndroid Build Coastguard Worker A list of black level values reordered in canonical order. 998*b7c941bbSAndroid Build Coastguard Worker """ 999*b7c941bbSAndroid Build Coastguard Worker if (cap is not None and 1000*b7c941bbSAndroid Build Coastguard Worker 'android.sensor.dynamicBlackLevel' in cap and 1001*b7c941bbSAndroid Build Coastguard Worker cap['android.sensor.dynamicBlackLevel'] is not None): 1002*b7c941bbSAndroid Build Coastguard Worker black_levels = cap['android.sensor.dynamicBlackLevel'] 1003*b7c941bbSAndroid Build Coastguard Worker else: 1004*b7c941bbSAndroid Build Coastguard Worker black_levels = props['android.sensor.blackLevelPattern'] 1005*b7c941bbSAndroid Build Coastguard Worker 1006*b7c941bbSAndroid Build Coastguard Worker idxs = get_canonical_cfa_order(props, is_quad_bayer) 1007*b7c941bbSAndroid Build Coastguard Worker if is_quad_bayer: 1008*b7c941bbSAndroid Build Coastguard Worker ordered_black_levels = [black_levels[i // 4] for i in idxs] 1009*b7c941bbSAndroid Build Coastguard Worker else: 1010*b7c941bbSAndroid Build Coastguard Worker ordered_black_levels = [black_levels[i] for i in idxs] 1011*b7c941bbSAndroid Build Coastguard Worker return ordered_black_levels 1012*b7c941bbSAndroid Build Coastguard Worker 1013*b7c941bbSAndroid Build Coastguard Worker 1014*b7c941bbSAndroid Build Coastguard Workerdef get_canonical_cfa_order(props, is_quad_bayer=False): 1015*b7c941bbSAndroid Build Coastguard Worker """Returns a list of channel indices according to color filter arrangement. 1016*b7c941bbSAndroid Build Coastguard Worker 1017*b7c941bbSAndroid Build Coastguard Worker Color filter arrangement index is a integer ranging from 0 to 3, which maps 1018*b7c941bbSAndroid Build Coastguard Worker the color filter arrangement in the following way. 1019*b7c941bbSAndroid Build Coastguard Worker 0: R, Gr, Gb, B, 1020*b7c941bbSAndroid Build Coastguard Worker 1: Gr, R, B, Gb, 1021*b7c941bbSAndroid Build Coastguard Worker 2: Gb, B, R, Gr, 1022*b7c941bbSAndroid Build Coastguard Worker 3: B, Gb, Gr, R. 1023*b7c941bbSAndroid Build Coastguard Worker 1024*b7c941bbSAndroid Build Coastguard Worker This function return a list of channel indices that can be used to reorder 1025*b7c941bbSAndroid Build Coastguard Worker the stats data as the canonical order: 1026*b7c941bbSAndroid Build Coastguard Worker (1) For standard Bayer: R, Gr, Gb, B. 1027*b7c941bbSAndroid Build Coastguard Worker (2) For quad Bayer: R0, R1, R2, R3, 1028*b7c941bbSAndroid Build Coastguard Worker Gr0, Gr1, Gr2, Gr3, 1029*b7c941bbSAndroid Build Coastguard Worker Gb0, Gb1, Gb2, Gb3, 1030*b7c941bbSAndroid Build Coastguard Worker B0, B1, B2, B3. 1031*b7c941bbSAndroid Build Coastguard Worker 1032*b7c941bbSAndroid Build Coastguard Worker Args: 1033*b7c941bbSAndroid Build Coastguard Worker props: Camera properties object. 1034*b7c941bbSAndroid Build Coastguard Worker is_quad_bayer: Boolean flag for Bayer or Quad Bayer capture. 1035*b7c941bbSAndroid Build Coastguard Worker 1036*b7c941bbSAndroid Build Coastguard Worker Returns: 1037*b7c941bbSAndroid Build Coastguard Worker A list of channel indices with values ranging from: 1038*b7c941bbSAndroid Build Coastguard Worker (1) [0, 3] for standard Bayer, 1039*b7c941bbSAndroid Build Coastguard Worker (2) [0, 15] for quad Bayer. 1040*b7c941bbSAndroid Build Coastguard Worker """ 1041*b7c941bbSAndroid Build Coastguard Worker cfa_pat = props['android.sensor.info.colorFilterArrangement'] 1042*b7c941bbSAndroid Build Coastguard Worker if not 0 <= cfa_pat < 4: 1043*b7c941bbSAndroid Build Coastguard Worker raise error_util.CameraItsError('Not supported') 1044*b7c941bbSAndroid Build Coastguard Worker 1045*b7c941bbSAndroid Build Coastguard Worker channel_indices = [] 1046*b7c941bbSAndroid Build Coastguard Worker if is_quad_bayer: 1047*b7c941bbSAndroid Build Coastguard Worker color_map = noise_model_constants.QUAD_BAYER_COLOR_FILTER_MAP[cfa_pat] 1048*b7c941bbSAndroid Build Coastguard Worker for ch in noise_model_constants.BAYER_COLORS: 1049*b7c941bbSAndroid Build Coastguard Worker channel_indices.extend(color_map[ch]) 1050*b7c941bbSAndroid Build Coastguard Worker else: 1051*b7c941bbSAndroid Build Coastguard Worker color_map = noise_model_constants.BAYER_COLOR_FILTER_MAP[cfa_pat] 1052*b7c941bbSAndroid Build Coastguard Worker channel_indices = [ 1053*b7c941bbSAndroid Build Coastguard Worker color_map[ch] for ch in noise_model_constants.BAYER_COLORS 1054*b7c941bbSAndroid Build Coastguard Worker ] 1055*b7c941bbSAndroid Build Coastguard Worker return channel_indices 1056*b7c941bbSAndroid Build Coastguard Worker 1057*b7c941bbSAndroid Build Coastguard Worker 1058*b7c941bbSAndroid Build Coastguard Workerdef unpack_rawstats_capture(cap, num_channels=4): 1059*b7c941bbSAndroid Build Coastguard Worker """Unpacks a stats image capture to the mean and variance images. 1060*b7c941bbSAndroid Build Coastguard Worker 1061*b7c941bbSAndroid Build Coastguard Worker Args: 1062*b7c941bbSAndroid Build Coastguard Worker cap: A capture object as returned by its_session_utils.do_capture. 1063*b7c941bbSAndroid Build Coastguard Worker num_channels: The number of color channels in the stats image capture, which 1064*b7c941bbSAndroid Build Coastguard Worker can be one of noise_model_constants.VALID_NUM_CHANNELS. 1065*b7c941bbSAndroid Build Coastguard Worker 1066*b7c941bbSAndroid Build Coastguard Worker Returns: 1067*b7c941bbSAndroid Build Coastguard Worker Tuple (mean_image var_image) of float-4 images, with non-normalized 1068*b7c941bbSAndroid Build Coastguard Worker pixel values computed from the RAW10/RAW16 images on the device 1069*b7c941bbSAndroid Build Coastguard Worker """ 1070*b7c941bbSAndroid Build Coastguard Worker if cap['format'] not in noise_model_constants.VALID_RAW_STATS_FORMATS: 1071*b7c941bbSAndroid Build Coastguard Worker raise AssertionError(f"Unsupported stats format: {cap['format']}") 1072*b7c941bbSAndroid Build Coastguard Worker 1073*b7c941bbSAndroid Build Coastguard Worker if num_channels not in noise_model_constants.VALID_NUM_CHANNELS: 1074*b7c941bbSAndroid Build Coastguard Worker raise AssertionError( 1075*b7c941bbSAndroid Build Coastguard Worker f'Unsupported number of channels {num_channels}, which should be in' 1076*b7c941bbSAndroid Build Coastguard Worker f' {noise_model_constants.VALID_NUM_CHANNELS}.' 1077*b7c941bbSAndroid Build Coastguard Worker ) 1078*b7c941bbSAndroid Build Coastguard Worker 1079*b7c941bbSAndroid Build Coastguard Worker w = cap['width'] 1080*b7c941bbSAndroid Build Coastguard Worker h = cap['height'] 1081*b7c941bbSAndroid Build Coastguard Worker img = numpy.ndarray( 1082*b7c941bbSAndroid Build Coastguard Worker shape=(2 * h * w * num_channels,), dtype='<f', buffer=cap['data'] 1083*b7c941bbSAndroid Build Coastguard Worker ) 1084*b7c941bbSAndroid Build Coastguard Worker analysis_image = img.reshape((2, h, w, num_channels)) 1085*b7c941bbSAndroid Build Coastguard Worker mean_image = analysis_image[0, :, :, :].reshape(h, w, num_channels) 1086*b7c941bbSAndroid Build Coastguard Worker var_image = analysis_image[1, :, :, :].reshape(h, w, num_channels) 1087*b7c941bbSAndroid Build Coastguard Worker return mean_image, var_image 1088*b7c941bbSAndroid Build Coastguard Worker 1089*b7c941bbSAndroid Build Coastguard Worker 1090*b7c941bbSAndroid Build Coastguard Workerdef get_image_patch(img, xnorm, ynorm, wnorm, hnorm): 1091*b7c941bbSAndroid Build Coastguard Worker """Get a patch (tile) of an image. 1092*b7c941bbSAndroid Build Coastguard Worker 1093*b7c941bbSAndroid Build Coastguard Worker Args: 1094*b7c941bbSAndroid Build Coastguard Worker img: Numpy float image array, with pixel values in [0,1]. 1095*b7c941bbSAndroid Build Coastguard Worker xnorm: 1096*b7c941bbSAndroid Build Coastguard Worker ynorm: 1097*b7c941bbSAndroid Build Coastguard Worker wnorm: 1098*b7c941bbSAndroid Build Coastguard Worker hnorm: Normalized (in [0,1]) coords for the tile. 1099*b7c941bbSAndroid Build Coastguard Worker 1100*b7c941bbSAndroid Build Coastguard Worker Returns: 1101*b7c941bbSAndroid Build Coastguard Worker Numpy float image array of the patch. 1102*b7c941bbSAndroid Build Coastguard Worker """ 1103*b7c941bbSAndroid Build Coastguard Worker hfull = img.shape[0] 1104*b7c941bbSAndroid Build Coastguard Worker wfull = img.shape[1] 1105*b7c941bbSAndroid Build Coastguard Worker xtile = int(math.ceil(xnorm * wfull)) 1106*b7c941bbSAndroid Build Coastguard Worker ytile = int(math.ceil(ynorm * hfull)) 1107*b7c941bbSAndroid Build Coastguard Worker wtile = int(math.floor(wnorm * wfull)) 1108*b7c941bbSAndroid Build Coastguard Worker htile = int(math.floor(hnorm * hfull)) 1109*b7c941bbSAndroid Build Coastguard Worker if len(img.shape) == 2: 1110*b7c941bbSAndroid Build Coastguard Worker return img[ytile:ytile + htile, xtile:xtile + wtile].copy() 1111*b7c941bbSAndroid Build Coastguard Worker else: 1112*b7c941bbSAndroid Build Coastguard Worker return img[ytile:ytile + htile, xtile:xtile + wtile, :].copy() 1113*b7c941bbSAndroid Build Coastguard Worker 1114*b7c941bbSAndroid Build Coastguard Worker 1115*b7c941bbSAndroid Build Coastguard Workerdef compute_image_means(img): 1116*b7c941bbSAndroid Build Coastguard Worker """Calculate the mean of each color channel in the image. 1117*b7c941bbSAndroid Build Coastguard Worker 1118*b7c941bbSAndroid Build Coastguard Worker Args: 1119*b7c941bbSAndroid Build Coastguard Worker img: Numpy float image array, with pixel values in [0,1]. 1120*b7c941bbSAndroid Build Coastguard Worker 1121*b7c941bbSAndroid Build Coastguard Worker Returns: 1122*b7c941bbSAndroid Build Coastguard Worker A list of mean values, one per color channel in the image. 1123*b7c941bbSAndroid Build Coastguard Worker """ 1124*b7c941bbSAndroid Build Coastguard Worker means = [] 1125*b7c941bbSAndroid Build Coastguard Worker chans = img.shape[2] 1126*b7c941bbSAndroid Build Coastguard Worker for i in range(chans): 1127*b7c941bbSAndroid Build Coastguard Worker means.append(numpy.mean(img[:, :, i], dtype=numpy.float64)) 1128*b7c941bbSAndroid Build Coastguard Worker return means 1129*b7c941bbSAndroid Build Coastguard Worker 1130*b7c941bbSAndroid Build Coastguard Worker 1131*b7c941bbSAndroid Build Coastguard Workerdef compute_image_variances(img): 1132*b7c941bbSAndroid Build Coastguard Worker """Calculate the variance of each color channel in the image. 1133*b7c941bbSAndroid Build Coastguard Worker 1134*b7c941bbSAndroid Build Coastguard Worker Args: 1135*b7c941bbSAndroid Build Coastguard Worker img: Numpy float image array, with pixel values in [0,1]. 1136*b7c941bbSAndroid Build Coastguard Worker 1137*b7c941bbSAndroid Build Coastguard Worker Returns: 1138*b7c941bbSAndroid Build Coastguard Worker A list of variance values, one per color channel in the image. 1139*b7c941bbSAndroid Build Coastguard Worker """ 1140*b7c941bbSAndroid Build Coastguard Worker variances = [] 1141*b7c941bbSAndroid Build Coastguard Worker chans = img.shape[2] 1142*b7c941bbSAndroid Build Coastguard Worker for i in range(chans): 1143*b7c941bbSAndroid Build Coastguard Worker variances.append(numpy.var(img[:, :, i], dtype=numpy.float64)) 1144*b7c941bbSAndroid Build Coastguard Worker return variances 1145*b7c941bbSAndroid Build Coastguard Worker 1146*b7c941bbSAndroid Build Coastguard Worker 1147*b7c941bbSAndroid Build Coastguard Workerdef compute_image_sharpness(img): 1148*b7c941bbSAndroid Build Coastguard Worker """Calculate the sharpness of input image. 1149*b7c941bbSAndroid Build Coastguard Worker 1150*b7c941bbSAndroid Build Coastguard Worker Args: 1151*b7c941bbSAndroid Build Coastguard Worker img: numpy float RGB/luma image array, with pixel values in [0,1]. 1152*b7c941bbSAndroid Build Coastguard Worker 1153*b7c941bbSAndroid Build Coastguard Worker Returns: 1154*b7c941bbSAndroid Build Coastguard Worker Sharpness estimation value based on the average of gradient magnitude. 1155*b7c941bbSAndroid Build Coastguard Worker Larger value means the image is sharper. 1156*b7c941bbSAndroid Build Coastguard Worker """ 1157*b7c941bbSAndroid Build Coastguard Worker chans = img.shape[2] 1158*b7c941bbSAndroid Build Coastguard Worker if chans != 1 and chans != 3: 1159*b7c941bbSAndroid Build Coastguard Worker raise AssertionError(f'Not RGB or MONO image! depth: {chans}') 1160*b7c941bbSAndroid Build Coastguard Worker if chans == 1: 1161*b7c941bbSAndroid Build Coastguard Worker luma = img[:, :, 0] 1162*b7c941bbSAndroid Build Coastguard Worker else: 1163*b7c941bbSAndroid Build Coastguard Worker luma = convert_rgb_to_grayscale(img) 1164*b7c941bbSAndroid Build Coastguard Worker gy, gx = numpy.gradient(luma) 1165*b7c941bbSAndroid Build Coastguard Worker return numpy.average(numpy.sqrt(gy*gy + gx*gx)) 1166*b7c941bbSAndroid Build Coastguard Worker 1167*b7c941bbSAndroid Build Coastguard Worker 1168*b7c941bbSAndroid Build Coastguard Workerdef compute_image_max_gradients(img): 1169*b7c941bbSAndroid Build Coastguard Worker """Calculate the maximum gradient of each color channel in the image. 1170*b7c941bbSAndroid Build Coastguard Worker 1171*b7c941bbSAndroid Build Coastguard Worker Args: 1172*b7c941bbSAndroid Build Coastguard Worker img: Numpy float image array, with pixel values in [0,1]. 1173*b7c941bbSAndroid Build Coastguard Worker 1174*b7c941bbSAndroid Build Coastguard Worker Returns: 1175*b7c941bbSAndroid Build Coastguard Worker A list of gradient max values, one per color channel in the image. 1176*b7c941bbSAndroid Build Coastguard Worker """ 1177*b7c941bbSAndroid Build Coastguard Worker grads = [] 1178*b7c941bbSAndroid Build Coastguard Worker chans = img.shape[2] 1179*b7c941bbSAndroid Build Coastguard Worker for i in range(chans): 1180*b7c941bbSAndroid Build Coastguard Worker grads.append(numpy.amax(numpy.gradient(img[:, :, i]))) 1181*b7c941bbSAndroid Build Coastguard Worker return grads 1182*b7c941bbSAndroid Build Coastguard Worker 1183*b7c941bbSAndroid Build Coastguard Worker 1184*b7c941bbSAndroid Build Coastguard Workerdef compute_image_snrs(img): 1185*b7c941bbSAndroid Build Coastguard Worker """Calculate the SNR (dB) of each color channel in the image. 1186*b7c941bbSAndroid Build Coastguard Worker 1187*b7c941bbSAndroid Build Coastguard Worker Args: 1188*b7c941bbSAndroid Build Coastguard Worker img: Numpy float image array, with pixel values in [0,1]. 1189*b7c941bbSAndroid Build Coastguard Worker 1190*b7c941bbSAndroid Build Coastguard Worker Returns: 1191*b7c941bbSAndroid Build Coastguard Worker A list of SNR values in dB, one per color channel in the image. 1192*b7c941bbSAndroid Build Coastguard Worker """ 1193*b7c941bbSAndroid Build Coastguard Worker means = compute_image_means(img) 1194*b7c941bbSAndroid Build Coastguard Worker variances = compute_image_variances(img) 1195*b7c941bbSAndroid Build Coastguard Worker std_devs = [math.sqrt(v) for v in variances] 1196*b7c941bbSAndroid Build Coastguard Worker snrs = [20 * math.log10(m/s) for m, s in zip(means, std_devs)] 1197*b7c941bbSAndroid Build Coastguard Worker return snrs 1198*b7c941bbSAndroid Build Coastguard Worker 1199*b7c941bbSAndroid Build Coastguard Worker 1200*b7c941bbSAndroid Build Coastguard Workerdef convert_rgb_to_grayscale(img): 1201*b7c941bbSAndroid Build Coastguard Worker """Convert a 3-D array RGB image to grayscale image. 1202*b7c941bbSAndroid Build Coastguard Worker 1203*b7c941bbSAndroid Build Coastguard Worker Args: 1204*b7c941bbSAndroid Build Coastguard Worker img: numpy 3-D array RGB image of type [0.0, 1.0] float or [0, 255] uint8. 1205*b7c941bbSAndroid Build Coastguard Worker 1206*b7c941bbSAndroid Build Coastguard Worker Returns: 1207*b7c941bbSAndroid Build Coastguard Worker 2-D grayscale image of same type as input. 1208*b7c941bbSAndroid Build Coastguard Worker """ 1209*b7c941bbSAndroid Build Coastguard Worker chans = img.shape[2] 1210*b7c941bbSAndroid Build Coastguard Worker if chans != 3: 1211*b7c941bbSAndroid Build Coastguard Worker raise AssertionError(f'Not an RGB image! Depth: {chans}') 1212*b7c941bbSAndroid Build Coastguard Worker img_gray = numpy.dot(img[..., :3], RGB2GRAY_WEIGHTS) 1213*b7c941bbSAndroid Build Coastguard Worker if img.dtype == 'uint8': 1214*b7c941bbSAndroid Build Coastguard Worker return img_gray.round().astype(numpy.uint8) 1215*b7c941bbSAndroid Build Coastguard Worker else: 1216*b7c941bbSAndroid Build Coastguard Worker return img_gray 1217*b7c941bbSAndroid Build Coastguard Worker 1218*b7c941bbSAndroid Build Coastguard Worker 1219*b7c941bbSAndroid Build Coastguard Workerdef normalize_img(img): 1220*b7c941bbSAndroid Build Coastguard Worker """Normalize the image values to between 0 and 1. 1221*b7c941bbSAndroid Build Coastguard Worker 1222*b7c941bbSAndroid Build Coastguard Worker Args: 1223*b7c941bbSAndroid Build Coastguard Worker img: 2-D numpy array of image values 1224*b7c941bbSAndroid Build Coastguard Worker Returns: 1225*b7c941bbSAndroid Build Coastguard Worker Normalized image 1226*b7c941bbSAndroid Build Coastguard Worker """ 1227*b7c941bbSAndroid Build Coastguard Worker return (img - numpy.amin(img))/(numpy.amax(img) - numpy.amin(img)) 1228*b7c941bbSAndroid Build Coastguard Worker 1229*b7c941bbSAndroid Build Coastguard Worker 1230*b7c941bbSAndroid Build Coastguard Workerdef rotate_img_per_argv(img): 1231*b7c941bbSAndroid Build Coastguard Worker """Rotate an image 180 degrees if "rotate" is in argv. 1232*b7c941bbSAndroid Build Coastguard Worker 1233*b7c941bbSAndroid Build Coastguard Worker Args: 1234*b7c941bbSAndroid Build Coastguard Worker img: 2-D numpy array of image values 1235*b7c941bbSAndroid Build Coastguard Worker Returns: 1236*b7c941bbSAndroid Build Coastguard Worker Rotated image 1237*b7c941bbSAndroid Build Coastguard Worker """ 1238*b7c941bbSAndroid Build Coastguard Worker img_out = img 1239*b7c941bbSAndroid Build Coastguard Worker if 'rotate180' in sys.argv: 1240*b7c941bbSAndroid Build Coastguard Worker img_out = numpy.fliplr(numpy.flipud(img_out)) 1241*b7c941bbSAndroid Build Coastguard Worker return img_out 1242*b7c941bbSAndroid Build Coastguard Worker 1243*b7c941bbSAndroid Build Coastguard Worker 1244*b7c941bbSAndroid Build Coastguard Workerdef compute_image_rms_difference_1d(rgb_x, rgb_y): 1245*b7c941bbSAndroid Build Coastguard Worker """Calculate the RMS difference between 2 RBG images as 1D arrays. 1246*b7c941bbSAndroid Build Coastguard Worker 1247*b7c941bbSAndroid Build Coastguard Worker Args: 1248*b7c941bbSAndroid Build Coastguard Worker rgb_x: image array 1249*b7c941bbSAndroid Build Coastguard Worker rgb_y: image array 1250*b7c941bbSAndroid Build Coastguard Worker 1251*b7c941bbSAndroid Build Coastguard Worker Returns: 1252*b7c941bbSAndroid Build Coastguard Worker rms_diff 1253*b7c941bbSAndroid Build Coastguard Worker """ 1254*b7c941bbSAndroid Build Coastguard Worker len_rgb_x = len(rgb_x) 1255*b7c941bbSAndroid Build Coastguard Worker len_rgb_y = len(rgb_y) 1256*b7c941bbSAndroid Build Coastguard Worker if len_rgb_y != len_rgb_x: 1257*b7c941bbSAndroid Build Coastguard Worker raise AssertionError('RGB images have different number of planes! ' 1258*b7c941bbSAndroid Build Coastguard Worker f'x: {len_rgb_x}, y: {len_rgb_y}') 1259*b7c941bbSAndroid Build Coastguard Worker return math.sqrt(sum([pow(rgb_x[i] - rgb_y[i], 2.0) 1260*b7c941bbSAndroid Build Coastguard Worker for i in range(len_rgb_x)]) / len_rgb_x) 1261*b7c941bbSAndroid Build Coastguard Worker 1262*b7c941bbSAndroid Build Coastguard Worker 1263*b7c941bbSAndroid Build Coastguard Workerdef compute_image_rms_difference_3d(rgb_x, rgb_y): 1264*b7c941bbSAndroid Build Coastguard Worker """Calculate the RMS difference between 2 RBG images as 3D arrays. 1265*b7c941bbSAndroid Build Coastguard Worker 1266*b7c941bbSAndroid Build Coastguard Worker Args: 1267*b7c941bbSAndroid Build Coastguard Worker rgb_x: image array in the form of w * h * channels 1268*b7c941bbSAndroid Build Coastguard Worker rgb_y: image array in the form of w * h * channels 1269*b7c941bbSAndroid Build Coastguard Worker 1270*b7c941bbSAndroid Build Coastguard Worker Returns: 1271*b7c941bbSAndroid Build Coastguard Worker rms_diff 1272*b7c941bbSAndroid Build Coastguard Worker """ 1273*b7c941bbSAndroid Build Coastguard Worker shape_rgb_x = numpy.shape(rgb_x) 1274*b7c941bbSAndroid Build Coastguard Worker shape_rgb_y = numpy.shape(rgb_y) 1275*b7c941bbSAndroid Build Coastguard Worker if shape_rgb_y != shape_rgb_x: 1276*b7c941bbSAndroid Build Coastguard Worker raise AssertionError('RGB images have different number of planes! ' 1277*b7c941bbSAndroid Build Coastguard Worker f'x: {shape_rgb_x}, y: {shape_rgb_y}') 1278*b7c941bbSAndroid Build Coastguard Worker if len(shape_rgb_x) != 3: 1279*b7c941bbSAndroid Build Coastguard Worker raise AssertionError(f'RGB images dimension {len(shape_rgb_x)} is not 3!') 1280*b7c941bbSAndroid Build Coastguard Worker 1281*b7c941bbSAndroid Build Coastguard Worker mean_square_sum = 0.0 1282*b7c941bbSAndroid Build Coastguard Worker for i in range(shape_rgb_x[0]): 1283*b7c941bbSAndroid Build Coastguard Worker for j in range(shape_rgb_x[1]): 1284*b7c941bbSAndroid Build Coastguard Worker for k in range(shape_rgb_x[2]): 1285*b7c941bbSAndroid Build Coastguard Worker mean_square_sum += pow(float(rgb_x[i][j][k]) - float(rgb_y[i][j][k]), 1286*b7c941bbSAndroid Build Coastguard Worker 2.0) 1287*b7c941bbSAndroid Build Coastguard Worker return (math.sqrt(mean_square_sum / 1288*b7c941bbSAndroid Build Coastguard Worker (shape_rgb_x[0] * shape_rgb_x[1] * shape_rgb_x[2]))) 1289*b7c941bbSAndroid Build Coastguard Worker 1290*b7c941bbSAndroid Build Coastguard Worker 1291*b7c941bbSAndroid Build Coastguard Workerdef compute_image_sad(img_x, img_y): 1292*b7c941bbSAndroid Build Coastguard Worker """Calculate the sum of absolute differences between 2 images. 1293*b7c941bbSAndroid Build Coastguard Worker 1294*b7c941bbSAndroid Build Coastguard Worker Args: 1295*b7c941bbSAndroid Build Coastguard Worker img_x: image array in the form of w * h * channels 1296*b7c941bbSAndroid Build Coastguard Worker img_y: image array in the form of w * h * channels 1297*b7c941bbSAndroid Build Coastguard Worker 1298*b7c941bbSAndroid Build Coastguard Worker Returns: 1299*b7c941bbSAndroid Build Coastguard Worker sad 1300*b7c941bbSAndroid Build Coastguard Worker """ 1301*b7c941bbSAndroid Build Coastguard Worker img_x = img_x[:, :, 1:].ravel() 1302*b7c941bbSAndroid Build Coastguard Worker img_y = img_y[:, :, 1:].ravel() 1303*b7c941bbSAndroid Build Coastguard Worker return numpy.sum(numpy.abs(numpy.subtract(img_x, img_y, dtype=float))) 1304*b7c941bbSAndroid Build Coastguard Worker 1305*b7c941bbSAndroid Build Coastguard Worker 1306*b7c941bbSAndroid Build Coastguard Workerdef get_img(buffer): 1307*b7c941bbSAndroid Build Coastguard Worker """Return a PIL.Image of the capture buffer. 1308*b7c941bbSAndroid Build Coastguard Worker 1309*b7c941bbSAndroid Build Coastguard Worker Args: 1310*b7c941bbSAndroid Build Coastguard Worker buffer: data field from the capture result. 1311*b7c941bbSAndroid Build Coastguard Worker 1312*b7c941bbSAndroid Build Coastguard Worker Returns: 1313*b7c941bbSAndroid Build Coastguard Worker A PIL.Image 1314*b7c941bbSAndroid Build Coastguard Worker """ 1315*b7c941bbSAndroid Build Coastguard Worker return Image.open(io.BytesIO(buffer)) 1316*b7c941bbSAndroid Build Coastguard Worker 1317*b7c941bbSAndroid Build Coastguard Worker 1318*b7c941bbSAndroid Build Coastguard Workerdef jpeg_has_icc_profile(jpeg_img): 1319*b7c941bbSAndroid Build Coastguard Worker """Checks if a jpeg PIL.Image has an icc profile attached. 1320*b7c941bbSAndroid Build Coastguard Worker 1321*b7c941bbSAndroid Build Coastguard Worker Args: 1322*b7c941bbSAndroid Build Coastguard Worker jpeg_img: The PIL.Image. 1323*b7c941bbSAndroid Build Coastguard Worker 1324*b7c941bbSAndroid Build Coastguard Worker Returns: 1325*b7c941bbSAndroid Build Coastguard Worker True if an icc profile is present, False otherwise. 1326*b7c941bbSAndroid Build Coastguard Worker """ 1327*b7c941bbSAndroid Build Coastguard Worker return jpeg_img.info.get('icc_profile') is not None 1328*b7c941bbSAndroid Build Coastguard Worker 1329*b7c941bbSAndroid Build Coastguard Worker 1330*b7c941bbSAndroid Build Coastguard Workerdef get_primary_chromaticity(primary): 1331*b7c941bbSAndroid Build Coastguard Worker """Given an ImageCms primary, returns just the xy chromaticity coordinates. 1332*b7c941bbSAndroid Build Coastguard Worker 1333*b7c941bbSAndroid Build Coastguard Worker Args: 1334*b7c941bbSAndroid Build Coastguard Worker primary: The primary from the ImageCms profile. 1335*b7c941bbSAndroid Build Coastguard Worker 1336*b7c941bbSAndroid Build Coastguard Worker Returns: 1337*b7c941bbSAndroid Build Coastguard Worker (float, float): The xy chromaticity coordinates of the primary. 1338*b7c941bbSAndroid Build Coastguard Worker """ 1339*b7c941bbSAndroid Build Coastguard Worker ((_, _, _), (x, y, _)) = primary 1340*b7c941bbSAndroid Build Coastguard Worker return x, y 1341*b7c941bbSAndroid Build Coastguard Worker 1342*b7c941bbSAndroid Build Coastguard Worker 1343*b7c941bbSAndroid Build Coastguard Workerdef is_jpeg_icc_profile_correct(jpeg_img, color_space, icc_profile_path=None): 1344*b7c941bbSAndroid Build Coastguard Worker """Compare a jpeg's icc profile to a color space's expected parameters. 1345*b7c941bbSAndroid Build Coastguard Worker 1346*b7c941bbSAndroid Build Coastguard Worker Args: 1347*b7c941bbSAndroid Build Coastguard Worker jpeg_img: The PIL.Image. 1348*b7c941bbSAndroid Build Coastguard Worker color_space: 'DISPLAY_P3' or 'SRGB' 1349*b7c941bbSAndroid Build Coastguard Worker icc_profile_path: Optional path to an icc file to be created with the 1350*b7c941bbSAndroid Build Coastguard Worker raw contents. 1351*b7c941bbSAndroid Build Coastguard Worker 1352*b7c941bbSAndroid Build Coastguard Worker Returns: 1353*b7c941bbSAndroid Build Coastguard Worker True if the icc profile matches expectations, False otherwise. 1354*b7c941bbSAndroid Build Coastguard Worker """ 1355*b7c941bbSAndroid Build Coastguard Worker icc = jpeg_img.info.get('icc_profile') 1356*b7c941bbSAndroid Build Coastguard Worker f = io.BytesIO(icc) 1357*b7c941bbSAndroid Build Coastguard Worker icc_profile = ImageCms.getOpenProfile(f) 1358*b7c941bbSAndroid Build Coastguard Worker 1359*b7c941bbSAndroid Build Coastguard Worker if icc_profile_path is not None: 1360*b7c941bbSAndroid Build Coastguard Worker raw_icc_bytes = f.getvalue() 1361*b7c941bbSAndroid Build Coastguard Worker f = open(icc_profile_path, 'wb') 1362*b7c941bbSAndroid Build Coastguard Worker f.write(raw_icc_bytes) 1363*b7c941bbSAndroid Build Coastguard Worker f.close() 1364*b7c941bbSAndroid Build Coastguard Worker 1365*b7c941bbSAndroid Build Coastguard Worker cms_profile = icc_profile.profile 1366*b7c941bbSAndroid Build Coastguard Worker (rx, ry) = get_primary_chromaticity(cms_profile.red_primary) 1367*b7c941bbSAndroid Build Coastguard Worker (gx, gy) = get_primary_chromaticity(cms_profile.green_primary) 1368*b7c941bbSAndroid Build Coastguard Worker (bx, by) = get_primary_chromaticity(cms_profile.blue_primary) 1369*b7c941bbSAndroid Build Coastguard Worker 1370*b7c941bbSAndroid Build Coastguard Worker if color_space == 'DISPLAY_P3': 1371*b7c941bbSAndroid Build Coastguard Worker # Expected primaries based on Apple's Display P3 primaries 1372*b7c941bbSAndroid Build Coastguard Worker expected_rx = EXPECTED_RX_P3 1373*b7c941bbSAndroid Build Coastguard Worker expected_ry = EXPECTED_RY_P3 1374*b7c941bbSAndroid Build Coastguard Worker expected_gx = EXPECTED_GX_P3 1375*b7c941bbSAndroid Build Coastguard Worker expected_gy = EXPECTED_GY_P3 1376*b7c941bbSAndroid Build Coastguard Worker expected_bx = EXPECTED_BX_P3 1377*b7c941bbSAndroid Build Coastguard Worker expected_by = EXPECTED_BY_P3 1378*b7c941bbSAndroid Build Coastguard Worker elif color_space == 'SRGB': 1379*b7c941bbSAndroid Build Coastguard Worker # Expected primaries based on Pixel sRGB profile 1380*b7c941bbSAndroid Build Coastguard Worker expected_rx = EXPECTED_RX_SRGB 1381*b7c941bbSAndroid Build Coastguard Worker expected_ry = EXPECTED_RY_SRGB 1382*b7c941bbSAndroid Build Coastguard Worker expected_gx = EXPECTED_GX_SRGB 1383*b7c941bbSAndroid Build Coastguard Worker expected_gy = EXPECTED_GY_SRGB 1384*b7c941bbSAndroid Build Coastguard Worker expected_bx = EXPECTED_BX_SRGB 1385*b7c941bbSAndroid Build Coastguard Worker expected_by = EXPECTED_BY_SRGB 1386*b7c941bbSAndroid Build Coastguard Worker else: 1387*b7c941bbSAndroid Build Coastguard Worker # Unsupported color space for comparison 1388*b7c941bbSAndroid Build Coastguard Worker return False 1389*b7c941bbSAndroid Build Coastguard Worker 1390*b7c941bbSAndroid Build Coastguard Worker cmp_values = [ 1391*b7c941bbSAndroid Build Coastguard Worker [rx, expected_rx], 1392*b7c941bbSAndroid Build Coastguard Worker [ry, expected_ry], 1393*b7c941bbSAndroid Build Coastguard Worker [gx, expected_gx], 1394*b7c941bbSAndroid Build Coastguard Worker [gy, expected_gy], 1395*b7c941bbSAndroid Build Coastguard Worker [bx, expected_bx], 1396*b7c941bbSAndroid Build Coastguard Worker [by, expected_by] 1397*b7c941bbSAndroid Build Coastguard Worker ] 1398*b7c941bbSAndroid Build Coastguard Worker 1399*b7c941bbSAndroid Build Coastguard Worker for (actual, expected) in cmp_values: 1400*b7c941bbSAndroid Build Coastguard Worker if not math.isclose(actual, expected, abs_tol=0.001): 1401*b7c941bbSAndroid Build Coastguard Worker # Values significantly differ 1402*b7c941bbSAndroid Build Coastguard Worker return False 1403*b7c941bbSAndroid Build Coastguard Worker 1404*b7c941bbSAndroid Build Coastguard Worker return True 1405*b7c941bbSAndroid Build Coastguard Worker 1406*b7c941bbSAndroid Build Coastguard Worker 1407*b7c941bbSAndroid Build Coastguard Workerdef area_of_triangle(x1, y1, x2, y2, x3, y3): 1408*b7c941bbSAndroid Build Coastguard Worker """Calculates the area of a triangle formed by three points. 1409*b7c941bbSAndroid Build Coastguard Worker 1410*b7c941bbSAndroid Build Coastguard Worker Args: 1411*b7c941bbSAndroid Build Coastguard Worker x1 (float): The x-coordinate of the first point. 1412*b7c941bbSAndroid Build Coastguard Worker y1 (float): The y-coordinate of the first point. 1413*b7c941bbSAndroid Build Coastguard Worker x2 (float): The x-coordinate of the second point. 1414*b7c941bbSAndroid Build Coastguard Worker y2 (float): The y-coordinate of the second point. 1415*b7c941bbSAndroid Build Coastguard Worker x3 (float): The x-coordinate of the third point. 1416*b7c941bbSAndroid Build Coastguard Worker y3 (float): The y-coordinate of the third point. 1417*b7c941bbSAndroid Build Coastguard Worker 1418*b7c941bbSAndroid Build Coastguard Worker Returns: 1419*b7c941bbSAndroid Build Coastguard Worker float: The area of the triangle. 1420*b7c941bbSAndroid Build Coastguard Worker """ 1421*b7c941bbSAndroid Build Coastguard Worker area = abs((x1 * (y2 - y3) + x2 * (y3 - y1) + x3 * (y1 - y2)) / 2.0) 1422*b7c941bbSAndroid Build Coastguard Worker return area 1423*b7c941bbSAndroid Build Coastguard Worker 1424*b7c941bbSAndroid Build Coastguard Worker 1425*b7c941bbSAndroid Build Coastguard Workerdef point_in_triangle(x1, y1, x2, y2, x3, y3, xp, yp, abs_tol): 1426*b7c941bbSAndroid Build Coastguard Worker """Checks if the point (xp, yp) is inside the triangle. 1427*b7c941bbSAndroid Build Coastguard Worker 1428*b7c941bbSAndroid Build Coastguard Worker Args: 1429*b7c941bbSAndroid Build Coastguard Worker x1 (float): The x-coordinate of the first point. 1430*b7c941bbSAndroid Build Coastguard Worker y1 (float): The y-coordinate of the first point. 1431*b7c941bbSAndroid Build Coastguard Worker x2 (float): The x-coordinate of the second point. 1432*b7c941bbSAndroid Build Coastguard Worker y2 (float): The y-coordinate of the second point. 1433*b7c941bbSAndroid Build Coastguard Worker x3 (float): The x-coordinate of the third point. 1434*b7c941bbSAndroid Build Coastguard Worker y3 (float): The y-coordinate of the third point. 1435*b7c941bbSAndroid Build Coastguard Worker xp (float): The x-coordinate of the point to check. 1436*b7c941bbSAndroid Build Coastguard Worker yp (float): The y-coordinate of the point to check. 1437*b7c941bbSAndroid Build Coastguard Worker abs_tol (float): Absolute tolerance amount. 1438*b7c941bbSAndroid Build Coastguard Worker 1439*b7c941bbSAndroid Build Coastguard Worker Returns: 1440*b7c941bbSAndroid Build Coastguard Worker bool: True if the point is inside the triangle, False otherwise. 1441*b7c941bbSAndroid Build Coastguard Worker """ 1442*b7c941bbSAndroid Build Coastguard Worker a = area_of_triangle(x1, y1, x2, y2, x3, y3) 1443*b7c941bbSAndroid Build Coastguard Worker a1 = area_of_triangle(xp, yp, x2, y2, x3, y3) 1444*b7c941bbSAndroid Build Coastguard Worker a2 = area_of_triangle(x1, y1, xp, yp, x3, y3) 1445*b7c941bbSAndroid Build Coastguard Worker a3 = area_of_triangle(x1, y1, x2, y2, xp, yp) 1446*b7c941bbSAndroid Build Coastguard Worker return math.isclose(a, (a1 + a2 + a3), abs_tol=abs_tol) 1447*b7c941bbSAndroid Build Coastguard Worker 1448*b7c941bbSAndroid Build Coastguard Worker 1449*b7c941bbSAndroid Build Coastguard Workerdef distance(p, q): 1450*b7c941bbSAndroid Build Coastguard Worker """Returns the Euclidean distance from point p to point q. 1451*b7c941bbSAndroid Build Coastguard Worker 1452*b7c941bbSAndroid Build Coastguard Worker Args: 1453*b7c941bbSAndroid Build Coastguard Worker p: an Iterable of numbers 1454*b7c941bbSAndroid Build Coastguard Worker q: an Iterable of numbers 1455*b7c941bbSAndroid Build Coastguard Worker """ 1456*b7c941bbSAndroid Build Coastguard Worker return math.sqrt(sum((px - qx) ** 2.0 for px, qx in zip(p, q))) 1457*b7c941bbSAndroid Build Coastguard Worker 1458*b7c941bbSAndroid Build Coastguard Worker 1459*b7c941bbSAndroid Build Coastguard Workerdef srgb_eotf(img): 1460*b7c941bbSAndroid Build Coastguard Worker """Returns the input sRGB-transferred image with a linear transfer function. 1461*b7c941bbSAndroid Build Coastguard Worker 1462*b7c941bbSAndroid Build Coastguard Worker Args: 1463*b7c941bbSAndroid Build Coastguard Worker img: The input image as a numpy array. 1464*b7c941bbSAndroid Build Coastguard Worker 1465*b7c941bbSAndroid Build Coastguard Worker Returns: 1466*b7c941bbSAndroid Build Coastguard Worker numpy.array: The same image with a linear transfer. 1467*b7c941bbSAndroid Build Coastguard Worker """ 1468*b7c941bbSAndroid Build Coastguard Worker 1469*b7c941bbSAndroid Build Coastguard Worker # Source: 1470*b7c941bbSAndroid Build Coastguard Worker # https://developer.android.com/reference/android/graphics/ColorSpace.Named#DISPLAY_P3 1471*b7c941bbSAndroid Build Coastguard Worker return numpy.where( 1472*b7c941bbSAndroid Build Coastguard Worker img < 0.04045, 1473*b7c941bbSAndroid Build Coastguard Worker img / 12.92, 1474*b7c941bbSAndroid Build Coastguard Worker numpy.pow((img + 0.055) / 1.055, 2.4) 1475*b7c941bbSAndroid Build Coastguard Worker ) 1476*b7c941bbSAndroid Build Coastguard Worker 1477*b7c941bbSAndroid Build Coastguard Worker 1478*b7c941bbSAndroid Build Coastguard Workerdef ciexyz_to_xy(img): 1479*b7c941bbSAndroid Build Coastguard Worker """Returns the input CIE XYZ image in the CIE xy colorspace. 1480*b7c941bbSAndroid Build Coastguard Worker 1481*b7c941bbSAndroid Build Coastguard Worker Args: 1482*b7c941bbSAndroid Build Coastguard Worker img: The input image as a numpy array 1483*b7c941bbSAndroid Build Coastguard Worker 1484*b7c941bbSAndroid Build Coastguard Worker Returns: 1485*b7c941bbSAndroid Build Coastguard Worker numpy.array: The same image in the CIE xy colorspace. 1486*b7c941bbSAndroid Build Coastguard Worker """ 1487*b7c941bbSAndroid Build Coastguard Worker img_sums = img.sum(axis=2) 1488*b7c941bbSAndroid Build Coastguard Worker img_sums[img_sums == 0] = 1 1489*b7c941bbSAndroid Build Coastguard Worker img[:, :, 0] = img[:, :, 0] / img_sums 1490*b7c941bbSAndroid Build Coastguard Worker img[:, :, 1] = img[:, :, 1] / img_sums 1491*b7c941bbSAndroid Build Coastguard Worker return img[:, :, :2] 1492*b7c941bbSAndroid Build Coastguard Worker 1493*b7c941bbSAndroid Build Coastguard Worker 1494*b7c941bbSAndroid Build Coastguard Workerdef p3_img_has_wide_gamut(wide_img): 1495*b7c941bbSAndroid Build Coastguard Worker """Check if a DISPLAY_P3 image contains wide gamut pixels. 1496*b7c941bbSAndroid Build Coastguard Worker 1497*b7c941bbSAndroid Build Coastguard Worker Given a DISPLAY_P3 image that should have a wider gamut than SRGB, checks all 1498*b7c941bbSAndroid Build Coastguard Worker pixel values to see if any reside outside the SRGB gamut. This is done by 1499*b7c941bbSAndroid Build Coastguard Worker converting to CIE xy chromaticities using a Bradford chromatic adaptation for 1500*b7c941bbSAndroid Build Coastguard Worker consistency with ICC profiles. 1501*b7c941bbSAndroid Build Coastguard Worker 1502*b7c941bbSAndroid Build Coastguard Worker Args: 1503*b7c941bbSAndroid Build Coastguard Worker wide_img: The PIL.Image in the DISPLAY_P3 color space. 1504*b7c941bbSAndroid Build Coastguard Worker 1505*b7c941bbSAndroid Build Coastguard Worker Returns: 1506*b7c941bbSAndroid Build Coastguard Worker True if the gamut of wide_img is greater than that of SRGB. 1507*b7c941bbSAndroid Build Coastguard Worker False otherwise. 1508*b7c941bbSAndroid Build Coastguard Worker """ 1509*b7c941bbSAndroid Build Coastguard Worker w = wide_img.size[0] 1510*b7c941bbSAndroid Build Coastguard Worker h = wide_img.size[1] 1511*b7c941bbSAndroid Build Coastguard Worker wide_arr = numpy.array(wide_img) 1512*b7c941bbSAndroid Build Coastguard Worker linear_arr = srgb_eotf(wide_arr / float(numpy.iinfo(numpy.uint8).max)) 1513*b7c941bbSAndroid Build Coastguard Worker 1514*b7c941bbSAndroid Build Coastguard Worker xyz_arr = numpy.matmul(linear_arr, P3_TO_XYZ) 1515*b7c941bbSAndroid Build Coastguard Worker xy_arr = ciexyz_to_xy(xyz_arr) 1516*b7c941bbSAndroid Build Coastguard Worker 1517*b7c941bbSAndroid Build Coastguard Worker for y in range(h): 1518*b7c941bbSAndroid Build Coastguard Worker for x in range(w): 1519*b7c941bbSAndroid Build Coastguard Worker # Check if the pixel chromaticity is inside or outside the SRGB gamut. 1520*b7c941bbSAndroid Build Coastguard Worker # This check is not guaranteed not to emit false positives / negatives, 1521*b7c941bbSAndroid Build Coastguard Worker # however the probability of either on an arbitrary DISPLAY_P3 camera 1522*b7c941bbSAndroid Build Coastguard Worker # capture is exceedingly unlikely. 1523*b7c941bbSAndroid Build Coastguard Worker if not point_in_triangle(x1=EXPECTED_RX_SRGB, y1=EXPECTED_RY_SRGB, 1524*b7c941bbSAndroid Build Coastguard Worker x2=EXPECTED_GX_SRGB, y2=EXPECTED_GY_SRGB, 1525*b7c941bbSAndroid Build Coastguard Worker x3=EXPECTED_BX_SRGB, y3=EXPECTED_BY_SRGB, 1526*b7c941bbSAndroid Build Coastguard Worker xp=xy_arr[y][x][0], yp=xy_arr[y][x][1], 1527*b7c941bbSAndroid Build Coastguard Worker abs_tol=COLORSPACE_TRIANGLE_AREA_TOL): 1528*b7c941bbSAndroid Build Coastguard Worker return True 1529*b7c941bbSAndroid Build Coastguard Worker 1530*b7c941bbSAndroid Build Coastguard Worker return False 1531*b7c941bbSAndroid Build Coastguard Worker 1532*b7c941bbSAndroid Build Coastguard Worker 1533*b7c941bbSAndroid Build Coastguard Workerdef compute_patch_noise(yuv_img, patch_region): 1534*b7c941bbSAndroid Build Coastguard Worker """Computes the noise statistics of a flat patch region in an image. 1535*b7c941bbSAndroid Build Coastguard Worker 1536*b7c941bbSAndroid Build Coastguard Worker For the patch region, the noise statistics are computed for the luma, chroma 1537*b7c941bbSAndroid Build Coastguard Worker U, and chroma V channels. 1538*b7c941bbSAndroid Build Coastguard Worker 1539*b7c941bbSAndroid Build Coastguard Worker Args: 1540*b7c941bbSAndroid Build Coastguard Worker yuv_img: The openCV YUV image to compute noise statistics for. 1541*b7c941bbSAndroid Build Coastguard Worker patch_region: The (x, y, w, h) region to compute noise statistics for. 1542*b7c941bbSAndroid Build Coastguard Worker Returns: 1543*b7c941bbSAndroid Build Coastguard Worker A dictionary of noise statistics with keys luma, chroma_u, chroma_v. 1544*b7c941bbSAndroid Build Coastguard Worker """ 1545*b7c941bbSAndroid Build Coastguard Worker x, y, w, h = patch_region 1546*b7c941bbSAndroid Build Coastguard Worker patch = yuv_img[y : y + h, x : x + w] 1547*b7c941bbSAndroid Build Coastguard Worker return { 1548*b7c941bbSAndroid Build Coastguard Worker 'luma': numpy.std(patch[:, :, 0]), 1549*b7c941bbSAndroid Build Coastguard Worker 'chroma_u': numpy.std(patch[:, :, 1]), 1550*b7c941bbSAndroid Build Coastguard Worker 'chroma_v': numpy.std(patch[:, :, 2]), 1551*b7c941bbSAndroid Build Coastguard Worker } 1552*b7c941bbSAndroid Build Coastguard Worker 1553*b7c941bbSAndroid Build Coastguard Worker 1554*b7c941bbSAndroid Build Coastguard Workerdef convert_image_coords_to_sensor_coords( 1555*b7c941bbSAndroid Build Coastguard Worker aa_width, aa_height, coords, img_width, img_height): 1556*b7c941bbSAndroid Build Coastguard Worker """Transform image coordinates to sensor coordinate system. 1557*b7c941bbSAndroid Build Coastguard Worker 1558*b7c941bbSAndroid Build Coastguard Worker Calculate the difference between sensor active array and image aspect ratio. 1559*b7c941bbSAndroid Build Coastguard Worker Taking the difference into account, figure out if the width or height has been 1560*b7c941bbSAndroid Build Coastguard Worker cropped. Using this information, transform the image coordinates to sensor 1561*b7c941bbSAndroid Build Coastguard Worker coordinates. 1562*b7c941bbSAndroid Build Coastguard Worker 1563*b7c941bbSAndroid Build Coastguard Worker Args: 1564*b7c941bbSAndroid Build Coastguard Worker aa_width: int; active array width. 1565*b7c941bbSAndroid Build Coastguard Worker aa_height: int; active array height. 1566*b7c941bbSAndroid Build Coastguard Worker coords: coordinates; a pair of (x, y) coordinates from image. 1567*b7c941bbSAndroid Build Coastguard Worker img_width: int; width of image. 1568*b7c941bbSAndroid Build Coastguard Worker img_height: int; height of image. 1569*b7c941bbSAndroid Build Coastguard Worker Returns: 1570*b7c941bbSAndroid Build Coastguard Worker sensor_coords: coordinates; corresponding coordinates on 1571*b7c941bbSAndroid Build Coastguard Worker sensor coordinate system. 1572*b7c941bbSAndroid Build Coastguard Worker """ 1573*b7c941bbSAndroid Build Coastguard Worker # TODO: b/330382627 - find out if distortion correction is ON/OFF 1574*b7c941bbSAndroid Build Coastguard Worker aa_aspect_ratio = aa_width / aa_height 1575*b7c941bbSAndroid Build Coastguard Worker image_aspect_ratio = img_width / img_height 1576*b7c941bbSAndroid Build Coastguard Worker if aa_aspect_ratio >= image_aspect_ratio: 1577*b7c941bbSAndroid Build Coastguard Worker # If aa aspect ratio is greater than image aspect ratio, then 1578*b7c941bbSAndroid Build Coastguard Worker # sensor width is being cropped 1579*b7c941bbSAndroid Build Coastguard Worker aspect_ratio_multiplication_factor = aa_height / img_height 1580*b7c941bbSAndroid Build Coastguard Worker crop_width = img_width * aspect_ratio_multiplication_factor 1581*b7c941bbSAndroid Build Coastguard Worker buffer = (aa_width - crop_width) / 2 1582*b7c941bbSAndroid Build Coastguard Worker sensor_coords = (coords[0] * aspect_ratio_multiplication_factor + buffer, 1583*b7c941bbSAndroid Build Coastguard Worker coords[1] * aspect_ratio_multiplication_factor) 1584*b7c941bbSAndroid Build Coastguard Worker else: 1585*b7c941bbSAndroid Build Coastguard Worker # If aa aspect ratio is less than image aspect ratio, then 1586*b7c941bbSAndroid Build Coastguard Worker # sensor height is being cropped 1587*b7c941bbSAndroid Build Coastguard Worker aspect_ratio_multiplication_factor = aa_width / img_width 1588*b7c941bbSAndroid Build Coastguard Worker crop_height = img_height * aspect_ratio_multiplication_factor 1589*b7c941bbSAndroid Build Coastguard Worker buffer = (aa_height - crop_height) / 2 1590*b7c941bbSAndroid Build Coastguard Worker sensor_coords = (coords[0] * aspect_ratio_multiplication_factor, 1591*b7c941bbSAndroid Build Coastguard Worker coords[1] * aspect_ratio_multiplication_factor + buffer) 1592*b7c941bbSAndroid Build Coastguard Worker logging.debug('Sensor coordinates: %s', sensor_coords) 1593*b7c941bbSAndroid Build Coastguard Worker return sensor_coords 1594*b7c941bbSAndroid Build Coastguard Worker 1595*b7c941bbSAndroid Build Coastguard Worker 1596*b7c941bbSAndroid Build Coastguard Workerdef convert_sensor_coords_to_image_coords( 1597*b7c941bbSAndroid Build Coastguard Worker aa_width, aa_height, coords, img_width, img_height): 1598*b7c941bbSAndroid Build Coastguard Worker """Transform sensor coordinates to image coordinate system. 1599*b7c941bbSAndroid Build Coastguard Worker 1600*b7c941bbSAndroid Build Coastguard Worker Calculate the difference between sensor active array and image aspect ratio. 1601*b7c941bbSAndroid Build Coastguard Worker Taking the difference into account, figure out if the width or height has been 1602*b7c941bbSAndroid Build Coastguard Worker cropped. Using this information, transform the sensor coordinates to image 1603*b7c941bbSAndroid Build Coastguard Worker coordinates. 1604*b7c941bbSAndroid Build Coastguard Worker 1605*b7c941bbSAndroid Build Coastguard Worker Args: 1606*b7c941bbSAndroid Build Coastguard Worker aa_width: int; active array width. 1607*b7c941bbSAndroid Build Coastguard Worker aa_height: int; active array height. 1608*b7c941bbSAndroid Build Coastguard Worker coords: coordinates; a pair of (x, y) coordinates from sensor. 1609*b7c941bbSAndroid Build Coastguard Worker img_width: int; width of image. 1610*b7c941bbSAndroid Build Coastguard Worker img_height: int; height of image. 1611*b7c941bbSAndroid Build Coastguard Worker Returns: 1612*b7c941bbSAndroid Build Coastguard Worker image_coords: coordinates; corresponding coordinates on 1613*b7c941bbSAndroid Build Coastguard Worker image coordinate system. 1614*b7c941bbSAndroid Build Coastguard Worker """ 1615*b7c941bbSAndroid Build Coastguard Worker aa_aspect_ratio = aa_width / aa_height 1616*b7c941bbSAndroid Build Coastguard Worker image_aspect_ratio = img_width / img_height 1617*b7c941bbSAndroid Build Coastguard Worker if aa_aspect_ratio >= image_aspect_ratio: 1618*b7c941bbSAndroid Build Coastguard Worker # If aa aspect ratio is greater than image aspect ratio, then 1619*b7c941bbSAndroid Build Coastguard Worker # sensor width is being cropped 1620*b7c941bbSAndroid Build Coastguard Worker aspect_ratio_multiplication_factor = aa_height / img_height 1621*b7c941bbSAndroid Build Coastguard Worker crop_width = img_width * aspect_ratio_multiplication_factor 1622*b7c941bbSAndroid Build Coastguard Worker buffer = (aa_width - crop_width) / 2 1623*b7c941bbSAndroid Build Coastguard Worker image_coords = ( 1624*b7c941bbSAndroid Build Coastguard Worker (coords[0] - buffer) / aspect_ratio_multiplication_factor, 1625*b7c941bbSAndroid Build Coastguard Worker coords[1] / aspect_ratio_multiplication_factor) 1626*b7c941bbSAndroid Build Coastguard Worker else: 1627*b7c941bbSAndroid Build Coastguard Worker # If aa aspect ratio is less than image aspect ratio, then 1628*b7c941bbSAndroid Build Coastguard Worker # sensor height is being cropped 1629*b7c941bbSAndroid Build Coastguard Worker aspect_ratio_multiplication_factor = aa_width / img_width 1630*b7c941bbSAndroid Build Coastguard Worker crop_height = img_height * aspect_ratio_multiplication_factor 1631*b7c941bbSAndroid Build Coastguard Worker buffer = (aa_height - crop_height) / 2 1632*b7c941bbSAndroid Build Coastguard Worker image_coords = ( 1633*b7c941bbSAndroid Build Coastguard Worker coords[0] / aspect_ratio_multiplication_factor, 1634*b7c941bbSAndroid Build Coastguard Worker (coords[1] - buffer) / aspect_ratio_multiplication_factor) 1635*b7c941bbSAndroid Build Coastguard Worker logging.debug('Image coordinates: %s', image_coords) 1636*b7c941bbSAndroid Build Coastguard Worker return image_coords 1637