|  |  |  | @ -1,4 +1,4 @@ | 
			
		
	
		
			
				
					|  |  |  |  | from collections import namedtuple | 
			
		
	
		
			
				
					|  |  |  |  | import itertools | 
			
		
	
		
			
				
					|  |  |  |  | from typing import Any, Dict, Tuple | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | import matplotlib | 
			
		
	
	
		
			
				
					|  |  |  | @ -8,9 +8,8 @@ import pygame  # pylint: disable=import-error | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | from common.transformations.camera import (eon_f_frame_size, eon_f_focal_length, | 
			
		
	
		
			
				
					|  |  |  |  |                                            tici_f_frame_size, tici_f_focal_length) | 
			
		
	
		
			
				
					|  |  |  |  | from selfdrive.config import RADAR_TO_CAMERA | 
			
		
	
		
			
				
					|  |  |  |  | from selfdrive.config import UIParams as UP | 
			
		
	
		
			
				
					|  |  |  |  | from tools.lib.lazy_property import lazy_property | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | RED = (255, 0, 0) | 
			
		
	
		
			
				
					|  |  |  |  | GREEN = (0, 255, 0) | 
			
		
	
	
		
			
				
					|  |  |  | @ -19,8 +18,6 @@ YELLOW = (255, 255, 0) | 
			
		
	
		
			
				
					|  |  |  |  | BLACK = (0, 0, 0) | 
			
		
	
		
			
				
					|  |  |  |  | WHITE = (255, 255, 255) | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | _PATH_X = np.arange(192.) | 
			
		
	
		
			
				
					|  |  |  |  | _PATH_XD = np.arange(192.) | 
			
		
	
		
			
				
					|  |  |  |  | _FULL_FRAME_SIZE = { | 
			
		
	
		
			
				
					|  |  |  |  | } | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
	
		
			
				
					|  |  |  | @ -46,7 +43,24 @@ for width, height, focal in cams: | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | METER_WIDTH = 20 | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | ModelUIData = namedtuple("ModelUIData", ["cpath", "lpath", "rpath", "lead", "lead_future"]) | 
			
		
	
		
			
				
					|  |  |  |  | class Calibration: | 
			
		
	
		
			
				
					|  |  |  |  |   def __init__(self, num_px, extrinsic, intrinsic): | 
			
		
	
		
			
				
					|  |  |  |  |     self.extrinsic = extrinsic | 
			
		
	
		
			
				
					|  |  |  |  |     self.intrinsic = intrinsic | 
			
		
	
		
			
				
					|  |  |  |  |     self.zoom = _BB_TO_FULL_FRAME[num_px][0, 0] | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |   def car_space_to_ff(self, x, y, z): | 
			
		
	
		
			
				
					|  |  |  |  |     ones = np.ones_like(x) | 
			
		
	
		
			
				
					|  |  |  |  |     car_space_projective = np.column_stack((x, y, z, ones)).T | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |     ep = self.extrinsic.dot(car_space_projective) | 
			
		
	
		
			
				
					|  |  |  |  |     kep = self.intrinsic.dot(ep) | 
			
		
	
		
			
				
					|  |  |  |  |     return (kep[:-1, :] / kep[-1, :]).T | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |   def car_space_to_bb(self, x, y, z): | 
			
		
	
		
			
				
					|  |  |  |  |     pts = self.car_space_to_ff(x, y, z) | 
			
		
	
		
			
				
					|  |  |  |  |     return pts / self.zoom | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | _COLOR_CACHE : Dict[Tuple[int, int, int], Any] = {} | 
			
		
	
		
			
				
					|  |  |  |  | def find_color(lidar_surface, color): | 
			
		
	
	
		
			
				
					|  |  |  | @ -55,7 +69,6 @@ def find_color(lidar_surface, color): | 
			
		
	
		
			
				
					|  |  |  |  |   tcolor = 0 | 
			
		
	
		
			
				
					|  |  |  |  |   ret = 255 | 
			
		
	
		
			
				
					|  |  |  |  |   for x in lidar_surface.get_palette(): | 
			
		
	
		
			
				
					|  |  |  |  |     #print tcolor, x | 
			
		
	
		
			
				
					|  |  |  |  |     if x[0:3] == color: | 
			
		
	
		
			
				
					|  |  |  |  |       ret = tcolor | 
			
		
	
		
			
				
					|  |  |  |  |       break | 
			
		
	
	
		
			
				
					|  |  |  | @ -63,12 +76,6 @@ def find_color(lidar_surface, color): | 
			
		
	
		
			
				
					|  |  |  |  |   _COLOR_CACHE[color] = ret | 
			
		
	
		
			
				
					|  |  |  |  |   return ret | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | def warp_points(pt_s, warp_matrix): | 
			
		
	
		
			
				
					|  |  |  |  |   # pt_s are the source points, nxm array. | 
			
		
	
		
			
				
					|  |  |  |  |   pt_d = np.dot(warp_matrix[:, :-1], pt_s.T) + warp_matrix[:, -1, None] | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |   # Divide by last dimension for representation in image space. | 
			
		
	
		
			
				
					|  |  |  |  |   return (pt_d[:-1, :] / pt_d[-1, :]).T | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | def to_lid_pt(y, x): | 
			
		
	
		
			
				
					|  |  |  |  |   px, py = -x * UP.lidar_zoom + UP.lidar_car_x, -y * UP.lidar_zoom + UP.lidar_car_y | 
			
		
	
	
		
			
				
					|  |  |  | @ -77,17 +84,10 @@ def to_lid_pt(y, x): | 
			
		
	
		
			
				
					|  |  |  |  |   return -1, -1 | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | def draw_path(y, x, color, img, calibration, top_down, lid_color=None): | 
			
		
	
		
			
				
					|  |  |  |  |   # TODO: Remove big box. | 
			
		
	
		
			
				
					|  |  |  |  |   uv_model_real = warp_points(np.column_stack((x, y)), calibration.car_to_model) | 
			
		
	
		
			
				
					|  |  |  |  |   uv_model = np.round(uv_model_real).astype(int) | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |   uv_model_dots = uv_model[np.logical_and.reduce((np.all(  # pylint: disable=no-member | 
			
		
	
		
			
				
					|  |  |  |  |     uv_model > 0, axis=1), uv_model[:, 0] < img.shape[1] - 1, uv_model[:, 1] < | 
			
		
	
		
			
				
					|  |  |  |  |                                                   img.shape[0] - 1))] | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |   for i, j in ((-1, 0), (0, -1), (0, 0), (0, 1), (1, 0)): | 
			
		
	
		
			
				
					|  |  |  |  |     img[uv_model_dots[:, 1] + i, uv_model_dots[:, 0] + j] = color | 
			
		
	
		
			
				
					|  |  |  |  | def draw_path(path, color, img, calibration, top_down, lid_color=None, z_off=0): | 
			
		
	
		
			
				
					|  |  |  |  |   x, y, z = np.asarray(path.x), np.asarray(path.y), np.asarray(path.z) | 
			
		
	
		
			
				
					|  |  |  |  |   pts = calibration.car_space_to_bb(x, -y, -z + z_off) | 
			
		
	
		
			
				
					|  |  |  |  |   pts = np.round(pts).astype(int) | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |   # draw lidar path point on lidar | 
			
		
	
		
			
				
					|  |  |  |  |   # find color in 8 bit | 
			
		
	
	
		
			
				
					|  |  |  | @ -98,28 +98,11 @@ def draw_path(y, x, color, img, calibration, top_down, lid_color=None): | 
			
		
	
		
			
				
					|  |  |  |  |       if px != -1: | 
			
		
	
		
			
				
					|  |  |  |  |         top_down[1][px, py] = tcolor | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | def draw_steer_path(speed_ms, curvature, color, img, | 
			
		
	
		
			
				
					|  |  |  |  |                     calibration, top_down, VM, lid_color=None): | 
			
		
	
		
			
				
					|  |  |  |  |   path_x = np.arange(101.) | 
			
		
	
		
			
				
					|  |  |  |  |   path_y = np.multiply(path_x, np.tan(np.arcsin(np.clip(path_x * curvature, -0.999, 0.999)) / 2.)) | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |   draw_path(path_y, path_x, color, img, calibration, top_down, lid_color) | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | def draw_lead_car(closest, top_down): | 
			
		
	
		
			
				
					|  |  |  |  |   if closest is not None: | 
			
		
	
		
			
				
					|  |  |  |  |     closest_y = int(round(UP.lidar_car_y - closest * UP.lidar_zoom)) | 
			
		
	
		
			
				
					|  |  |  |  |     if closest_y > 0: | 
			
		
	
		
			
				
					|  |  |  |  |       top_down[1][int(round(UP.lidar_car_x - METER_WIDTH * 2)):int( | 
			
		
	
		
			
				
					|  |  |  |  |         round(UP.lidar_car_x + METER_WIDTH * 2)), closest_y] = find_color( | 
			
		
	
		
			
				
					|  |  |  |  |           top_down[0], (255, 0, 0)) | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | def draw_lead_on(img, closest_x_m, closest_y_m, calibration, color, sz=10, img_offset=(0, 0)): | 
			
		
	
		
			
				
					|  |  |  |  |   uv = warp_points(np.asarray([closest_x_m, closest_y_m]), calibration.car_to_bb)[0] | 
			
		
	
		
			
				
					|  |  |  |  |   u, v = int(uv[0] + img_offset[0]), int(uv[1] + img_offset[1]) | 
			
		
	
		
			
				
					|  |  |  |  |   if u > 0 and u < 640 and v > 0 and v < 480 - 5: | 
			
		
	
		
			
				
					|  |  |  |  |     img[v - 5 - sz:v - 5 + sz, u] = color | 
			
		
	
		
			
				
					|  |  |  |  |     img[v - 5, u - sz:u + sz] = color | 
			
		
	
		
			
				
					|  |  |  |  |   return u, v | 
			
		
	
		
			
				
					|  |  |  |  |   height, width = img.shape[:2] | 
			
		
	
		
			
				
					|  |  |  |  |   for x, y in pts: | 
			
		
	
		
			
				
					|  |  |  |  |     if 1 < x < width - 1 and 1 < y < height - 1: | 
			
		
	
		
			
				
					|  |  |  |  |       for a, b in itertools.permutations([-1, 0, -1], 2): | 
			
		
	
		
			
				
					|  |  |  |  |         img[y + a, x + b] = color | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | def init_plots(arr, name_to_arr_idx, plot_xlims, plot_ylims, plot_names, plot_colors, plot_styles, bigplots=False): | 
			
		
	
	
		
			
				
					|  |  |  | @ -197,97 +180,36 @@ def init_plots(arr, name_to_arr_idx, plot_xlims, plot_ylims, plot_names, plot_co | 
			
		
	
		
			
				
					|  |  |  |  |   return draw_plots | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | def draw_mpc(liveMpc, top_down): | 
			
		
	
		
			
				
					|  |  |  |  |   mpc_color = find_color(top_down[0], (0, 255, 0)) | 
			
		
	
		
			
				
					|  |  |  |  |   for p in zip(liveMpc.x, liveMpc.y): | 
			
		
	
		
			
				
					|  |  |  |  |     px, py = to_lid_pt(*p) | 
			
		
	
		
			
				
					|  |  |  |  |     top_down[1][px, py] = mpc_color | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | class CalibrationTransformsForWarpMatrix(object): | 
			
		
	
		
			
				
					|  |  |  |  |   def __init__(self, num_px, model_to_full_frame, K, E): | 
			
		
	
		
			
				
					|  |  |  |  |     self._model_to_full_frame = model_to_full_frame | 
			
		
	
		
			
				
					|  |  |  |  |     self._K = K | 
			
		
	
		
			
				
					|  |  |  |  |     self._E = E | 
			
		
	
		
			
				
					|  |  |  |  |     self.num_px = num_px | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |   @property | 
			
		
	
		
			
				
					|  |  |  |  |   def model_to_bb(self): | 
			
		
	
		
			
				
					|  |  |  |  |     return _FULL_FRAME_TO_BB[self.num_px].dot(self._model_to_full_frame) | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |   @lazy_property | 
			
		
	
		
			
				
					|  |  |  |  |   def model_to_full_frame(self): | 
			
		
	
		
			
				
					|  |  |  |  |     return self._model_to_full_frame | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |   @lazy_property | 
			
		
	
		
			
				
					|  |  |  |  |   def car_to_model(self): | 
			
		
	
		
			
				
					|  |  |  |  |     return np.linalg.inv(self._model_to_full_frame).dot(self._K).dot( | 
			
		
	
		
			
				
					|  |  |  |  |       self._E[:, [0, 1, 3]]) | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |   @lazy_property | 
			
		
	
		
			
				
					|  |  |  |  |   def car_to_bb(self): | 
			
		
	
		
			
				
					|  |  |  |  |     return _BB_TO_FULL_FRAME[self.num_px].dot(self._K).dot(self._E[:, [0, 1, 3]]) | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | def pygame_modules_have_loaded(): | 
			
		
	
		
			
				
					|  |  |  |  |   return pygame.display.get_init() and pygame.font.get_init() | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | def draw_var(y, x, var, color, img, calibration, top_down): | 
			
		
	
		
			
				
					|  |  |  |  |   # otherwise drawing gets stupid | 
			
		
	
		
			
				
					|  |  |  |  |   var = max(1e-1, min(var, 0.7)) | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |   varcolor = tuple(np.array(color)*0.5) | 
			
		
	
		
			
				
					|  |  |  |  |   draw_path(y - var, x, varcolor, img, calibration, top_down) | 
			
		
	
		
			
				
					|  |  |  |  |   draw_path(y + var, x, varcolor, img, calibration, top_down) | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | class ModelPoly(object): | 
			
		
	
		
			
				
					|  |  |  |  |   def __init__(self, model_path): | 
			
		
	
		
			
				
					|  |  |  |  |     if len(model_path.poly) == 0: | 
			
		
	
		
			
				
					|  |  |  |  |       self.valid = False | 
			
		
	
		
			
				
					|  |  |  |  |       return | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |     self.poly = np.array(model_path.poly) | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |     self.prob = model_path.prob | 
			
		
	
		
			
				
					|  |  |  |  |     self.std = model_path.std | 
			
		
	
		
			
				
					|  |  |  |  |     self.y = np.polyval(self.poly, _PATH_XD) | 
			
		
	
		
			
				
					|  |  |  |  |     self.valid = True | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | def extract_model_data(md): | 
			
		
	
		
			
				
					|  |  |  |  |   return ModelUIData( | 
			
		
	
		
			
				
					|  |  |  |  |     cpath=ModelPoly(md.path), | 
			
		
	
		
			
				
					|  |  |  |  |     lpath=ModelPoly(md.leftLane), | 
			
		
	
		
			
				
					|  |  |  |  |     rpath=ModelPoly(md.rightLane), | 
			
		
	
		
			
				
					|  |  |  |  |     lead=md.lead, | 
			
		
	
		
			
				
					|  |  |  |  |     lead_future=md.leadFuture, | 
			
		
	
		
			
				
					|  |  |  |  |     ) | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | def plot_model(m, VM, v_ego, curvature, imgw, calibration, top_down, d_poly, top_down_color=216): | 
			
		
	
		
			
				
					|  |  |  |  | def plot_model(m, img, calibration, top_down): | 
			
		
	
		
			
				
					|  |  |  |  |   if calibration is None or top_down is None: | 
			
		
	
		
			
				
					|  |  |  |  |     return | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |   for lead in [m.lead, m.lead_future]: | 
			
		
	
		
			
				
					|  |  |  |  |   for lead in m.leads: | 
			
		
	
		
			
				
					|  |  |  |  |     if lead.prob < 0.5: | 
			
		
	
		
			
				
					|  |  |  |  |       continue | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |     lead_dist_from_radar = lead.dist - RADAR_TO_CAMERA | 
			
		
	
		
			
				
					|  |  |  |  |     _, py_top = to_lid_pt(lead_dist_from_radar + lead.std, lead.relY) | 
			
		
	
		
			
				
					|  |  |  |  |     px, py_bottom = to_lid_pt(lead_dist_from_radar - lead.std, lead.relY) | 
			
		
	
		
			
				
					|  |  |  |  |     top_down[1][int(round(px - 4)):int(round(px + 4)), py_top:py_bottom] = top_down_color | 
			
		
	
		
			
				
					|  |  |  |  |     x, y, _, _ = lead.xyva | 
			
		
	
		
			
				
					|  |  |  |  |     x_std, _, _, _ = lead.xyvaStd | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |     _, py_top = to_lid_pt(x + x_std, y) | 
			
		
	
		
			
				
					|  |  |  |  |     px, py_bottom = to_lid_pt(x - x_std, y) | 
			
		
	
		
			
				
					|  |  |  |  |     top_down[1][int(round(px - 4)):int(round(px + 4)), py_top:py_bottom] = find_color(top_down[0], YELLOW) | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |   color = (0, int(255 * m.lpath.prob), 0) | 
			
		
	
		
			
				
					|  |  |  |  |   for path in [m.cpath, m.lpath, m.rpath]: | 
			
		
	
		
			
				
					|  |  |  |  |     if path.valid: | 
			
		
	
		
			
				
					|  |  |  |  |       draw_path(path.y, _PATH_XD, color, imgw, calibration, top_down, YELLOW) | 
			
		
	
		
			
				
					|  |  |  |  |       draw_var(path.y, _PATH_XD, path.std, color, imgw, calibration, top_down) | 
			
		
	
		
			
				
					|  |  |  |  |   for path, prob, _ in zip(m.laneLines, m.laneLineProbs, m.laneLineStds): | 
			
		
	
		
			
				
					|  |  |  |  |     color = (0, int(255 * prob), 0) | 
			
		
	
		
			
				
					|  |  |  |  |     draw_path(path, color, img, calibration, top_down, YELLOW, 1.22) | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |   if d_poly is not None: | 
			
		
	
		
			
				
					|  |  |  |  |     dpath_y = np.polyval(d_poly, _PATH_X) | 
			
		
	
		
			
				
					|  |  |  |  |     draw_path(dpath_y, _PATH_X, RED, imgw, calibration, top_down, RED) | 
			
		
	
		
			
				
					|  |  |  |  |   for edge, std in zip(m.roadEdges, m.roadEdgeStds): | 
			
		
	
		
			
				
					|  |  |  |  |     prob = max(1 - std, 0) | 
			
		
	
		
			
				
					|  |  |  |  |     color = (int(255 * prob), 0, 0) | 
			
		
	
		
			
				
					|  |  |  |  |     draw_path(edge, color, img, calibration, top_down, RED, 1.22) | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |   # draw user path from curvature | 
			
		
	
		
			
				
					|  |  |  |  |   draw_steer_path(v_ego, curvature, BLUE, imgw, calibration, top_down, VM, BLUE) | 
			
		
	
		
			
				
					|  |  |  |  |   color = (255, 0, 0) | 
			
		
	
		
			
				
					|  |  |  |  |   draw_path(m.position, color, img, calibration, top_down, RED) | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | def maybe_update_radar_points(lt, lid_overlay): | 
			
		
	
	
		
			
				
					|  |  |  | 
 |