layout.py
Go to the documentation of this file.
1 '''
2 funzioni utili alla costruzione del layout delle stanze partendo da un immagine nota della mappa, dalla quale non si conosce nulla.
3 '''
4 import math
5 
6 import cv2
7 import numpy
8 from shapely.geometry import Polygon
9 from shapely.ops import cascaded_union
10 from sklearn.cluster import DBSCAN
11 
12 import util.matrice as mtx
13 from object import ExtendedSegment as ext
14 from object import Line as rt
15 from object import Segment as sg
16 from object import Space as sp
17 from object import Surface as fc
18 from util import mean_shift as ms
19 from util import rototranslation as rotot
20 import os
21 from skimage import io
22 import numpy as np
23 from matplotlib import pyplot as plt
24 
25 def start_canny_and_hough(image, param_obj):
26  #image_2 = cv2.cvtColor(image.copy(), cv2.COLOR_BGR2GRAY)
27  image_3 = cv2.bitwise_not(image.copy())
28 
29  # CANNY AND HOUGH to find walls. it's an edge detection method
30 
31  # canny
32  # canny_edges = cv2.Canny(image, low_thrash, high_thresh, apertureSize=param_obj.apertureSize)
33  # canny_edges_laplace = cv2.Canny(aaa, low_thrash, high_thresh, apertureSize=param_obj.apertureSize)
34 
35  # hough
36  walls = cv2.HoughLinesP(image_3, param_obj.rho, param_obj.theta, param_obj.thresholdHough, param_obj.minLineLength, param_obj.maxLineGap)
37  # TODO upgrade this version.
38  if cv2.__version__[0] == '3' or cv2.__version__[0]=='4':
39  walls = [i[0]for i in walls]
40  elif cv2.__version__[0] == '2':
41  walls = walls[0]
42  else:
43  raise EnvironmentError('Opencv Version Error. You should have OpenCv 2.* or 3.* or 4.*')
44  return walls, 0
45 
46 
47 def flip_lines(lines, height):
48  # flip the y of lines, because origin of pixel is top left, instead i need bottom left
49  for line in lines:
50  line[1] = height - line[1]
51  line[3] = height - line[3]
52  return lines
53 
54 
55 def create_walls(lines):
56  # transform lines in object of type Segment, and return list of Segment
57  walls = []
58  for wall in lines:
59  x1 = float(wall[0])
60  y1 = float(wall[1])
61  x2 = float(wall[2])
62  y2 = float(wall[3])
63  walls.append(sg.Segment(x1, y1, x2, y2))
64  return walls
65 
66 
67 def external_contour(img_rgb):
68  if len(img_rgb.shape) == 3:
69  # transforming the image in gray scale to use cv2 functions.
70  img_gray = cv2.cvtColor(img_rgb, cv2.COLOR_BGR2GRAY)
71  else:
72  img_gray = img_rgb.copy()
73  # highlighting all the pixels with value > 253
74  ret, thresh = cv2.threshold(img_gray.copy(), 253, 255, cv2.THRESH_BINARY_INV)
75  if cv2.__version__[0] == '3':
76  # finding all the contours of this image
77  img_contour, contours, hierarchy = cv2.findContours(thresh.copy(), cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
78  # removing the external contour, the rectangular shape of the image
79  contours = list(contours)
80  contours.pop(0)
81  # draw all this contours
82  img_contour = cv2.drawContours(img_contour.copy(), contours, -1, (0, 255, 0), 3, cv2.LINE_8)
83  # finding all the contours of this new image found with cv2.drawContours()
84  img_contour, contours, hierarchy = cv2.findContours(img_contour, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
85 
86  elif cv2.__version__[0] =='4':
87  # finding all the contours of this image
88  contours, hierarchy = cv2.findContours(thresh.copy(), cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
89  # removing the external contour, the rectangular shape of the image
90  contours = list(contours)
91  contours.pop(0)
92  # draw all this contours
93  img_contour = cv2.drawContours(thresh.copy(), contours, -1, (0, 255, 0), 3, cv2.LINE_8)
94  # finding all the contours of this new image found with cv2.drawContours()
95  contours, hierarchy = cv2.findContours(img_contour, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
96  else:
97  raise EnvironmentError('Opencv Version Error. You should have OpenCv 3.* or Opencv 4.*')
98 
99  contours = sorted(contours, key=cv2.contourArea, reverse=True)[:10]
100  contours_max = contours[1]
101  perimeter = cv2.arcLength(contours_max, True)
102  approx = cv2.approxPolyDP(contours_max, 0.0002 * perimeter, True)
103  screen_cnt = approx
104 
105  # flip the contour
106  # for c in screen_cnt:
107  # c[0][1] = img_rgb.shape[0]-1 - c[0][1]
108 
109  vertices = []
110  for c in screen_cnt:
111  vertices.append([float(c[0][0]), float(c[0][1])])
112 
113  return screen_cnt, vertices
114 
115 
116 def cluster_ang(h, min_offset, walls, num_min=3, min_lenght=3, diagonals=True):
117  # make angular clusters
118  # make clusters centers with mean-shift
119  cluster_centers = ms.mean_shift(h, min_offset, walls)
120  # there is some angular clusters caused by small line segment. they are noise.
121  # TODO check that elimination of angular clusters is not a problem
122  # delete these clusters from list clusters_centers and delete also the segments from walls.
123  indexes = ms.indexes_to_be_deleted(num_min, min_lenght, cluster_centers, walls, diagonals)
124  # selected the indexes of angular clusters and walls to be deleted,remove from walls and cluster_centers,
125  # starting from higher indexes
126  for i in sorted(indexes, reverse=True):
127  del walls[i]
128  del cluster_centers[i]
129  # for clusters very similar I'll do a simple average
130  united = ms.merge_similar_cluster(cluster_centers)
131  while united:
132  united = ms.merge_similar_cluster(cluster_centers)
133  # assign clusters to walls
134  walls = sg.assign_angular_cluster(walls, cluster_centers)
135  # compute list of angular clusters
136  angular_clusters = []
137  for wall in walls:
138  angular_clusters.append(wall.angular_cluster)
139  return indexes, walls, angular_clusters
140 
141 
142 def extend_line(spatial_clusters, walls, x_min, x_max, y_min, y_max):
143  # compute extended lines
144  extended_lines = rt.create_extended_lines(spatial_clusters, walls, x_min, y_min)
145  # extended_lines have a point, an angular_cluster and a spatial_cluster, to draw them we need 2 points.
146  # compute list of extended_segments
147  extended_segments = ext.create_extended_segments(x_min, x_max, y_min, y_max, extended_lines)
148  return extended_lines, extended_segments
149 
150 
151 def classification_surface(vertices, cells, threshold):
152  # classification of cells(surfaces) that are selected by extended segments
153  contour = Polygon(vertices)
154  # add buffer to clean contour
155  # per pulire il contorno che ha dei self_interserctin, splitto il poligono e lo faccio diventare un MULTIPOLYGON
156  contour = contour.buffer(0)
157  polygon_cells = []
158  indexes = []
159  cells_out = []
160  cells_partial = []
161  points = []
162  for index, f in enumerate(cells):
163  points = []
164  for b in f.borders:
165  points.append([float(b.x1), float(b.y1)])
166  points.append([float(b.x2), float(b.y2)])
167  # obtain vertices of cell without repetition
168  points = sort_and_deduplicate(points)
169  # ordered clockwise (senso orario)
170  x = [p[0] for p in points]
171  y = [p[1] for p in points]
172  global centroid
173  centroid = (sum(x) / len(points), sum(y) / len(points))
174  points.sort(key=algo)
175  # create polygon of cell
176  cell = Polygon(points)
177  # if polygon of cell doesn't intersect the contour, the cell is out.
178  if not cell.intersects(contour):
179  f.set_out(True)
180  f.set_partial(False)
181  cells_out.append(f)
182  # if polygon of cell does intersect the contour
183  if cell.intersects(contour):
184  # if intersection is grater than threshold the cell is inside
185  if cell.intersection(contour).area >= cell.area/threshold:
186  f.set_out(False)
187  # otherwise is external
188  else:
189  f.set_out(True)
190  f.set_partial(False)
191  cells_out.append(f)
192  # TODO cells_partial are not calculated here!!!
193  # remove from cells_out the cells_partial
194  cells_out = list(set(cells_out)-set(cells_partial))
195  # removed from cells the cells_out and cells_partials
196  cells = list(set(cells) - set(cells_out))
197  cells = list(set(cells) - set(cells_partial))
198  return cells, cells_out, polygon_cells, indexes, cells_partial, contour, centroid, points
199 
200 
201 def algo(p):
202  '''
203  riordina i punti in senso orario
204  '''
205  return (math.atan2(p[0] - centroid[0], p[1] - centroid[1]) + 2 * math.pi) % (2*math.pi)
206 
207 
209  # delete copy from a list of pairs
210  return list(uniq(sorted(l, reverse=True)))
211 
212 
213 def uniq(lst):
214  last = object()
215  for item in lst:
216  if item == last:
217  continue
218  yield item
219  last = item
220 
221 
222 def create_polygon(cells, cells_out, cells_partial):
223  # create polygon starting from cells
224  # polygon cells
225  cells_polygon = []
226  for f in cells:
227  points = []
228  for b in f.borders:
229  points.append([float(b.x1), float(b.y1)])
230  points.append([float(b.x2), float(b.y2)])
231  points = sort_and_deduplicate(points)
232  x = [p[0] for p in points]
233  y = [p[1] for p in points]
234  global centroid
235  centroid = (sum(x) / len(points), sum(y) / len(points))
236  points.sort(key=algo)
237  cell = Polygon(points)
238  cells_polygon.append(cell)
239 
240  # polygon cells_out
241  polygon_out = []
242  for f in cells_out:
243  points = []
244  for b in f.borders:
245  points.append([float(b.x1), float(b.y1)])
246  points.append([float(b.x2), float(b.y2)])
247  points = sort_and_deduplicate(points)
248  x = [p[0] for p in points]
249  y = [p[1] for p in points]
250  centroid = (sum(x) / len(points), sum(y) / len(points))
251  points.sort(key=algo)
252  cell = Polygon(points)
253  polygon_out.append(cell)
254 
255  # polygon cells_partial
256  polygon_partial = []
257  for f in cells_partial:
258  points = []
259  for b in f.borders:
260  points.append([float(b.x1),float(b.y1)])
261  points.append([float(b.x2),float(b.y2)])
262  points = sort_and_deduplicate(points)
263  x = [p[0] for p in points]
264  y = [p[1] for p in points]
265  centroid = (sum(x) / len(points), sum(y) / len(points))
266  points.sort(key=algo)
267  cell = Polygon(points)
268  polygon_partial.append(cell)
269 
270  return cells_polygon, polygon_out, polygon_partial, centroid
271 
272 
273 def create_matrices(cells, sigma=0.00000125, val=0):
274  # sigma was 0.1
275  matrix_l = mtx.create_matrix_l(cells, sigma, val)
276  matrix_d = mtx.create_matrix_d(matrix_l)
277  matrix_d_inv = matrix_d.getI()
278  matrix_m = matrix_d_inv.dot(matrix_l)
279  matrix_m = mtx.symmetry(matrix_m)
280  X = 1-matrix_m
281  return matrix_l, matrix_d, matrix_d_inv, X
282 
283 
284 def DB_scan(eps, min_points, X, celle_poligoni):
285  cluster_cells = clustering_dbscan_cells(eps, min_points, X)
286  return cluster_cells
287 
288 
289 def clustering_dbscan_cells(eps, min_samples, X):
290  # compute dbscan clustering on cells, take in input eps (maximum distance between 2 sample to be considered in the
291  # same neighborhood), min_samples (number of samples in a neighborhood for point to be considered a core point)
292  # and X (1-local affinity matrix among cells).
293 
294  af = DBSCAN(eps, min_samples, metric="precomputed").fit(X)
295  return af.labels_
296 
297 
298 def create_space(clusters_cells, cells, polygon_cells):
299  rooms, spaces = merge_cells(clusters_cells, cells, polygon_cells)
300  return rooms, spaces
301 
302 
303 def merge_cells(clusters, cells, polygon_cells):
304  # polygon of cells of same cluster will be merged in a single polygon, which is the room.
305 
306  # spaces are same thing as rooms instead I create an object space
307  rooms = []
308  spaces = []
309 
310  for l in set(clusters):
311  polygons = []
312  matching_cells = []
313  for index, cluster in enumerate(clusters):
314  if (l == cluster) and not cells[index].out:
315  polygons.append(polygon_cells[index])
316  matching_cells.append(cells[index])
317  room = cascaded_union(polygons)
318  rooms.append(room)
319  space = sp.Space(polygons, room, matching_cells, index)
320  spaces.append(space)
321  return rooms, spaces
322 
323 
324 def get_distance_matrix(walls, minLateralSeparation= 7):
325  '''
326  calcola la matrice delle distanze tra tutti i segmenti
327  '''
328 
329  #questo per metodo di classicazione=2
330  matrix = []
331  min=0
332  for segmento1 in walls:
333  row = []
334  for segmento2 in walls:
335  min = sg.segments_distance(segmento1.x1, segmento1.y1, segmento1.x2, segmento1.y2, segmento2.x1, segmento2.y1, segmento2.x2, segmento2.y2)
336  #if segmento1.angular_cluster == segmento2.angular_cluster and min <= minLateralSeparation :
337  row.append(min)
338  # if min > minLateralSeparation:
339 # min = 9999
340 # row.append(min)
341 # else:
342 # row.append(min)
343 #
344 
345  matrix.append(row)
346 
347  return matrix
348 
349 
350 def compute_clustering(matrix):
351  # hierarchical clustering
352  af = DBSCAN(eps=7, min_samples=1, metric="precomputed").fit(matrix)
353  return af.labels_
354 
355 
356 def get_wall_clusters(walls, angular_clusters):
357  # compute wall cluster.
358 
359  # obtain angular_clusters
360  ang = set(angular_clusters)
361 
362  # it's a matrix of type [[..,..,..], [..,..,..,..]] based on number of angular clusters
363  angular_ordered = []
364  for c in ang:
365  row = []
366  for wall in walls:
367  if wall.angular_cluster == c:
368  row.append(wall)
369  angular_ordered.append(row)
370 
371  # obtain affinity matrix of segments for each angular cluster
372  # every element of m contains a matrix of distances on which compute clustering
373  m = []
374  for ang in angular_ordered:
375  matrix = get_distance_matrix(ang)
376  m.append(matrix)
377 
378  # for each angular cluster obtain a set of label from wall clustering.
379  label = []
380  for mat in m:
381  label.append(compute_clustering(mat))
382 
383  # set labels to all segments (every segment has a wall labels)
384  walls = sg.set_segment_label2(angular_ordered, label)
385 
386  # create list of wall_cluster
387  wall_cluster = []
388  for segment in walls:
389  wall_cluster.append(segment.wall_cluster)
390  return wall_cluster
391 
392 
393 def get_representatives(walls, wall_cluster):
394  # compute representatives segments of a cluster
395  representatives = []
396  for cluster in set(wall_cluster):
397  lista = []
398  distance_vector = []
399  # compute list of elements with same wall_cluster
400  for wall in walls:
401  if wall.wall_cluster == cluster:
402  lista.append(wall)
403 
404  # take first element to project all the others elements of same cluster on a line passing to centre of this element.
405  candidate = lista[0]
406  x = (candidate.x1+candidate.x2)/2
407  y = (candidate.y1+candidate.y2)/2
408  angle = candidate.angular_cluster * (180/math.pi)
409 
410  # take all the projections on the orthogonal line of candidate and do a rotary translation of points.
411  X = []
412  Y = []
413  for i in lista:
414  (x_projected, y_projected) = sg.get_projected_points(candidate, i)
415  X.append(x_projected)
416  Y.append(y_projected)
417 
418  my_function = rotot.transform_points(x, y, angle)
419  for px, py in zip(X, Y):
420  # take only x, y or is 0 or very close to 0
421  distance_vector.append(my_function(px,py)[0])
422 
423  # take median of list distance_vector
424  index_representative = numpy.argsort(distance_vector)[len(distance_vector)//2-1]
425  # solo questo deve rimanere
426  representatives.append(lista[index_representative])
427  return representatives
428 
429 
430 def new_spatial_cluster(walls, representatives_segments, param_obj):
431  # creo la lista di tutti i cluster spaziali e la restituisco. Inoltre Per ogni segmento
432  # in walls il cui cluster spaziale non e' ancora stato settato,lo setto controllando che appartenga allo stesso wall_cluster del rappresentante.
433 
434  # compute list of spatial clusters
435  spatial_clusters = []
436  for wall in walls:
437  if wall.spatial_cluster is not None:
438  spatial_clusters.append(wall.spatial_cluster)
439 
440  for cluster in list(set(spatial_clusters)):
441  # put together wall clusters with same spatial_clusters
442  same_wall_cluster = []
443  for segment in representatives_segments:
444  if segment.spatial_cluster == cluster:
445  same_wall_cluster.append(segment.wall_cluster)
446 
447  same_wall_cluster = list(set(same_wall_cluster))
448 
449  for segment in walls:
450  if segment.wall_cluster in same_wall_cluster:
451  segment.set_spatial_cluster(cluster)
452 
453  # handle outliers
454 
455  # merge to the closest cluster every segment of outliers. if doesn't exist the cluster, create a new one.
456  # obtain the outliers
457  outliers = []
458  for wall in walls:
459  if wall.wall_cluster == -1:
460  outliers.append(wall)
461  set_cluster_spaziale_to_outliers(walls, outliers, representatives_segments, param_obj.spatialClusteringLineSegmentsThreshold)
462 
463  spatial_cluster = []
464  for wall in walls:
465  spatial_cluster.append(wall.spatial_cluster)
466 
467  return spatial_cluster
468 
469 
470 def set_cluster_spaziale_to_outliers(walls, outliers, representatives_segments, lateral_threshold):
471  # set spatial clusters to outliers. try to merge to spatial cluster already present
472  # if an outlier is very close to a cluster, merge to it, otherwise create a new one.
473 
474  for outlier in outliers:
475  min_distance_to_cluster = 999999
476  representative = representatives_segments[0]
477  for segment in representatives_segments:
478  dist = sg.lateral_separation(outlier, segment)
479  if outlier.angular_cluster == segment.angular_cluster:
480  if dist <= min_distance_to_cluster:
481  min_distance_to_cluster = dist
482  representative = segment
483  # assign to outlier same spatial cluster of selected representative
484  if min_distance_to_cluster <= lateral_threshold:
485  outlier.spatial_cluster = representative.spatial_cluster
486  outlier.wall_cluster = representative.wall_cluster
487  else:
488  # create new cluster (take as index position in walls of outliers)
489  new_cluster = walls.index(outlier)
490  outlier.spatial_cluster = new_cluster
491  # put new cluster among representatives otherwise outliers outliers cannot be merged as group but only associated to other groups
492  representatives_segments.append(outlier)
493 
494 
495 def get_partial_cells(cells, cells_out, border_coordinates):
496  # compute the partial_cells
497 
498  # cells that aren't out, but are adjacent to border of img (adjacent_cell < len(border)) are partial.
499  partial_cells = []
500  for c in cells:
501  for co in cells_out:
502  if fc.adjacent(c, co):
503  if fc.common_edge(c, co)[0].weight < 0.05:
504  # if a cell is partial, is not out.
505  c.set_out(False)
506  c.set_partial(True)
507  partial_cells.append(c)
508  # if the cell has a border attach to the border of image is partial
509  for border in c.borders:
510  x_list = [border_coordinates[0], border_coordinates[2]]
511  y_list = [border_coordinates[1], border_coordinates[3]]
512  if (border.x1 or border.x2) in x_list or (border.y1 or border.y2) in y_list:
513  # if a cell is partial, is not out.
514  c.set_out(False)
515  c.set_partial(True)
516  partial_cells.append(c)
517  # polygons partial cells
518  partial_polygons = []
519  for f in partial_cells:
520  points = []
521  for b in f.borders:
522  points.append([float(b.x1), float(b.y1)])
523  points.append([float(b.x2), float(b.y2)])
524  points = sort_and_deduplicate(points)
525  x = [p[0] for p in points]
526  y = [p[1] for p in points]
527  global centroid
528  centroid = (sum(x) / len(points), sum(y) / len(points))
529  points.sort(key=algo)
530  cell = Polygon(points)
531  partial_polygons.append(cell)
532  return partial_cells, partial_polygons
533 
534 
535 def get_out_polygons(cells_out):
536  # compute polygons related to cells_out
537  polygons_out = []
538  for f in cells_out:
539  points = []
540  for b in f.borders:
541  points.append([float(b.x1), float(b.y1)])
542  points.append([float(b.x2), float(b.y2)])
543  points = sort_and_deduplicate(points)
544  x = [p[0] for p in points]
545  y = [p[1] for p in points]
546  global centroid
547  centroid = (sum(x) / len(points), sum(y) / len(points))
548  points.sort(key=algo)
549  cell = Polygon(points)
550  polygons_out.append(cell)
551  return polygons_out
552 
553 
554 def set_weight_offset(extended_segments, x_max, x_min, y_max, y_min):
555  border_lines = []
556  for segment in extended_segments:
557  if (segment.x1 == x_max and segment.x2 == x_max) or (segment.x1 == x_min and segment.x2 == x_min) or (segment.y1 == y_max and segment.y2 == y_max) or (segment.y1 == y_min and segment.y2 == y_min):
558  segment.weight = 1
559  border_lines.append((segment.angular_cluster, segment.spatial_cluster))
560  return border_lines
561 
562 
563 def assign_orebro_direction(comp, walls):
564  angular_clusters = []
565  for wall in walls:
566  absolute_min = 1000
567  index = 1000
568  # print('wall direction: ', wall.direction)
569  for i, direction in enumerate(comp):
570  # print('orebro direction: ', direction)
571  minimum = abs(direction - wall.direction)
572  # print('minimum: ', minimum)
573  if minimum < absolute_min:
574  absolute_min = minimum
575  index = i
576  if index % 2 == 0:
577  wall.set_angular_cluster(comp[index])
578  else:
579  wall.set_angular_cluster(comp[index - 1])
580  # print('new angular cluster: ', wall.angular_cluster)
581  angular_clusters.append(wall.angular_cluster)
582  return angular_clusters
layout.cluster_ang
def cluster_ang(h, min_offset, walls, num_min=3, min_lenght=3, diagonals=True)
Definition: layout.py:116
layout.get_representatives
def get_representatives(walls, wall_cluster)
Definition: layout.py:393
layout.classification_surface
def classification_surface(vertices, cells, threshold)
Definition: layout.py:151
layout.create_polygon
def create_polygon(cells, cells_out, cells_partial)
Definition: layout.py:222
layout.get_wall_clusters
def get_wall_clusters(walls, angular_clusters)
Definition: layout.py:356
layout.set_weight_offset
def set_weight_offset(extended_segments, x_max, x_min, y_max, y_min)
Definition: layout.py:554
layout.create_matrices
def create_matrices(cells, sigma=0.00000125, val=0)
Definition: layout.py:273
layout.clustering_dbscan_cells
def clustering_dbscan_cells(eps, min_samples, X)
Definition: layout.py:289
layout.get_distance_matrix
def get_distance_matrix(walls, minLateralSeparation=7)
Definition: layout.py:324
layout.set_cluster_spaziale_to_outliers
def set_cluster_spaziale_to_outliers(walls, outliers, representatives_segments, lateral_threshold)
Definition: layout.py:470
layout.start_canny_and_hough
def start_canny_and_hough(image, param_obj)
Definition: layout.py:25
layout.create_walls
def create_walls(lines)
Definition: layout.py:55
layout.compute_clustering
def compute_clustering(matrix)
Definition: layout.py:350
layout.new_spatial_cluster
def new_spatial_cluster(walls, representatives_segments, param_obj)
Definition: layout.py:430
layout.merge_cells
def merge_cells(clusters, cells, polygon_cells)
Definition: layout.py:303
layout.DB_scan
def DB_scan(eps, min_points, X, celle_poligoni)
Definition: layout.py:284
layout.get_partial_cells
def get_partial_cells(cells, cells_out, border_coordinates)
Definition: layout.py:495
layout.assign_orebro_direction
def assign_orebro_direction(comp, walls)
Definition: layout.py:563
layout.create_space
def create_space(clusters_cells, cells, polygon_cells)
Definition: layout.py:298
layout.flip_lines
def flip_lines(lines, height)
Definition: layout.py:47
layout.algo
def algo(p)
Definition: layout.py:201
layout.sort_and_deduplicate
def sort_and_deduplicate(l)
Definition: layout.py:208
layout.extend_line
def extend_line(spatial_clusters, walls, x_min, x_max, y_min, y_max)
Definition: layout.py:142
layout.uniq
def uniq(lst)
Definition: layout.py:213
layout.get_out_polygons
def get_out_polygons(cells_out)
Definition: layout.py:535
layout.external_contour
def external_contour(img_rgb)
Definition: layout.py:67


rose2
Author(s): Gabriele Somaschini, Matteo Luperto
autogenerated on Wed Jun 28 2023 02:21:53