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.
- batoolset/GUI/__init__.py +0 -0
- batoolset/GUI/list_gui.py +513 -0
- batoolset/GUI/multi_image_display.py +989 -0
- batoolset/GUI/open.py +325 -0
- batoolset/GUI/overlay_hints.py +45 -0
- batoolset/GUI/paint2.py +1345 -0
- batoolset/GUI/scrollablepaint.py +1152 -0
- batoolset/IJ_ROIs/__init__.py +0 -0
- batoolset/IJ_ROIs/tools.py +40 -0
- batoolset/PATH/__init__.py +0 -0
- batoolset/PATH/tools.py +79 -0
- batoolset/SQLite_tools/__init__.py +0 -0
- batoolset/SQLite_tools/tools.py +840 -0
- batoolset/__init__.py +0 -0
- batoolset/binary/__init__.py +0 -0
- batoolset/binary/tools.py +123 -0
- batoolset/binarytools/__init__.py +0 -0
- batoolset/binarytools/cell_center_detector.py +290 -0
- batoolset/clipboard_tools/__init__.py +0 -0
- batoolset/clipboard_tools/tools.py +7 -0
- batoolset/colors/__init__.py +0 -0
- batoolset/colors/colorgen.py +193 -0
- batoolset/colors/tools.py +22 -0
- batoolset/command_line/__init__.py +0 -0
- batoolset/command_line/tools.py +18 -0
- batoolset/dialogs/__init__.py +0 -0
- batoolset/dialogs/opensave.py +228 -0
- batoolset/dicts/__init__.py +0 -0
- batoolset/dicts/tools.py +299 -0
- batoolset/dims/__init__.py +0 -0
- batoolset/dims/tools.py +83 -0
- batoolset/draw/__init__.py +0 -0
- batoolset/draw/shapes/Position.py +183 -0
- batoolset/draw/shapes/__init__.py +0 -0
- batoolset/draw/shapes/automated_alignment_drawing_refactored.py +346 -0
- batoolset/draw/shapes/circle2d.py +99 -0
- batoolset/draw/shapes/compute_optimal_size_all_merged.py +317 -0
- batoolset/draw/shapes/ellipse2d.py +336 -0
- batoolset/draw/shapes/freehand2d.py +235 -0
- batoolset/draw/shapes/group.py +1830 -0
- batoolset/draw/shapes/image2d.py +2469 -0
- batoolset/draw/shapes/line2d.py +575 -0
- batoolset/draw/shapes/point2d.py +257 -0
- batoolset/draw/shapes/polygon2d.py +357 -0
- batoolset/draw/shapes/polyline2d.py +235 -0
- batoolset/draw/shapes/rect2d.py +388 -0
- batoolset/draw/shapes/rectangle2d.py +601 -0
- batoolset/draw/shapes/scalebar.py +304 -0
- batoolset/draw/shapes/serializablefont.py +126 -0
- batoolset/draw/shapes/serializableguiparams.py +10 -0
- batoolset/draw/shapes/square2d.py +103 -0
- batoolset/draw/shapes/textorientation.py +45 -0
- batoolset/draw/shapes/tools.py +467 -0
- batoolset/draw/shapes/transform2D.py +350 -0
- batoolset/draw/shapes/txt2d.py +550 -0
- batoolset/draw/widgets/__init__.py +0 -0
- batoolset/draw/widgets/col.py +343 -0
- batoolset/draw/widgets/image_group.py +248 -0
- batoolset/draw/widgets/image_organizer.py +96 -0
- batoolset/draw/widgets/paint.py +184 -0
- batoolset/draw/widgets/vectorial.py +257 -0
- batoolset/figure/__init__.py +0 -0
- batoolset/figure/alignment.py +814 -0
- batoolset/figure/column.py +1117 -0
- batoolset/figure/fig_tools.py +285 -0
- batoolset/figure/minimal_debugger_scale_etc.py +139 -0
- batoolset/figure/panel.py +697 -0
- batoolset/figure/row.py +852 -0
- batoolset/figure/user_selection.py +287 -0
- batoolset/files/__init__.py +0 -0
- batoolset/files/tools.py +1321 -0
- batoolset/images/__init__.py +0 -0
- batoolset/images/tools.py +81 -0
- batoolset/img.py +5216 -0
- batoolset/linux/__init__.py +0 -0
- batoolset/linux/tools.py +72 -0
- batoolset/lists/__init__.py +0 -0
- batoolset/lists/tools.py +436 -0
- batoolset/luts/__init__.py +0 -0
- batoolset/luts/lut_minimal_test.py +1207 -0
- batoolset/matplolib/__init__.py +0 -0
- batoolset/matplolib/tools.py +44 -0
- batoolset/np_tools/__init__.py +0 -0
- batoolset/np_tools/tools.py +252 -0
- batoolset/np_tools/trash.py +15 -0
- batoolset/pandas_tools/__init__.py +0 -0
- batoolset/pandas_tools/tools.py +880 -0
- batoolset/partials/__init__.py +0 -0
- batoolset/partials/tools.py +83 -0
- batoolset/plot_tools/__init__.py +0 -0
- batoolset/plot_tools/tools.py +70 -0
- batoolset/pyqt/__init__.py +0 -0
- batoolset/pyqt/tools.py +691 -0
- batoolset/re_tools/__init__.py +0 -0
- batoolset/re_tools/tools.py +35 -0
- batoolset/serialization/__init__.py +0 -0
- batoolset/serialization/tools.py +1192 -0
- batoolset/sets/__init__.py +0 -0
- batoolset/sets/tools.py +109 -0
- batoolset/settings/__init__.py +0 -0
- batoolset/settings/global_settings.py +134 -0
- batoolset/strings/__init__.py +0 -0
- batoolset/strings/tools.py +225 -0
- batoolset/ta/__init__.py +0 -0
- batoolset/ta/database/__init__.py +0 -0
- batoolset/ta/database/advanced_sql_plotter.py +1170 -0
- batoolset/ta/database/my_qt_table.py +79 -0
- batoolset/ta/database/preview_and_edit_sqlite_db.py +405 -0
- batoolset/ta/database/sql.py +2141 -0
- batoolset/ta/database/sql_column_name_dialog.py +122 -0
- batoolset/ta/measurements/TAmeasures.py +2226 -0
- batoolset/ta/measurements/__init__.py +0 -0
- batoolset/ta/measurements/measurements3D/__init__.py +0 -0
- batoolset/ta/measurements/measurements3D/get_point_on_surface_if_centroid_is_bad.py +175 -0
- batoolset/ta/measurements/measurements3D/height_map_to_projection.py +82 -0
- batoolset/ta/measurements/measurements3D/measures3D.py +208 -0
- batoolset/ta/measurements/measurements3D/triangulate.py +331 -0
- batoolset/ta/measurements/nematic.py +562 -0
- batoolset/ta/measurements/nematic_measurements.py +107 -0
- batoolset/ta/segmentation/__init__.py +0 -0
- batoolset/ta/segmentation/neo_wshed.py +314 -0
- batoolset/ta/selections/__init__.py +0 -0
- batoolset/ta/selections/selection.py +329 -0
- batoolset/ta/tracking/__init__.py +0 -0
- batoolset/ta/tracking/last_tracking_based_on_matching_or_on_translation_from_mermaid_warp.py +653 -0
- batoolset/ta/tracking/local_to_track_correspondance.py +138 -0
- batoolset/ta/tracking/rapid_detection_of_vertices.py +517 -0
- batoolset/ta/tracking/registration.py +108 -0
- batoolset/ta/tracking/swap_detector_and_fixer.py +296 -0
- batoolset/ta/tracking/track_correction.py +238 -0
- batoolset/ta/tracking/tracking_error_detector_and_fixer.py +3099 -0
- batoolset/ta/tracking/tracking_yet_another_approach_pyramidal_registration_n_neo_swapping_correction.py +725 -0
- batoolset/ta/utils/TA_tools.py +43 -0
- batoolset/ta/utils/__init__.py +0 -0
- batoolset/ta/utils/rps_tools.py +1783 -0
- batoolset/tests/__init__.py +0 -0
- batoolset/tests/rps_tools.py +51 -0
- batoolset/tests/tools.py +10 -0
- batoolset/tools/__init__.py +0 -0
- batoolset/tools/early_stopper_class.py +9 -0
- batoolset/tools/guided_data_recoverer.py +110 -0
- batoolset/tools/logger.py +100 -0
- batoolset/tools/mytimer.py +31 -0
- batoolset/tools/prompt_color.py +160 -0
- batoolset/tools/qthandler.py +118 -0
- batoolset/uitools/__init__.py +0 -0
- batoolset/uitools/blinker.py +56 -0
- batoolset/unsafe/__init__.py +0 -0
- batoolset/unsafe/tools.py +115 -0
- batoolset/utils/__init__.py +0 -0
- batoolset/utils/commontools.py +158 -0
- batoolset/utils/loadlist.py +728 -0
- batoolset/version.py +10 -0
- batoolset/xml/__init__.py +0 -0
- batoolset/xml/tools.py +65 -0
- batoolset/zip_tools/__init__.py +0 -0
- batoolset/zip_tools/tools.py +76 -0
- batoolset-0.1.0.dist-info/LICENSE +24 -0
- batoolset-0.1.0.dist-info/METADATA +45 -0
- batoolset-0.1.0.dist-info/RECORD +162 -0
- batoolset-0.1.0.dist-info/WHEEL +5 -0
- 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
|