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,108 @@
|
|
|
1
|
+
from skimage.registration import phase_cross_correlation # change in scipy 0.19
|
|
2
|
+
# image = data.camera()
|
|
3
|
+
from skimage import transform
|
|
4
|
+
import numpy as np
|
|
5
|
+
|
|
6
|
+
__DEBUG__ = False
|
|
7
|
+
|
|
8
|
+
def apply_translation(img, y, x):
|
|
9
|
+
"""
|
|
10
|
+
Applies translation to an image.
|
|
11
|
+
|
|
12
|
+
Args:
|
|
13
|
+
img (numpy.ndarray): The input image.
|
|
14
|
+
y (float): Translation along the y-axis.
|
|
15
|
+
x (float): Translation along the x-axis.
|
|
16
|
+
|
|
17
|
+
Returns:
|
|
18
|
+
numpy.ndarray: The translated image.
|
|
19
|
+
"""
|
|
20
|
+
afine_tf = transform.AffineTransform(translation=(x, y))
|
|
21
|
+
translated = transform.warp(img, inverse_map=afine_tf, order=0, preserve_range=True)
|
|
22
|
+
return translated
|
|
23
|
+
|
|
24
|
+
def pre_register_images(orig_t0, orig_t1, apply_to_orig_t0=False):
|
|
25
|
+
"""
|
|
26
|
+
Performs pre-registration between two images.
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
orig_t0 (numpy.ndarray): The first image.
|
|
30
|
+
orig_t1 (numpy.ndarray): The second image.
|
|
31
|
+
apply_to_orig_t0 (bool, optional): Whether to apply the translation to orig_t0. Defaults to False.
|
|
32
|
+
|
|
33
|
+
Returns:
|
|
34
|
+
tuple or numpy.ndarray: If apply_to_orig_t0 is False, returns the global translations (gloabl_t0, gloabl_t1).
|
|
35
|
+
If apply_to_orig_t0 is True, returns the translated orig_t0 image.
|
|
36
|
+
|
|
37
|
+
Notes:
|
|
38
|
+
- The function uses phase cross-correlation to estimate the global translation between the two images.
|
|
39
|
+
- The global translation is then applied to either orig_t0 or both orig_t0 and orig_t1, depending on the
|
|
40
|
+
value of apply_to_orig_t0.
|
|
41
|
+
"""
|
|
42
|
+
global_translation, error, diffphase = phase_cross_correlation(orig_t0, orig_t1)
|
|
43
|
+
gloabl_t0 = -global_translation[0]
|
|
44
|
+
gloabl_t1 = -global_translation[1]
|
|
45
|
+
if not apply_to_orig_t0:
|
|
46
|
+
return gloabl_t0, gloabl_t1
|
|
47
|
+
else:
|
|
48
|
+
return apply_translation(orig_t0, -gloabl_t0, -gloabl_t1)
|
|
49
|
+
|
|
50
|
+
def get_pyramidal_registration(orig_t0, orig_t1, depth=2, threshold_translation=20):
|
|
51
|
+
"""
|
|
52
|
+
Performs pyramidal registration between two images.
|
|
53
|
+
|
|
54
|
+
Args:
|
|
55
|
+
orig_t0 (numpy.ndarray): The first image.
|
|
56
|
+
orig_t1 (numpy.ndarray): The second image.
|
|
57
|
+
depth (int, optional): The depth of the pyramid. Defaults to 2.
|
|
58
|
+
threshold_translation (float, optional): The threshold for ignoring large translations. Defaults to 20.
|
|
59
|
+
|
|
60
|
+
Returns:
|
|
61
|
+
numpy.ndarray: The translation matrix representing the translations for each pixel.
|
|
62
|
+
|
|
63
|
+
Notes:
|
|
64
|
+
- The function applies pre-registration between orig_t0 and orig_t1.
|
|
65
|
+
- It then iteratively performs registration on blocks of decreasing size.
|
|
66
|
+
- Translations are accumulated in the translation matrix.
|
|
67
|
+
"""
|
|
68
|
+
|
|
69
|
+
translation_matrix = np.zeros((*orig_t0.shape, 2))
|
|
70
|
+
|
|
71
|
+
gloabl_t0, gloabl_t1 = pre_register_images(orig_t0, orig_t1)
|
|
72
|
+
|
|
73
|
+
translation_matrix[..., 0] += gloabl_t0
|
|
74
|
+
translation_matrix[..., 1] += gloabl_t1
|
|
75
|
+
|
|
76
|
+
height = orig_t0.shape[0]
|
|
77
|
+
width = orig_t0.shape[1]
|
|
78
|
+
|
|
79
|
+
block_size = 256
|
|
80
|
+
|
|
81
|
+
for i in range(depth):
|
|
82
|
+
try:
|
|
83
|
+
tile_height = block_size // (i * 2)
|
|
84
|
+
tile_width = block_size // (i * 2)
|
|
85
|
+
except:
|
|
86
|
+
tile_height = block_size
|
|
87
|
+
tile_width = block_size
|
|
88
|
+
|
|
89
|
+
for y in range(0, height, tile_height):
|
|
90
|
+
for x in range(0, width, tile_width):
|
|
91
|
+
afine_tf = transform.AffineTransform(
|
|
92
|
+
translation=(-translation_matrix[y, x, 1], -translation_matrix[y, x, 0]))
|
|
93
|
+
translated = transform.warp(orig_t0, inverse_map=afine_tf, order=0, preserve_range=True)
|
|
94
|
+
|
|
95
|
+
tile_t0 = translated[y:y + tile_height, x:x + tile_width]
|
|
96
|
+
tile_t1 = orig_t1[y:y + tile_height, x:x + tile_width]
|
|
97
|
+
|
|
98
|
+
shift, error, diffphase = phase_cross_correlation(tile_t0, tile_t1)
|
|
99
|
+
t0 = -shift[0]
|
|
100
|
+
t1 = -shift[1]
|
|
101
|
+
|
|
102
|
+
if abs(t0) > threshold_translation or abs(t1) > threshold_translation:
|
|
103
|
+
continue
|
|
104
|
+
|
|
105
|
+
translation_matrix[y:y + tile_height, x:x + tile_width, 0] += t0
|
|
106
|
+
translation_matrix[y:y + tile_height, x:x + tile_width, 1] += t1
|
|
107
|
+
|
|
108
|
+
return translation_matrix
|
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
from batoolset.img import int24_to_RGB, RGB_to_int24
|
|
2
|
+
from timeit import default_timer as timer
|
|
3
|
+
import numpy as np
|
|
4
|
+
from batoolset.img import Img
|
|
5
|
+
# really very good --> now make it recursive then done --> extract the cell from there and continue
|
|
6
|
+
from natsort import natsorted
|
|
7
|
+
import os
|
|
8
|
+
|
|
9
|
+
# parameters
|
|
10
|
+
from batoolset.utils.loadlist import loadlist
|
|
11
|
+
from batoolset.ta.tracking.rapid_detection_of_vertices import associate_vertices_to_cells, detect_vertices_and_bonds
|
|
12
|
+
|
|
13
|
+
__DEBUG__ = False
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _fix_swapping_internal(tracked_cells_t0, tracked_cells_t1, vertices_t0=None, vertices_t1=None, MIN_CUT_OFF_SWAPPING=None):
|
|
18
|
+
"""
|
|
19
|
+
Fixes swapping of cells between two time steps.
|
|
20
|
+
|
|
21
|
+
Args:
|
|
22
|
+
tracked_cells_t0 (numpy.ndarray): Array of tracked cells at time t0.
|
|
23
|
+
tracked_cells_t1 (numpy.ndarray): Array of tracked cells at time t1.
|
|
24
|
+
vertices_t0 (numpy.ndarray, optional): Array of vertices at time t0. Defaults to None.
|
|
25
|
+
vertices_t1 (numpy.ndarray, optional): Array of vertices at time t1. Defaults to None.
|
|
26
|
+
MIN_CUT_OFF_SWAPPING (float, optional): Minimum cutoff score for swapping. Defaults to None.
|
|
27
|
+
|
|
28
|
+
Returns:
|
|
29
|
+
numpy.ndarray: Updated array of tracked cells at time t1.
|
|
30
|
+
|
|
31
|
+
"""
|
|
32
|
+
if __DEBUG__:
|
|
33
|
+
cell_to_check = 0x29d785 # 0x976494 #0x29d785 # 0x746502 #0x96c9ef #0x542358 # 0xf36619 #0x496bbd #0x1bf38f
|
|
34
|
+
# map 9921684 2742149 7628034 9882095 5514072 15951385 4811709
|
|
35
|
+
print('cell to check', cell_to_check, format(cell_to_check, 'x'))
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
# inner_time = timer()
|
|
39
|
+
if vertices_t0 is None:
|
|
40
|
+
vertices_t0 = detect_vertices_and_bonds(tracked_cells_t0)
|
|
41
|
+
vertices_t0 = np.where(vertices_t0 == 255)
|
|
42
|
+
if vertices_t1 is None:
|
|
43
|
+
vertices_t1 = detect_vertices_and_bonds(tracked_cells_t1)
|
|
44
|
+
vertices_t1 = np.where(vertices_t1 == 255)
|
|
45
|
+
# print('current time0', timer() - inner_time)
|
|
46
|
+
|
|
47
|
+
# inner_time = timer()
|
|
48
|
+
t0_cells_n_vertices = associate_vertices_to_cells(tracked_cells_t0, vertices_t0,
|
|
49
|
+
forbidden_colors=(0, 0xFFFFFF),
|
|
50
|
+
output_cells_and_their_neighbors=True)
|
|
51
|
+
t1_cells_n_vertices = associate_vertices_to_cells(tracked_cells_t1, vertices_t1,
|
|
52
|
+
forbidden_colors=(0, 0xFFFFFF),
|
|
53
|
+
output_cells_and_their_neighbors=True)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
if __DEBUG__:
|
|
57
|
+
print('t0_cells_n_vertices', t0_cells_n_vertices)
|
|
58
|
+
print('t1_cells_n_vertices', t1_cells_n_vertices)
|
|
59
|
+
|
|
60
|
+
# inner_time = timer()
|
|
61
|
+
best_pairs = {} # dict that contains pairs --> a good idea
|
|
62
|
+
best_pairs_score = {}
|
|
63
|
+
|
|
64
|
+
for key, value in t0_cells_n_vertices.items():
|
|
65
|
+
max_match = 0.
|
|
66
|
+
best_match = 0
|
|
67
|
+
neighs_at_t0 = t0_cells_n_vertices[key]
|
|
68
|
+
best_pair_size = 0
|
|
69
|
+
|
|
70
|
+
for key2, value2 in t1_cells_n_vertices.items():
|
|
71
|
+
neighs_at_t1 = t1_cells_n_vertices[key2]
|
|
72
|
+
common_neighs = set(neighs_at_t1).intersection(neighs_at_t0)
|
|
73
|
+
|
|
74
|
+
# if two are equal --> try to take the best --> find a way to do it smartly
|
|
75
|
+
if len(common_neighs) > 0 and len(
|
|
76
|
+
common_neighs) >= max_match: # if found twice --> find a way to select which cells to take
|
|
77
|
+
# max_match = len(common_neighs) / ((len(neighs_at_t1)+len(neighs_at_t0))/2)
|
|
78
|
+
|
|
79
|
+
conti = True
|
|
80
|
+
if len(common_neighs) == max_match:
|
|
81
|
+
if len(neighs_at_t1) == best_pair_size:
|
|
82
|
+
if key == key2:
|
|
83
|
+
# do favor identity as much as possible
|
|
84
|
+
conti = True
|
|
85
|
+
# find a trick or random
|
|
86
|
+
elif key2 in neighs_at_t0: # not sure this is very smart but give it a try NB recursion may fix bugs
|
|
87
|
+
conti = False
|
|
88
|
+
elif len(neighs_at_t1) > best_pair_size:
|
|
89
|
+
conti = False
|
|
90
|
+
# si les 2 font la meme taille --> pb à nouveau --> y a t'il une astuce...
|
|
91
|
+
|
|
92
|
+
if conti:
|
|
93
|
+
max_match = len(common_neighs) # / ((len(neighs_at_t1)+len(neighs_at_t0))/2)
|
|
94
|
+
best_match = key2
|
|
95
|
+
best_pair_size = len(neighs_at_t1)
|
|
96
|
+
if __DEBUG__:
|
|
97
|
+
if key == cell_to_check:
|
|
98
|
+
hex_list_0 = [format(x, 'x') for x in neighs_at_t0]
|
|
99
|
+
hex_list_1 = [format(x, 'x') for x in neighs_at_t1]
|
|
100
|
+
print('pairs for a test', neighs_at_t0, neighs_at_t1, len(common_neighs), max_match,
|
|
101
|
+
format(key, 'x'), format(key2, 'x'), key, key2, hex_list_0,
|
|
102
|
+
hex_list_1) # that works --> so indeed the best match is found but then cells are lost and swapped --> why is that --> I still miss a ctrl somewhere --> or a secondary swap that is less good somehow --> check that or a coding error!!
|
|
103
|
+
else:
|
|
104
|
+
if __DEBUG__:
|
|
105
|
+
if key == cell_to_check:
|
|
106
|
+
hex_list_0 = [format(x, 'x') for x in neighs_at_t0]
|
|
107
|
+
hex_list_1 = [format(x, 'x') for x in neighs_at_t1]
|
|
108
|
+
print('discarded pairs for a test', neighs_at_t0, neighs_at_t1, len(common_neighs),
|
|
109
|
+
max_match,
|
|
110
|
+
format(key, 'x'), format(key2, 'x'), key, key2, hex_list_0,
|
|
111
|
+
hex_list_1)
|
|
112
|
+
if __DEBUG__:
|
|
113
|
+
if key == cell_to_check:
|
|
114
|
+
print('final match', max_match, cell_to_check, best_match, format(cell_to_check, 'x'),
|
|
115
|
+
format(best_match, 'x'))
|
|
116
|
+
# or best --> compare to its current match and if better then take it otherwise ignore --> maybe
|
|
117
|
+
|
|
118
|
+
# in fact I take all here but is that really worth ??? --> maybe take one but only if the cell has just one neighbor ???? --> shall I start with 2 ??? that would dramatically reduce the nb of cells I guess --> think about it
|
|
119
|
+
if max_match >= 0.5: # take all cells that have at least half of their neighbs --> 40--> maybe ok
|
|
120
|
+
|
|
121
|
+
# compare neighbors of current match versus those in the previous
|
|
122
|
+
best_pairs[key] = best_match
|
|
123
|
+
best_pairs_score[key] = max_match # will be used later to avoid issues
|
|
124
|
+
|
|
125
|
+
# remove cells that did not change much
|
|
126
|
+
if MIN_CUT_OFF_SWAPPING is not None and MIN_CUT_OFF_SWAPPING>0:
|
|
127
|
+
pairs_to_ignore = [k for k, v in best_pairs_score.items() if v <= MIN_CUT_OFF_SWAPPING]
|
|
128
|
+
for ids in pairs_to_ignore:
|
|
129
|
+
del best_pairs[ids]
|
|
130
|
+
|
|
131
|
+
# finding duplicate values
|
|
132
|
+
# from dictionary using set
|
|
133
|
+
rev_dict = {}
|
|
134
|
+
for key, value in best_pairs.items():
|
|
135
|
+
rev_dict.setdefault(value, set()).add(key)
|
|
136
|
+
|
|
137
|
+
dupes_matches_to_be_removed = []
|
|
138
|
+
for key, value in rev_dict.items():
|
|
139
|
+
|
|
140
|
+
if __DEBUG__:
|
|
141
|
+
if key == cell_to_check:
|
|
142
|
+
print('MEGA THERE######')
|
|
143
|
+
|
|
144
|
+
if len(value) > 1:
|
|
145
|
+
best_match = 0
|
|
146
|
+
max_match = 0
|
|
147
|
+
# keep only best and remove all others from the table
|
|
148
|
+
for iii, val in enumerate(value):
|
|
149
|
+
if __DEBUG__:
|
|
150
|
+
if key == cell_to_check:
|
|
151
|
+
print(iii, val, best_pairs_score[val])
|
|
152
|
+
|
|
153
|
+
if best_pairs_score[val] == max_match:
|
|
154
|
+
if val == key:
|
|
155
|
+
max_match = best_pairs_score[val]
|
|
156
|
+
best_match = val
|
|
157
|
+
elif best_pairs_score[val] > max_match:
|
|
158
|
+
max_match = best_pairs_score[val]
|
|
159
|
+
best_match = val
|
|
160
|
+
|
|
161
|
+
if __DEBUG__:
|
|
162
|
+
if key == cell_to_check:
|
|
163
|
+
print(value, val, max_match)
|
|
164
|
+
|
|
165
|
+
value.remove(best_match)
|
|
166
|
+
# I do have a bug here but I don't get it in fact but ok for now
|
|
167
|
+
dupes_matches_to_be_removed.extend(
|
|
168
|
+
value) # not a good idea --> just need to remove it from the value not from everywhere... is that correct ???? remove it only if present again in duplicates in final stuff after cleaning
|
|
169
|
+
|
|
170
|
+
if __DEBUG__:
|
|
171
|
+
print('dupes_matches_to_be_removed', dupes_matches_to_be_removed,
|
|
172
|
+
[format(x, 'x') for x in dupes_matches_to_be_removed])
|
|
173
|
+
|
|
174
|
+
for key in dupes_matches_to_be_removed:
|
|
175
|
+
if key in best_pairs:
|
|
176
|
+
del best_pairs[key]
|
|
177
|
+
|
|
178
|
+
def swap(pairs_of_cells_to_swap,
|
|
179
|
+
tracked_cells_t1): # this is super slow and would benefit from recoding it in C --> this takes most of the time of the function
|
|
180
|
+
# inner_time = timer()
|
|
181
|
+
clone_tracks = np.array(tracked_cells_t1, copy=True)
|
|
182
|
+
# print('current time', timer() - inner_time)
|
|
183
|
+
|
|
184
|
+
for cell1, cell2 in pairs_of_cells_to_swap.items():
|
|
185
|
+
if cell1 == cell2: # remove identity cause it slows down a lot the process
|
|
186
|
+
continue
|
|
187
|
+
|
|
188
|
+
# if cell2 not in pairs_of_cells_to_swap.keys(): # very slow --> find a way to make that faster
|
|
189
|
+
clone_tracks[tracked_cells_t1 == cell1] = cell2
|
|
190
|
+
clone_tracks[tracked_cells_t1 == cell2] = cell1
|
|
191
|
+
|
|
192
|
+
if __DEBUG__:
|
|
193
|
+
print('converting', cell2, '--> ', cell1, format(cell2, 'x'), '-->', format(cell1, 'x'))
|
|
194
|
+
|
|
195
|
+
# find a way todo things and to not duplicate stuff
|
|
196
|
+
|
|
197
|
+
# pb --> this creates duplications but otherwise it does swap correct cells --> find a way to prevent this --> I need to order swaps and other stuff
|
|
198
|
+
# print('current time2', timer() - inner_time)
|
|
199
|
+
return clone_tracks
|
|
200
|
+
|
|
201
|
+
# inner_time = timer()
|
|
202
|
+
tracked_cells_t1 = swap(best_pairs,
|
|
203
|
+
tracked_cells_t1) # c'est ce truc qui est super lent --> plus de la moitié du temps --> try improve that
|
|
204
|
+
|
|
205
|
+
if __DEBUG__:
|
|
206
|
+
print('best_pairs', len(best_pairs), best_pairs) # maybe best ratio versus its own size
|
|
207
|
+
# TODO maybe save swapped cells --> only show those and blacken all the rest
|
|
208
|
+
|
|
209
|
+
return tracked_cells_t1
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
if __name__ == '__main__':
|
|
213
|
+
|
|
214
|
+
swap_fix_recursions = 3
|
|
215
|
+
|
|
216
|
+
# pb is because after translation the cell is also present in both --> skipped by my stuff --> need detect those cells and chose to get only one in fact
|
|
217
|
+
# pb is because after translation the cell is also present in both --> skipped by my stuff --> need detect those cells and chose to get only one in fact
|
|
218
|
+
# or first do the
|
|
219
|
+
|
|
220
|
+
start_all = timer()
|
|
221
|
+
|
|
222
|
+
# path = '/E/Sample_images/tracking_test/leg_uncropped' # quick test of the tracking algo
|
|
223
|
+
# path = '/E/Sample_images/tracking_test/leg_cropped' # quick test of the tracking algo
|
|
224
|
+
path = '/E/Sample_images/tracking_test/test_uncropped' # quick test of the tracking algo
|
|
225
|
+
# path = '/E/Sample_images/tracking_test/test_cropped' # quick test of the tracking algo
|
|
226
|
+
|
|
227
|
+
if not path.lower().endswith('.txt') and not path.lower().endswith('.lst'):
|
|
228
|
+
images_to_analyze = os.listdir(path)
|
|
229
|
+
images_to_analyze = [os.path.join(path, f) for f in images_to_analyze if
|
|
230
|
+
os.path.isfile(os.path.join(path, f))] # list only files and only if they end by tif
|
|
231
|
+
images_to_analyze = natsorted(images_to_analyze)
|
|
232
|
+
else:
|
|
233
|
+
images_to_analyze = loadlist(path)
|
|
234
|
+
|
|
235
|
+
# get current and next in fact and map next
|
|
236
|
+
|
|
237
|
+
# process first image directly then move on to next
|
|
238
|
+
# images_to_analyze = []
|
|
239
|
+
|
|
240
|
+
# for image 0 --> just randomly fill it with colors --> make sure no dupe and that's it
|
|
241
|
+
|
|
242
|
+
# TODO --> do the first
|
|
243
|
+
|
|
244
|
+
print(images_to_analyze)
|
|
245
|
+
|
|
246
|
+
for l in range(len(images_to_analyze) - 1):
|
|
247
|
+
|
|
248
|
+
start_loop = timer()
|
|
249
|
+
|
|
250
|
+
file_path_0 = images_to_analyze[l]
|
|
251
|
+
file_path_1 = images_to_analyze[l + 1]
|
|
252
|
+
|
|
253
|
+
print('files', file_path_1, file_path_0)
|
|
254
|
+
|
|
255
|
+
if not file_path_1.endswith('.tif') or not file_path_0.endswith('.tif'):
|
|
256
|
+
continue
|
|
257
|
+
|
|
258
|
+
# print('files', file_path_1, file_path_0)
|
|
259
|
+
|
|
260
|
+
filename0_without_ext = os.path.splitext(file_path_0)[0]
|
|
261
|
+
filename1_without_ext = os.path.splitext(file_path_1)[0]
|
|
262
|
+
|
|
263
|
+
tracked_cells_t0 = Img(os.path.join(filename0_without_ext, 'tracked_cells_resized.tif'))
|
|
264
|
+
tracked_cells_t1 = Img(os.path.join(filename1_without_ext, 'tracked_cells_resized.tif'))
|
|
265
|
+
|
|
266
|
+
vertices_t0 = detect_vertices_and_bonds(RGB_to_int24(tracked_cells_t0))
|
|
267
|
+
vertices_t0 = np.where(vertices_t0==255)
|
|
268
|
+
vertices_t1 = detect_vertices_and_bonds(RGB_to_int24(tracked_cells_t1))
|
|
269
|
+
vertices_t1 = np.where(vertices_t1 == 255)
|
|
270
|
+
|
|
271
|
+
# time is always increasing but why is that is some set not reset
|
|
272
|
+
for fix in range(swap_fix_recursions):
|
|
273
|
+
print('swapping fix recursion #', fix)
|
|
274
|
+
begin_loop = timer()
|
|
275
|
+
tracked_cells_t1 = _fix_swapping_internal(tracked_cells_t0, tracked_cells_t1, vertices_t0=vertices_t0, vertices_t1=vertices_t1)
|
|
276
|
+
# tracked_cells_t1 = _fix_swapping_internal(tracked_cells_t0, tracked_cells_t1)
|
|
277
|
+
print('current time', timer() - begin_loop)
|
|
278
|
+
# Img(tracked_cells_t1).save(os.path.join(filename0_without_ext, 'track_after_swap_correction'+ str(fix)+ '.tif'), mode='raw')
|
|
279
|
+
|
|
280
|
+
# example of swapped cells
|
|
281
|
+
print('total time', timer() - start_all) # --> 47 secs --> un peu lent qd meme
|
|
282
|
+
|
|
283
|
+
Img(int24_to_RGB(tracked_cells_t1)).save(os.path.join(filename0_without_ext, 'track_after_swap_correction.tif'), mode='raw')
|
|
284
|
+
|
|
285
|
+
# puis je la faire dynamiquement pr eviter les swaps en live
|
|
286
|
+
# --> void aussi si rentable ou pas
|
|
287
|
+
|
|
288
|
+
# TODO -->
|
|
289
|
+
# this now seems ok and could be done recursively...
|
|
290
|
+
# could also use neighbor cells to reassign most likely cell --> cell that matches most of the surrounding IDs
|
|
291
|
+
|
|
292
|
+
# pas mal --> see how many recursions might be needed to get the stuff to work --> might be time consuming to do plenty of recursion... --> maybe just update the map so that not everything needs be calculated again
|
|
293
|
+
|
|
294
|
+
# if a cell is unsure try reassign it by its neighborood...
|
|
295
|
+
|
|
296
|
+
# TODO --> reidentify cells by neighborhood --> simply use the vertices to detect the neighbors --> split the code
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
# zut le code est pas finalisé...
|
|
2
|
+
|
|
3
|
+
# USE a track object to store the click and its content...
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
# do a code to do smart track correction that avoids creating dupes
|
|
7
|
+
# checks for dupes, etc...
|
|
8
|
+
# can connect tracks and or swap tracks
|
|
9
|
+
|
|
10
|
+
# si les deux couleurs existent dans les deux images alors c'est du swap et faut swapper à partir de la dernière image sinon
|
|
11
|
+
# si chaque couleur existe dans une image et pas dans l'autre alors faire un connect track. Si le connect risque de dupliquer une cellule alors il faut faire un swap de la cellule puis connecter la track afin de ne pas dupliquer les cellules
|
|
12
|
+
# reflechir mais ça devrait etre bon
|
|
13
|
+
# existe t'il un moyen perenne de restaurer les tracks corrected ???? si oui lequel pr que le job ne soit pas perdu
|
|
14
|
+
# peut etre simplement faire un truc du genre update tracks et rajouter les cellules perdues de maniere aleatoire ? pas si simple en fait
|
|
15
|
+
# puis je aussi swapper plusieurs cellules en même temps ??? peut etre mais pas si simple
|
|
16
|
+
# comment faire un algo de swap intelligent
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
# need to store cell id or centroid or centre of area and the frame --> should be sufficient to do everything --> the most robust then detect the color of the other
|
|
20
|
+
|
|
21
|
+
# offer apply to all or just before or after the current image
|
|
22
|
+
# do the codes for that
|
|
23
|
+
|
|
24
|
+
# aussi faire un code pour reappliquer les tracks
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
from batoolset.img import RGB_to_int24, int24_to_RGB
|
|
28
|
+
from timeit import default_timer as timer
|
|
29
|
+
import matplotlib.pyplot as plt
|
|
30
|
+
from batoolset.img import Img
|
|
31
|
+
from batoolset.utils.loadlist import loadlist
|
|
32
|
+
from batoolset.files.tools import smart_name_parser
|
|
33
|
+
from batoolset.tools.logger import TA_logger # logging
|
|
34
|
+
|
|
35
|
+
logger = TA_logger()
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def swap(img, id1, id2):
|
|
39
|
+
"""
|
|
40
|
+
Swaps the pixel values of two IDs in the image.
|
|
41
|
+
|
|
42
|
+
Args:
|
|
43
|
+
img (numpy.ndarray): The input image.
|
|
44
|
+
id1: The first ID to swap.
|
|
45
|
+
id2: The second ID to swap.
|
|
46
|
+
|
|
47
|
+
Returns:
|
|
48
|
+
numpy.ndarray: The modified image with swapped IDs.
|
|
49
|
+
|
|
50
|
+
"""
|
|
51
|
+
idcs1 = img == id1
|
|
52
|
+
idcs2 = img == id2
|
|
53
|
+
img[idcs1], img[idcs2] = id2, id1
|
|
54
|
+
return img
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def assign_id(img, old_id, new_id):
|
|
58
|
+
"""
|
|
59
|
+
Assigns a new ID to all pixels with the specified old ID in the image.
|
|
60
|
+
|
|
61
|
+
Args:
|
|
62
|
+
img (numpy.ndarray): The input image.
|
|
63
|
+
old_id: The ID to be replaced.
|
|
64
|
+
new_id: The new ID to assign.
|
|
65
|
+
|
|
66
|
+
Returns:
|
|
67
|
+
numpy.ndarray: The modified image with updated IDs.
|
|
68
|
+
|
|
69
|
+
"""
|
|
70
|
+
img[img == old_id] = new_id
|
|
71
|
+
return img
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def swap_tracks(lst, frame_of_first_connection, id_t0, id_t1, __preview_only=False):
|
|
75
|
+
"""
|
|
76
|
+
Swaps the IDs of two tracks in a list of tracks.
|
|
77
|
+
|
|
78
|
+
Args:
|
|
79
|
+
lst (list): The list of tracks.
|
|
80
|
+
frame_of_first_connection: The frame at which the first connection occurs.
|
|
81
|
+
id_t0: The ID of the first track.
|
|
82
|
+
id_t1: The ID of the second track.
|
|
83
|
+
__preview_only (bool, optional): Whether to only preview the swap. Defaults to False.
|
|
84
|
+
|
|
85
|
+
"""
|
|
86
|
+
correct_track(lst, frame_of_first_connection, id_t0, id_t1, correction_mode='swap', __preview_only=__preview_only)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def connect_tracks(lst, frame_of_first_connection, id_t0, id_t1, __preview_only=False):
|
|
90
|
+
"""
|
|
91
|
+
Connects two tracks in a list of tracks.
|
|
92
|
+
|
|
93
|
+
Args:
|
|
94
|
+
lst (list): The list of tracks.
|
|
95
|
+
frame_of_first_connection: The frame at which the first connection occurs.
|
|
96
|
+
id_t0: The ID of the first track.
|
|
97
|
+
id_t1: The ID of the second track.
|
|
98
|
+
__preview_only (bool, optional): Whether to only preview the connection. Defaults to False.
|
|
99
|
+
|
|
100
|
+
"""
|
|
101
|
+
correct_track(lst, frame_of_first_connection, id_t0, id_t1, correction_mode='connect', __preview_only=__preview_only)
|
|
102
|
+
|
|
103
|
+
def correct_track(lst, frame_of_first_connection, id_t0, id_t1, correction_mode='connect', __preview_only=False, early_stop=True):
|
|
104
|
+
"""
|
|
105
|
+
Corrects a track in a list of tracks based on the specified correction mode.
|
|
106
|
+
|
|
107
|
+
Args:
|
|
108
|
+
lst (list): The list of tracks.
|
|
109
|
+
frame_of_first_connection: The frame at which the first connection occurs.
|
|
110
|
+
id_t0: The ID of the first track.
|
|
111
|
+
id_t1: The ID of the second track.
|
|
112
|
+
correction_mode (str, optional): The correction mode. Defaults to 'connect'.
|
|
113
|
+
__preview_only (bool, optional): Whether to only preview the correction. Defaults to False.
|
|
114
|
+
early_stop (bool, optional): Whether to stop the correction process early. Defaults to True.
|
|
115
|
+
|
|
116
|
+
"""
|
|
117
|
+
if frame_of_first_connection >= len(lst):
|
|
118
|
+
logger.error('error wrong connection frame nb')
|
|
119
|
+
return
|
|
120
|
+
|
|
121
|
+
for l in range(frame_of_first_connection, len(lst)):
|
|
122
|
+
tracked_cell_path = smart_name_parser(lst[l], ordered_output='tracked_cells_resized.tif')
|
|
123
|
+
tracked_cells_t0 = RGB_to_int24(Img(tracked_cell_path))
|
|
124
|
+
|
|
125
|
+
cellpos_1 = id_t0 in tracked_cells_t0
|
|
126
|
+
cellpos_2 = id_t1 in tracked_cells_t0
|
|
127
|
+
|
|
128
|
+
if not cellpos_1 and not cellpos_2:
|
|
129
|
+
logger.info('cells not found --> ignoring track correction')
|
|
130
|
+
continue
|
|
131
|
+
|
|
132
|
+
if cellpos_1 and cellpos_2 and correction_mode == 'connect':
|
|
133
|
+
logger.info('IDs are both present in the same image, to avoid track ID duplication the desired change will be ignored, please ensure the selected cells are not simply swapped in the image')
|
|
134
|
+
break
|
|
135
|
+
|
|
136
|
+
if not cellpos_1 and cellpos_2 or not cellpos_2 and cellpos_1 and correction_mode == 'swap':
|
|
137
|
+
logger.info('missing cell at frame ' + str(l) + ' swapping ignored')
|
|
138
|
+
if early_stop:
|
|
139
|
+
logger.info('quitting track edition')
|
|
140
|
+
break
|
|
141
|
+
else:
|
|
142
|
+
continue
|
|
143
|
+
|
|
144
|
+
if cellpos_1 and cellpos_2 and correction_mode == 'swap':
|
|
145
|
+
logger.info('swapped cell ids: ' + str(id_t0) + ' and ' + str(id_t1))
|
|
146
|
+
tracked_cells_t0 = swap(tracked_cells_t0, id_t0, id_t1)
|
|
147
|
+
if __preview_only:
|
|
148
|
+
plt.imshow(int24_to_RGB(tracked_cells_t0))
|
|
149
|
+
plt.show()
|
|
150
|
+
else:
|
|
151
|
+
Img(int24_to_RGB(tracked_cells_t0), dimensions='hwc').save(tracked_cell_path, mode='raw')
|
|
152
|
+
else:
|
|
153
|
+
if cellpos_1:
|
|
154
|
+
logger.info('changed id ' + str(id_t0) + ' to ' + str(id_t1))
|
|
155
|
+
tracked_cells_t0 = assign_id(tracked_cells_t0, id_t0, id_t1)
|
|
156
|
+
elif cellpos_2:
|
|
157
|
+
logger.info('changed id ' + str(id_t1) + ' to ' + str(id_t0))
|
|
158
|
+
tracked_cells_t0 = assign_id(tracked_cells_t0, id_t1, id_t0)
|
|
159
|
+
if __preview_only:
|
|
160
|
+
plt.imshow(int24_to_RGB(tracked_cells_t0))
|
|
161
|
+
plt.show()
|
|
162
|
+
else:
|
|
163
|
+
Img(int24_to_RGB(tracked_cells_t0), dimensions='hwc').save(tracked_cell_path, mode='raw')
|
|
164
|
+
|
|
165
|
+
if __name__ == "__main__":
|
|
166
|
+
start_all = timer()
|
|
167
|
+
|
|
168
|
+
# path = '/E/Sample_images/tracking_test'
|
|
169
|
+
path = '/E/Sample_images/sample_images_PA/mini10_fake_swaps/liste.lst'
|
|
170
|
+
# path = '/E/Sample_images/test_tracking_with_shift' # quick test of the algo
|
|
171
|
+
#
|
|
172
|
+
# images_to_analyze = os.listdir(path)
|
|
173
|
+
# images_to_analyze = [os.path.join(path, f) for f in images_to_analyze if
|
|
174
|
+
# os.path.isfile(os.path.join(path, f))] # list only files and only if they end by tif
|
|
175
|
+
# images_to_analyze = natsorted(images_to_analyze)
|
|
176
|
+
|
|
177
|
+
images_to_analyze = loadlist(path)
|
|
178
|
+
|
|
179
|
+
# then do amrutha's code watershed imagendarray (2-D, 3-D, …) of integers--> cool --> will also work for 3D --> then need segmentation in 3D
|
|
180
|
+
#
|
|
181
|
+
#
|
|
182
|
+
# for l in range(len(images_to_analyze)):
|
|
183
|
+
# # in fact I don't even need to reopen the mask file because the track file should suffice ???
|
|
184
|
+
#
|
|
185
|
+
# file_path_0 = images_to_analyze[l]
|
|
186
|
+
# if not file_path_0.endswith('.tif'):
|
|
187
|
+
# continue
|
|
188
|
+
#
|
|
189
|
+
#
|
|
190
|
+
# # print('files', file_path_1, file_path_0)
|
|
191
|
+
#
|
|
192
|
+
# filename0_without_ext = os.path.splitext(file_path_0)[0]
|
|
193
|
+
#
|
|
194
|
+
# tracked_cells_t0 = Img(os.path.join(filename0_without_ext, 'tracked_cells_resized.tif')) # 455:455 + 128, 435:435 + 128 #455:455 + 256, 435:435 + 290 #768:1024,0:256 #0:128, 0:128 #500:500+128, 500:500+128
|
|
195
|
+
#
|
|
196
|
+
#
|
|
197
|
+
#
|
|
198
|
+
#
|
|
199
|
+
# # TODO always get the mask instead of any other files in order to have the minimal nb of files available
|
|
200
|
+
# # mask_t0 = Img(os.path.join(filename0_without_ext, 'handCorrection.tif'))[
|
|
201
|
+
# # ..., 0] # 455:455 + 128, 435:435 + 128 #455:455 + 256, 435:435 + 290 #768:1024,0:256 #0:128, 0:128 #500:500+128, 500:500+128
|
|
202
|
+
#
|
|
203
|
+
#
|
|
204
|
+
# # labels_t0 = measure.label(mask_t0, connectivity=1,
|
|
205
|
+
# # background=255) # FOUR_CONNECTED # use bg = 255 to avoid having to invert the image --> a gain of time I think and could be applied more generally
|
|
206
|
+
#
|
|
207
|
+
# height = tracked_cells_t0.shape[0]
|
|
208
|
+
# width = tracked_cells_t0.shape[1]
|
|
209
|
+
#
|
|
210
|
+
#
|
|
211
|
+
# # could maybe store the ID of duplicate cell as a reseed --> can be a pb with extruding cells but should most often be ok though
|
|
212
|
+
# #
|
|
213
|
+
#
|
|
214
|
+
# labels_tracking_t0 = measure.label(tracked_cells_t0[..., 0], connectivity=1, background=255)
|
|
215
|
+
# rps_t0 = regionprops(labels_tracking_t0)
|
|
216
|
+
#
|
|
217
|
+
#
|
|
218
|
+
# centroids_t0 = []
|
|
219
|
+
# for region in rps_t0:
|
|
220
|
+
# # take regions with large enough areas
|
|
221
|
+
# centroids_t0.append(region.centroid)
|
|
222
|
+
|
|
223
|
+
# try create a dupe to see if it fails -> it will and I should really avoid that
|
|
224
|
+
|
|
225
|
+
# swap_tracks(images_to_analyze, 1, 0x0ef5dd, 0xa28ad6, __preview_only=True) # both cells exist
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
swap_tracks(images_to_analyze, 0, 0x0ef5dd, 0xFAFAFF, __preview_only=True) # one cells exist
|
|
229
|
+
|
|
230
|
+
# ça marche mais reflechir que je ne puisse pas creer des pbs avec ça
|
|
231
|
+
# connect_tracks(images_to_analyze, 0, 0xffdd5d, 0x3b2cdb, __preview_only=True) # both cells exist
|
|
232
|
+
# connect_tracks(images_to_analyze, 0, 0xffdd5d, 0x0ef5dd, __preview_only=True) # create a duplicate cell # --> breaking
|
|
233
|
+
|
|
234
|
+
# codes are the same --> just keep one and
|
|
235
|
+
|
|
236
|
+
# tt a l'air de marcher --> maybe add a flag for recursive --> in fact not...
|
|
237
|
+
|
|
238
|
+
print('total time', timer() - start_all)
|