Siromanec commited on
Commit
8b48d4d
β€’
1 Parent(s): c0998c1

slightly improved performance

Browse files
Files changed (2) hide show
  1. handcrafted_solution.py +100 -117
  2. test_solution.ipynb +29 -17
handcrafted_solution.py CHANGED
@@ -28,7 +28,7 @@ def empty_solution():
28
 
29
  def undesired_objects(image):
30
  image = image.astype('uint8')
31
- nb_components, output, stats, centroids = cv2.connectedComponentsWithStats(image, connectivity=8)
32
  sizes = stats[:, -1]
33
  max_label = 1
34
  max_size = sizes[1]
@@ -73,8 +73,8 @@ def get_vertices(image_gestalt, *, color_range=4., dialations=3, erosions=1, ker
73
  eave_end_point_mask = cv2.morphologyEx(eave_end_point_mask, cv2.MORPH_DILATE, kernel, iterations=dialations)
74
  eave_end_point_mask = cv2.morphologyEx(eave_end_point_mask, cv2.MORPH_ERODE, kernel, iterations=erosions)
75
 
76
- *_, apex_centroids = cv2.connectedComponentsWithStats(apex_mask, connectivity=8, stats=cv2.CV_32S)
77
- *_, other_centroids = cv2.connectedComponentsWithStats(eave_end_point_mask, connectivity=8, stats=cv2.CV_32S)
78
 
79
  return apex_centroids[1:], other_centroids[1:], apex_mask, eave_end_point_mask
80
 
@@ -96,7 +96,7 @@ def infer_vertices(image_gestalt, *, color_range=4.):
96
  intersection_mask = cv2.bitwise_and(ridge_mask, rake_mask)
97
  intersection_mask = cv2.morphologyEx(intersection_mask, cv2.MORPH_DILATE, np.ones((11, 11)), iterations=3)
98
 
99
- *_, inferred_centroids = cv2.connectedComponentsWithStats(intersection_mask, connectivity=8, stats=cv2.CV_32S)
100
 
101
  return inferred_centroids[1:], intersection_mask
102
 
@@ -144,15 +144,17 @@ def get_vertices_and_edges_from_segmentation(gest_seg_np, *, color_range=4., poi
144
  # missed_vertices = get_missed_vertices(vertices, inferred_vertices, **kwargs)
145
  # vertices = np.concatenate([vertices, missed_vertices])
146
 
147
- scale = 1
148
- vertex_size = np.zeros(vertices.shape[0])
149
- for i, coords in enumerate(vertices):
150
- # coords = np.round(coords).astype(np.uint32)
151
- radius = point_radius # np.clip(int(max_depth//2 + depth_np[coords[1], coords[0]]), 10, 30)#int(np.clip(max_depth - depth_np[coords[1], coords[0]], 10, 20))
152
- vertex_size[i] = (scale * radius) ** 2 # because we are using squared distances
 
 
153
 
154
  for edge_class in ['eave', 'ridge', 'rake', 'valley', 'flashing', 'step_flashing']:
155
- if len(vertices) < 2:
156
  break
157
  edge_color = np.array(gestalt_color_mapping[edge_class])
158
 
@@ -162,117 +164,100 @@ def get_vertices_and_edges_from_segmentation(gest_seg_np, *, color_range=4., poi
162
  mask = cv2.morphologyEx(mask,
163
  cv2.MORPH_DILATE, np.ones((3, 3)), iterations=1)
164
 
165
- if np.any(mask):
 
166
 
167
- rho = 1 # distance resolution in pixels of the Hough grid
168
- theta = np.pi / 180 # angular resolution in radians of the Hough grid
169
- threshold = 20 # minimum number of votes (intersections in Hough grid cell)
170
- min_line_length = 60 # minimum number of pixels making up a line
171
- max_line_gap = 40 # maximum gap in pixels between connectable line segments
172
 
173
- # Run Hough on edge detected image
174
- # Output "lines" is an array containing endpoints of detected line segments
175
- cv2.GaussianBlur(mask, (11, 11), 0, mask)
176
- lines = cv2.HoughLinesP(mask, rho, theta, threshold, np.array([]),
177
- min_line_length, max_line_gap)
178
 
179
- edges = []
180
 
181
- if lines is None:
182
- continue
183
 
184
- line_directions = np.zeros((len(lines), 2))
185
- for line_idx, line in enumerate(lines):
186
- for x1, y1, x2, y2 in line:
187
- if x1 < x2:
188
- x1, y1, x2, y2 = x2, y2, x1, y1
189
- direction = (np.array([x2 - x1, y2 - y1]))
190
- direction = direction / np.linalg.norm(direction)
191
- line_directions[line_idx] = direction
192
 
193
- direction = extend * direction
194
 
195
- x1, y1 = (-direction + (x1, y1)).astype(np.int32)
196
- x2, y2 = (+ direction + (x2, y2)).astype(np.int32)
197
 
198
- edges.append((x1, y1, x2, y2))
199
 
200
- edges = np.array(edges)
201
- if len(edges) < 1:
202
- continue
203
- # calculate the distances between the vertices and the edge ends
204
- begin_distances = cdist(vertices, edges[:, :2], metric="sqeuclidean")
205
- end_distances = cdist(vertices, edges[:, 2:], metric="sqeuclidean")
206
-
207
- begin_in_range_mask = begin_distances < vertex_size[:, np.newaxis]
208
- end_in_range_mask = end_distances < vertex_size[:, np.newaxis]
209
-
210
- in_range_connected_mask = np.logical_and(np.any(begin_in_range_mask, axis=0),
211
- np.any(end_in_range_mask, axis=0))
212
-
213
- # where both ends are in range
214
- begin_in_range_mask = np.logical_and(begin_in_range_mask, in_range_connected_mask)
215
- end_in_range_mask = np.logical_and(end_in_range_mask, in_range_connected_mask)
216
-
217
- begin_candidates = np.array(np.where(begin_in_range_mask))
218
- end_candidates = np.array(np.where(end_in_range_mask))
219
-
220
- # sort the candidates by line index; required for the seamlessnes np.split
221
- sorted_begin_indices = np.argsort(begin_candidates[1])
222
- sorted_end_indices = np.argsort(end_candidates[1])
223
- begin_candidates = begin_candidates[:, sorted_begin_indices]
224
- end_candidates = end_candidates[:, sorted_end_indices]
225
-
226
- # create all possible connections between begin and end candidates that correspond to a line
227
- grouped_begins = np.split(begin_candidates[0], np.unique(begin_candidates[1], return_index=True)[1][1:])
228
- grouped_ends = np.split(end_candidates[0], np.unique(end_candidates[1], return_index=True)[1][1:])
229
- line_indices = np.unique(begin_candidates[1])
230
-
231
- # create all possible connections between begin and end candidates that correspond to a line
232
- begin_vertex_list = []
233
- end_vertex_list = []
234
- line_idx_list = []
235
- for begin_vertex, end_vertex, line_idx in zip(grouped_begins, grouped_ends, line_indices):
236
- begin_vertex, end_vertex = np.meshgrid(begin_vertex, end_vertex)
237
- begin_vertex_list.extend(begin_vertex.flatten())
238
- end_vertex_list.extend(end_vertex.flatten())
239
- line_idx_list.extend([line_idx] * len(begin_vertex.flatten()))
240
-
241
- line_idx_list = np.array(line_idx_list)
242
- all_connections = np.array([begin_vertex_list, end_vertex_list])
243
-
244
- # decrease the number of possible connections to reduce number of calculations
245
- possible_connections = np.unique(all_connections, axis=1)
246
- possible_connections = np.sort(possible_connections, axis=0)
247
- possible_connections = np.unique(possible_connections, axis=1)
248
- possible_connections = possible_connections[:, possible_connections[0, :] != possible_connections[1, :]]
249
-
250
- if possible_connections.shape[1] < 1:
251
- continue
252
 
253
- # precalculate the possible direction vectors
254
- possible_direction_vectors = vertices[possible_connections[0]] - vertices[possible_connections[1]]
255
- possible_direction_vectors = possible_direction_vectors / np.linalg.norm(possible_direction_vectors,
256
- axis=1)[:, np.newaxis]
257
-
258
- owned_lines_per_possible_connections = [list() for i in range(possible_connections.shape[1])]
259
-
260
- # assign lines to possible connections
261
- for line_idx, i, j in zip(line_idx_list, begin_vertex_list, end_vertex_list):
262
- if i == j:
263
- continue
264
- i, j = min(i, j), max(i, j)
265
- for connection_idx, connection in enumerate(possible_connections.T):
266
- if np.all((i, j) == connection):
267
- owned_lines_per_possible_connections[connection_idx].append(line_idx)
268
- break
269
-
270
- # check if the lines are in the same direction as the possible connection
271
- for fitted_line_idx, owned_lines_per_possible_connection in enumerate(owned_lines_per_possible_connections):
272
- line_deviations = np.abs(np.dot(line_directions[owned_lines_per_possible_connection],
273
- possible_direction_vectors[fitted_line_idx]))
274
- if np.any(line_deviations > deviation_threshold):
275
- connections.append(possible_connections[:, fitted_line_idx])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
276
 
277
  vertices = [{"xy": v, "type": "apex"} for v in apex_centroids]
278
  # vertices += [{"xy": v, "type": "apex"} for v in missed_vertices]
@@ -282,10 +267,8 @@ def get_vertices_and_edges_from_segmentation(gest_seg_np, *, color_range=4., poi
282
 
283
  def get_uv_depth(vertices, depth):
284
  '''Get the depth of the vertices from the depth image'''
285
- uv = []
286
- for v in vertices:
287
- uv.append(v['xy'])
288
- uv = np.array(uv)
289
  uv_int = uv.astype(np.int32)
290
  H, W = depth.shape[:2]
291
  uv_int[:, 0] = np.clip(uv_int[:, 0], 0, W - 1)
@@ -387,7 +370,7 @@ def predict(entry, visualize=False, scale_estimation_coefficient=2.5, **kwargs)
387
  gest_seg = gest.resize(depth.size)
388
  gest_seg_np = np.array(gest_seg).astype(np.uint8)
389
  # Metric3D
390
- depth_np = np.array(depth) / scale_estimation_coefficient # 2.5 is the scale estimation coefficient
391
  vertices, connections = get_vertices_and_edges_from_segmentation(gest_seg_np, **kwargs)
392
  if (len(vertices) < 2) or (len(connections) < 1):
393
  print(f'Not enough vertices or connections in image {i}')
 
28
 
29
  def undesired_objects(image):
30
  image = image.astype('uint8')
31
+ nb_components, output, stats, centroids = cv2.connectedComponentsWithStats(image, connectivity=4)
32
  sizes = stats[:, -1]
33
  max_label = 1
34
  max_size = sizes[1]
 
73
  eave_end_point_mask = cv2.morphologyEx(eave_end_point_mask, cv2.MORPH_DILATE, kernel, iterations=dialations)
74
  eave_end_point_mask = cv2.morphologyEx(eave_end_point_mask, cv2.MORPH_ERODE, kernel, iterations=erosions)
75
 
76
+ *_, apex_centroids = cv2.connectedComponentsWithStats(apex_mask, connectivity=4, stats=cv2.CV_32S)
77
+ *_, other_centroids = cv2.connectedComponentsWithStats(eave_end_point_mask, connectivity=4, stats=cv2.CV_32S)
78
 
79
  return apex_centroids[1:], other_centroids[1:], apex_mask, eave_end_point_mask
80
 
 
96
  intersection_mask = cv2.bitwise_and(ridge_mask, rake_mask)
97
  intersection_mask = cv2.morphologyEx(intersection_mask, cv2.MORPH_DILATE, np.ones((11, 11)), iterations=3)
98
 
99
+ *_, inferred_centroids = cv2.connectedComponentsWithStats(intersection_mask, connectivity=4, stats=cv2.CV_32S)
100
 
101
  return inferred_centroids[1:], intersection_mask
102
 
 
144
  # missed_vertices = get_missed_vertices(vertices, inferred_vertices, **kwargs)
145
  # vertices = np.concatenate([vertices, missed_vertices])
146
 
147
+ vertices = KDTree(vertices)
148
+
149
+ # scale = 1
150
+ # vertex_size = np.zeros(vertices.shape[0])
151
+ # for i, coords in enumerate(vertices):
152
+ # # coords = np.round(coords).astype(np.uint32)
153
+ # radius = point_radius # np.clip(int(max_depth//2 + depth_np[coords[1], coords[0]]), 10, 30)#int(np.clip(max_depth - depth_np[coords[1], coords[0]], 10, 20))
154
+ # vertex_size[i] = (scale * radius) ** 2 # because we are using squared distances
155
 
156
  for edge_class in ['eave', 'ridge', 'rake', 'valley', 'flashing', 'step_flashing']:
157
+ if len(vertices.data) < 2:
158
  break
159
  edge_color = np.array(gestalt_color_mapping[edge_class])
160
 
 
164
  mask = cv2.morphologyEx(mask,
165
  cv2.MORPH_DILATE, np.ones((3, 3)), iterations=1)
166
 
167
+ if not np.any(mask):
168
+ continue
169
 
170
+ rho = 1 # distance resolution in pixels of the Hough grid
171
+ theta = np.pi / 180 # angular resolution in radians of the Hough grid
172
+ threshold = 20 # minimum number of votes (intersections in Hough grid cell)
173
+ min_line_length = 60 # minimum number of pixels making up a line
174
+ max_line_gap = 40 # maximum gap in pixels between connectable line segments
175
 
176
+ # Run Hough on edge detected image
177
+ # Output "lines" is an array containing endpoints of detected line segments
178
+ cv2.GaussianBlur(mask, (11, 11), 0, mask)
179
+ lines = cv2.HoughLinesP(mask, rho, theta, threshold, np.array([]),
180
+ min_line_length, max_line_gap)
181
 
182
+ edges = []
183
 
184
+ if lines is None:
185
+ continue
186
 
187
+ line_directions = np.zeros((len(lines), 2))
188
+ for line_idx, line in enumerate(lines):
189
+ for x1, y1, x2, y2 in line:
190
+ if x1 < x2:
191
+ x1, y1, x2, y2 = x2, y2, x1, y1
192
+ direction = (np.array([x2 - x1, y2 - y1]))
193
+ direction = direction / np.linalg.norm(direction)
194
+ line_directions[line_idx] = direction
195
 
196
+ direction = extend * direction
197
 
198
+ x1, y1 = (-direction + (x1, y1)).astype(np.int32)
199
+ x2, y2 = (+ direction + (x2, y2)).astype(np.int32)
200
 
201
+ edges.append((x1, y1, x2, y2))
202
 
203
+ edges = np.array(edges).astype(np.float64)
204
+ if len(edges) < 1:
205
+ continue
206
+ # calculate the distances between the vertices and the edge ends
207
+
208
+ begin_edges = KDTree(edges[:, :2])
209
+ end_edges = KDTree(edges[:, 2:])
210
+
211
+ begin_indices = begin_edges.query_ball_tree(vertices, point_radius)
212
+ end_indices = end_edges.query_ball_tree(vertices, point_radius)
213
+
214
+ line_indices = np.where(np.array([len(i) and len(j) for i, j in zip(begin_indices, end_indices)]))[0]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
215
 
216
+ # create all possible connections between begin and end candidates that correspond to a line
217
+ begin_vertex_list = []
218
+ end_vertex_list = []
219
+ line_idx_list = []
220
+ for line_idx in line_indices:
221
+ begin_vertex, end_vertex = begin_indices[line_idx], end_indices[line_idx]
222
+ begin_vertex, end_vertex = np.meshgrid(begin_vertex, end_vertex)
223
+ begin_vertex_list.extend(begin_vertex.flatten())
224
+ end_vertex_list.extend(end_vertex.flatten())
225
+
226
+ line_idx_list.extend([line_idx] * len(begin_vertex.flatten()))
227
+
228
+ line_idx_list = np.array(line_idx_list)
229
+ all_connections = np.array([begin_vertex_list, end_vertex_list])
230
+
231
+ # decrease the number of possible connections to reduce number of calculations
232
+ possible_connections = np.unique(all_connections, axis=1)
233
+ possible_connections = np.sort(possible_connections, axis=0)
234
+ possible_connections = np.unique(possible_connections, axis=1)
235
+ possible_connections = possible_connections[:, possible_connections[0, :] != possible_connections[1, :]]
236
+
237
+ if possible_connections.shape[1] < 1:
238
+ continue
239
+
240
+ # precalculate the possible direction vectors
241
+ possible_direction_vectors = vertices.data[possible_connections[0]] - vertices.data[possible_connections[1]]
242
+ possible_direction_vectors = possible_direction_vectors / np.linalg.norm(possible_direction_vectors, axis=1)[:, np.newaxis]
243
+
244
+ owned_lines_per_possible_connections = [list() for i in range(possible_connections.shape[1])]
245
+
246
+ # assign lines to possible connections
247
+ for line_idx, i,j in zip(line_idx_list, begin_vertex_list, end_vertex_list):
248
+ if i == j:
249
+ continue
250
+ i, j = min(i, j), max(i, j)
251
+ for connection_idx, connection in enumerate(possible_connections.T):
252
+ if np.all((i, j) == connection):
253
+ owned_lines_per_possible_connections[connection_idx].append(line_idx)
254
+ break
255
+
256
+ # check if the lines are in the same direction as the possible connection
257
+ for fitted_line_idx, owned_lines_per_possible_connection in enumerate(owned_lines_per_possible_connections):
258
+ line_deviations = np.abs(np.dot(line_directions[owned_lines_per_possible_connection], possible_direction_vectors[fitted_line_idx]))
259
+ if np.any(line_deviations > deviation_threshold):
260
+ connections.append(possible_connections[:, fitted_line_idx])
261
 
262
  vertices = [{"xy": v, "type": "apex"} for v in apex_centroids]
263
  # vertices += [{"xy": v, "type": "apex"} for v in missed_vertices]
 
267
 
268
  def get_uv_depth(vertices, depth):
269
  '''Get the depth of the vertices from the depth image'''
270
+
271
+ uv = np.array([v['xy'] for v in vertices])
 
 
272
  uv_int = uv.astype(np.int32)
273
  H, W = depth.shape[:2]
274
  uv_int[:, 0] = np.clip(uv_int[:, 0], 0, W - 1)
 
370
  gest_seg = gest.resize(depth.size)
371
  gest_seg_np = np.array(gest_seg).astype(np.uint8)
372
  # Metric3D
373
+ depth_np = np.array(depth) / scale_estimation_coefficient
374
  vertices, connections = get_vertices_and_edges_from_segmentation(gest_seg_np, **kwargs)
375
  if (len(vertices) < 2) or (len(connections) < 1):
376
  print(f'Not enough vertices or connections in image {i}')
test_solution.ipynb CHANGED
@@ -6,8 +6,8 @@
6
  "metadata": {
7
  "collapsed": true,
8
  "ExecuteTime": {
9
- "end_time": "2024-05-30T16:17:13.293274Z",
10
- "start_time": "2024-05-30T16:17:10.165200Z"
11
  }
12
  },
13
  "source": [
@@ -44,8 +44,8 @@
44
  {
45
  "metadata": {
46
  "ExecuteTime": {
47
- "end_time": "2024-05-30T16:17:13.299692Z",
48
- "start_time": "2024-05-30T16:17:13.294282Z"
49
  }
50
  },
51
  "cell_type": "code",
@@ -64,8 +64,8 @@
64
  {
65
  "metadata": {
66
  "ExecuteTime": {
67
- "end_time": "2024-05-30T16:17:13.303805Z",
68
- "start_time": "2024-05-30T16:17:13.300698Z"
69
  }
70
  },
71
  "cell_type": "code",
@@ -83,8 +83,8 @@
83
  {
84
  "metadata": {
85
  "ExecuteTime": {
86
- "end_time": "2024-05-30T16:58:50.530994Z",
87
- "start_time": "2024-05-30T16:57:19.315889Z"
88
  }
89
  },
90
  "cell_type": "code",
@@ -93,10 +93,16 @@
93
  "\n",
94
  "solution = []\n",
95
  "from concurrent.futures import ProcessPoolExecutor\n",
96
- "with ProcessPoolExecutor(max_workers=12) as pool:\n",
97
  " results = []\n",
98
  " for i, sample in enumerate(tqdm(dataset)):\n",
99
- " results.append(pool.submit(predict, sample, point_radius=25, max_angle=15, extend=30, merge_th=3.0, min_missing_distance=10000.0, scale_estimation_coefficient=4))\n",
 
 
 
 
 
 
100
  "\n",
101
  " for i, result in enumerate(tqdm(results)):\n",
102
  " key, pred_vertices, pred_edges = result.result()\n",
@@ -116,18 +122,18 @@
116
  "name": "stderr",
117
  "output_type": "stream",
118
  "text": [
119
- "346it [00:11, 30.58it/s] \n",
120
- "100%|β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ| 346/346 [01:18<00:00, 4.40it/s]\n"
121
  ]
122
  }
123
  ],
124
- "execution_count": 37
125
  },
126
  {
127
  "metadata": {
128
  "ExecuteTime": {
129
- "end_time": "2024-05-30T16:58:53.239999Z",
130
- "start_time": "2024-05-30T16:58:53.049258Z"
131
  }
132
  },
133
  "cell_type": "code",
@@ -160,12 +166,18 @@
160
  "DescribeResult(nobs=173, minmax=(1.3699674819647794, 3.507189015362208), mean=2.159528576281002, variance=0.19517651860816773, skewness=0.5493231777924736, kurtosis=-0.14096706768318912)"
161
  ]
162
  },
163
- "execution_count": 38,
164
  "metadata": {},
165
  "output_type": "execute_result"
166
  }
167
  ],
168
- "execution_count": 38
 
 
 
 
 
 
169
  },
170
  {
171
  "metadata": {
 
6
  "metadata": {
7
  "collapsed": true,
8
  "ExecuteTime": {
9
+ "end_time": "2024-05-30T19:40:29.917386Z",
10
+ "start_time": "2024-05-30T19:40:26.483885Z"
11
  }
12
  },
13
  "source": [
 
44
  {
45
  "metadata": {
46
  "ExecuteTime": {
47
+ "end_time": "2024-05-30T19:40:29.922957Z",
48
+ "start_time": "2024-05-30T19:40:29.918391Z"
49
  }
50
  },
51
  "cell_type": "code",
 
64
  {
65
  "metadata": {
66
  "ExecuteTime": {
67
+ "end_time": "2024-05-30T19:40:29.927010Z",
68
+ "start_time": "2024-05-30T19:40:29.923961Z"
69
  }
70
  },
71
  "cell_type": "code",
 
83
  {
84
  "metadata": {
85
  "ExecuteTime": {
86
+ "end_time": "2024-05-30T19:46:53.539149Z",
87
+ "start_time": "2024-05-30T19:45:17.379935Z"
88
  }
89
  },
90
  "cell_type": "code",
 
93
  "\n",
94
  "solution = []\n",
95
  "from concurrent.futures import ProcessPoolExecutor\n",
96
+ "with ProcessPoolExecutor(max_workers=10) as pool:\n",
97
  " results = []\n",
98
  " for i, sample in enumerate(tqdm(dataset)):\n",
99
+ " results.append(pool.submit(predict, sample,\n",
100
+ " point_radius=25, \n",
101
+ " max_angle=15, \n",
102
+ " extend=30, \n",
103
+ " merge_th=3.0, \n",
104
+ " min_missing_distance=10000.0, \n",
105
+ " scale_estimation_coefficient=4))\n",
106
  "\n",
107
  " for i, result in enumerate(tqdm(results)):\n",
108
  " key, pred_vertices, pred_edges = result.result()\n",
 
122
  "name": "stderr",
123
  "output_type": "stream",
124
  "text": [
125
+ "346it [00:11, 29.44it/s] \n",
126
+ "100%|β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ| 346/346 [01:23<00:00, 4.15it/s]\n"
127
  ]
128
  }
129
  ],
130
+ "execution_count": 7
131
  },
132
  {
133
  "metadata": {
134
  "ExecuteTime": {
135
+ "end_time": "2024-05-30T19:42:15.830560Z",
136
+ "start_time": "2024-05-30T19:42:15.185713Z"
137
  }
138
  },
139
  "cell_type": "code",
 
166
  "DescribeResult(nobs=173, minmax=(1.3699674819647794, 3.507189015362208), mean=2.159528576281002, variance=0.19517651860816773, skewness=0.5493231777924736, kurtosis=-0.14096706768318912)"
167
  ]
168
  },
169
+ "execution_count": 5,
170
  "metadata": {},
171
  "output_type": "execute_result"
172
  }
173
  ],
174
+ "execution_count": 5
175
+ },
176
+ {
177
+ "metadata": {},
178
+ "cell_type": "markdown",
179
+ "source": "best mean=2.159528576281002",
180
+ "id": "1d3cde94dcfc4c56"
181
  },
182
  {
183
  "metadata": {