batoolset 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (162) hide show
  1. batoolset/GUI/__init__.py +0 -0
  2. batoolset/GUI/list_gui.py +513 -0
  3. batoolset/GUI/multi_image_display.py +989 -0
  4. batoolset/GUI/open.py +325 -0
  5. batoolset/GUI/overlay_hints.py +45 -0
  6. batoolset/GUI/paint2.py +1345 -0
  7. batoolset/GUI/scrollablepaint.py +1152 -0
  8. batoolset/IJ_ROIs/__init__.py +0 -0
  9. batoolset/IJ_ROIs/tools.py +40 -0
  10. batoolset/PATH/__init__.py +0 -0
  11. batoolset/PATH/tools.py +79 -0
  12. batoolset/SQLite_tools/__init__.py +0 -0
  13. batoolset/SQLite_tools/tools.py +840 -0
  14. batoolset/__init__.py +0 -0
  15. batoolset/binary/__init__.py +0 -0
  16. batoolset/binary/tools.py +123 -0
  17. batoolset/binarytools/__init__.py +0 -0
  18. batoolset/binarytools/cell_center_detector.py +290 -0
  19. batoolset/clipboard_tools/__init__.py +0 -0
  20. batoolset/clipboard_tools/tools.py +7 -0
  21. batoolset/colors/__init__.py +0 -0
  22. batoolset/colors/colorgen.py +193 -0
  23. batoolset/colors/tools.py +22 -0
  24. batoolset/command_line/__init__.py +0 -0
  25. batoolset/command_line/tools.py +18 -0
  26. batoolset/dialogs/__init__.py +0 -0
  27. batoolset/dialogs/opensave.py +228 -0
  28. batoolset/dicts/__init__.py +0 -0
  29. batoolset/dicts/tools.py +299 -0
  30. batoolset/dims/__init__.py +0 -0
  31. batoolset/dims/tools.py +83 -0
  32. batoolset/draw/__init__.py +0 -0
  33. batoolset/draw/shapes/Position.py +183 -0
  34. batoolset/draw/shapes/__init__.py +0 -0
  35. batoolset/draw/shapes/automated_alignment_drawing_refactored.py +346 -0
  36. batoolset/draw/shapes/circle2d.py +99 -0
  37. batoolset/draw/shapes/compute_optimal_size_all_merged.py +317 -0
  38. batoolset/draw/shapes/ellipse2d.py +336 -0
  39. batoolset/draw/shapes/freehand2d.py +235 -0
  40. batoolset/draw/shapes/group.py +1830 -0
  41. batoolset/draw/shapes/image2d.py +2469 -0
  42. batoolset/draw/shapes/line2d.py +575 -0
  43. batoolset/draw/shapes/point2d.py +257 -0
  44. batoolset/draw/shapes/polygon2d.py +357 -0
  45. batoolset/draw/shapes/polyline2d.py +235 -0
  46. batoolset/draw/shapes/rect2d.py +388 -0
  47. batoolset/draw/shapes/rectangle2d.py +601 -0
  48. batoolset/draw/shapes/scalebar.py +304 -0
  49. batoolset/draw/shapes/serializablefont.py +126 -0
  50. batoolset/draw/shapes/serializableguiparams.py +10 -0
  51. batoolset/draw/shapes/square2d.py +103 -0
  52. batoolset/draw/shapes/textorientation.py +45 -0
  53. batoolset/draw/shapes/tools.py +467 -0
  54. batoolset/draw/shapes/transform2D.py +350 -0
  55. batoolset/draw/shapes/txt2d.py +550 -0
  56. batoolset/draw/widgets/__init__.py +0 -0
  57. batoolset/draw/widgets/col.py +343 -0
  58. batoolset/draw/widgets/image_group.py +248 -0
  59. batoolset/draw/widgets/image_organizer.py +96 -0
  60. batoolset/draw/widgets/paint.py +184 -0
  61. batoolset/draw/widgets/vectorial.py +257 -0
  62. batoolset/figure/__init__.py +0 -0
  63. batoolset/figure/alignment.py +814 -0
  64. batoolset/figure/column.py +1117 -0
  65. batoolset/figure/fig_tools.py +285 -0
  66. batoolset/figure/minimal_debugger_scale_etc.py +139 -0
  67. batoolset/figure/panel.py +697 -0
  68. batoolset/figure/row.py +852 -0
  69. batoolset/figure/user_selection.py +287 -0
  70. batoolset/files/__init__.py +0 -0
  71. batoolset/files/tools.py +1321 -0
  72. batoolset/images/__init__.py +0 -0
  73. batoolset/images/tools.py +81 -0
  74. batoolset/img.py +5216 -0
  75. batoolset/linux/__init__.py +0 -0
  76. batoolset/linux/tools.py +72 -0
  77. batoolset/lists/__init__.py +0 -0
  78. batoolset/lists/tools.py +436 -0
  79. batoolset/luts/__init__.py +0 -0
  80. batoolset/luts/lut_minimal_test.py +1207 -0
  81. batoolset/matplolib/__init__.py +0 -0
  82. batoolset/matplolib/tools.py +44 -0
  83. batoolset/np_tools/__init__.py +0 -0
  84. batoolset/np_tools/tools.py +252 -0
  85. batoolset/np_tools/trash.py +15 -0
  86. batoolset/pandas_tools/__init__.py +0 -0
  87. batoolset/pandas_tools/tools.py +880 -0
  88. batoolset/partials/__init__.py +0 -0
  89. batoolset/partials/tools.py +83 -0
  90. batoolset/plot_tools/__init__.py +0 -0
  91. batoolset/plot_tools/tools.py +70 -0
  92. batoolset/pyqt/__init__.py +0 -0
  93. batoolset/pyqt/tools.py +691 -0
  94. batoolset/re_tools/__init__.py +0 -0
  95. batoolset/re_tools/tools.py +35 -0
  96. batoolset/serialization/__init__.py +0 -0
  97. batoolset/serialization/tools.py +1192 -0
  98. batoolset/sets/__init__.py +0 -0
  99. batoolset/sets/tools.py +109 -0
  100. batoolset/settings/__init__.py +0 -0
  101. batoolset/settings/global_settings.py +134 -0
  102. batoolset/strings/__init__.py +0 -0
  103. batoolset/strings/tools.py +225 -0
  104. batoolset/ta/__init__.py +0 -0
  105. batoolset/ta/database/__init__.py +0 -0
  106. batoolset/ta/database/advanced_sql_plotter.py +1170 -0
  107. batoolset/ta/database/my_qt_table.py +79 -0
  108. batoolset/ta/database/preview_and_edit_sqlite_db.py +405 -0
  109. batoolset/ta/database/sql.py +2141 -0
  110. batoolset/ta/database/sql_column_name_dialog.py +122 -0
  111. batoolset/ta/measurements/TAmeasures.py +2226 -0
  112. batoolset/ta/measurements/__init__.py +0 -0
  113. batoolset/ta/measurements/measurements3D/__init__.py +0 -0
  114. batoolset/ta/measurements/measurements3D/get_point_on_surface_if_centroid_is_bad.py +175 -0
  115. batoolset/ta/measurements/measurements3D/height_map_to_projection.py +82 -0
  116. batoolset/ta/measurements/measurements3D/measures3D.py +208 -0
  117. batoolset/ta/measurements/measurements3D/triangulate.py +331 -0
  118. batoolset/ta/measurements/nematic.py +562 -0
  119. batoolset/ta/measurements/nematic_measurements.py +107 -0
  120. batoolset/ta/segmentation/__init__.py +0 -0
  121. batoolset/ta/segmentation/neo_wshed.py +314 -0
  122. batoolset/ta/selections/__init__.py +0 -0
  123. batoolset/ta/selections/selection.py +329 -0
  124. batoolset/ta/tracking/__init__.py +0 -0
  125. batoolset/ta/tracking/last_tracking_based_on_matching_or_on_translation_from_mermaid_warp.py +653 -0
  126. batoolset/ta/tracking/local_to_track_correspondance.py +138 -0
  127. batoolset/ta/tracking/rapid_detection_of_vertices.py +517 -0
  128. batoolset/ta/tracking/registration.py +108 -0
  129. batoolset/ta/tracking/swap_detector_and_fixer.py +296 -0
  130. batoolset/ta/tracking/track_correction.py +238 -0
  131. batoolset/ta/tracking/tracking_error_detector_and_fixer.py +3099 -0
  132. batoolset/ta/tracking/tracking_yet_another_approach_pyramidal_registration_n_neo_swapping_correction.py +725 -0
  133. batoolset/ta/utils/TA_tools.py +43 -0
  134. batoolset/ta/utils/__init__.py +0 -0
  135. batoolset/ta/utils/rps_tools.py +1783 -0
  136. batoolset/tests/__init__.py +0 -0
  137. batoolset/tests/rps_tools.py +51 -0
  138. batoolset/tests/tools.py +10 -0
  139. batoolset/tools/__init__.py +0 -0
  140. batoolset/tools/early_stopper_class.py +9 -0
  141. batoolset/tools/guided_data_recoverer.py +110 -0
  142. batoolset/tools/logger.py +100 -0
  143. batoolset/tools/mytimer.py +31 -0
  144. batoolset/tools/prompt_color.py +160 -0
  145. batoolset/tools/qthandler.py +118 -0
  146. batoolset/uitools/__init__.py +0 -0
  147. batoolset/uitools/blinker.py +56 -0
  148. batoolset/unsafe/__init__.py +0 -0
  149. batoolset/unsafe/tools.py +115 -0
  150. batoolset/utils/__init__.py +0 -0
  151. batoolset/utils/commontools.py +158 -0
  152. batoolset/utils/loadlist.py +728 -0
  153. batoolset/version.py +10 -0
  154. batoolset/xml/__init__.py +0 -0
  155. batoolset/xml/tools.py +65 -0
  156. batoolset/zip_tools/__init__.py +0 -0
  157. batoolset/zip_tools/tools.py +76 -0
  158. batoolset-0.1.0.dist-info/LICENSE +24 -0
  159. batoolset-0.1.0.dist-info/METADATA +45 -0
  160. batoolset-0.1.0.dist-info/RECORD +162 -0
  161. batoolset-0.1.0.dist-info/WHEEL +5 -0
  162. batoolset-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,1783 @@
1
+ import numpy as np
2
+ from skimage.data._binary_blobs import binary_blobs
3
+ from sklearn.neighbors import NearestNeighbors
4
+ from skimage.draw import line, rectangle
5
+ from skimage.measure import label, regionprops
6
+ from scipy.spatial.distance import cdist
7
+ import collections
8
+ import matplotlib.pyplot as plt
9
+ from skimage.measure._regionprops import RegionProperties
10
+ import math
11
+ from timeit import default_timer as timer
12
+ from sklearn.metrics import silhouette_score
13
+ from sklearn.cluster import KMeans
14
+ from numba import jit, njit
15
+
16
+ __DEBUG__ = False # for pavlidis code -> stores and prints decisions and directions
17
+
18
+
19
+ pavlidis_front_up = [[-1, -1], # px up left
20
+ [-1, 0], # px up
21
+ [-1, 1]] # px up right
22
+
23
+ pavlidis_front_left = [[1, -1], # px lower left
24
+ [0, -1], # left
25
+ [-1, -1]] # upper left
26
+
27
+ pavlidis_front_bottom = [[1, 1], # px lower right
28
+ [1, 0], # px bottom center
29
+ [1, -1]] # px lower left
30
+
31
+ pavlidis_front_right = [[-1, 1], # px upper right
32
+ [0, 1], # right
33
+ [1, 1]] # px lower right
34
+
35
+ pavlidis_lwr_pixels_up =[[1, -1], # px lower left
36
+ [0, -1], # left
37
+ [1, 1]] # px lower right
38
+
39
+ pavlidis_lwr_pixels_0 =[[1, -1], # px lower left
40
+ [0, -1], # left
41
+ [1, 1]] # px lower right
42
+
43
+ pavlidis_lwr_pixels_90 =[[-1, -1], # px upper left
44
+ [-1, 0], # up
45
+ [1, -1]] # px lower left
46
+
47
+ pavlidis_lwr_pixels_180 =[[-1, 1], # px upper right
48
+ [-1, -1], # upper left
49
+ [0, 1]] # px right
50
+
51
+ pavlidis_lwr_pixels_270 =[[-1, 1], # px upper right
52
+ [1, 0], # bottom
53
+ [1, 1]] # px lower right
54
+
55
+ starting_pavlidis = [pavlidis_lwr_pixels_0, pavlidis_lwr_pixels_90, pavlidis_lwr_pixels_180, pavlidis_lwr_pixels_270]
56
+
57
+ orientation_pavlidis = [pavlidis_front_up, pavlidis_front_right, pavlidis_front_bottom, pavlidis_front_left]
58
+
59
+
60
+ def __check_pixel_pavlidis(img, y, x, color_of_interest=None):
61
+ """
62
+ Check if a pixel in the image matches the specified color of interest.
63
+
64
+ Args:
65
+ img (np.ndarray or list): Image or region properties.
66
+ y (int): Y-coordinate of the pixel.
67
+ x (int): X-coordinate of the pixel.
68
+ color_of_interest: Color value to compare against.
69
+
70
+ Returns:
71
+ bool: True if the pixel matches the color of interest, False otherwise.
72
+ """
73
+ if isinstance(img, np.ndarray):
74
+ return img[y, x] == color_of_interest
75
+ if isinstance(img, list):
76
+ return [y, x] in img
77
+
78
+
79
+ def get_feret_from_region(region):
80
+ """
81
+ Calculate the Feret diameter (major axis) of a region.
82
+
83
+ Args:
84
+ region: Region properties.
85
+
86
+ Returns:
87
+ tuple: Coordinates of the two endpoints of the major axis.
88
+ """
89
+ major_axis_length = region.major_axis_length
90
+ orientation = region.orientation
91
+ centroid = region.centroid
92
+
93
+ x0 = centroid[1] - np.sin(orientation) * major_axis_length / 2
94
+ y0 = centroid[0] - np.cos(orientation) * major_axis_length / 2
95
+ x1 = centroid[1] + np.sin(orientation) * major_axis_length / 2
96
+ y1 = centroid[0] + np.cos(orientation) * major_axis_length / 2
97
+
98
+ return (y0,x0),(y1,x1)
99
+
100
+ @njit
101
+ def pavlidis2(img, start_y=None, start_x=None, closed_contour=False, color_of_interest=None,
102
+ auto_detect_extreme_points_for_region_upon_missing_start_coords=True):
103
+ """
104
+ Traces a contour in an image using the Pavlidis algorithm.
105
+
106
+ Args:
107
+ img (ndarray): The input image.
108
+ start_y (int): The starting y-coordinate for the contour tracing. Default is None.
109
+ start_x (int): The starting x-coordinate for the contour tracing. Default is None.
110
+ closed_contour (bool): Flag indicating whether the contour is closed. Default is False.
111
+ color_of_interest: The color value of interest for contour tracing. Default is None.
112
+ auto_detect_extreme_points_for_region_upon_missing_start_coords (bool): Flag indicating whether
113
+ to automatically detect extreme points for the region if start coordinates are missing.
114
+ Default is True.
115
+
116
+ Returns:
117
+ list: A list of (y, x) coordinates representing the traced contour.
118
+
119
+ # Examples:
120
+ # >>> img = np.array([[0, 0, 0, 0, 0],
121
+ # ... [0, 1, 1, 1, 0],
122
+ # ... [0, 1, 0, 1, 0],
123
+ # ... [0, 1, 1, 1, 0],
124
+ # ... [0, 0, 0, 0, 0]])
125
+ # >>> contour = pavlidis2(img, start_y=2, start_x=1, closed_contour=True)
126
+ # >>> print(contour)
127
+ # [(2, 1), (1, 2), (1, 3), (2, 4), (3, 3), (3, 2)]
128
+ #
129
+ # >>> img = np.array([[0, 0, 0, 0, 0],
130
+ # ... [0, 1, 1, 1, 0],
131
+ # ... [0, 1, 0, 1, 0],
132
+ # ... [0, 1, 1, 1, 0],
133
+ # ... [0, 0, 0, 0, 0]])
134
+ # >>> contour = pavlidis2(img, start_y=2, start_x=1, closed_contour=False)
135
+ # >>> print(contour)
136
+ # [(2, 1), (1, 2), (1, 3), (2, 4), (3, 3)]
137
+ """
138
+
139
+ coords = []
140
+
141
+ if start_x >= img.shape[1] or start_y >= img.shape[0] or start_x < 0 or start_y < 0:
142
+ print('error coordinates outside the image')
143
+ return coords
144
+
145
+ if color_of_interest is None:
146
+ color_of_interest = img[start_y, start_x]
147
+
148
+ if img[start_y, start_x] != color_of_interest:
149
+ print('error starting coordinates outside the object')
150
+ return coords
151
+
152
+ cur_coord_x = start_x
153
+ cur_coord_y = start_y
154
+ coords.append((start_y, start_x))
155
+
156
+ look_direction = 0
157
+ counter = 0
158
+ no_success_counter = 0
159
+ nb_of_encounters_of_first_pixel = 0
160
+
161
+ while True:
162
+ counter += 1
163
+ success = False
164
+ no_success_counter = 0
165
+
166
+ if no_success_counter >= 3:
167
+ return coords
168
+
169
+ if nb_of_encounters_of_first_pixel >= 3:
170
+ return coords
171
+
172
+ if cur_coord_x == start_x and cur_coord_y == start_y:
173
+ nb_of_encounters_of_first_pixel += 1
174
+
175
+ print('look_direction', look_direction, counter)
176
+
177
+ for ppp in range(orientation_pavlidis[look_direction].shape[0]+1):
178
+ shift = orientation_pavlidis[look_direction][ppp]
179
+ print('shift', shift, coords)
180
+
181
+ coords_to_test_x = cur_coord_x + int(shift[1])
182
+ coords_to_test_y = cur_coord_y + int(shift[0])
183
+
184
+ print(coords_to_test_x, coords_to_test_y)
185
+
186
+ if True: # Avoiding infinite loop for non-closed contours
187
+ if (coords_to_test_y, coords_to_test_x) in coords:
188
+ return coords
189
+
190
+ if coords_to_test_y >= img.shape[0] or coords_to_test_x >= img.shape[1] or \
191
+ coords_to_test_y < 0 or coords_to_test_x < 0:
192
+ continue
193
+
194
+ if img[coords_to_test_y, coords_to_test_x] == color_of_interest:
195
+ cur_coord_y = coords_to_test_y
196
+ cur_coord_x = coords_to_test_x
197
+
198
+ if cur_coord_x == start_x and cur_coord_y == start_y:
199
+ return coords
200
+ coords.append((cur_coord_y, cur_coord_x))
201
+
202
+ if ppp == 0:
203
+ look_direction -= 1
204
+ if look_direction < 0:
205
+ look_direction = 3
206
+ break
207
+
208
+ if not success:
209
+ look_direction += 1
210
+ if look_direction > 3:
211
+ look_direction = 0
212
+
213
+ if no_success_counter >= 4:
214
+ return coords
215
+ no_success_counter += 1
216
+
217
+ return coords
218
+
219
+
220
+ def pavlidis(img, start_y=None, start_x=None, closed_contour=False, color_of_interest=None,
221
+ auto_detect_extreme_points_for_region_upon_missing_start_coords=True, starting_orientation=0, early_stop=None):
222
+ """
223
+ Traces a contour in an image using the Pavlidis algorithm.
224
+
225
+ Args:
226
+ img: The input image.
227
+ start_y: The starting y-coordinate for the contour tracing. Default is None.
228
+ start_x: The starting x-coordinate for the contour tracing. Default is None.
229
+ closed_contour: Flag indicating whether the contour is closed. Default is False.
230
+ color_of_interest: The color value of interest for contour tracing. Default is None.
231
+ auto_detect_extreme_points_for_region_upon_missing_start_coords: Flag indicating whether
232
+ to automatically detect extreme points for the region if start coordinates are missing.
233
+ Default is True.
234
+ starting_orientation: The starting orientation for contour tracing. Default is 0.
235
+ early_stop: The number of pixels to trace before stopping. Default is None.
236
+
237
+ Returns:
238
+ list: A list of (y, x) coordinates representing the traced contour.
239
+
240
+ Examples:
241
+ >>> img = np.array([[0, 0, 0, 0, 0],
242
+ ... [0, 1, 1, 1, 0],
243
+ ... [0, 1, 0, 1, 0],
244
+ ... [0, 1, 1, 1, 0],
245
+ ... [0, 0, 0, 0, 0]])
246
+ >>> contour = pavlidis(img, start_y=1, start_x=1, closed_contour=True)
247
+ >>> print(contour)
248
+ [(1, 1), (1, 2), (1, 3), (2, 3), (3, 3), (3, 2), (3, 1), (2, 1)]
249
+
250
+ >>> img = np.array([[0, 0, 0, 0, 0],
251
+ ... [0, 1, 1, 1, 0],
252
+ ... [0, 1, 0, 1, 0],
253
+ ... [0, 1, 1, 1, 0],
254
+ ... [0, 0, 0, 0, 0]])
255
+ >>> contour = pavlidis(img, start_y=3, start_x=1, closed_contour=True)
256
+ >>> print(contour)
257
+ [(3, 1), (2, 1), (1, 1), (1, 2), (1, 3), (2, 3), (3, 3), (3, 2)]
258
+ """
259
+ coords = []
260
+
261
+ if not isinstance(img, RegionProperties):
262
+ if start_x is not None and start_y is not None:
263
+ if start_x >= img.shape[1] or start_y >= img.shape[0] or start_x < 0 or start_y < 0:
264
+ print('error coordinates outside the image')
265
+ return coords
266
+ if color_of_interest is None:
267
+ color_of_interest = img[start_y, start_x]
268
+ if img[start_y, start_x] != color_of_interest:
269
+ print('error starting coordinates outside the object')
270
+ return coords
271
+ else:
272
+ feret_1, feret_2 = get_feret_from_points(extreme_points(img))
273
+ start_y = feret_1[0]
274
+ start_x = feret_1[1]
275
+ img = img.tolist()
276
+ else:
277
+ img = img.coords
278
+ if start_x is None or start_y is None:
279
+ if not auto_detect_extreme_points_for_region_upon_missing_start_coords:
280
+ start_y, start_x = img[0][0], img[0][1]
281
+ else:
282
+ feret_1, feret_2 = get_feret_from_points(extreme_points(img))
283
+ start_y = feret_1[0]
284
+ start_x = feret_1[1]
285
+ else:
286
+ if not __check_pixel_pavlidis(img.tolist(), start_y, start_x):
287
+ print('error coordinates outside the image')
288
+ return coords
289
+ img = img.tolist()
290
+
291
+ cur_coord_x = start_x
292
+ cur_coord_y = start_y
293
+ coords.append((start_y, start_x))
294
+
295
+ look_direction = 0
296
+ if starting_orientation != 0:
297
+ look_direction = starting_orientation
298
+
299
+ counter = 0
300
+ no_success_counter = 0
301
+
302
+ old_coords_x = -1
303
+ old_coords_y = -1
304
+
305
+ infinite_loop_counter = 0
306
+
307
+ limit = None
308
+ if isinstance(img, list):
309
+ limit = 10 * len(img)
310
+
311
+ while True:
312
+ counter += 1
313
+ success = False
314
+ no_success_counter = 0
315
+
316
+ if early_stop is not None and len(coords) >= early_stop:
317
+ return coords
318
+
319
+ if limit is not None:
320
+ if counter > limit:
321
+ print('error infinite loop Pavlidis --> breaking')
322
+ return coords
323
+
324
+ if infinite_loop_counter > 100:
325
+ return coords
326
+
327
+ if old_coords_y == cur_coord_y and old_coords_x == cur_coord_x:
328
+ infinite_loop_counter += 1
329
+ else:
330
+ old_coords_y = cur_coord_y
331
+ old_coords_x = cur_coord_x
332
+ infinite_loop_counter = 0
333
+
334
+ for ppp, shift in enumerate(orientation_pavlidis[look_direction]):
335
+ coords_to_test_x = cur_coord_x + shift[1]
336
+ coords_to_test_y = cur_coord_y + shift[0]
337
+
338
+ if counter > 1:
339
+ if coords_to_test_x == start_x and coords_to_test_y == start_y:
340
+ return coords
341
+
342
+ if not closed_contour:
343
+ if (coords_to_test_y, coords_to_test_x) in coords:
344
+ return coords
345
+
346
+ if isinstance(img, np.ndarray):
347
+ if coords_to_test_y >= img.shape[0] or coords_to_test_x >= img.shape[1] or \
348
+ coords_to_test_y < 0 or coords_to_test_x < 0:
349
+ continue
350
+
351
+ if __check_pixel_pavlidis(img, coords_to_test_y, coords_to_test_x, color_of_interest):
352
+ cur_coord_y = coords_to_test_y
353
+ cur_coord_x = coords_to_test_x
354
+
355
+ if cur_coord_x == start_x and cur_coord_y == start_y:
356
+ return coords
357
+ coords.append((cur_coord_y, cur_coord_x))
358
+
359
+ if ppp == 0:
360
+ look_direction -= 1
361
+ if look_direction < 0:
362
+ look_direction = 3
363
+ success = True
364
+ break
365
+
366
+ if not success:
367
+ look_direction += 1
368
+ if look_direction > 3:
369
+ look_direction = 0
370
+ if no_success_counter >= 4:
371
+ return coords
372
+ no_success_counter += 1
373
+
374
+ return coords
375
+
376
+
377
+ def __plot_pavlidis(img, coords, skip_plot=True):
378
+ """
379
+ Plot the image or regions (regionprops) with corresponding Pavlidis coordinates.
380
+
381
+ Args:
382
+ img: Image or RegionProperties object.
383
+ coords: List of coordinates.
384
+ skip_plot: Boolean value indicating whether to skip plotting the image.
385
+
386
+ Returns:
387
+ None
388
+ """
389
+
390
+ if isinstance(img, RegionProperties):
391
+ region = img
392
+ tmp_img = np.zeros((max(region.bbox[2], region.bbox[0]) + 1,
393
+ max(region.bbox[1], region.bbox[3]) + 1))
394
+ tmp_img[region.coords[:, 0], region.coords[:, 1]] = 255
395
+ img = tmp_img
396
+
397
+ output = np.zeros_like(img)
398
+
399
+ if contains_duplicate_coordinates(coords):
400
+ coords = remove_duplicate_coordinates(coords)
401
+
402
+ for val, (y, x) in enumerate(coords):
403
+ output[y, x] = val + 1
404
+
405
+ if not skip_plot:
406
+ plt.imshow(output)
407
+ plt.show()
408
+
409
+ output[output != 0] = 255
410
+
411
+ if not (output == img).all():
412
+ missed = np.copy(img)
413
+ missed[output != 0] = 128
414
+ if not skip_plot:
415
+ plt.imshow(missed)
416
+ plt.show()
417
+ else:
418
+ print('FULL MATCH')
419
+
420
+
421
+ def extreme_points(coords, return_array=True):
422
+ """
423
+ Find the extreme points from a set of coordinates.
424
+
425
+ Args:
426
+ coords: List of coordinates.
427
+ return_array: Boolean value indicating whether to return the extreme points as a list or individual tuples.
428
+
429
+ Returns:
430
+ List of extreme points [(top), (bottom), (left), (right)] if return_array is True, else returns individual tuples.
431
+ """
432
+ tmp_coords = coords
433
+
434
+ if isinstance(coords, tuple):
435
+ tmp_coords = np.asarray(list(zip(coords[0].tolist(), coords[1].tolist())))
436
+
437
+ top = tuple(tmp_coords[tmp_coords[..., 1].argmin()])
438
+ bottom = tuple(tmp_coords[tmp_coords[..., 1].argmax()])
439
+ left = tuple(tmp_coords[tmp_coords[..., 0].argmin()])
440
+ right = tuple(tmp_coords[tmp_coords[..., 0].argmax()])
441
+
442
+ if not return_array:
443
+ return top, bottom, left, right
444
+ else:
445
+ return [top, bottom, left, right]
446
+
447
+ # returns the most distant points in a set of points
448
+ # nb I found a bug in this code in some cases --> needs a fix --> see /home/aigouy/mon_prog/Python/epyseg_pkg/personal/wing/ferret_error.tif
449
+ # no clue why though
450
+ # the feret is wrong with values ((612, 457), (612, 458), (612, 459), (612, 460), (612, 461), (612, 462), (612, 463), (612, 464), (613, 441), (613, 442), (613, 443), (613, 444), (613, 445), (613, 446), (613, 447), (613, 448), (613, 449), (613, 450), (613, 451), (613, 452), (613, 453), (613, 454), (613, 455), (613, 456), (614, 426), (614, 427), (614, 428), (614, 429), (614, 430), (614, 431), (614, 432), (614, 433), (614, 434), (614, 435), (614, 436), (614, 437), (614, 438), (614, 439), (614, 440), (615, 411), (615, 412), (615, 413), (615, 414), (615, 415), (615, 416), (615, 417), (615, 418), (615, 419), (615, 420), (615, 421), (615, 422), (615, 423), (615, 424), (615, 425), (616, 395), (616, 396), (616, 397), (616, 398), (616, 399), (616, 400), (616, 401), (616, 402), (616, 403), (616, 404), (616, 405), (616, 406), (616, 407), (616, 408), (616, 409), (616, 410), (617, 380), (617, 381), (617, 382), (617, 383), (617, 384), (617, 385), (617, 386), (617, 387), (617, 388), (617, 389), (617, 390), (617, 391), (617, 392), (617, 393), (617, 394), (617, 499), (617, 500), (617, 501), (617, 502), (617, 503), (617, 504), (617, 505), (617, 506), (617, 507), (617, 508), (617, 509), (617, 510), (617, 511), (617, 512), (617, 513), (617, 514), (617, 515), (617, 516), (618, 372), (618, 373), (618, 374), (618, 375), (618, 376), (618, 377), (618, 378), (618, 379), (618, 473), (618, 474), (618, 475), (618, 476), (618, 477), (618, 478), (618, 479), (618, 480), (618, 481), (618, 482), (618, 483), (618, 484), (618, 485), (618, 486), (618, 487), (618, 488), (618, 489), (618, 490), (618, 491), (618, 492), (618, 493), (618, 494), (618, 495), (618, 496), (618, 497), (618, 498), (618, 517), (618, 518), (618, 519), (618, 520), (618, 521), (618, 522), (618, 523), (618, 524), (618, 525), (618, 526), (619, 374), (619, 375), (619, 447), (619, 448), (619, 449), (619, 450), (619, 451), (619, 452), (619, 453), (619, 454), (619, 455), (619, 456), (619, 457), (619, 458), (619, 459), (619, 460), (619, 461), (619, 462), (619, 463), (619, 464), (619, 465), (619, 466), (619, 467), (619, 468), (619, 469), (619, 470), (619, 471), (619, 472), (619, 527), (619, 528), (619, 529), (619, 530), (619, 531), (619, 532), (619, 533), (619, 534), (619, 535), (619, 536), (620, 376), (620, 377), (620, 378), (620, 421), (620, 422), (620, 423), (620, 424), (620, 425), (620, 426), (620, 427), (620, 428), (620, 429), (620, 430), (620, 431), (620, 432), (620, 433), (620, 434), (620, 435), (620, 436), (620, 437), (620, 438), (620, 439), (620, 440), (620, 441), (620, 442), (620, 443), (620, 444), (620, 445), (620, 446), (620, 537), (620, 538), (620, 539), (620, 540), (620, 541), (620, 542), (620, 543), (620, 544), (620, 545), (620, 546), (621, 379), (621, 380), (621, 395), (621, 396), (621, 397), (621, 398), (621, 399), (621, 400), (621, 401), (621, 402), (621, 403), (621, 404), (621, 405), (621, 406), (621, 407), (621, 408), (621, 409), (621, 410), (621, 411), (621, 412), (621, 413), (621, 414), (621, 415), (621, 416), (621, 417), (621, 418), (621, 419), (621, 420), (621, 547), (621, 548), (621, 549), (621, 550), (621, 551), (621, 552), (621, 553), (621, 554), (621, 555), (621, 556), (622, 381), (622, 382), (622, 383), (622, 384), (622, 385), (622, 386), (622, 387), (622, 388), (622, 389), (622, 390), (622, 391), (622, 392), (622, 393), (622, 394), (622, 557), (622, 558), (622, 559), (622, 560), (622, 561), (622, 562), (622, 563), (622, 564), (622, 565), (622, 566), (623, 567), (623, 568), (623, 569), (623, 570), (623, 571), (623, 572), (623, 573), (623, 574), (623, 575), (623, 576), (624, 577), (624, 578), (624, 579), (624, 580), (624, 581), (624, 582), (624, 583), (624, 584), (624, 585), (624, 586), (625, 587), (625, 588), (625, 589), (625, 590), (625, 591), (625, 592), (625, 593), (625, 594), (625, 595), (625, 596), (626, 597), (626, 598), (626, 599), (626, 600), (626, 601), (626, 602), (626, 603), (626, 604), (626, 605), (626, 606), (627, 607), (627, 608), (627, 609), (627, 610), (627, 611), (627, 612), (627, 613), (627, 614), (627, 615), (627, 616), (628, 617), (628, 618), (628, 619), (628, 620), (628, 621), (628, 622), (629, 623), (629, 624), (629, 625), (630, 626), (630, 627), (630, 628), (631, 629), (631, 630), (631, 631), (632, 632), (632, 633), (632, 634), (633, 635), (633, 636), (633, 637), (634, 638), (634, 639), (634, 640), (635, 641), (635, 642), (635, 643), (636, 644), (636, 645), (636, 646), (637, 647), (637, 648), (637, 649), (638, 650), (638, 651), (638, 652), (639, 653), (639, 654), (639, 655), (640, 656), (640, 657), (640, 658), (641, 659), (641, 660), (641, 661), (642, 662), (642, 663), (643, 664), (643, 665), (643, 666), (644, 667), (644, 668), (644, 669), (645, 670), (645, 671), (645, 672), (645, 819), (645, 820), (645, 821), (645, 822), (645, 823), (645, 824), (645, 825), (645, 826), (645, 827), (645, 828), (645, 829), (645, 830), (645, 831), (645, 832), (645, 833), (645, 834), (645, 835), (645, 836), (645, 837), (645, 838), (645, 839), (645, 840), (645, 841), (645, 842), (645, 843), (645, 844), (645, 845), (645, 846), (645, 847), (645, 848), (645, 849), (645, 850), (645, 851), (645, 852), (645, 853), (645, 854), (645, 855), (645, 856), (645, 857), (645, 858), (645, 859), (645, 860), (645, 861), (645, 862), (645, 863), (645, 864), (645, 865), (645, 866), (645, 867), (645, 868), (645, 869), (645, 870), (645, 871), (645, 872), (645, 873), (645, 874), (645, 875), (645, 876), (645, 877), (645, 878), (645, 879), (645, 880), (645, 881), (646, 673), (646, 674), (646, 675), (646, 750), (646, 751), (646, 752), (646, 753), (646, 754), (646, 755), (646, 756), (646, 757), (646, 758), (646, 759), (646, 760), (646, 761), (646, 762), (646, 763), (646, 764), (646, 765), (646, 766), (646, 767), (646, 768), (646, 769), (646, 770), (646, 771), (646, 772), (646, 773), (646, 774), (646, 775), (646, 776), (646, 777), (646, 778), (646, 779), (646, 780), (646, 781), (646, 782), (646, 783), (646, 784), (646, 785), (646, 786), (646, 787), (646, 788), (646, 789), (646, 790), (646, 791), (646, 792), (646, 793), (646, 794), (646, 795), (646, 796), (646, 797), (646, 798), (646, 799), (646, 800), (646, 801), (646, 802), (646, 803), (646, 804), (646, 805), (646, 806), (646, 807), (646, 808), (646, 809), (646, 810), (646, 811), (646, 812), (646, 813), (646, 814), (646, 815), (646, 816), (646, 817), (646, 818), (647, 676), (647, 677), (647, 678), (647, 745), (647, 746), (647, 747), (647, 748), (647, 749), (648, 679), (648, 680), (648, 681), (648, 740), (648, 741), (648, 742), (648, 743), (648, 744), (649, 682), (649, 683), (649, 684), (649, 736), (649, 737), (649, 738), (649, 739), (650, 685), (650, 686), (650, 687), (650, 731), (650, 732), (650, 733), (650, 734), (650, 735), (651, 688), (651, 689), (651, 690), (651, 726), (651, 727), (651, 728), (651, 729), (651, 730), (652, 691), (652, 692), (652, 693), (652, 721), (652, 722), (652, 723), (652, 724), (652, 725), (653, 694), (653, 695), (653, 696), (653, 716), (653, 717), (653, 718), (653, 719), (653, 720), (654, 697), (654, 698), (654, 699), (654, 712), (654, 713), (654, 714), (654, 715), (655, 700), (655, 701), (655, 702), (655, 707), (655, 708), (655, 709), (655, 710), (655, 711), (656, 703), (656, 704), (656, 705), (656, 706))
451
+ # BIG BUG THE SOLUTION MAY NOT BE UNIQUE --> NEED A FIX WHEN POSSIBLE IN THE CASE OF BONDS!!--> see example just above and the rps_tools test class
452
+
453
+ def get_feret_from_points(contour_or_extreme_points):
454
+ """
455
+ Calculate the Feret diameter (maximum distance) between two points from a given set of contour or extreme points.
456
+
457
+ Args:
458
+ contour_or_extreme_points: List or array-like object containing contour or extreme points.
459
+
460
+ Returns:
461
+ Tuple of two points representing the Feret diameter.
462
+
463
+ # Examples:
464
+ # >>> contour_points = [(10, 20), (30, 40), (50, 60), (70, 80)]
465
+ # >>> extreme_points(contour_points, return_array=True)
466
+ #
467
+ # # Calculate Feret diameter from contour points
468
+ # >>> feret_contour = get_feret_from_points(contour_points)
469
+ # >>> print('Feret Diameter (Contour Points):', feret_contour)
470
+ #
471
+ # # Calculate Feret diameter from extreme points
472
+ # >>> feret_extreme = get_feret_from_points(extreme_points)
473
+ # >>> print('Feret Diameter (Extreme Points):', feret_extreme)
474
+ """
475
+ C = cdist(contour_or_extreme_points, contour_or_extreme_points)
476
+ furthest_points = np.where(C == C.max())
477
+ feret_1 = contour_or_extreme_points[furthest_points[0][0]]
478
+ feret_2 = contour_or_extreme_points[furthest_points[0][1]]
479
+ return feret_1, feret_2
480
+
481
+
482
+ # it is a pixel sorting algo and a very smart alternative to the pavlidis algo for lines or perimeter (especially because it is insensitive to holes in the connectivity and insensitive to the choice of the start point for non closed shapes)
483
+ # THIS IS MUCH FASTER AND POWERFUL THAN PAVLIDIS --> RATHER USE THAT FOR CONTOURS AND FOR BONDS (JUST USE PAVLIDIS FOR FILLED SHAPES)!!!
484
+ # pb can have big jumps in it --> may need rotate the result array --> TODO URGENT --> DO THAT ESPECIALLY IF CONTOUR IS LONG
485
+ # compute dist to first or last or between last and first and detect closest and do roll of the array so that all is ok after that --> TODO
486
+ # RELY ON PAVLIDIS FOR NOW!!!
487
+
488
+ # def nearest_neighbor_ordering(coords, remove_dupes=False):
489
+ # """
490
+ # Sort a set of coordinates to form a continuous line using the nearest neighbor algorithm.
491
+ #
492
+ # Args:
493
+ # coords (array-like): List or array-like object containing the coordinates.
494
+ # remove_dupes (bool): Flag indicating whether to remove duplicate coordinates (default: False).
495
+ #
496
+ # Returns:
497
+ # Sorted coordinates forming a continuous line.
498
+ #
499
+ # # Examples:
500
+ # # >>> coords = [(0, 0), (1, 1), (2, 2), (3, 3)]
501
+ # # >>> ordered_coords = nearest_neighbor_ordering(coords)
502
+ # # >>> print('Ordered Coordinates:', ordered_coords)
503
+ # #
504
+ # # >>> coords = [(0, 0), (1, 1), (2, 2), (1, 1)]
505
+ # # >>> ordered_coords = nearest_neighbor_ordering(coords, remove_dupes=True)
506
+ # # >>> print('Ordered Coordinates (No Dupes):', ordered_coords)
507
+ # """
508
+ # import networkx as nx
509
+ # if not isinstance(coords, np.ndarray):
510
+ # coords = np.asarray(coords)
511
+ #
512
+ # clf = NearestNeighbors(n_neighbors=2).fit(coords)
513
+ # G = clf.kneighbors_graph()
514
+ # T = nx.from_scipy_sparse_matrix(G)
515
+ # order = list(nx.dfs_preorder_nodes(T, 0))
516
+ # coords = coords[order]
517
+ #
518
+ # if remove_dupes and contains_duplicate_coordinates(coords):
519
+ # no_dupes = remove_duplicate_coordinates(coords.tolist())
520
+ # coords = np.asarray(no_dupes)
521
+ #
522
+ # return coords
523
+
524
+
525
+ def find_duplicate_coordinates(coords):
526
+ """
527
+ Find duplicate coordinates from a given list.
528
+
529
+ Args:
530
+ coords (array-like): List or array-like object containing the coordinates.
531
+
532
+ Returns:
533
+ List of duplicate coordinates.
534
+ """
535
+ return [item for item, count in collections.Counter(coords).items() if count > 1]
536
+
537
+
538
+ def contains_duplicate_coordinates(coords):
539
+ """
540
+ Check if a list contains duplicate coordinates.
541
+
542
+ Args:
543
+ coords (array-like): List or array-like object containing the coordinates.
544
+
545
+ Returns:
546
+ True if duplicate coordinates are found, False otherwise.
547
+ """
548
+ if not isinstance(coords, list):
549
+ coords = coords.tolist()
550
+ try:
551
+ if len(set(coords)) != len(coords):
552
+ return True
553
+ except:
554
+ tmp = {tuple(coords) for coords in coords}
555
+ if len(tmp) != len(coords):
556
+ return True
557
+ return False
558
+
559
+
560
+ def remove_duplicate_coordinates(coords):
561
+ """
562
+ Remove duplicate coordinates from a list.
563
+
564
+ Args:
565
+ coords (array-like): List or array-like object containing the coordinates.
566
+
567
+ Returns:
568
+ List of coordinates without duplicates.
569
+ """
570
+ fixed_coords = []
571
+ [fixed_coords.append(item) for item in coords if item not in fixed_coords]
572
+ return fixed_coords
573
+
574
+
575
+ def dist2D(pt1, pt2):
576
+ """
577
+ Calculate the 2D Euclidean distance between two points.
578
+
579
+ Args:
580
+ pt1 (tuple): First point (x1, y1).
581
+ pt2 (tuple): Second point (x2, y2).
582
+
583
+ Returns:
584
+ Euclidean distance between the two points.
585
+ """
586
+ return math.sqrt((pt2[0] - pt1[0]) ** 2 + (pt2[1] - pt1[1]) ** 2)
587
+
588
+
589
+ def compute_perimeter(perimeter_pixels):
590
+ """
591
+ Compute the perimeter length using a set of pixels.
592
+
593
+ Args:
594
+ perimeter_pixels (np.ndarray): Array of shape (N, 2 or 3) containing the perimeter pixels.
595
+
596
+ Returns:
597
+ Perimeter length.
598
+ """
599
+ length = compute_distance_between_consecutive_points(perimeter_pixels).sum()
600
+ return length
601
+
602
+ def associate_rps_to_rps_using_centroid_distance(primary_rps, rps_to_be_associated, no_repick=False):
603
+ # MEGA TODO needs be greatly be improved but the idea is there and it's much faster than computing the overlap score
604
+ from personal.coccinelle_ladybug.HashableRegionProps import HashableRegionProps
605
+ from batoolset.ta.measurements.TAmeasures import distance_between_points
606
+ output={}
607
+ # detected_arena = []
608
+ for primary_region in primary_rps:
609
+ # primary_region = (HashableRegionProps) primary_region
610
+ output[ (HashableRegionProps)(primary_region)]=[]
611
+ for region in rps_to_be_associated:
612
+ # if compute_coords_overlap_between_0_and_1(region.coords, primary_region.coords)!=0:
613
+ # output[(HashableRegionProps)(primary_region)].append(region)
614
+ if distance_between_points(primary_region.centroid, region.centroid)<6:
615
+ # detected_arena.append(region)
616
+ output[(HashableRegionProps)(primary_region)].append(region)
617
+ if no_repick:
618
+ break
619
+ # if no_repick:
620
+ # rps_to_be_associated = [region for region in rps_to_be_associated if region not in detected_arena]
621
+ return output
622
+
623
+ def associate_rps_to_rps_tmp(primary_rps, rps_to_be_associated, no_repick=False):
624
+ # THIS CODE IS ALSO NOT GREAT BECAUSE I SHOULD BREAK WHEN I FIND OVERLAP IF NO REPICK
625
+ # in fact I should maximize the overlap to do this better
626
+ from personal.coccinelle_ladybug.HashableRegionProps import HashableRegionProps
627
+ from personal.geom.tools import compute_coords_overlap_between_0_and_1
628
+ output={}
629
+ # detected_arena = []
630
+ for primary_region in primary_rps:
631
+ # primary_region = (HashableRegionProps) primary_region
632
+ output[ (HashableRegionProps)(primary_region)]=[]
633
+ for region in rps_to_be_associated:
634
+ if compute_coords_overlap_between_0_and_1(region.coords, primary_region.coords)!=0: # REALLY NOT GREAT I SHOULD TAKE THE MOST OVERLAPPING REGION
635
+ output[(HashableRegionProps)(primary_region)].append(region)
636
+ if no_repick:
637
+ break
638
+ # if no_repick:
639
+ # detected_arena.append(region)
640
+ # if no_repick:
641
+ # rps_to_be_associated = [region for region in rps_to_be_associated if region not in detected_arena]
642
+ return output
643
+
644
+
645
+ def associate_rps_to_rps(primary_rps, rps_to_be_associated, no_repick=False): # THE COOL THING ABOUT THIS CODE IS THAT IT CAN ASSOCAITE SEVERAL SMALL RPS TO A BIG ONE --> WHICH IS USEFUL FOR ARENA MATCHING
646
+ # THIS CODE IS ALSO NOT GREAT BECAUSE I SHOULD BREAK WHEN I FIND OVERLAP IF NO REPICK
647
+ # in fact I should maximize the overlap to do this better
648
+ from personal.coccinelle_ladybug.HashableRegionProps import HashableRegionProps
649
+ from personal.geom.tools import compute_coords_overlap_between_0_and_1
650
+ output={}
651
+ detected_arena = []
652
+ for primary_region in primary_rps:
653
+ # primary_region = (HashableRegionProps) primary_region
654
+ output[ (HashableRegionProps)(primary_region)]=[]
655
+ for region in rps_to_be_associated:
656
+ if compute_coords_overlap_between_0_and_1(region.coords, primary_region.coords)!=0: # REALLY NOT GREAT I SHOULD TAKE THE MOST OVERLAPPING REGION
657
+ output[(HashableRegionProps)(primary_region)].append(region)
658
+ if no_repick:
659
+ detected_arena.append(region)
660
+ if no_repick:
661
+ rps_to_be_associated = [region for region in rps_to_be_associated if region not in detected_arena]
662
+ return output
663
+
664
+
665
+
666
+ def associate_rps_to_rps2(primary_rps, rps_to_be_associated, no_repick=False):
667
+ from personal.coccinelle_ladybug.HashableRegionProps import HashableRegionProps
668
+ from personal.geom.tools import compute_coords_overlap_between_0_and_1
669
+ output={}
670
+ detected_arena = []
671
+ for primary_region in primary_rps:
672
+ # primary_region = (HashableRegionProps) primary_region
673
+ output[ primary_region]=[]
674
+ for region in rps_to_be_associated:
675
+ if compute_coords_overlap_between_0_and_1(region.coords, primary_region)!=0:
676
+ output[primary_region].append(region)
677
+ if no_repick:
678
+ detected_arena.append(region)
679
+ if no_repick:
680
+ rps_to_be_associated = [region for region in rps_to_be_associated if region not in detected_arena]
681
+ return output
682
+
683
+ def sort_rps_by_feature_using_its_name(rps, feature_name, feature_element=None, return_index=False):
684
+ '''
685
+ sorts rps by feature (area, centroid, ...) and feature index (i.e first element/axis of centroid)
686
+ can return the sorted features or an index
687
+ '''
688
+ sorted_list = sorted(enumerate(rps), key=lambda p: getattr(p[1], feature_name) if feature_element is None else getattr(p[1], feature_name)[feature_element])
689
+ if return_index:
690
+ return sorted_list
691
+ else:
692
+ return [region for idx, region in sorted_list]
693
+
694
+
695
+ def get_combined_bbox(regions):
696
+ # Initialize variables for the combined bounding box
697
+ min_row = float('inf')
698
+ min_col = float('inf')
699
+ max_row = 0
700
+ max_col = 0
701
+
702
+ # Iterate over the regions to find the combined bounding box
703
+ for region in regions:
704
+ minr, minc, maxr, maxc = region.bbox
705
+ min_row = min(min_row, minr)
706
+ min_col = min(min_col, minc)
707
+ max_row = max(max_row, maxr)
708
+ max_col = max(max_col, maxc)
709
+
710
+ # Return the combined bounding box as a tuple
711
+ combined_bbox = (min_row, min_col, max_row, max_col)
712
+ return combined_bbox
713
+
714
+ def get_rps_features_by_name(rps, feature_name, feature_element=None):
715
+ """
716
+ Returns a list of region property features with the specified name, optionally returning only a specific element
717
+ of tuple features.
718
+
719
+ Parameters
720
+ ----------
721
+ rps : list of skimage.measure._regionprops._RegionProperties
722
+ A list of region properties objects obtained from skimage.measure.regionprops.
723
+ feature_name : str
724
+ The name of the feature to extract from each region properties object.
725
+ feature_element : int or str, optional
726
+ The index or key of the element to extract from tuple features. Defaults to None.
727
+
728
+ Returns
729
+ -------
730
+ list
731
+ A list of features with the specified name, optionally containing only the specified element of tuple features.
732
+
733
+ Examples
734
+ --------
735
+ >>> # Get region properties and feature values for each region
736
+ >>> props = get_sample_rps()
737
+ >>> print('area_values', get_rps_features_by_name(props, 'area'))
738
+ area_values [42, 36, 45, 4, 3, 6, 3, 43, 32, 76, 2, 2, 35, 3, 104, 2, 4, 1, 37, 4, 5, 1, 1, 6, 3, 230, 1, 2, 2, 48, 5, 6, 7, 128, 40, 2, 1, 1, 6, 7, 1, 1, 8, 7, 7, 223, 3, 2, 82, 1, 5, 55, 3, 184, 1, 2, 28, 1, 38, 1]
739
+ >>> print('centroid_x_values', get_rps_features_by_name(props, 'centroid', 0))
740
+ centroid_x_values [1.5714285714285714, 1.6111111111111112, 5.133333333333334, 3.5, 5.333333333333333, 7.833333333333333, 10.333333333333334, 15.093023255813954, 15.90625, 21.55263157894737, 18.5, 18.5, 23.914285714285715, 24.333333333333332, 31.375, 26.5, 32.75, 33.0, 37.0, 35.25, 36.0, 38.0, 40.0, 41.5, 48.333333333333336, 56.582608695652176, 48.0, 52.0, 57.5, 60.354166666666664, 60.2, 64.83333333333333, 64.71428571428571, 77.359375, 68.5, 67.0, 75.0, 75.0, 78.16666666666667, 82.28571428571429, 81.0, 82.0, 84.0, 85.71428571428571, 88.0, 103.24215246636771, 92.33333333333333, 92.0, 98.28048780487805, 96.0, 100.0, 105.0, 101.66666666666667, 112.59782608695652, 106.0, 109.5, 117.5, 117.0, 124.5, 123.0]
741
+ """
742
+
743
+ # Get feature values for each region
744
+ features = [getattr(region, feature_name) for region in rps]
745
+
746
+ # If feature is a tuple, extract specified element
747
+ if feature_element is not None:
748
+ features = [feature[feature_element] for feature in features]
749
+
750
+ return features
751
+
752
+
753
+ def get_sample_rps():
754
+ """
755
+ Returns region properties for connected regions in a binary blobs image.
756
+
757
+ Returns
758
+ -------
759
+ list of skimage.measure._regionprops._RegionProperties
760
+ A list of region properties objects obtained from skimage.measure.regionprops on a binary blobs image.
761
+
762
+ Examples
763
+ --------
764
+ >>> # Get region properties for connected regions in a binary blobs image
765
+ >>> print(len(get_sample_rps()))
766
+ 60
767
+ """
768
+
769
+ # Load binary blobs image with 3 blobs
770
+ image = binary_blobs(length=128, blob_size_fraction=0.1, n_dim=2, volume_fraction=0.1, seed=1)
771
+
772
+ # Label connected regions in image
773
+ label_image = label(image)
774
+
775
+ # Calculate regionprops for each labeled region
776
+ props = regionprops(label_image)
777
+ return props
778
+
779
+ def cluster_regionprops_by_feature(rps, feature_for_clustering, feature_element=None):
780
+
781
+ # sorted_list = sorted(enumerate(rps), key=lambda p: p[1].centroid[SORTING_AXIS])
782
+ sorted_list = sort_rps_by_feature_using_its_name(rps, feature_for_clustering, feature_element=feature_element, return_index=True)
783
+ # sorted_indices = [i for i, _ in sorted_list]
784
+ objects = [object for _, object in sorted_list]
785
+
786
+ # Sort the regionprops based on their centroids Y position
787
+ # objects = sorted(rps, key=lambda p: p.centroid[0])
788
+ # Sort the list and get a list of tuples containing the original index and element
789
+
790
+ # Extract the sorted indices from the list of tuples
791
+ # sorted_indices = [i for i, _ in sorted_list]
792
+
793
+ # indices_comparison = [elm.index()]
794
+
795
+ # X = np.array([[p.centroid[0]] for p in objects])
796
+ #
797
+ # print('X', X.shape)
798
+
799
+ # Determine the optimal number of clusters using the silhouette score
800
+ X = np.array(get_rps_features_by_name(objects, feature_for_clustering, feature_element=feature_element))
801
+ if len(X.shape) ==1:
802
+ X = X.reshape(-1, 1)
803
+
804
+ # best_k=-1
805
+ silhouette_scores = []
806
+ K = range(2, len(np.unique(X))) # max nb of clusters is actually the max nb of unique elements in X
807
+
808
+
809
+
810
+ # print('K',K)
811
+ for k in K:
812
+ kmeans = KMeans(n_clusters=k, random_state=0, n_init='auto').fit(X) # quick fix change in KMeans
813
+ labels = kmeans.labels_
814
+ # if len(labels)==X.shape[0]:
815
+ # best_k = 1
816
+ # else:
817
+ score = silhouette_score(X, labels)
818
+ silhouette_scores.append(score)
819
+
820
+
821
+ # if best_k==-1:
822
+ best_k = K[np.argmax(silhouette_scores)]
823
+
824
+ # print('best_k',best_k)
825
+
826
+ if best_k != 1:
827
+ # Cluster the rows based on the y-coordinates of the centroids
828
+ kmeans = KMeans(n_clusters=best_k, random_state=0,n_init='auto').fit(X)
829
+ labels = kmeans.labels_
830
+ else:
831
+ labels = [0 for _ in objects]
832
+
833
+ # print('labels', len(labels))
834
+
835
+ # Create a new list with the elements of `my_list` in the sorted order
836
+ # reordered_labels = [labels[i] for i in sorted_indices]
837
+
838
+
839
+ # maybe return labels with
840
+ # now group every ROI in each group --> TODO
841
+ groups_n_rps = {}
842
+ for iii,label in enumerate(labels):
843
+ if label in groups_n_rps:
844
+ regions = groups_n_rps[label]
845
+ else:
846
+ regions = []
847
+ regions.append(objects[iii])
848
+ groups_n_rps[label] = regions
849
+
850
+ return groups_n_rps
851
+
852
+
853
+
854
+ def compute_distance_between_consecutive_points(ordered_pixels):
855
+ """
856
+ Compute the distance between consecutive points.
857
+
858
+ Args:
859
+ ordered_pixels (array-like): List or array-like object containing the ordered pixels.
860
+
861
+ Returns:
862
+ Array of distances between consecutive points.
863
+ """
864
+ if isinstance(ordered_pixels, list):
865
+ ordered_pixels = np.asarray(ordered_pixels)
866
+ d = np.diff(ordered_pixels, axis=0)
867
+ consecutive_distances = np.sqrt((d ** 2).sum(axis=1))
868
+ return consecutive_distances
869
+
870
+
871
+ def is_distance_continuous(ordered_distances):
872
+ """
873
+ Check if a set of ordered distances is continuous.
874
+
875
+ Args:
876
+ ordered_distances (array-like): List or array-like object containing the ordered distances.
877
+
878
+ Returns:
879
+ True if the distances are continuous, otherwise returns the maximum distance and its position.
880
+ """
881
+ if isinstance(ordered_distances, list):
882
+ ordered_distances = np.asarray(ordered_distances)
883
+ if len(ordered_distances.shape) == 2:
884
+ ordered_distances = compute_distance_between_consecutive_points(ordered_distances)
885
+ max_dist_pos = np.argmax(ordered_distances)
886
+ max_dist = ordered_distances[max_dist_pos]
887
+
888
+ if max_dist <= math.sqrt(2):
889
+ return True
890
+ else:
891
+ return max_dist, max_dist_pos
892
+
893
+ def find_pavlidis_optimal_orientation(pixel_coordinates, start_coords=None):
894
+ """
895
+ Find the optimal orientation for the Pavlidis algorithm based on the pixel coordinates.
896
+
897
+ Args:
898
+ pixel_coordinates (np.ndarray): Array of shape (N, 2) containing the pixel coordinates.
899
+ start_coords (tuple): Tuple containing the start coordinates (default: None).
900
+
901
+ Returns:
902
+ Optimal orientation value (0, 1, 2, or 3).
903
+
904
+ Notes:
905
+ The optimal orientation is chosen such that when standing on the start pixel, the left adjacent pixel is white.
906
+ #
907
+ # Examples:
908
+ # >>> pixel_coords = np.array([[0, 0], [1, 1], [2, 2], [3, 3]])
909
+ # >>> orientation = find_pavlidis_optimal_orientation(pixel_coords, start_coords=(1, 1))
910
+ # >>> print('Optimal Orientation:', orientation)
911
+ #
912
+ # >>> pixel_coords = np.array([[0, 0], [1, 1], [2, 2], [3, 3]])
913
+ # >>> orientation = find_pavlidis_optimal_orientation(pixel_coords)
914
+ # >>> print('Optimal Orientation:', orientation)
915
+ """
916
+ pixel_coordinates = pixel_coordinates.tolist()
917
+ if start_coords is not None:
918
+ y = start_coords[0]
919
+ x = start_coords[1]
920
+ if [y - 1, x] in pixel_coordinates:
921
+ return 0
922
+ if [y, x + 1] in pixel_coordinates:
923
+ return 1
924
+ if [y + 1, x] in pixel_coordinates:
925
+ return 2
926
+ if [y, x - 1] in pixel_coordinates:
927
+ return 3
928
+
929
+ for y, x in pixel_coordinates:
930
+ if [y - 1, x] in pixel_coordinates:
931
+ return 0
932
+ if [y, x + 1] in pixel_coordinates:
933
+ return 1
934
+ if [y + 1, x] in pixel_coordinates:
935
+ return 2
936
+ if [y, x - 1] in pixel_coordinates:
937
+ return 3
938
+ return None
939
+
940
+
941
+ def find_pavlidis_optimal_start2(pixel_coordinates):
942
+ """
943
+ Find the optimal start pixel for the Pavlidis algorithm based on the pixel coordinates.
944
+
945
+ Args:
946
+ pixel_coordinates (np.ndarray): Array of shape (N, 2) containing the pixel coordinates.
947
+
948
+ Returns:
949
+ Optimal start pixel coordinates.
950
+
951
+ Notes:
952
+ The optimal start pixel is chosen such that when initially standing on it, the left adjacent pixel is not black.
953
+
954
+ # Examples:
955
+ # >>> pixel_coords = np.array([[0, 0], [1, 1], [2, 2], [3, 3]])
956
+ # >>> start_coords = find_pavlidis_optimal_start2(pixel_coords)
957
+ # >>> print('Optimal Start Coordinates:', start_coords)
958
+ """
959
+ pixel_coordinates = pixel_coordinates.tolist()
960
+
961
+ for y, x in pixel_coordinates:
962
+ if [y - 1, x] not in pixel_coordinates:
963
+ return y, x
964
+ return None
965
+
966
+
967
+ def find_pavlidis_optimal_start(pixel_coordinates):
968
+ """
969
+ Find the optimal start pixel and orientation for the Pavlidis algorithm based on the pixel coordinates.
970
+
971
+ Args:
972
+ pixel_coordinates (np.ndarray): Array of shape (N, 2) containing the pixel coordinates.
973
+
974
+ Returns:
975
+ Optimal start pixel coordinates and orientation.
976
+
977
+ # Examples:
978
+ # >>> pixel_coords = np.array([[0, 0], [1, 1], [2, 2], [3, 3]])
979
+ # >>> start_coords, orientation = find_pavlidis_optimal_start(pixel_coords)
980
+ # >>> print('Optimal Start Coordinates:', start_coords)
981
+ # >>> print('Optimal Orientation:', orientation)
982
+ """
983
+ px_coords = pixel_coordinates.tolist()
984
+
985
+ for y, x in px_coords:
986
+ for orientation, pavlidis_shift in enumerate(starting_pavlidis):
987
+ counter_white = 0
988
+ for shift in pavlidis_shift:
989
+ if (y + shift[0], x + shift[1]) in px_coords:
990
+ counter_white += 1
991
+
992
+ if counter_white != 0:
993
+ print('counter_white', counter_white)
994
+ if counter_white == 3:
995
+ return (y, x), orientation
996
+
997
+ print('Error: Optimal pavlidis not found. Returning None')
998
+ return None, 0
999
+
1000
+
1001
+ def order_regions_by_y_axis(region1, region2):
1002
+ # Assuming region1 and region2 are instances of regionprops
1003
+
1004
+ # Calculate y-axis centroids
1005
+ centroid_y1 = region1.centroid[0]
1006
+ centroid_y2 = region2.centroid[0]
1007
+
1008
+ # Compare based on y-axis centroid
1009
+ if centroid_y1 < centroid_y2:
1010
+ return region1, region2
1011
+ else:
1012
+ return region2, region1
1013
+
1014
+
1015
+ if __name__ == '__main__':
1016
+
1017
+ if True:
1018
+ # TODO finalize my sorting code --> TODO
1019
+ # generate random shapes
1020
+
1021
+ # [('triangle', ((12, 41), (6, 39)))] --> with this traingle the pavlidis is the contour algo that works the best !!!
1022
+
1023
+ # NB with this traingle that really does not work!!!! -->
1024
+ # [('triangle', ((29, 51), (4, 29)))]
1025
+ # True
1026
+ # [[30 16]
1027
+ # [29 16]]
1028
+
1029
+ import skimage.draw
1030
+ # img, labels = skimage.draw.random_shapes((32, 32), max_shapes=3)
1031
+ img, lbs = skimage.draw.random_shapes((64, 64), max_shapes=1)
1032
+
1033
+ img = img[..., 0]
1034
+ img[img == 255] = 0
1035
+ img[img != 0] = 255
1036
+ skip_plot = False
1037
+
1038
+ # single point image
1039
+ # check for single pixel sorting --> TODO
1040
+ # debug for one pixel wide pavlidis -> KEEP
1041
+ # img = np.zeros_like(img)
1042
+ # img[int(img.shape[0]/2), int(img.shape[1]/2)] = 255
1043
+
1044
+ # np.nonzero(x)
1045
+ # plt.imshow(img)
1046
+ # plt.show()
1047
+
1048
+
1049
+ first_pixel = np.nonzero(img) # np.argwhere(img!=255)#np.nonzero(img)
1050
+ print(lbs) # NB THERE ARE DUPLICATED POINTS WITH TRIANGLES --> IS THERE A SMART WAY TO HANDLE DUPLICATED POINTS --> AND OR TO RECONNECT THE MISSING PARTS --> MAYBE BUT THINK ABOUT IT!!!
1051
+ # NB SHALL I DO A NO REPICK !!! BY CUTTING POINTS ALREADY TAKEN ?????
1052
+
1053
+ # need get the contour just otherwsie does not work --> cheat !!!
1054
+
1055
+ lb = label(img, connectivity=1, background=0)
1056
+ rps = regionprops(lb)
1057
+
1058
+
1059
+ first_pixel = np.nonzero(img) # np.argwhere(img!=255)#np.nonzero(img)
1060
+
1061
+ # could loop over those pixels to find a pavlidis compatible entry
1062
+
1063
+ # pb here is I need check orientation
1064
+ # neo_start, orientation = find_pavlidis_optimal_start(first_pixel) # faisl for [('triangle', ((58, 60), (54, 56)))] also for [('circle', ((3, 4), (20, 21)))]
1065
+ # print(first_pixel[0][0], first_pixel[1][0], 'optimal pavlidis start', neo_start)
1066
+
1067
+
1068
+ start = timer()
1069
+
1070
+
1071
+ # simple algo that fails for triangles but ok in fact # maybe a better way would be to run it several times and instead of removing dupes to take the longest pixel repeat
1072
+ sortedpx = pavlidis(img, first_pixel[0][0], first_pixel[1][0], True)
1073
+ # sortedpx = pavlidis(img, neo_start[0], neo_start[1], True) # --> [('circle', ((23, 24), (1, 2)))] infinite loop with neo start --> need check if begining is encountered twice and return if that is True
1074
+
1075
+ # ça marche pr les triangles mais plante des fois pr les cercles --> implement the no start twice algo
1076
+ # if neo_start is None:
1077
+ # neo_start,_ = get_feret_from_points(extreme_points(first_pixel))
1078
+ # plt.imshow(lb)
1079
+ # plt.show()
1080
+ # print('corrected start',neo_start)
1081
+
1082
+ # [('circle', ((3, 4), (20, 21)))] --> infinite loop why --> in fact these are the bounds --> if no empty pixel in the shape then stuff will not work --> must not compute the pavlidis because may enter an infinite loop !!!
1083
+ # somehow would need a control for that!!!
1084
+ # can there be bugs if no data is generated through
1085
+
1086
+ # if there is a single point just return directly coords as there is really nothing todo
1087
+
1088
+ # [('ellipse', ((24, 41), (47, 58)))] --> bug in detection of the contour (but probably an impossible shape --> somehow need to do controls, still but ok
1089
+
1090
+ # sortedpx = pavlidis(img, neo_start[0], neo_start[1], True, starting_orientation=orientation) # evn when distance > sqrt 2 the order of pixels is actually still correct!!! # --> [('circle', ((23, 24), (1, 2)))] infinite loop with neo start --> need check if begining is encountered twice and return if that is True
1091
+ # sortedpx = pavlidis(img, first_pixel[0][0], first_pixel[0][1], True) # evn when distance > sqrt 2 the order of pixels is actually still correct!!! # --> [('circle', ((23, 24), (1, 2)))] infinite loop with neo start --> need check if begining is encountered twice and return if that is True
1092
+ duration = timer() - start
1093
+
1094
+ print('end pavlidis 1')
1095
+
1096
+
1097
+ # --> infinite loop unfortunately...
1098
+ #fails for [('rectangle', ((42, 54), (25, 29)))]
1099
+ # start = timer()
1100
+ # sortedpx2 = pavlidis2(img, first_pixel[0][0], first_pixel[1][0], True) # --> infinite loop for [('rectangle', ((40, 49), (13, 53)))]
1101
+ # duration2 = timer() - start
1102
+
1103
+ # print('pvalidis time difference', duration, duration2, sortedpx, sortedpx2)
1104
+
1105
+
1106
+ __plot_pavlidis(img, sortedpx, skip_plot=False)
1107
+ img[img!=0]=0
1108
+
1109
+ sortedpx = np.asarray(sortedpx)
1110
+
1111
+
1112
+ img[sortedpx[:,0], sortedpx[:,1]]=255
1113
+ # img[sortedpx]=255
1114
+
1115
+
1116
+ lb = label(img, connectivity=1, background=0)
1117
+ rps = regionprops(lb)
1118
+
1119
+ # errors should not happen with a proper entry point for pavlidis --> maybe check that --> auto would do the job
1120
+
1121
+
1122
+ # NB none of these new algos works as nicely to sort pixels as does the pavlidis algo --> stick to that for now
1123
+ # test of other algos compared to pavlidis --> nothing works as nicely
1124
+ # they are immediately stuck at the entry in the case of triangles
1125
+ # both work for squares though and classical shapes --> if px count is ok then maybe I can rely on them and if not ok then roll back to pavlidis --> just to gain speed --> INDEED TRY THAT!!!
1126
+ # none of the two work for ellipses neither but not clear why --> are these algos somehow just four connected --> check them deeply
1127
+
1128
+
1129
+ # crap and if only one pixel --> a bug
1130
+ # sorted_pixels = sort_pixels_clowkise2(rps[0].coords)
1131
+ # print(is_distance_continuous(sorted_pixels))
1132
+
1133
+
1134
+ # mount a talk
1135
+ # __plot_pavlidis(img, sorted_pixels, skip_plot=False)
1136
+
1137
+ # sorted_pixels = nearest_neighbor_ordering(rps[0].coords) # for small triangles it can crash ValueError: Expected n_neighbors <= n_samples, but n_samples = 2, n_neighbors = 3 --> put it in a try loop if used
1138
+ # __plot_pavlidis(img, sorted_pixels, skip_plot=False)
1139
+
1140
+ # qsdqsqsdsqdqsdqsdsqd
1141
+
1142
+ if False:
1143
+ array = np.asarray([[0, 0, 0], [1, 1, 1], [2, 2, 2]])
1144
+ # array=np.asarray([[0,0],[1,1],[2,2]])
1145
+ print(array)
1146
+ print(array.shape)
1147
+ print(np.roll(array, -1, axis=-1))
1148
+ print(np.roll(array, -1, axis=0)) # --> this is what I wwnat in fact
1149
+ print(np.roll(array, 1, axis=0)) # --> this is what I wwnat in fact
1150
+ # print(np.roll(array, -1))
1151
+
1152
+ line_test = np.asarray(
1153
+ [[255, 256], [255, 257], [255, 258], [255, 259], [255, 260], [255, 261], [255, 262], [255, 263], [255, 264],
1154
+ [255, 265], [255, 266], [255, 267], [255, 268], [255, 269], [255, 270], [255, 271], [255, 272], [255, 273],
1155
+ [255, 274], [255, 275], [255, 276], [255, 277], [255, 278], [255, 279], [255, 280], [255, 281], [255, 282],
1156
+ [255, 283], [255, 284], [255, 285], [255, 286], [255, 287], [255, 288], [255, 289], [255, 290], [255, 291],
1157
+ [255, 292], [255, 293], [255, 294], [255, 295], [255, 296], [255, 297], [255, 298], [255, 299], [255, 300],
1158
+ [255, 301], [255, 302], [255, 303], [255, 304], [255, 305], [255, 306], [255, 307], [255, 308], [255, 309],
1159
+ [255, 310], [255, 311], [255, 312], [255, 313], [255, 314], [255, 315], [255, 316], [255, 317], [255, 318],
1160
+ [255, 319], [255, 320], [255, 321], [255, 322], [255, 323], [255, 324], [255, 325], [255, 326], [255, 327],
1161
+ [255, 328], [255, 329], [255, 330], [255, 331], [255, 332], [255, 333], [255, 334], [255, 335], [255, 336],
1162
+ [255, 337], [255, 338], [255, 339], [255, 340], [255, 341], [255, 342], [255, 343], [255, 344], [255, 345],
1163
+ [255, 346], [255, 347], [255, 348], [255, 349], [255, 350], [255, 351], [255, 352], [255, 353], [255, 354],
1164
+ [255, 355], [255, 356], [255, 357], [255, 358], [255, 359], [255, 360], [255, 361], [255, 362], [255, 363],
1165
+ [255, 364], [255, 365], [255, 366], [255, 367], [255, 368], [255, 369], [255, 370], [255, 371], [255, 372],
1166
+ [255, 373], [255, 374], [255, 375], [255, 376], [255, 377], [255, 378], [255, 379], [255, 380], [255, 381],
1167
+ [255, 382], [255, 383], [255, 384], [255, 385], [255, 386], [255, 387], [255, 388], [255, 389], [255, 390],
1168
+ [255, 391], [255, 392], [255, 393], [255, 394], [255, 395], [255, 396], [255, 397], [255, 398], [255, 399],
1169
+ [255, 400], [255, 401], [255, 402], [255, 403], [255, 404], [255, 405], [255, 406], [255, 407], [255, 408],
1170
+ [255, 409], [255, 410], [255, 411], [255, 412], [255, 413], [255, 414], [255, 415], [255, 416], [255, 417],
1171
+ [255, 418], [255, 419], [255, 420], [255, 421], [255, 422], [255, 423], [255, 424], [255, 425], [255, 426],
1172
+ [255, 427], [255, 428], [255, 429], [255, 430], [255, 431], [255, 432], [255, 433], [255, 434], [255, 435],
1173
+ [255, 436], [255, 437], [255, 438], [255, 439], [255, 440], [255, 441], [255, 442], [255, 443], [255, 444],
1174
+ [255, 445], [255, 446], [255, 447], [255, 448], [255, 449], [255, 450], [255, 451], [255, 452], [255, 453],
1175
+ [255, 454], [255, 455], [255, 456], [255, 457], [255, 458], [255, 459], [255, 460], [255, 461], [255, 462],
1176
+ [255, 463], [255, 464], [255, 465], [255, 466], [255, 467], [255, 468], [255, 469], [255, 470], [255, 471],
1177
+ [255, 472], [255, 473], [255, 474], [255, 475], [255, 476], [255, 477], [255, 478], [255, 479], [255, 480],
1178
+ [255, 481], [255, 482], [255, 483], [255, 484], [255, 485], [255, 486], [255, 487], [255, 488], [255, 489],
1179
+ [255, 490], [255, 491], [255, 492], [255, 493], [255, 494], [255, 495], [255, 496], [255, 497], [255, 498],
1180
+ [255, 499], [255, 500], [255, 501], [255, 502], [255, 503], [255, 504], [255, 505], [255, 506], [255, 507],
1181
+ [255, 508], [255, 509], [255, 510], [255, 511], [256, 255], [256, 254], [256, 253], [256, 252], [256, 251],
1182
+ [256, 250], [256, 249], [256, 248], [256, 247], [256, 246], [256, 245], [256, 244], [256, 243], [256, 242],
1183
+ [256, 241], [256, 240], [256, 239], [256, 238], [256, 237], [256, 236], [256, 235], [256, 234], [256, 233],
1184
+ [256, 232], [256, 231], [256, 230], [256, 229], [256, 228], [256, 227], [256, 226], [256, 225], [256, 224],
1185
+ [256, 223], [256, 222], [256, 221], [256, 220], [256, 219], [256, 218], [256, 217], [256, 216], [256, 215],
1186
+ [256, 214], [256, 213], [256, 212], [256, 211], [256, 210], [256, 209], [256, 208], [256, 207], [256, 206],
1187
+ [256, 205], [256, 204], [256, 203], [256, 202], [256, 201], [256, 200], [256, 199], [256, 198], [256, 197],
1188
+ [256, 196], [256, 195], [256, 194], [256, 193], [256, 192], [256, 191], [256, 190], [256, 189], [256, 188],
1189
+ [256, 187], [256, 186], [256, 185], [256, 184], [256, 183], [256, 182], [256, 181], [256, 180], [256, 179],
1190
+ [256, 178], [256, 177], [256, 176], [256, 175], [256, 174], [256, 173], [256, 172], [256, 171], [256, 170],
1191
+ [256, 169], [256, 168], [256, 167], [256, 166], [256, 165], [256, 164], [256, 163], [256, 162], [256, 161],
1192
+ [256, 160], [256, 159], [256, 158], [256, 157], [256, 156], [256, 155], [256, 154], [256, 153], [256, 152],
1193
+ [256, 151], [256, 150], [256, 149], [256, 148], [256, 147], [256, 146], [256, 145], [256, 144], [256, 143],
1194
+ [256, 142], [256, 141], [256, 140], [256, 139], [256, 138], [256, 137], [256, 136], [256, 135], [256, 134],
1195
+ [256, 133], [256, 132], [256, 131], [256, 130], [256, 129], [256, 128], [256, 127], [256, 126], [256, 125],
1196
+ [256, 124], [256, 123], [256, 122], [256, 121], [256, 120], [256, 119], [256, 118], [256, 117], [256, 116],
1197
+ [256, 115], [256, 114], [256, 113], [256, 112], [256, 111], [256, 110], [256, 109], [256, 108], [256, 107],
1198
+ [256, 106], [256, 105], [256, 104], [256, 103], [256, 102], [256, 101], [256, 100], [256, 99], [256, 98],
1199
+ [256, 97], [256, 96], [256, 95], [256, 94], [256, 93], [256, 92], [256, 91], [256, 90], [256, 89],
1200
+ [256, 88], [256, 87], [256, 86], [256, 85], [256, 84], [256, 83], [256, 82], [256, 81], [256, 80],
1201
+ [256, 79], [256, 78], [256, 77], [256, 76], [256, 75], [256, 74], [256, 73], [256, 72], [256, 71],
1202
+ [256, 70], [256, 69], [256, 68], [256, 67], [256, 66], [256, 65], [256, 64], [256, 63], [256, 62],
1203
+ [256, 61], [256, 60], [256, 59], [256, 58], [256, 57], [256, 56], [256, 55], [256, 54], [256, 53],
1204
+ [256, 52], [256, 51], [256, 50], [256, 49], [256, 48], [256, 47], [256, 46], [256, 45], [256, 44],
1205
+ [256, 43], [256, 42], [256, 41], [256, 40], [256, 39], [256, 38], [256, 37], [256, 36], [256, 35],
1206
+ [256, 34], [256, 33], [256, 32], [256, 31], [256, 30], [256, 29], [256, 28], [256, 27], [256, 26],
1207
+ [256, 25], [256, 24], [256, 23], [256, 22], [256, 21], [256, 20], [256, 19], [256, 18], [256, 17],
1208
+ [256, 16], [256, 15], [256, 14], [256, 13], [256, 12], [256, 11], [256, 10], [256, 9], [256, 8], [256, 7],
1209
+ [256, 6], [256, 5], [256, 4], [256, 3], [256, 2], [256, 0], [256, 1]])
1210
+
1211
+ print(line_test[255])
1212
+ print(line_test.shape[0])
1213
+ print(np.roll(line_test, -255 - 1, axis=0))
1214
+
1215
+ # en fait c'est juste pas possible de corriger car le ligne est tt simplement inverser --> faudrait inverser l'ordre complet de la ligne --> reflechir à comment faire en fait --> pas si simple --> TODO
1216
+
1217
+ import sys
1218
+
1219
+ sys.exit(0)
1220
+
1221
+ # test for nearest neighbor ordering
1222
+ # if True:
1223
+ # coords = [(4, 30), (5, 30), (6, 31), (7, 31), (8, 32), (9, 32), (10, 33), (11, 33), (12, 34), (13, 34),
1224
+ # (14, 35),
1225
+ # (15, 36), (15, 35), (15, 34), (15, 33), (15, 32), (15, 31), (15, 30), (15, 29), (15, 28), (15, 27),
1226
+ # (15, 26), (15, 25), (15, 24), (14, 25), (13, 26), (12, 26), (11, 27), (10, 27), (9, 28), (8, 28),
1227
+ # (7, 29),
1228
+ # (6, 29), (5, 30)]
1229
+ # print(coords)
1230
+ #
1231
+ # # remove dupes
1232
+ # # coords = list(set(coords))
1233
+ #
1234
+ # # print(coords.shape)
1235
+ # nn = nearest_neighbor_ordering(coords, remove_dupes=True)
1236
+ #
1237
+ # print(nn)
1238
+ # print(nn.shape)
1239
+ #
1240
+ # # print('ordered coords', no_dupes)
1241
+ # print('dist extremities', dist2D(nn[0], nn[-1]),
1242
+ # dist2D(nn[0], nn[-1]) > math.sqrt(2)) # still dist > sqrt 2 --> impossible to manage for such shape
1243
+
1244
+ # ça marche
1245
+ # test for ordering of pixels of bond like structures --> should always work I think
1246
+ # if True:
1247
+ # img = np.zeros((512, 512))
1248
+ # rr, cc = line(256, 0, 255, img.shape[1] - 1)
1249
+ #
1250
+ # img[rr, cc] = 255
1251
+ #
1252
+ # labels = label(img, connectivity=2, background=0)
1253
+ # rps = regionprops(labels)
1254
+ #
1255
+ # # in the middle there is
1256
+ # # 255 511
1257
+ # # 256 0 --> pixels are scanned line by line and not ordered --> length of the line need be calculated after a pavlidis algo --> practically see how I can do that in a smart way based on ROIs
1258
+ #
1259
+ # for region in rps:
1260
+ # # that is a lot of code and can all be put and stored directly in the pavlidis --> TODO
1261
+ #
1262
+ # # TODO try a plot of the region
1263
+ # # maybe get bounds and plot within them --> should be easy in fact
1264
+ #
1265
+ # print(region.bbox)
1266
+ # # create an image for the bbox
1267
+ #
1268
+ # # tmp_img = np.zeros((abs(region.bbox[2]-region.bbox[0]),abs(region.bbox[1]-region.bbox[3])))
1269
+ # # tmp_img = np.zeros((max(region.bbox[2],region.bbox[0])+1,max(region.bbox[1],region.bbox[3])+1)) # then easy to plt my object in it
1270
+ # # print(tmp_img.shape)
1271
+ # # print(region.coords.shape)
1272
+ # # # fill numpy array using region coords TOP MEGA TOP TIP TIPTOP TIP TOP
1273
+ # # tmp_img[region.coords[:,0], region.coords[:,1]] = 255 # so cool another very easy way to plot coords --> but is that fast or slow ??? --> IF FAST COULD REPLACE SO MANY OF MY CALLS !!!
1274
+ # # tmp_img[np.split(region.coords, axis=-1)] = 255 # there must be a way to do it like that too!!
1275
+ #
1276
+ # # create an image
1277
+ # # then try to plot in it
1278
+ # # plt.imshow(tmp_img)
1279
+ # # plt.show()
1280
+ #
1281
+ # # quite good by the way
1282
+ # # smart is maybe to try to use as a seed of of the most distant points
1283
+ #
1284
+ # # pavlidis for region --> allow __plot_pavlidis to plot this and maybe add checks for the stuff
1285
+ # # add pavlidis
1286
+ #
1287
+ # # --> really not worth it for contours --> rather use nn instead
1288
+ # # total pavlidis 18.360223728988785
1289
+ # # total nn 2.1951778780203313
1290
+ # # start_pavlidis = timer()
1291
+ # # for i in range(100):
1292
+ #
1293
+ #
1294
+ # # optimal_pavlidis_entry = find_pavlidis_optimal_start(region.coords) --> this unfortunately does not work for non closed stuff --> need the stuff really be closed otherwise take an extremity is required...
1295
+ # # print('optimal_pavlidis_entry', optimal_pavlidis_entry)
1296
+ # sorted_coords = pavlidis(region) # --> marche pas si pas à une extremite en fait --> in that case I May need to start from the extreme point --> in that case would that always work
1297
+ # # sorted_coords = pavlidis(region, optimal_pavlidis_entry[0], optimal_pavlidis_entry[1]) # --> marche pas si pas à une extremite en fait --> in that case I May need to start from the extreme point --> in that case would that always work
1298
+ # # pb est que je peux avoir des jumps --> faudrait faire une rotation du truc pr y arriver
1299
+ # # sorted_coords = nearest_neighbor_ordering(region.coords) # --> marche pas si pas à une extremite en fait --> in that case I May need to start from the extreme point --> in that case would that always work
1300
+ #
1301
+ # print('mega test of all', np.asarray(sorted_coords).shape)
1302
+ # compute_perimeter(sorted_coords)
1303
+ #
1304
+ # print('is_distance_continuous pavlidis', is_distance_continuous(sorted_coords))
1305
+ # print('is_distance_continuous nn', is_distance_continuous(nearest_neighbor_ordering(
1306
+ # region.coords))) # could return the coord of the max -- give me the roll to apply # --> TODO but maybe ok in fact
1307
+ # output = is_distance_continuous(nearest_neighbor_ordering(region.coords))
1308
+ #
1309
+ # # orderd_yet_another = order_points_new(region.coords)
1310
+ # # seems to work well too --> but try with different shapes
1311
+ # start_pavlidis = timer()
1312
+ #
1313
+ # # for i in range(1):
1314
+ # # orderd_yet_another = sort_pixels_clowkise2(region.coords)
1315
+ # # print('total other', timer() - start_pavlidis) # total other 0.2618692510004621 --> this is super fast --> if that works then I would
1316
+ #
1317
+ # # print('is_distance_continuous another', is_distance_continuous(orderd_yet_another))
1318
+ # #
1319
+ # # __plot_pavlidis(region, orderd_yet_another, skip_plot=False)
1320
+ #
1321
+ # try:
1322
+ # if len(output) == 2:
1323
+ # max_dist, max_dist_pos = output
1324
+ #
1325
+ # uncorrected = nearest_neighbor_ordering(region.coords)
1326
+ #
1327
+ # print(uncorrected[max_dist_pos]) # --> 255, 511 --> ok in fact
1328
+ #
1329
+ # # cannot correct because would need to invert a big chunk of the ordering --> literally reverse half of the array
1330
+ #
1331
+ # # or I would need signs maybe vectors
1332
+ #
1333
+ # # corrected = np.roll(uncorrected, -max_dist_pos, axis=0) # I HAVE A BUG IN MY ROLL --> FIX IT!!!
1334
+ # # corrected = np.roll(uncorrected, -(len(uncorrected)-max_dist_pos), axis=0) # I HAVE A BUG IN MY ROLL --> FIX IT!!!
1335
+ # # corrected = np.roll(uncorrected, max_dist_pos, axis=0) # I HAVE A BUG IN MY ROLL --> FIX IT!!!
1336
+ # # # , axis = 0
1337
+ # # print('uncorrected', uncorrected.tolist())
1338
+ # #
1339
+ # # # ça ne marche pas le roll --> comment le faire en fait
1340
+ # # print('corrected', corrected.tolist())
1341
+ # # print('is_distance_continuous nn',
1342
+ # # is_distance_continuous(corrected))
1343
+ #
1344
+ # __plot_pavlidis(region, uncorrected, skip_plot=False)
1345
+ # except:
1346
+ # # if iscontinuous --> ignore and do not try to roll
1347
+ # pass
1348
+ #
1349
+ # # crappy_test = 23
1350
+ # # print(len(crappy_test))
1351
+ #
1352
+ # # print('total pavlidis', timer()-start_pavlidis)
1353
+ #
1354
+ # # start_pavlidis = timer()
1355
+ # # for i in range(100):
1356
+ # # sorted_coords = nearest_neighbor_ordering(
1357
+ # # region.coords) # --> marche pas si pas à une extremite en fait --> in that case I May need to start from the extreme point --> in that case would that always work
1358
+ # # print('total nn', timer() - start_pavlidis)
1359
+ #
1360
+ # # ça marche et c'est assez simple en fait
1361
+ # __plot_pavlidis(region, sorted_coords, skip_plot=False)
1362
+ #
1363
+ # # shall I add checks in pavlidis
1364
+ # #
1365
+ #
1366
+ # # print(sorted_coords)
1367
+ # print(len(sorted_coords), region.coords.shape[0])
1368
+ #
1369
+ # # bug ????
1370
+ # # print(sorted_coords)
1371
+ # # if still some missing points then can use the other stuff
1372
+ #
1373
+ # plt.imshow(img)
1374
+ # plt.show()
1375
+ #
1376
+ # import sys
1377
+ #
1378
+ # sys.exit(0)
1379
+ # almost ok now just try to plot stuff
1380
+
1381
+ # test pavlidis on several shapes
1382
+ if True:
1383
+ start_time = timer()
1384
+ img = np.zeros((5, 5), dtype=np.uint8)
1385
+ start = (1, 1)
1386
+ extent = (3, 3)
1387
+ rr, cc = rectangle(start, extent=extent, shape=img.shape)
1388
+ img[rr, cc] = 255
1389
+ # img
1390
+ # array([[0, 0, 0, 0, 0],
1391
+ # [0, 1, 1, 1, 0],
1392
+ # [0, 1, 1, 1, 0],
1393
+ # [0, 1, 1, 1, 0],
1394
+ # [0, 0, 0, 0, 0]], dtype=uint8)
1395
+ # img =
1396
+
1397
+ # false
1398
+ skip_plot = True
1399
+
1400
+ print(pavlidis(img, 0, 0, True, color_of_interest=255))
1401
+ print(pavlidis(img, 4, 4, True, color_of_interest=255))
1402
+ print(pavlidis(img, 120, 120, True, color_of_interest=255))
1403
+ print(pavlidis(img, -1, 2, True, color_of_interest=255))
1404
+ # ok
1405
+ __plot_pavlidis(img, pavlidis(img, 1, 1, True),
1406
+ skip_plot=skip_plot) # expected [(1, 1), (1, 2), (1, 3), (2, 3), (3, 3), (3, 2), (3, 1), (2, 1)]
1407
+ __plot_pavlidis(img, pavlidis(img, 1, 2, True), skip_plot=skip_plot) # expected
1408
+
1409
+ # ça a l'air de marcher et c'est facile --> test speed
1410
+
1411
+ img = np.zeros((2048, 2048), dtype=np.uint8)
1412
+ start = (1, 1)
1413
+ extent = (2046, 2046)
1414
+ rr, cc = rectangle(start, extent=extent, shape=img.shape)
1415
+ img[rr, cc] = 255
1416
+ # img
1417
+ # array([[0, 0, 0, 0, 0],
1418
+ # [0, 1, 1, 1, 0],
1419
+ # [0, 1, 1, 1, 0],
1420
+ # [0, 1, 1, 1, 0],
1421
+ # [0, 0, 0, 0, 0]], dtype=uint8)
1422
+ # img =
1423
+
1424
+ __plot_pavlidis(img, pavlidis(img, 1, 1, True),
1425
+ skip_plot=skip_plot) # expected [(1, 1), (1, 2), (1, 3), (2, 3), (3, 3), (3, 2), (3, 1), (2, 1)]
1426
+ __plot_pavlidis(img, pavlidis(img, 1, 2, True), skip_plot=skip_plot) # expected
1427
+
1428
+ # print(len(pavlidis(img, 1, 1, True)))
1429
+
1430
+ print("done 1", timer() - start_time) # 1000 gigantic sorted contours -->
1431
+
1432
+ # for i in range(100):
1433
+ # print(pavlidis(img, 1, 1)) # expected [(1, 1), (1, 2), (1, 3), (2, 3), (3, 3), (3, 2), (3, 1), (2, 1)]
1434
+ # print(pavlidis(img, 1, 2)) # expected
1435
+ #
1436
+ # # tt a l'air ok --> juste voir le resultat et tt plotter
1437
+ # print("done 100", timer() - start_time) # 1000 gigantic sorted contours --> 14.264636867999798s --> probably not that bad in fact --> if still too slow then code it in Cython --> TODO
1438
+
1439
+ # try with a circle too --> TODO
1440
+
1441
+ from skimage.draw import polygon_perimeter
1442
+
1443
+ img = np.zeros((10, 10), dtype=np.uint8)
1444
+ rr, cc = polygon_perimeter([5, -1, 5, 10], [-1, 5, 11, 5], shape=img.shape, clip=True)
1445
+ img[rr, cc] = 255
1446
+ # img
1447
+ # array([[0, 0, 0, 0, 1, 1, 1, 0, 0, 0],
1448
+ # [0, 0, 0, 1, 0, 0, 0, 1, 0, 0],
1449
+ # [0, 0, 1, 0, 0, 0, 0, 0, 1, 0],
1450
+ # [0, 1, 0, 0, 0, 0, 0, 0, 0, 1],
1451
+ # [1, 0, 0, 0, 0, 0, 0, 0, 0, 1],
1452
+ # [1, 0, 0, 0, 0, 0, 0, 0, 0, 1],
1453
+ # [1, 0, 0, 0, 0, 0, 0, 0, 0, 1],
1454
+ # [0, 1, 1, 0, 0, 0, 0, 0, 0, 1],
1455
+ # [0, 0, 0, 1, 0, 0, 0, 1, 1, 0],
1456
+ # [0, 0, 0, 0, 1, 1, 1, 0, 0, 0]], dtype=uint8)
1457
+
1458
+ # print('in polygon')
1459
+ __plot_pavlidis(img, pavlidis(img, 4, 0, True), skip_plot=skip_plot) # on a polygon
1460
+ # print('done')
1461
+
1462
+ import numpy as np
1463
+ from skimage.draw import bezier_curve
1464
+
1465
+ img = np.zeros((10, 10), dtype=np.uint8)
1466
+ rr, cc = bezier_curve(1, 5, 5, -2, 8, 8, 2)
1467
+ img[rr, cc] = 255
1468
+
1469
+ print('in bezier')
1470
+ # maybe blacken the pixel and or restore it in the end???
1471
+ __plot_pavlidis(img, pavlidis(img, 4, 1, False),
1472
+ skip_plot=skip_plot) # on a bezier --> not closed --> if takes same point again --> then get out --> will be incomple
1473
+
1474
+ print('img[2,6]', img[2, 6]) # there is a bug cause 2,6 = 0 and should never be in
1475
+
1476
+ # il y a un bug ici qui le fait remonter --> marche pas --> maybe record all
1477
+ __plot_pavlidis(img, pavlidis(img, 1, 5, False),
1478
+ skip_plot=skip_plot) # in fact I really need to do things this way !!! and really need check contour or disconnect after me --> no cluse# to get it right --> need start from a vertex # can be set to True if starting from a vertex --> in fact that is even required to get the stuff done properly otherwise pxs are missing # NB THE NO REPICK IS PROBABLY NOT A SMART IDEA
1479
+ __plot_pavlidis(img, pavlidis(img, 8, 8, False), skip_plot=skip_plot)
1480
+
1481
+ # --> there is a bug with duplicated pixels in here--> WHY
1482
+ print(
1483
+ 'done bezier') # --> pb as no repick is set the stuff always goes back and forth --> need a no repick just to be sure --> if countour is not connected --> it is needed
1484
+ # img
1485
+ # array([[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
1486
+ # [0, 0, 0, 0, 0, 1, 0, 0, 0, 0],
1487
+ # [0, 0, 0, 1, 1, 0, 0, 0, 0, 0],
1488
+ # [0, 0, 1, 0, 0, 0, 0, 0, 0, 0],
1489
+ # [0, 1, 0, 0, 0, 0, 0, 0, 0, 0],
1490
+ # [0, 1, 0, 0, 0, 0, 0, 0, 0, 0],
1491
+ # [0, 0, 1, 1, 0, 0, 0, 0, 0, 0],
1492
+ # [0, 0, 0, 0, 1, 1, 1, 0, 0, 0],
1493
+ # [0, 0, 0, 0, 0, 0, 0, 1, 1, 0],
1494
+ # [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]], dtype=uint8)
1495
+
1496
+ # NB IL ME MANQUE DES CHANGEMENTS D'orientation sur P1 --> OK now !!!
1497
+
1498
+ # ça marche sur des trucs comme ça
1499
+
1500
+ print("final", timer() - start_time) # 1000 gigantic sorted contours -->
1501
+
1502
+ # all is ok in fact
1503
+
1504
+ from skimage.draw import circle_perimeter
1505
+
1506
+ img = np.zeros((10, 10), dtype=np.uint8)
1507
+ rr, cc = circle_perimeter(4, 4, 3)
1508
+ img[rr, cc] = 255
1509
+ # img
1510
+ # array([[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
1511
+ # [0, 0, 0, 1, 1, 1, 0, 0, 0, 0],
1512
+ # [0, 0, 1, 0, 0, 0, 1, 0, 0, 0],
1513
+ # [0, 1, 0, 0, 0, 0, 0, 1, 0, 0],
1514
+ # [0, 1, 0, 0, 0, 0, 0, 1, 0, 0],
1515
+ # [0, 1, 0, 0, 0, 0, 0, 1, 0, 0],
1516
+ # [0, 0, 1, 0, 0, 0, 1, 0, 0, 0],
1517
+ # [0, 0, 0, 1, 1, 1, 0, 0, 0, 0],
1518
+ # [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
1519
+ # [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]], dtype=uint8)
1520
+
1521
+ print(img[1, 3])
1522
+ __plot_pavlidis(img, pavlidis(img, 1, 3, False), skip_plot=skip_plot)
1523
+
1524
+ from skimage.draw import circle_perimeter_aa
1525
+
1526
+ img = np.zeros((10, 10), dtype=np.uint8)
1527
+ rr, cc, _ = circle_perimeter_aa(4, 4, 3)
1528
+ img[rr, cc] = 255
1529
+ # img
1530
+ # array([[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
1531
+ # [ 0, 0, 60, 211, 255, 211, 60, 0, 0, 0],
1532
+ # [ 0, 60, 194, 43, 0, 43, 194, 60, 0, 0],
1533
+ # [ 0, 211, 43, 0, 0, 0, 43, 211, 0, 0],
1534
+ # [ 0, 255, 0, 0, 0, 0, 0, 255, 0, 0],
1535
+ # [ 0, 211, 43, 0, 0, 0, 43, 211, 0, 0],
1536
+ # [ 0, 60, 194, 43, 0, 43, 194, 60, 0, 0],
1537
+ # [ 0, 0, 60, 211, 255, 211, 60, 0, 0, 0],
1538
+ # [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
1539
+ # [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]], dtype=uint8)
1540
+
1541
+ __plot_pavlidis(img, pavlidis(img, 1, 2, True), skip_plot=skip_plot)
1542
+ __plot_pavlidis(img, pavlidis(img, 1, 3, True), skip_plot=skip_plot)
1543
+ # that really seems to work now!!!!
1544
+
1545
+ from skimage.draw import disk
1546
+
1547
+ img = np.zeros((10, 10), dtype=np.uint8)
1548
+ rr, cc = disk((4, 4), 5)
1549
+ img[rr, cc] = 255
1550
+ # img
1551
+ # array([[0, 0, 1, 1, 1, 1, 1, 0, 0, 0],
1552
+ # [0, 1, 1, 1, 1, 1, 1, 1, 0, 0],
1553
+ # [1, 1, 1, 1, 1, 1, 1, 1, 1, 0],
1554
+ # [1, 1, 1, 1, 1, 1, 1, 1, 1, 0],
1555
+ # [1, 1, 1, 1, 1, 1, 1, 1, 1, 0],
1556
+ # [1, 1, 1, 1, 1, 1, 1, 1, 1, 0],
1557
+ # [1, 1, 1, 1, 1, 1, 1, 1, 1, 0],
1558
+ # [0, 1, 1, 1, 1, 1, 1, 1, 0, 0],
1559
+ # [0, 0, 1, 1, 1, 1, 1, 0, 0, 0],
1560
+ # [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]], dtype=uint8)
1561
+
1562
+ __plot_pavlidis(img, pavlidis(img, 0, 2, True), skip_plot=skip_plot)
1563
+
1564
+ from skimage.draw import ellipse
1565
+
1566
+ img = np.zeros((10, 12), dtype=np.uint8)
1567
+ rr, cc = ellipse(5, 6, 3, 5, rotation=np.deg2rad(30))
1568
+ img[rr, cc] = 255
1569
+ # img
1570
+ # array([[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
1571
+ # [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
1572
+ # [0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0],
1573
+ # [0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0],
1574
+ # [0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0],
1575
+ # [0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0],
1576
+ # [0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0],
1577
+ # [0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0],
1578
+ # [0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0],
1579
+ # [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]], dtype=uint8)
1580
+
1581
+ __plot_pavlidis(img, pavlidis(img, 2, 6, True), skip_plot=skip_plot)
1582
+
1583
+ from skimage.draw import ellipse_perimeter
1584
+
1585
+ rr, cc = ellipse_perimeter(2, 3, 4, 5)
1586
+ img = np.zeros((9, 12), dtype=np.uint8)
1587
+ img[rr, cc] = 255
1588
+ # img
1589
+ # array([[0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1],
1590
+ # [0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0],
1591
+ # [0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0],
1592
+ # [0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0],
1593
+ # [0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1],
1594
+ # [1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0],
1595
+ # [0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0],
1596
+ # [0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0],
1597
+ # [1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0]], dtype=uint8)
1598
+ __plot_pavlidis(img, pavlidis(img, 0, 7, False), skip_plot=skip_plot)
1599
+ __plot_pavlidis(img, pavlidis(img, 8, 0, False), skip_plot=skip_plot)
1600
+
1601
+ img = np.zeros((10, 10), dtype=np.uint8)
1602
+ rr, cc = ellipse_perimeter(5, 5, 3, 4)
1603
+ img[rr, cc] = 255
1604
+ # img
1605
+ # array([[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
1606
+ # [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
1607
+ # [0, 0, 0, 1, 1, 1, 1, 1, 0, 0],
1608
+ # [0, 0, 1, 0, 0, 0, 0, 0, 1, 0],
1609
+ # [0, 1, 0, 0, 0, 0, 0, 0, 0, 1],
1610
+ # [0, 1, 0, 0, 0, 0, 0, 0, 0, 1],
1611
+ # [0, 1, 0, 0, 0, 0, 0, 0, 0, 1],
1612
+ # [0, 0, 1, 0, 0, 0, 0, 0, 1, 0],
1613
+ # [0, 0, 0, 1, 1, 1, 1, 1, 0, 0],
1614
+ # [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]], dtype=uint8)
1615
+
1616
+ __plot_pavlidis(img, pavlidis(img, 4, 1, True), skip_plot=skip_plot)
1617
+
1618
+ # this algo works exactly as I desire --> quite good!!!
1619
+ from skimage.draw import line
1620
+
1621
+ img = np.zeros((10, 10), dtype=np.uint8)
1622
+ rr, cc = line(1, 1, 8, 8)
1623
+ img[rr, cc] = 255
1624
+ # img
1625
+ # array([[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
1626
+ # [0, 1, 0, 0, 0, 0, 0, 0, 0, 0],
1627
+ # [0, 0, 1, 0, 0, 0, 0, 0, 0, 0],
1628
+ # [0, 0, 0, 1, 0, 0, 0, 0, 0, 0],
1629
+ # [0, 0, 0, 0, 1, 0, 0, 0, 0, 0],
1630
+ # [0, 0, 0, 0, 0, 1, 0, 0, 0, 0],
1631
+ # [0, 0, 0, 0, 0, 0, 1, 0, 0, 0],
1632
+ # [0, 0, 0, 0, 0, 0, 0, 1, 0, 0],
1633
+ # [0, 0, 0, 0, 0, 0, 0, 0, 1, 0],
1634
+ # [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]], dtype=uint8)
1635
+
1636
+ __plot_pavlidis(img, pavlidis(img, 1, 1, False), skip_plot=skip_plot)
1637
+ __plot_pavlidis(img, pavlidis(img, 8, 8, False), skip_plot=skip_plot)
1638
+
1639
+ from skimage.draw import line_aa
1640
+
1641
+ img = np.zeros((10, 10), dtype=np.uint8)
1642
+ rr, cc, val = line_aa(1, 1, 8, 8)
1643
+ img[rr, cc] = 255
1644
+ # img
1645
+ # array([[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
1646
+ # [0, 255, 74, 0, 0, 0, 0, 0, 0, 0],
1647
+ # [0, 74, 255, 74, 0, 0, 0, 0, 0, 0],
1648
+ # [0, 0, 74, 255, 74, 0, 0, 0, 0, 0],
1649
+ # [0, 0, 0, 74, 255, 74, 0, 0, 0, 0],
1650
+ # [0, 0, 0, 0, 74, 255, 74, 0, 0, 0],
1651
+ # [0, 0, 0, 0, 0, 74, 255, 74, 0, 0],
1652
+ # [0, 0, 0, 0, 0, 0, 74, 255, 74, 0],
1653
+ # [0, 0, 0, 0, 0, 0, 0, 74, 255, 0],
1654
+ # [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]], dtype=uint8)
1655
+
1656
+ __plot_pavlidis(img, pavlidis(img, 1, 1, False),
1657
+ skip_plot=skip_plot) # ça marche du tonnerre c'est vraiment exactement le contour et juste le contour --> parfait!!!
1658
+ __plot_pavlidis(img, pavlidis(img, 8, 8, False),
1659
+ skip_plot=skip_plot) # ici il y a une petite erreur mais je pense ok en fait --> pr eviter erreur faudrait partir avec un orientation differente --> TODO see if I can find a fix otherwise ok for now -> check if this is one of the exception for the starting pixels --> not so bad in fact and I dunno how I could prevent that
1660
+
1661
+ from skimage.draw import polygon
1662
+
1663
+ img = np.zeros((10, 10), dtype=np.uint8)
1664
+ r = np.array([1, 2, 8])
1665
+ c = np.array([1, 7, 4])
1666
+ rr, cc = polygon(r, c)
1667
+ img[rr, cc] = 255
1668
+ # img
1669
+ # array([[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
1670
+ # [0, 1, 0, 0, 0, 0, 0, 0, 0, 0],
1671
+ # [0, 0, 1, 1, 1, 1, 1, 1, 0, 0],
1672
+ # [0, 0, 1, 1, 1, 1, 1, 0, 0, 0],
1673
+ # [0, 0, 0, 1, 1, 1, 1, 0, 0, 0],
1674
+ # [0, 0, 0, 1, 1, 1, 0, 0, 0, 0],
1675
+ # [0, 0, 0, 0, 1, 1, 0, 0, 0, 0],
1676
+ # [0, 0, 0, 0, 1, 0, 0, 0, 0, 0],
1677
+ # [0, 0, 0, 0, 1, 0, 0, 0, 0, 0],
1678
+ # [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]], dtype=uint8)
1679
+
1680
+ __plot_pavlidis(img, pavlidis(img, 1, 1, True),
1681
+ skip_plot=skip_plot) # this one has a bug but it's a case I'll never face --> would have to choose a better start
1682
+ __plot_pavlidis(img, pavlidis(img, 8, 4, True),
1683
+ skip_plot=skip_plot) # this one has a bug but it's a case I'll never face --> would have to choose a better start # bug here too --> gives duplicated pixels
1684
+ __plot_pavlidis(img, pavlidis(img, 4, 3, True),
1685
+ skip_plot=skip_plot) # I think there is anyway no way of getting a clockwise sorted contour for this shape OR ?
1686
+
1687
+ from skimage.draw import polygon_perimeter
1688
+
1689
+ img = np.zeros((10, 10), dtype=np.uint8)
1690
+ rr, cc = polygon_perimeter([5, -1, 5, 10],
1691
+ [-1, 5, 11, 5],
1692
+ shape=img.shape, clip=True)
1693
+ img[rr, cc] = 255
1694
+ # img
1695
+ # array([[0, 0, 0, 0, 1, 1, 1, 0, 0, 0],
1696
+ # [0, 0, 0, 1, 0, 0, 0, 1, 0, 0],
1697
+ # [0, 0, 1, 0, 0, 0, 0, 0, 1, 0],
1698
+ # [0, 1, 0, 0, 0, 0, 0, 0, 0, 1],
1699
+ # [1, 0, 0, 0, 0, 0, 0, 0, 0, 1],
1700
+ # [1, 0, 0, 0, 0, 0, 0, 0, 0, 1],
1701
+ # [1, 0, 0, 0, 0, 0, 0, 0, 0, 1],
1702
+ # [0, 1, 1, 0, 0, 0, 0, 0, 0, 1],
1703
+ # [0, 0, 0, 1, 0, 0, 0, 1, 1, 0],
1704
+ # [0, 0, 0, 0, 1, 1, 1, 0, 0, 0]], dtype=uint8)
1705
+
1706
+ __plot_pavlidis(img, pavlidis(img, 0, 4, True), skip_plot=skip_plot)
1707
+
1708
+ # generate random shapes
1709
+ import skimage.draw
1710
+
1711
+ # img, labels = skimage.draw.random_shapes((32, 32), max_shapes=3)
1712
+ img, labels = skimage.draw.random_shapes((64, 64), max_shapes=1)
1713
+
1714
+ img = img[..., 0]
1715
+ img[img == 255] = 0
1716
+ img[img != 0] = 255
1717
+ skip_plot = False
1718
+ # np.nonzero(x)
1719
+ # plt.imshow(img)
1720
+ # plt.show()
1721
+
1722
+ first_pixel = np.nonzero(img) # np.argwhere(img!=255)#np.nonzero(img)
1723
+ print(
1724
+ labels) # NB THERE ARE DUPLICATED POINTS WITH TRIANGLES --> IS THERE A SMART WAY TO HANDLE DUPLICATED POINTS --> AND OR TO RECONNECT THE MISSING PARTS --> MAYBE BUT THINK ABOUT IT!!!
1725
+ # NB SHALL I DO A NO REPICK !!! BY CUTTING POINTS ALREADY TAKEN ?????
1726
+
1727
+ __plot_pavlidis(img, pavlidis(img, first_pixel[0][0], first_pixel[1][0], True),
1728
+ skip_plot=skip_plot) # maybe will be a pb with 1 px wide cells --> test it and see if there is a fix for that or not ???
1729
+ # it really seems to work well!!!
1730
+
1731
+ # coords of duplicated triangle (31, 47)
1732
+ # [(30, 47), (31, 47), (32, 48), (33, 48), (34, 49), (35, 49), (36, 50), (37, 51), (38, 51), (39, 52), (40, 52),
1733
+ # (41, 53), (42, 54), (43, 54), (44, 55), (45, 55), (46, 56), (47, 56), (48, 57), (49, 58), (50, 58), (51, 59),
1734
+ # (52, 59), (53, 60), (54, 61), (54, 60), (54, 59), (54, 58), (54, 57), (54, 56), (54, 55), (54, 54), (54, 53),
1735
+ # (54, 52), (54, 51), (54, 50), (54, 49), (54, 48), (54, 47), (54, 46), (54, 45), (54, 44), (54, 43), (54, 42),
1736
+ # (54, 41), (54, 40), (54, 39), (54, 38), (54, 37), (54, 36), (54, 35), (54, 34), (53, 35), (52, 36), (51, 36),
1737
+ # (50, 37), (49, 37), (48, 38), (47, 38), (46, 39), (45, 39), (44, 40), (43, 40), (42, 41), (41, 42), (40, 42),
1738
+ # (39, 43), (38, 43), (37, 44), (36, 44), (35, 45), (34, 45), (33, 46), (32, 46), (31, 47)]
1739
+
1740
+ # another duplicated triangle --> is there an easy fix ???
1741
+ # [(37, 28), (38, 28), (39, 29), (40, 29), (41, 30), (42, 30), (43, 31), (44, 31), (45, 32), (46, 33), (47, 33),
1742
+ # (48, 34), (49, 34), (50, 35), (51, 35), (52, 36), (53, 37), (53, 36), (53, 35), (53, 34), (53, 33), (53, 32),
1743
+ # (53, 31), (53, 30), (53, 29), (53, 28), (53, 27), (53, 26), (53, 25), (53, 24), (53, 23), (53, 22), (53, 21),
1744
+ # (53, 20), (53, 19), (52, 20), (51, 21), (50, 21), (49, 22), (48, 22), (47, 23), (46, 23), (45, 24), (44, 25),
1745
+ # (43, 25), (42, 26), (41, 26), (40, 27), (39, 27), (38, 28)]
1746
+ # DUPLICATED
1747
+ # POINTS
1748
+ # DETECTED
1749
+
1750
+ # almost all done --> just need fix all soon --> TODO
1751
+
1752
+ # [('triangle', ((16, 31), (0, 17)))]
1753
+ # [(16, 8), (17, 8), (18, 9), (19, 9), (20, 10), (21, 10), (22, 11), (23, 12), (24, 12), (25, 13), (26, 13), (27, 14),
1754
+ # (28, 14), (29, 15), (30, 16), (30, 15), (30, 14), (30, 13), (30, 12), (30, 11), (30, 10), (30, 9), (30, 8),
1755
+ # (30, 7), (30, 6), (30, 5), (30, 4), (30, 3), (30, 2), (30, 1), (30, 0), (29, 1), (28, 2), (27, 2), (26, 3),
1756
+ # (25, 3), (24, 4), (23, 4), (22, 5), (21, 6), (20, 6), (19, 7), (18, 7), (17, 8)]
1757
+
1758
+ # [('triangle', ((38, 41), (52, 55)))]
1759
+ # 38 53 255 color_of_interest
1760
+ # [(38, 53), (39, 53), (40, 54), (40, 53), (40, 52), (39, 53)]
1761
+ # DUPLICATED POINTS DETECTED
1762
+ # [(39, 53)]
1763
+
1764
+ # maybe remove last point/dupe to get rid of the error --> would that work ???
1765
+
1766
+ # deduplicated
1767
+ # coords[(29, 50), (30, 50), (31, 51), (31, 50), (31, 49)]
1768
+
1769
+ # nb mini ellipse equivalent to a single px area cell has no error in contour and no dupes --> VERY GOOD
1770
+ # [('ellipse', ((21, 24), (0, 3)))]
1771
+ # [(21, 1), (22, 2), (23, 1), (22, 0)]
1772
+ # no dupes
1773
+
1774
+ # MEGA TODO: NB DEDUPLICATION DID NOT WORK --> distance between end points > sqrt 2 --> is there any way I can fix duplication in a smarter way maybe by 2D distance between adjacent points --> so that I minimize it ???? --> THINK ABOUT THAT!!!
1775
+ # but in fact there is no way to get a perfect contour from this OR --> maybe yes upon removal of pixels futher away than sqrt 2 --> THINK ABOUT IT AND TEST THAT!!!!
1776
+ # nb if I remove 4,30 and one 5,30 then it WOULD WORK --> I WOULD GET A CLOSED CONTOUR THAT IS MINIMAL --> THINK ABOUT THE FASTEST WAY TO DO THAT
1777
+ # [('triangle', ((4, 16), (24, 37)))]
1778
+ # 4 30 255 color_of_interest
1779
+ # [(4, 30), (5, 30), (6, 31), (7, 31), (8, 32), (9, 32), (10, 33), (11, 33), (12, 34), (13, 34), (14, 35), (15, 36), (15, 35), (15, 34), (15, 33), (15, 32), (15, 31), (15, 30), (15, 29), (15, 28), (15, 27), (15, 26), (15, 25), (15, 24), (14, 25), (13, 26), (12, 26), (11, 27), (10, 27), (9, 28), (8, 28), (7, 29), (6, 29), (5, 30)]
1780
+ # DUPLICATED POINTS DETECTED
1781
+ # [(5, 30)]
1782
+ # deduplicated coords [(4, 30), (5, 30), (6, 31), (7, 31), (8, 32), (9, 32), (10, 33), (11, 33), (12, 34), (13, 34), (14, 35), (15, 36), (15, 35), (15, 34), (15, 33), (15, 32), (15, 31), (15, 30), (15, 29), (15, 28), (15, 27), (15, 26), (15, 25), (15, 24), (14, 25), (13, 26), (12, 26), (11, 27), (10, 27), (9, 28), (8, 28), (7, 29), (6, 29)]
1783
+ # dist between extreme points after deduplication 2.23606797749979 False