File size: 7,976 Bytes
3cf288d |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 |
import cv2
import numpy as np
from Map import MapIn, CVLineThickness
import random
import config
class AxisFinder:
clicked_points = []
def __init__(self,map:MapIn) -> None:
self.map = map
self.axis_res = []
"""
opencv click callback for customized axis drawing
"""
def click_callback(event, x, y, flags, params):
if event == cv2.EVENT_LBUTTONDOWN:
config.log(f"Clicked Y:{y}, X:{x}")
AxisFinder.clicked_points.append((y,x))
"""
parts parcel number divider based on area of
parts. omits fixed facility areas
"""
def cal_split_parcels(u_map:MapIn,d_map:MapIn,parcels_cnt:int):
u_ff_area = np.sum(u_map.facility_filled_mask)/255
u_block_area = np.sum(u_map.block_mask)/255
u_area = u_block_area - u_ff_area
d_ff_area = np.sum(d_map.facility_filled_mask)/255
d_block_area = np.sum(d_map.block_mask)/255
d_area = d_block_area - d_ff_area
t_area = u_area+d_area
precnt = u_area/t_area
u_parcels = int(parcels_cnt*precnt)
d_parcels = parcels_cnt - u_parcels
return (u_parcels,d_parcels)
"""
sort results by best fitness
"""
def sort_fitness(self,sub_li):
return sub_li.sort(key = lambda x: x[0])
"""
calculates access balance ratio with the provided up&down masks
max value of fitness is 1
"""
def cal_access_split_fitness(self,access_mask:np.ndarray,up_mask:np.ndarray,down_mask:np.ndarray):
img = access_mask
# calculate access ratio
up_sum_mask = up_mask & img
down_sum_mask = down_mask & img
sum_up_access = np.sum(up_sum_mask)/255
sum_down_access = np.sum(down_sum_mask)/255
return 1 - (abs(sum_up_access-sum_down_access)/(np.sum(img)/255))
"""
calculates area balance ratio with provided up&down masks
max value of fitness is 1
"""
def cal_area_split_fitness(self,block_mask:np.ndarray,up_mask:np.ndarray,down_mask:np.ndarray):
imgray = block_mask
up_area = up_mask & imgray
down_area = down_mask & imgray
sum_up_area = np.sum(up_area)/255
sum_down_area = np.sum(down_area)/255
return 1 - (abs(sum_up_area-sum_down_area)/(np.sum(imgray)/255))
"""
calculates fixed facility hit
best answer is 1
"""
def cal_fixed_facilities_fitness(self,facility_mask:np.ndarray,p0:tuple,p1:tuple):
imgray = facility_mask
plain = np.zeros((imgray.shape))
thickness = self.map.roud_thickness + self.map.facility_safe_dist
# input points are (y,x)->(x,y)
plain = cv2.line(plain,(p0[1],p0[0]),(p1[1],p1[0]),255,CVLineThickness.thickness_solver(thickness))
collision = plain.astype(np.uint8) & imgray
max_collision = np.sum(imgray)/255
if max_collision == 0: return 1
collision = np.sum(collision)/255
return 1 - (collision/max_collision)
"""
calculates cut trees with given points
no cut tree = 1
"""
def cal_carbon_fitness(self,tree_mask:np.ndarray,p0:tuple,p1:tuple):
mask = tree_mask
plain = np.zeros((mask.shape))
thickness = self.map.roud_thickness + self.map.tree_safe_dist
# input points are (y,x)->(x,y)
plain = cv2.line(plain,(p0[1],p0[0]),(p1[1],p1[0]),255,CVLineThickness.thickness_solver(thickness))
collision = plain.astype(np.uint8) & mask
# frame must be preprocessed
max_carbon = np.sum(mask)
if max_carbon == 0: return (1,0)
return (1-(np.sum(mask[collision>0])/max_carbon),len(mask[collision>0]))
"""
axis finding fitness function
"""
def fitness_axis(self,solution:tuple,mymap:MapIn,center:tuple):
# solution is y,x of one point
# other point is the center
y_max = mymap.frame_shape[0]-1
x_max = mymap.frame_shape[1]-1
# slope = inf, x=const
if solution[1]==center[1]:
point0 = (0,center[1])
point1 = (y_max,center[1])
# slope = zero, y=const
elif solution[0]==center[0]:
point0 = (center[0],0)
point1 = (center[0],x_max)
# normal line
else:
slope = (solution[0]-center[0])/(solution[1]-center[1])
intercept = center[0] - (slope*center[1])
point0 = (int(intercept),0)
point1 = (int((slope*x_max)+intercept),x_max)
# config.log(point1,point0)
up_mask,down_mask = mymap.line_split_mask_maker(point0,point1)
access_split_fitness = self.cal_access_split_fitness(mymap.access_mask,up_mask,down_mask)
area_split_fitness = self.cal_area_split_fitness(mymap.block_mask,up_mask,down_mask)
fixed_facilities_fitness = self.cal_fixed_facilities_fitness(mymap.fixed_f_mask,point0,point1)
carbon_fitness = self.cal_carbon_fitness(mymap.trees_mask,point0,point1)
# config.log(solution,slope,intercept,point0,point1)
weights = config.A_ACCESS_SPLIT_WEIGHT + config.A_AREA_SPLIT_WEIGHT + config.A_FIXED_FACILITIES_WEIGHT + config.A_CARBON_WEIGHT
score = (config.A_ACCESS_SPLIT_WEIGHT * access_split_fitness + config.A_AREA_SPLIT_WEIGHT * area_split_fitness +
config.A_FIXED_FACILITIES_WEIGHT * fixed_facilities_fitness + config.A_CARBON_WEIGHT * carbon_fitness[0])
# scale score between (0,1]
if fixed_facilities_fitness != 1:
return (-1,point0,point1)
return (score/weights,point0,point1,solution,center,[access_split_fitness,area_split_fitness,fixed_facilities_fitness,carbon_fitness])
"""
iterate through all border pixels and
draw line on start_point to border
finding best line (for the first step of axis finding only)
"""
def iterate_throughall(self,mymap:MapIn, start_point=None):
borders_access = []
if start_point != None:
borders_access = [start_point]
else:
borders_access = mymap.access_mask
borders_access = np.asarray(np.where(borders_access==255))
borders_access = list(zip(borders_access[0], borders_access[1]))
borders_random = mymap.boundry_mask
borders_random = np.asarray(np.where(borders_random==255))
borders_random = list(zip(borders_random[0], borders_random[1]))
borders_random = random.sample(borders_random, int(len(borders_random)*0.4))
res = []
for pixel in borders_access:
for second_point in borders_random:
if np.linalg.norm(np.asarray(pixel)-np.asarray(second_point)) > config.VALID_POINT_DISTANCE:
res.append(self.fitness_axis(pixel,mymap,second_point))
self.sort_fitness(res)
self.axis_res = res[-10:]
return res[-10:]
"""
iterate over old boundries (without division line)
and find best line for New York design
"""
def iterate_old_boundries_new_york(self):
res = []
last_axis_points = self.map.line_points
boundary = self.map.old_boundry_mask
boundary = np.asarray(np.where(boundary==255))
boundary = list(zip(boundary[0], boundary[1]))
# cal perpendicular center (pixelhit line)
for pixel in boundary:
y1, x1 = last_axis_points[0]
y2, x2 = last_axis_points[1]
y3, x3 = pixel
px, py = (x2-x1,y2-y1)
dAB = px*px + py*py
u = ((x3 - x1) * px + (y3 - y1) * py) / dAB
# (y,x)
ppcenter = (int(y1 + u * py),int(x1 + u * px))
if np.linalg.norm(np.asarray(pixel)-np.asarray(ppcenter)) > config.VALID_POINT_DISTANCE:
res.append(self.fitness_axis(pixel,self.map,ppcenter))
self.sort_fitness(res)
self.axis_res = res[-10:]
return res[-10:]
|