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,138 @@
1
+ from skimage.measure import regionprops, label
2
+ import numpy as np
3
+ from batoolset.files.tools import smart_name_parser
4
+ from batoolset.img import Img, RGB_to_int24
5
+ from batoolset.tools.early_stopper_class import early_stop
6
+ from batoolset.utils.loadlist import loadlist
7
+ from batoolset.ta.database.sql import TAsql
8
+ import traceback
9
+ import os
10
+ from batoolset.tools.logger import TA_logger # logging
11
+
12
+ logger = TA_logger()
13
+
14
+ def save_local_id_and_track_correspondence(local_id_track_correspondence, db_file, type='cell'):
15
+ """
16
+ Saves the local ID and track correspondence in a database.
17
+
18
+ Args:
19
+ local_id_track_correspondence (dict): The dictionary containing the local ID and track correspondence.
20
+ db_file (str): The path to the database file.
21
+ type (str, optional): The type of correspondence. Defaults to 'cell'.
22
+
23
+ """
24
+ # Open the database and save the file in it
25
+ db = TAsql(filename_or_connection=db_file)
26
+ table_name = type + '_tracks'
27
+ db.drop_table(table_name)
28
+
29
+ if local_id_track_correspondence is None:
30
+ db.close()
31
+ return
32
+
33
+ # Change the dict to another dict
34
+ local_ids = list(local_id_track_correspondence.keys())
35
+ track_ids = list(local_id_track_correspondence.values())
36
+ data_with_headers = {'local_id': local_ids, 'track_id': track_ids}
37
+ db.create_and_append_table(table_name, data_with_headers)
38
+ db.close()
39
+
40
+
41
+ def get_local_id_n_track_correspondence_from_images(filename):
42
+ """
43
+ Retrieves the local ID and track correspondence from the images.
44
+
45
+ Args:
46
+ filename (str): The filename of the images.
47
+
48
+ Returns:
49
+ dict: The dictionary containing the local ID and track correspondence.
50
+
51
+ """
52
+ if filename is None:
53
+ return None
54
+
55
+ local_id_track_correspondence = {}
56
+
57
+ try:
58
+ tracked_image_path = smart_name_parser(filename, ordered_output='tracked_cells_resized.tif')
59
+
60
+ cell_id_image = None
61
+
62
+ handCorrection1, handCorrection2 = smart_name_parser(filename, ordered_output=['handCorrection.png', 'handCorrection.tif'])
63
+
64
+ if os.path.isfile(handCorrection2):
65
+ cell_id_image = Img(handCorrection2)
66
+ elif os.path.isfile(handCorrection1):
67
+ cell_id_image = Img(handCorrection1)
68
+
69
+ if cell_id_image is None:
70
+ logger.error('File not found ' + str(handCorrection2) + ' please segment the images first')
71
+ return
72
+
73
+ if len(cell_id_image.shape) >= 3:
74
+ cell_id_image = cell_id_image[..., 0]
75
+ cell_id_image = label(cell_id_image, connectivity=1, background=255)
76
+
77
+ tracked_image = None
78
+ if os.path.isfile(tracked_image_path):
79
+ tracked_image = RGB_to_int24(Img(tracked_image_path))
80
+
81
+ if tracked_image is None:
82
+ logger.error('File not found ' + str(tracked_image_path) + ' please track cells first')
83
+ return
84
+
85
+ for region in regionprops(cell_id_image):
86
+ color = tracked_image[region.coords[0][0], region.coords[0][1]]
87
+ if color == 0:
88
+ logger.warning("Tracks and cells don't match, correspondence will be meaningless, please update your files")
89
+ return None
90
+ local_id_track_correspondence[region.label] = color
91
+ except:
92
+ traceback.print_exc()
93
+ logger.error('Something went wrong when converting local ID to tracks for file ' + str(filename))
94
+ return None
95
+
96
+ return local_id_track_correspondence
97
+
98
+ def add_localID_to_trackID_correspondance_in_DB(lst, progress_callback=None):
99
+ """
100
+ Adds the local ID to track ID correspondence to the database.
101
+
102
+ Args:
103
+ lst (list): The list of files.
104
+ progress_callback (function, optional): The callback function for reporting progress. Defaults to None.
105
+
106
+ """
107
+ if lst is not None and lst:
108
+ for lll, file in enumerate(lst):
109
+ try:
110
+ if early_stop.stop == True:
111
+ return
112
+ if progress_callback is not None:
113
+ progress_callback.emit(int((lll / len(lst)) * 100))
114
+ else:
115
+ print(str((lll / len(lst)) * 100) + '%')
116
+ except:
117
+ pass
118
+ db_file = smart_name_parser(file, ordered_output='pyTA.db')
119
+ local_to_global_correspondence = get_local_id_n_track_correspondence_from_images(file)
120
+ save_local_id_and_track_correspondence(local_to_global_correspondence, db_file)
121
+
122
+
123
+ if __name__ == "__main__":
124
+
125
+ if True:
126
+ lst = loadlist('/E/Sample_images/sample_images_PA/mini_different_nb_of_channels/list.lst')
127
+ add_localID_to_trackID_correspondance_in_DB(lst)
128
+
129
+ if False:
130
+ # test_local_to_global_correspondece = {1: 0xFF0000, 2: 0x00FF00, 3: 0x0000FF}
131
+ db_file_for_test = '/E/Sample_images/sample_images_PA/mini_different_nb_of_channels/focused_Series012/pyTA.db'
132
+
133
+ test_local_to_global_correspondece = get_local_id_n_track_correspondence_from_images('/E/Sample_images/sample_images_PA/mini_different_nb_of_channels/focused_Series012.png')
134
+ # print(test_local_to_global_correspondece)
135
+ save_local_id_and_track_correspondence(test_local_to_global_correspondece, db_file_for_test)
136
+ # try get the stuff
137
+
138
+ # pass
@@ -0,0 +1,517 @@
1
+ import numpy as np
2
+ from functools import partial
3
+ # from PIL import Image
4
+ from scipy.ndimage import generic_filter
5
+ from batoolset.img import Img, pad_border_xy
6
+ import matplotlib.pyplot as plt
7
+
8
+ def associate_vertices_to_cells(cell_labels, vertices, forbidden_colors=(0, 0xFFFFFF),
9
+ output_vertices_and_associated_cells=False, output_cells_and_their_vertices=False,
10
+ output_cells_and_their_neighbors=False):
11
+ """
12
+ Associates vertices with corresponding cells based on cell labels.
13
+
14
+ Args:
15
+ cell_labels (numpy.ndarray): Array containing cell labels.
16
+ vertices (tuple or numpy.ndarray): Tuple of vertex coordinates or vertex array.
17
+ forbidden_colors (tuple, optional): Colors to ignore when associating vertices to cells. Defaults to (0, 0xFFFFFF).
18
+ output_vertices_and_associated_cells (bool, optional): Whether to output vertices and their associated cells. Defaults to False.
19
+ output_cells_and_their_vertices (bool, optional): Whether to output cells and their associated vertices. Defaults to False.
20
+ output_cells_and_their_neighbors (bool, optional): Whether to output cells and their neighboring cells. Defaults to False.
21
+
22
+ Returns:
23
+ dict or list: Output based on the selected options.
24
+
25
+ """
26
+ outputs = []
27
+ cell_id_n_vx_coords = {}
28
+ vertices_n_associated_cells = {}
29
+
30
+ if isinstance(vertices, tuple):
31
+ vx_coords = vertices
32
+ else:
33
+ vx_coords = np.where(vertices == 255)
34
+
35
+ for i in range(len(vx_coords[0])):
36
+ y = vx_coords[0][i]
37
+ x = vx_coords[1][i]
38
+ ids = neighbors8((y, x), cell_labels)
39
+
40
+ ids = set(ids)
41
+
42
+ if forbidden_colors:
43
+ for col in forbidden_colors:
44
+ if col in ids:
45
+ ids.remove(col)
46
+
47
+ vertices_n_associated_cells[(y, x)] = ids
48
+
49
+ for id in ids:
50
+ if id in cell_id_n_vx_coords:
51
+ cell_id_n_vx_coords[id].append((y, x))
52
+ else:
53
+ cell_id_n_vx_coords[id] = [(y, x)]
54
+
55
+ if output_cells_and_their_vertices:
56
+ outputs.append(cell_id_n_vx_coords)
57
+
58
+ if output_vertices_and_associated_cells:
59
+ outputs.append(vertices_n_associated_cells)
60
+
61
+ if output_cells_and_their_neighbors:
62
+ cell_ids_n_neighbor_ids = {}
63
+ for key, value in cell_id_n_vx_coords.items():
64
+ neighbs = []
65
+ for vx in value:
66
+ neighbs += vertices_n_associated_cells[vx]
67
+
68
+ neighbs = set(neighbs)
69
+ neighbs.remove(key)
70
+
71
+ cell_ids_n_neighbor_ids[key] = neighbs
72
+
73
+ outputs.append(cell_ids_n_neighbor_ids)
74
+
75
+ if len(outputs) == 1:
76
+ return outputs[0]
77
+ else:
78
+ if outputs:
79
+ return outputs
80
+
81
+ return cell_id_n_vx_coords
82
+
83
+
84
+ def detect_neighbors_of_current_cell(cell_id, cell_vertices, cell_labels, forbidden_colors=(0,0xFFFFFF)):
85
+ """
86
+ Detects the neighbors of a current cell based on its vertices.
87
+
88
+ Args:
89
+ cell_id (int): ID of the cell.
90
+ cell_vertices (list): List of cell vertices.
91
+ cell_labels (numpy.ndarray): Array containing cell labels.
92
+ forbidden_colors (tuple, optional): Colors to ignore when detecting neighbors. Defaults to (0, 0xFFFFFF).
93
+
94
+ Returns:
95
+ set: Set of neighboring cell IDs.
96
+
97
+ """
98
+ neighbs = []
99
+ for vx in cell_vertices:
100
+ neighbs += neighbors8(vx, cell_labels).tolist()
101
+
102
+ neighbs = set(neighbs)
103
+ neighbs.remove(cell_id)
104
+
105
+ if forbidden_colors:
106
+ for col in forbidden_colors:
107
+ if col in neighbs:
108
+ neighbs.remove(col)
109
+
110
+ return neighbs
111
+
112
+ def neighbors8(vx_coords, cell_labels):
113
+ """
114
+ Calculates the 8 neighbors surrounding a given vertex in a matrix of cell labels.
115
+
116
+ Args:
117
+ vx_coords (tuple): The coordinates of the vertex (y, x).
118
+ cell_labels (ndarray): The matrix of cell labels.
119
+
120
+ Returns:
121
+ ndarray: The cell labels of the neighboring cells as a 1D array.
122
+
123
+ """
124
+ min_y = vx_coords[0] - 1
125
+ max_y = vx_coords[0] + 2
126
+ min_x = vx_coords[1] - 1
127
+ max_x = vx_coords[1] + 2
128
+
129
+ min_y = min_y if min_y >= 0 else 0
130
+ max_y = max_y if max_y < cell_labels.shape[0] else cell_labels.shape[0]
131
+ min_x = min_x if min_x >= 0 else 0
132
+ max_x = max_x if max_x < cell_labels.shape[1] else cell_labels.shape[1]
133
+
134
+ return cell_labels[min_y:max_y, min_x:max_x].ravel()
135
+
136
+
137
+ def neighbors8_shift_mapping(vx_coords, cell_labels):
138
+ """
139
+ Calculates the coordinates of the 8 neighbors surrounding a given vertex in a matrix of cell labels.
140
+
141
+ Args:
142
+ vx_coords (tuple): The coordinates of the vertex (y, x).
143
+ cell_labels (ndarray): The matrix of cell labels.
144
+
145
+ Returns:
146
+ list: The coordinates of the neighboring vertices.
147
+
148
+ """
149
+ min_y = vx_coords[0] - 1
150
+ max_y = vx_coords[0] + 2
151
+ min_x = vx_coords[1] - 1
152
+ max_x = vx_coords[1] + 2
153
+
154
+ min_y = min_y if min_y >= 0 else 0
155
+ max_y = max_y if max_y < cell_labels.shape[0] else cell_labels.shape[0]
156
+ min_x = min_x if min_x >= 0 else 0
157
+ max_x = max_x if max_x < cell_labels.shape[1] else cell_labels.shape[1]
158
+
159
+ coords = []
160
+ for y in range(min_y, max_y):
161
+ for x in range(min_x, max_x):
162
+ coords.append([y, x])
163
+
164
+ return coords
165
+
166
+ def cantor_color_bonds(RGB24_or_lab, vertices, boundary_color=0xFFFFFF, copy=True):
167
+ """
168
+ Applies the Cantor pairing function to color the bonds between vertices in an image.
169
+
170
+ Args:
171
+ RGB24_or_lab (ndarray): The input image in RGB or LAB format.
172
+ vertices (ndarray): The matrix of vertices in the image.
173
+ boundary_color (int or list): The color(s) used for boundary detection. Defaults to 0xFFFFFF.
174
+ copy (bool): Specifies whether to create a copy of the image. Defaults to True.
175
+
176
+ Returns:
177
+ ndarray: The matrix of colored bonds.
178
+
179
+ """
180
+ if isinstance(boundary_color, int):
181
+ boundary_color = [boundary_color]
182
+
183
+ method = partial(_cantor_color_bonds, boundary_colors=boundary_color)
184
+
185
+ if copy:
186
+ RGB24 = np.copy(RGB24_or_lab)
187
+ else:
188
+ RGB24 = RGB24_or_lab
189
+
190
+ bonds = generic_filter(RGB24, method, (3, 3))
191
+ bonds[vertices != 0] = 0
192
+
193
+ max_id = bonds.max() + 1
194
+
195
+ for iii in range(RGB24.shape[1]):
196
+ if vertices[0, iii] == 255:
197
+ max_id += 1
198
+ continue
199
+ bonds[0, iii] = max_id
200
+
201
+ for iii in range(RGB24.shape[1]):
202
+ if vertices[RGB24.shape[0] - 1, iii] == 255:
203
+ max_id += 1
204
+ continue
205
+ bonds[RGB24.shape[0] - 1, iii] = max_id
206
+
207
+ for jjj in range(RGB24.shape[0]):
208
+ if vertices[jjj, 0] == 255:
209
+ max_id += 1
210
+ continue
211
+ bonds[jjj, 0] = max_id
212
+
213
+ for jjj in range(RGB24.shape[0]):
214
+ if vertices[jjj, RGB24.shape[1] - 1] == 255:
215
+ max_id += 1
216
+ continue
217
+ bonds[jjj, RGB24.shape[1] - 1] = max_id
218
+
219
+ return bonds
220
+
221
+
222
+ def detect_vertices_and_bonds(RGB24_or_lab, detect_bonds=False,
223
+ boundary_color=0xFFFFFF, split_bonds_and_vertices=False, copy=True,
224
+ pad_to_detect_vertices=True):
225
+ """
226
+ Detects vertices and bonds in an image.
227
+
228
+ Args:
229
+ RGB24_or_lab (ndarray): The input image in RGB or LAB format.
230
+ detect_bonds (bool): Specifies whether to detect bonds. Defaults to False.
231
+ boundary_color (int or list): The color(s) used for boundary detection. Defaults to 0xFFFFFF.
232
+ split_bonds_and_vertices (bool): Specifies whether to split bonds and vertices into separate matrices.
233
+ Defaults to False.
234
+ copy (bool): Specifies whether to create a copy of the image. Defaults to True.
235
+ pad_to_detect_vertices (bool): Specifies whether to pad the image for vertex detection. Defaults to True.
236
+
237
+ Returns:
238
+ ndarray or tuple: The matrix of vertices, or a tuple of matrices if split_bonds_and_vertices is True.
239
+
240
+ """
241
+ if isinstance(boundary_color, int):
242
+ boundary_color = [boundary_color]
243
+
244
+ method = partial(_detect_vertices_and_bonds, boundary_colors=boundary_color)
245
+
246
+ if copy and not pad_to_detect_vertices:
247
+ RGB24 = np.copy(RGB24_or_lab)
248
+ else:
249
+ RGB24 = RGB24_or_lab
250
+
251
+ if pad_to_detect_vertices:
252
+ RGB24 = np.pad(RGB24, pad_width=1, mode='constant', constant_values=False)
253
+ RGB24 = pad_border_xy(RGB24, mode=RGB24.max() + 1)
254
+
255
+ vertices = generic_filter(RGB24, method, (3, 3))
256
+
257
+ if pad_to_detect_vertices:
258
+ vertices = vertices[1:-1, 1:-1]
259
+ vertices[0, 0] = 255
260
+ vertices[-1, 0] = 255
261
+ vertices[0, -1] = 255
262
+ vertices[-1, -1] = 255
263
+
264
+ RGB24 = RGB24[1:-1, 1:-1]
265
+ else:
266
+ for jjj in range(1, RGB24.shape[0] - 1):
267
+ neighbs = RGB24[jjj - 1:jjj + 2, 0:2]
268
+ different_neighbours = set(neighbs.ravel().tolist())
269
+ if len(different_neighbours) == 3:
270
+ vertices[jjj][0] = 255
271
+ neighbs = RGB24[jjj - 1:jjj + 2, RGB24.shape[0] - 2:RGB24.shape[0]]
272
+ different_neighbours = set(neighbs.ravel().tolist())
273
+ if len(different_neighbours) == 3:
274
+ vertices[jjj][RGB24.shape[0] - 1] = 255
275
+
276
+ for iii in range(1, RGB24.shape[1] - 1):
277
+ neighbs = RGB24[0:2, iii - 1:iii + 2]
278
+ different_neighbours = set(neighbs.ravel().tolist())
279
+ if len(different_neighbours) == 3:
280
+ vertices[0][iii] = 255
281
+ neighbs = RGB24[RGB24.shape[1] - 2:RGB24.shape[1], iii - 1:iii + 2]
282
+ different_neighbours = set(neighbs.ravel().tolist())
283
+ if len(different_neighbours) == 3:
284
+ vertices[RGB24.shape[0] - 1][iii] = 255
285
+
286
+ vertices[0][0] = 255
287
+ vertices[RGB24.shape[0] - 1][0] = 255
288
+ vertices[RGB24.shape[0] - 1][RGB24.shape[1] - 1] = 255
289
+ vertices[0][RGB24.shape[1] - 1] = 255
290
+
291
+ if detect_bonds:
292
+ vertices[np.where((RGB24 == boundary_color) & (vertices != 255))] = 128
293
+
294
+ if split_bonds_and_vertices and detect_bonds:
295
+ bonds = np.zeros_like(vertices)
296
+ bonds[vertices == 128] = 128
297
+ vertices[vertices == 128] = 0
298
+ return vertices, bonds
299
+
300
+ return vertices
301
+
302
+ # see also https://stackoverflow.com/questions/919612/mapping-two-integers-to-one-in-a-unique-and-deterministic-way
303
+ def _sudzik_pairing(id1, id2, _sort_points=True):
304
+ """
305
+ Compute a pairing value for two integer IDs using Sudzik's pairing function.
306
+
307
+ Sudzik's pairing function combines two integers (IDs) into a unique value.
308
+ The function can optionally sort the input IDs before computing the pairing value.
309
+
310
+ Parameters:
311
+ - id1 (int): The first integer ID.
312
+ - id2 (int): The second integer ID.
313
+ - _sort_points (bool, optional): If True (default), the input IDs are sorted before pairing.
314
+
315
+ Returns:
316
+ int: The computed pairing value.
317
+
318
+ Example:
319
+ >>> _sudzik_pairing(3, 2)
320
+ 11
321
+ >>> _sudzik_pairing(2, 3)
322
+ 11
323
+ >>> _sudzik_pairing(128, 256)
324
+ 65664
325
+ >>> _sudzik_pairing(256, 128) # I guess I want always the sorted version for my purpose
326
+ 65664
327
+ >>> _sudzik_pairing(256, 128, _sort_points=False)
328
+ 65920
329
+ >>> _sudzik_pairing(128, 256, _sort_points=False)
330
+ 65664
331
+ """
332
+ if _sort_points and id1 > id2:
333
+ id2, id1 = id1, id2
334
+ return id1 * id1 + id1 + id2 if id1 >= id2 else id1 + id2 * id2
335
+
336
+ # TODO --> extensively test all the cantor and sudzik parirings stuff and move them to color class (maybe always use sudzik, check with big numbers just to see)
337
+ # TODO also add the other color based cantors because this may be very useful !!!
338
+
339
+ def _cantor_pairing(id1, id2, _sort_points=True):
340
+ """
341
+ Applies the Cantor pairing function to two IDs to generate a unique bond ID.
342
+
343
+ Args:
344
+ id1 (int): The first ID.
345
+ id2 (int): The second ID.
346
+ _sort_points (bool): Specifies whether to sort the IDs before applying the function. Defaults to True.
347
+
348
+ Returns:
349
+ int: The bond ID.
350
+
351
+ Examples:
352
+ >>> _cantor_pairing(3, 4)
353
+ 31
354
+ >>> _cantor_pairing(4, 3)
355
+ 31
356
+ >>> _cantor_pairing(10, 5)
357
+ 125
358
+ """
359
+ x = id1
360
+ y = id2
361
+ if _sort_points and id1 < id2:
362
+ x = id2
363
+ y = id1
364
+ return int(((x + y) * (x + y + 1) / 2) + y)
365
+
366
+
367
+
368
+ def _detect_vertices_and_bonds_old_doing_useless_stuff(P, detect_bonds=False, boundary_colors=(0xFFFFFF, 0)):
369
+ if boundary_colors:
370
+ if P[4] not in boundary_colors: # not pure white
371
+ return 0
372
+
373
+ size = len(set(P)) # 4-way vertices only but why not
374
+ if detect_bonds and size == 3:
375
+ return 128
376
+ return 255 if size >= 4 else 0
377
+
378
+
379
+ def _detect_vertices_and_bonds(P, boundary_colors=(0xFFFFFF, 0)):
380
+ """
381
+ Detects vertices in a neighborhood and assigns IDs to them.
382
+
383
+ Args:
384
+ P (ndarray): Neighborhood array.
385
+ boundary_colors (tuple): Tuple of boundary colors. Defaults to (0xFFFFFF, 0).
386
+
387
+ Returns:
388
+ int: The vertex ID.
389
+
390
+ """
391
+ if boundary_colors:
392
+ if P[4] not in boundary_colors: # not pure white
393
+ return 0
394
+
395
+ size = len(set(P)) # 4-way vertices only but why not
396
+ return 255 if size >= 4 else 0
397
+
398
+
399
+ def _identify_vertices_and_bonds(P, detect_bonds=False, boundary_colors=(0xFFFFFF, 0)):
400
+ """
401
+ Identifies vertices and assigns IDs to them. Optionally detects bonds.
402
+
403
+ Args:
404
+ P (ndarray): Neighborhood array.
405
+ detect_bonds (bool): Specifies whether to detect bonds. Defaults to False.
406
+ boundary_colors (tuple): Tuple of boundary colors. Defaults to (0xFFFFFF, 0).
407
+
408
+ Returns:
409
+ int: The vertex ID or bond ID.
410
+
411
+ """
412
+ if boundary_colors:
413
+ if P[4] not in boundary_colors: # not pure white
414
+ return 0
415
+
416
+ corrected_ids = set(P)
417
+ size = len(corrected_ids) # 4-way vertices only but why not
418
+
419
+ if detect_bonds and size == 3:
420
+ corrected_ids = list(corrected_ids.difference(boundary_colors))
421
+ return _cantor_pairing(corrected_ids[0], corrected_ids[1])
422
+
423
+ return 255 if size >= 4 else 0
424
+
425
+
426
+ def _cantor_color_bonds(P, boundary_colors=(0xFFFFFF, 0)):
427
+ if boundary_colors:
428
+ if P[4] not in boundary_colors: # not pure white
429
+ return 0
430
+
431
+ corrected_ids = set(P)
432
+ size = len(corrected_ids) # 4-way vertices only but why not
433
+
434
+ if size == 3:
435
+ corrected_ids = list(corrected_ids.difference(boundary_colors))
436
+ return _cantor_pairing(corrected_ids[0], corrected_ids[1])
437
+
438
+ return 0
439
+
440
+
441
+ if __name__ == '__main__':
442
+
443
+ if False:
444
+ import sys
445
+ print(_cantor_pairing(0xFFFFFF,0xFFFFFE)) #562949382996104 562949886312449
446
+ print(_cantor_pairing(0xFFFFFF,0xFFFFFF)) #562949382996104 562949886312449
447
+ print(_cantor_pairing(0xFFFFFFFF,0xFFFFFFFE)) #3.6893488130239234e+19 --> very big
448
+ print(_cantor_pairing(0xFFFFFFFF,0xFFFFFFFF)) #3.689348813882917e+19 --> very big too # try avoid such big number but ok if can't do better...
449
+ print(_cantor_pairing(0, 0))
450
+ print(_cantor_pairing(0, 1)) # 0,1 and 1, 0 differ --> need sort the points before passing them or after passing them
451
+ print(_cantor_pairing(1, 0))
452
+ print(_cantor_pairing(10, 23)) # ça marche variment bien mais faire +1 pr eviter d'avoir 0 --> en fait je m'en fous car pourra jamais etre = 0
453
+ print(_cantor_pairing(23, 10))
454
+ print(_cantor_pairing(1, 32))
455
+ print(_cantor_pairing(32, 1))
456
+
457
+ # --> I love this because this is new and elegant
458
+
459
+
460
+ # very good idea
461
+ # I love this cantor formula
462
+ sys.exit(0)
463
+
464
+
465
+ # Open image and make into Numpy array
466
+ # PILim = Image.open('patches.png').convert('RGB')
467
+ RGBim = Img('/E/Sample_images/segmentation_assistant/ovipo_uncropped/200709_armGFP_suz_46hAPF_ON.lif - Series011/tracked_cells_resized.tif')[0:64, 0:64]
468
+ # RGBim = np.array(PILim)
469
+
470
+ # Make a single channel 24-bit image rather than 3 channels of 8-bit each
471
+
472
+ print(RGBim.shape)
473
+
474
+ # there is a bug but almost there still
475
+ RGB24 = (RGBim[..., 0].astype(np.uint32) << 16) | (RGBim[..., 1].astype(np.uint32) << 8) | RGBim[..., 2].astype(
476
+ np.uint32)
477
+
478
+ print(RGB24.max(), RGB24.min())
479
+ # RGB24 = (RGBim[...,0].astype(np.uint32)<<16) | (RGBim[...,1].astype(np.uint32)<<8) | RGBim[...,2].astype(np.uint32)
480
+
481
+ # Run generic filter counting unique colours in neighbourhood
482
+ result = detect_vertices_and_bonds(RGB24)
483
+
484
+
485
+ # then could recolor them if needed --> could try that
486
+ # give a unique id to bonds --> very easy in facts --> maybe replace old code ??? --> pb is that I need a uint64 image that will be quite big in mem but ok I guess or need relabel the image afterwards --> maybe by the way...
487
+ result = generic_filter(RGB24.astype(np.uint64), partial(_identify_vertices_and_bonds,detect_bonds=True), (3, 3))
488
+ # try to see how I can do that better!!!
489
+
490
+
491
+ # result = generic_filter(RGB24.astype(float), partial(_identify_vertices_and_bonds,detect_bonds=True), (3, 3))
492
+ print(result.dtype)
493
+ # result = result.astype(np.uint8)
494
+
495
+ # Save result
496
+ # Image.fromarray(result.astype(np.uint8)).save('result.png')
497
+ plt.imshow(result)
498
+ plt.show()
499
+
500
+ # plt.imshow(RGB24)
501
+ # plt.show()
502
+
503
+ indices = (result == 255)
504
+ print('indices', indices)
505
+
506
+ vertices = np.where(result == 255) #c'est
507
+ print(vertices)
508
+ if vertices[0].size != 0:
509
+ print('y', vertices[0][0])
510
+ if vertices[1].size != 0:
511
+ print('x', vertices[1][0])
512
+ if vertices[0].size + vertices[1].size != 0:
513
+ print('first vx', result[vertices[0][0], vertices[1][0]])
514
+
515
+ print(associate_vertices_to_cells(RGB24,
516
+ result)) # ça marche en fait --> j'ai associé les vertex à chaque cellule --> cool
517
+ # pb need add vertices at the corners too --> like the first or second pixel away from the border