cellects 0.2.7__py3-none-any.whl → 0.3.1__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.
- cellects/core/cellects_threads.py +46 -184
- cellects/core/motion_analysis.py +74 -45
- cellects/core/one_image_analysis.py +233 -528
- cellects/core/program_organizer.py +157 -98
- cellects/core/script_based_run.py +13 -23
- cellects/gui/first_window.py +7 -4
- cellects/gui/image_analysis_window.py +45 -35
- cellects/gui/ui_strings.py +3 -2
- cellects/image_analysis/image_segmentation.py +21 -77
- cellects/image_analysis/morphological_operations.py +9 -13
- cellects/image_analysis/one_image_analysis_threads.py +312 -182
- cellects/image_analysis/shape_descriptors.py +1068 -1067
- cellects/utils/formulas.py +3 -1
- cellects/utils/load_display_save.py +1 -1
- {cellects-0.2.7.dist-info → cellects-0.3.1.dist-info}/METADATA +1 -1
- {cellects-0.2.7.dist-info → cellects-0.3.1.dist-info}/RECORD +20 -20
- {cellects-0.2.7.dist-info → cellects-0.3.1.dist-info}/LICENSE +0 -0
- {cellects-0.2.7.dist-info → cellects-0.3.1.dist-info}/WHEEL +0 -0
- {cellects-0.2.7.dist-info → cellects-0.3.1.dist-info}/entry_points.txt +0 -0
- {cellects-0.2.7.dist-info → cellects-0.3.1.dist-info}/top_level.txt +0 -0
|
@@ -109,7 +109,6 @@ class ImageAnalysisWindow(MainTabsType):
|
|
|
109
109
|
self.available_bio_names = np.arange(1, 1000, dtype=np.uint16)
|
|
110
110
|
self.available_back_names = np.arange(1, 1000, dtype=np.uint16)
|
|
111
111
|
self.parent().po.current_combination_id = 0
|
|
112
|
-
greyscale = len(self.parent().po.first_im.shape) == 2
|
|
113
112
|
|
|
114
113
|
self.display_image = np.zeros((self.parent().im_max_width, self.parent().im_max_width, 3), np.uint8)
|
|
115
114
|
self.display_image = InsertImage(self.display_image, self.parent().im_max_height, self.parent().im_max_width)
|
|
@@ -447,7 +446,8 @@ class ImageAnalysisWindow(MainTabsType):
|
|
|
447
446
|
self.thread["GetFirstIm"] = GetFirstImThread(self.parent())
|
|
448
447
|
self.reinitialize_image_and_masks(self.parent().po.first_im)
|
|
449
448
|
self.thread["GetLastIm"] = GetLastImThread(self.parent())
|
|
450
|
-
self.
|
|
449
|
+
if self.parent().po.all['im_or_vid'] == 0:
|
|
450
|
+
self.thread["GetLastIm"].start()
|
|
451
451
|
self.parent().po.first_image = OneImageAnalysis(self.parent().po.first_im)
|
|
452
452
|
self.thread["FirstImageAnalysis"] = FirstImageAnalysisThread(self.parent())
|
|
453
453
|
self.thread["LastImageAnalysis"] = LastImageAnalysisThread(self.parent())
|
|
@@ -541,7 +541,6 @@ class ImageAnalysisWindow(MainTabsType):
|
|
|
541
541
|
self.thread["GetFirstIm"].start()
|
|
542
542
|
self.thread["GetFirstIm"].message_when_thread_finished.connect(self.reinitialize_image_and_masks)
|
|
543
543
|
self.reinitialize_bio_and_back_legend()
|
|
544
|
-
self.reinitialize_image_and_masks(self.parent().po.first_im)
|
|
545
544
|
|
|
546
545
|
|
|
547
546
|
def several_blob_per_arena_check(self):
|
|
@@ -1019,6 +1018,7 @@ class ImageAnalysisWindow(MainTabsType):
|
|
|
1019
1018
|
self.parent().po.visualize = False
|
|
1020
1019
|
self.parent().po.basic = False
|
|
1021
1020
|
self.parent().po.network_shaped = True
|
|
1021
|
+
self.select_option.clear()
|
|
1022
1022
|
if self.is_first_image_flag:
|
|
1023
1023
|
self.run_first_image_analysis()
|
|
1024
1024
|
else:
|
|
@@ -1298,11 +1298,13 @@ class ImageAnalysisWindow(MainTabsType):
|
|
|
1298
1298
|
self.message.setText("Make sure that scaling metric and spot size are correct")
|
|
1299
1299
|
else:
|
|
1300
1300
|
self.parent().po.vars['convert_for_motion'] = im_combinations[self.parent().po.current_combination_id]["csc"]
|
|
1301
|
-
if "filter_spec" in im_combinations[self.parent().po.current_combination_id]:
|
|
1302
|
-
self.parent().po.vars['filter_spec'] = im_combinations[self.parent().po.current_combination_id]["filter_spec"]
|
|
1303
|
-
self.parent().po.vars['rolling_window_segmentation']['do'] = im_combinations[self.parent().po.current_combination_id]["rolling_window"]
|
|
1304
|
-
self.update_filter_display()
|
|
1305
1301
|
self.decision_label.setText("Do colored contours correctly match cell(s) contours?")
|
|
1302
|
+
if "rolling_window" in im_combinations[self.parent().po.current_combination_id]:
|
|
1303
|
+
self.parent().po.vars['rolling_window_segmentation']['do'] = im_combinations[self.parent().po.current_combination_id]["rolling_window"]
|
|
1304
|
+
if "filter_spec" in im_combinations[self.parent().po.current_combination_id]:
|
|
1305
|
+
self.parent().po.vars['filter_spec'] = im_combinations[self.parent().po.current_combination_id][
|
|
1306
|
+
"filter_spec"]
|
|
1307
|
+
self.update_filter_display()
|
|
1306
1308
|
|
|
1307
1309
|
def generate_csc_editing(self):
|
|
1308
1310
|
"""
|
|
@@ -1593,10 +1595,13 @@ class ImageAnalysisWindow(MainTabsType):
|
|
|
1593
1595
|
def update_filter_display(self):
|
|
1594
1596
|
self.filter1.setCurrentText(self.parent().po.vars['filter_spec']['filter1_type'])
|
|
1595
1597
|
self.filter1_param1.setValue(self.parent().po.vars['filter_spec']['filter1_param'][0])
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
self.
|
|
1599
|
-
|
|
1598
|
+
if len(self.parent().po.vars['filter_spec']['filter1_param']) > 1:
|
|
1599
|
+
self.filter1_param2.setValue(self.parent().po.vars['filter_spec']['filter1_param'][1])
|
|
1600
|
+
if 'filter2_type' in self.parent().po.vars['filter_spec']:
|
|
1601
|
+
self.filter2.setCurrentText(self.parent().po.vars['filter_spec']['filter2_type'])
|
|
1602
|
+
self.filter2_param1.setValue(self.parent().po.vars['filter_spec']['filter2_param'][0])
|
|
1603
|
+
if len(self.parent().po.vars['filter_spec']['filter2_param']) > 1:
|
|
1604
|
+
self.filter2_param2.setValue(self.parent().po.vars['filter_spec']['filter2_param'][1])
|
|
1600
1605
|
|
|
1601
1606
|
def filter1_changed(self):
|
|
1602
1607
|
"""
|
|
@@ -1952,7 +1957,7 @@ class ImageAnalysisWindow(MainTabsType):
|
|
|
1952
1957
|
(self.row21[1].value(), self.row21[2].value(), self.row21[3].value()),
|
|
1953
1958
|
(self.row22[1].value(), self.row22[2].value(), self.row22[3].value()),
|
|
1954
1959
|
(self.row23[1].value(), self.row23[2].value(), self.row23[3].value())),
|
|
1955
|
-
dtype=np.
|
|
1960
|
+
dtype=np.float64)
|
|
1956
1961
|
if self.logical_operator_between_combination_result.currentText() != 'None':
|
|
1957
1962
|
spaces = np.concatenate((spaces, np.array((
|
|
1958
1963
|
self.row21[0].currentText() + "2", self.row22[0].currentText() + "2",
|
|
@@ -1960,7 +1965,7 @@ class ImageAnalysisWindow(MainTabsType):
|
|
|
1960
1965
|
channels = np.concatenate((channels, np.array(((self.row21[1].value(), self.row21[2].value(), self.row21[3].value()),
|
|
1961
1966
|
(self.row22[1].value(), self.row22[2].value(), self.row22[3].value()),
|
|
1962
1967
|
(self.row23[1].value(), self.row23[2].value(), self.row23[3].value())),
|
|
1963
|
-
dtype=np.
|
|
1968
|
+
dtype=np.float64)))
|
|
1964
1969
|
self.csc_dict['logical'] = self.logical_operator_between_combination_result.currentText()
|
|
1965
1970
|
else:
|
|
1966
1971
|
self.csc_dict['logical'] = 'None'
|
|
@@ -2057,29 +2062,37 @@ class ImageAnalysisWindow(MainTabsType):
|
|
|
2057
2062
|
self.select_option.setVisible(False)
|
|
2058
2063
|
self.select_option_label.setVisible(False)
|
|
2059
2064
|
|
|
2060
|
-
def delineate_is_done(self,
|
|
2065
|
+
def delineate_is_done(self, analysis_status: dict):
|
|
2061
2066
|
"""
|
|
2062
2067
|
Update GUI after delineation is complete.
|
|
2063
2068
|
"""
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
self.thread["UpdateImage"].
|
|
2073
|
-
|
|
2074
|
-
|
|
2069
|
+
if analysis_status['continue']:
|
|
2070
|
+
logging.info("Delineation is done, update GUI")
|
|
2071
|
+
self.message.setText(analysis_status["message"])
|
|
2072
|
+
self.arena_shape_label.setVisible(False)
|
|
2073
|
+
self.arena_shape.setVisible(False)
|
|
2074
|
+
self.reinitialize_bio_and_back_legend()
|
|
2075
|
+
self.reinitialize_image_and_masks(self.parent().po.first_image.bgr)
|
|
2076
|
+
self.delineation_done = True
|
|
2077
|
+
if self.thread["UpdateImage"].isRunning():
|
|
2078
|
+
self.thread["UpdateImage"].wait()
|
|
2079
|
+
self.thread["UpdateImage"].start()
|
|
2080
|
+
self.thread["UpdateImage"].message_when_thread_finished.connect(self.automatic_delineation_display_done)
|
|
2075
2081
|
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2082
|
+
try:
|
|
2083
|
+
self.thread['CropScaleSubtractDelineate'].message_from_thread.disconnect()
|
|
2084
|
+
self.thread['CropScaleSubtractDelineate'].message_when_thread_finished.disconnect()
|
|
2085
|
+
except RuntimeError:
|
|
2086
|
+
pass
|
|
2087
|
+
if not self.slower_delineation_flag:
|
|
2088
|
+
self.asking_delineation_flag = True
|
|
2089
|
+
else:
|
|
2090
|
+
self.delineation_done = False
|
|
2091
|
+
self.asking_delineation_flag = False
|
|
2092
|
+
self.auto_delineation_flag = False
|
|
2093
|
+
self.asking_slower_or_manual_delineation_flag = False
|
|
2094
|
+
self.slower_delineation_flag = False
|
|
2095
|
+
self.manual_delineation()
|
|
2083
2096
|
|
|
2084
2097
|
def automatic_delineation_display_done(self, boole):
|
|
2085
2098
|
"""
|
|
@@ -2093,7 +2106,6 @@ class ImageAnalysisWindow(MainTabsType):
|
|
|
2093
2106
|
self.auto_delineation_flag = False
|
|
2094
2107
|
self.select_option_label.setVisible(False)
|
|
2095
2108
|
self.select_option.setVisible(False)
|
|
2096
|
-
|
|
2097
2109
|
self.arena_shape_label.setVisible(True)
|
|
2098
2110
|
self.arena_shape.setVisible(True)
|
|
2099
2111
|
|
|
@@ -2337,8 +2349,6 @@ class ImageAnalysisWindow(MainTabsType):
|
|
|
2337
2349
|
self.yes.setVisible(True)
|
|
2338
2350
|
self.cell.setVisible(False)
|
|
2339
2351
|
self.background.setVisible(False)
|
|
2340
|
-
self.arena_shape_label.setVisible(False)
|
|
2341
|
-
self.arena_shape.setVisible(False)
|
|
2342
2352
|
self.no.setVisible(False)
|
|
2343
2353
|
self.one_blob_per_arena.setVisible(False)
|
|
2344
2354
|
self.one_blob_per_arena_label.setVisible(False)
|
cellects/gui/ui_strings.py
CHANGED
|
@@ -657,8 +657,9 @@ AP["Specimens_have_same_direction"]["label"] = "All specimens have the same dire
|
|
|
657
657
|
AP["Specimens_have_same_direction"]["tips"] = \
|
|
658
658
|
f"""Select to optimize arena detection for specimens moving move in the same direction.
|
|
659
659
|
- **Checked** → Uses motion pattern analysis for arena localization.
|
|
660
|
-
- **Unchecked** → Employs standard centroid
|
|
661
|
-
|
|
660
|
+
- **Unchecked** → Employs standard centroid based algorithm.
|
|
661
|
+
NB:
|
|
662
|
+
- Both options work equally when growth is roughly isotropic.
|
|
662
663
|
"""
|
|
663
664
|
# END_TIP
|
|
664
665
|
|
|
@@ -46,7 +46,7 @@ filter_dict = {"": {'': {}},
|
|
|
46
46
|
'Param2': {'Name': 'Sigma max:', 'Minimum': 0., 'Maximum': 1000., 'Default': 10.}},
|
|
47
47
|
"Hessian": {'Param1': {'Name': 'Sigma min:', 'Minimum': 0., 'Maximum': 1000., 'Default': 1.},
|
|
48
48
|
'Param2': {'Name': 'Sigma max:', 'Minimum': 0., 'Maximum': 1000., 'Default': 10.}},
|
|
49
|
-
"Laplace": {'Param1': {'Name': 'Ksize:', 'Minimum':
|
|
49
|
+
"Laplace": {'Param1': {'Name': 'Ksize:', 'Minimum': 3, 'Maximum': 100, 'Default': 5}},
|
|
50
50
|
"Sharpen": {'': {}},
|
|
51
51
|
"Mexican hat": {'': {}},
|
|
52
52
|
"Farid": {'': {}},
|
|
@@ -142,7 +142,7 @@ def apply_filter(image: NDArray, filter_type: str, param, rescale_to_uint8=False
|
|
|
142
142
|
elif filter_type == "Hessian":
|
|
143
143
|
image = hessian(image, sigmas=np.linspace(param[0], param[1], num=3))
|
|
144
144
|
elif filter_type == "Laplace":
|
|
145
|
-
image = laplace(image, ksize=int(param[0]))
|
|
145
|
+
image = laplace(image, ksize=np.max((3, int(np.ceil(param[0])))))
|
|
146
146
|
elif filter_type == "Sharpen":
|
|
147
147
|
image = cv2.filter2D(image, -1, np.array([[-1, -1, -1], [-1, 9, -1], [-1, -1, -1]]))
|
|
148
148
|
elif filter_type == "Mexican hat":
|
|
@@ -339,65 +339,6 @@ def generate_color_space_combination(bgr_image: NDArray[np.uint8], c_spaces: lis
|
|
|
339
339
|
return greyscale_image, greyscale_image2, all_c_spaces, first_pc_vector
|
|
340
340
|
|
|
341
341
|
|
|
342
|
-
@njit()
|
|
343
|
-
def get_window_allowed_for_segmentation(im_shape: Tuple, mask:NDArray[np.uint8]=None, padding: int=0) -> Tuple[int, int, int, int]:
|
|
344
|
-
"""
|
|
345
|
-
Get the allowed window for segmentation within an image.
|
|
346
|
-
|
|
347
|
-
This function calculates a bounding box (min_y, max_y, min_x, max_x) around
|
|
348
|
-
a segmentation mask or the entire image if no mask is provided.
|
|
349
|
-
|
|
350
|
-
Parameters
|
|
351
|
-
----------
|
|
352
|
-
im_shape : Tuple[int, int]
|
|
353
|
-
The shape of the image (height, width).
|
|
354
|
-
mask : NDArray[np.uint8], optional
|
|
355
|
-
The binary mask for segmentation. Default is `None`.
|
|
356
|
-
padding : int, optional
|
|
357
|
-
Additional padding around the bounding box. Default is 0.
|
|
358
|
-
|
|
359
|
-
Returns
|
|
360
|
-
-------
|
|
361
|
-
Tuple[int, int, int, int]
|
|
362
|
-
A tuple containing the bounding box coordinates (min_y, max_y,
|
|
363
|
-
min_x, max_x).
|
|
364
|
-
|
|
365
|
-
Notes
|
|
366
|
-
-----
|
|
367
|
-
This function uses NumPy operations to determine the bounding box.
|
|
368
|
-
If `mask` is `None`, the full image dimensions are used.
|
|
369
|
-
|
|
370
|
-
Examples
|
|
371
|
-
--------
|
|
372
|
-
>>> im_shape = (500, 400)
|
|
373
|
-
>>> mask = np.zeros((500, 400), dtype=np.uint8)
|
|
374
|
-
>>> mask[200:300, 200:300] = 1
|
|
375
|
-
>>> result = get_window_allowed_for_segmentation(im_shape, mask, padding=10)
|
|
376
|
-
>>> print(result)
|
|
377
|
-
(190, 310, 190, 310)
|
|
378
|
-
|
|
379
|
-
>>> result = get_window_allowed_for_segmentation(im_shape)
|
|
380
|
-
>>> print(result)
|
|
381
|
-
(0, 500, 0, 400)
|
|
382
|
-
"""
|
|
383
|
-
if mask is None or mask.sum() == 0:
|
|
384
|
-
min_y = 0
|
|
385
|
-
min_x = 0
|
|
386
|
-
max_y = im_shape[0]
|
|
387
|
-
max_x = im_shape[1]
|
|
388
|
-
else:
|
|
389
|
-
y, x = np.nonzero(mask)
|
|
390
|
-
min_y = np.min(y)
|
|
391
|
-
min_y = np.max((min_y - padding, 0))
|
|
392
|
-
min_x = np.min(x)
|
|
393
|
-
min_x = np.max((min_x - padding, 0))
|
|
394
|
-
max_y = np.max(y)
|
|
395
|
-
max_y = np.min((max_y + padding + 1, mask.shape[0]))
|
|
396
|
-
max_x = np.max(x)
|
|
397
|
-
max_x = np.min((max_x + padding + 1, mask.shape[0]))
|
|
398
|
-
return min_y, max_y, min_x, max_x
|
|
399
|
-
|
|
400
|
-
|
|
401
342
|
@njit()
|
|
402
343
|
def get_otsu_threshold(image: NDArray):
|
|
403
344
|
"""
|
|
@@ -479,11 +420,10 @@ def otsu_thresholding(image: NDArray) -> NDArray[np.uint8]:
|
|
|
479
420
|
"""
|
|
480
421
|
threshold = get_otsu_threshold(image)
|
|
481
422
|
binary_image = (image > threshold)
|
|
482
|
-
|
|
483
|
-
if binary_image.sum() < binary_image2.sum():
|
|
423
|
+
if binary_image.sum() < binary_image.size / 2:
|
|
484
424
|
return binary_image.astype(np.uint8)
|
|
485
425
|
else:
|
|
486
|
-
return
|
|
426
|
+
return np.logical_not(binary_image).astype(np.uint8)
|
|
487
427
|
|
|
488
428
|
|
|
489
429
|
def segment_with_lum_value(converted_video: NDArray, basic_bckgrnd_values: NDArray, l_threshold, lighter_background: bool) -> Tuple[NDArray, NDArray]:
|
|
@@ -554,7 +494,7 @@ def segment_with_lum_value(converted_video: NDArray, basic_bckgrnd_values: NDArr
|
|
|
554
494
|
|
|
555
495
|
|
|
556
496
|
def kmeans(greyscale: NDArray, greyscale2: NDArray=None, kmeans_clust_nb: int=2,
|
|
557
|
-
|
|
497
|
+
bio_mask: NDArray[np.uint8]=None, back_mask: NDArray[np.uint8]=None, logical: str='None',
|
|
558
498
|
bio_label=None, bio_label2=None, previous_binary_image: NDArray[np.uint8]=None):
|
|
559
499
|
"""
|
|
560
500
|
|
|
@@ -572,9 +512,9 @@ def kmeans(greyscale: NDArray, greyscale2: NDArray=None, kmeans_clust_nb: int=2,
|
|
|
572
512
|
A second greyscale image for logical operations. Default is `None`.
|
|
573
513
|
kmeans_clust_nb : int, optional
|
|
574
514
|
Number of clusters for K-means. Default is `2`.
|
|
575
|
-
|
|
515
|
+
bio_mask : NDArray[np.uint8], optional
|
|
576
516
|
Mask for selecting biological objects. Default is `None`.
|
|
577
|
-
|
|
517
|
+
back_mask : NDArray[np.uint8], optional
|
|
578
518
|
Mask for selecting background regions. Default is `None`.
|
|
579
519
|
logical : str, optional
|
|
580
520
|
Logical operation flag to enable processing of the second image. Default is `'None'`.
|
|
@@ -605,6 +545,10 @@ def kmeans(greyscale: NDArray, greyscale2: NDArray=None, kmeans_clust_nb: int=2,
|
|
|
605
545
|
- Default clustering uses 2 clusters, modify `kmeans_clust_nb` for different needs.
|
|
606
546
|
|
|
607
547
|
"""
|
|
548
|
+
if isinstance(bio_mask, np.ndarray):
|
|
549
|
+
bio_mask = np.nonzero(bio_mask)
|
|
550
|
+
if isinstance(back_mask, np.ndarray):
|
|
551
|
+
back_mask = np.nonzero(back_mask)
|
|
608
552
|
new_bio_label = None
|
|
609
553
|
new_bio_label2 = None
|
|
610
554
|
binary_image2 = None
|
|
@@ -628,13 +572,13 @@ def kmeans(greyscale: NDArray, greyscale2: NDArray=None, kmeans_clust_nb: int=2,
|
|
|
628
572
|
binary_image[kmeans_image == bio_label] = 1
|
|
629
573
|
new_bio_label = bio_label
|
|
630
574
|
else:
|
|
631
|
-
if
|
|
632
|
-
all_labels = kmeans_image[
|
|
575
|
+
if bio_mask is not None:
|
|
576
|
+
all_labels = kmeans_image[bio_mask[0], bio_mask[1]]
|
|
633
577
|
for i in range(kmeans_clust_nb):
|
|
634
578
|
sum_per_label[i] = (all_labels == i).sum()
|
|
635
579
|
new_bio_label = np.argsort(sum_per_label)[1]
|
|
636
|
-
elif
|
|
637
|
-
all_labels = kmeans_image[
|
|
580
|
+
elif back_mask is not None:
|
|
581
|
+
all_labels = kmeans_image[back_mask[0], back_mask[1]]
|
|
638
582
|
for i in range(kmeans_clust_nb):
|
|
639
583
|
sum_per_label[i] = (all_labels == i).sum()
|
|
640
584
|
new_bio_label = np.argsort(sum_per_label)[-2]
|
|
@@ -642,9 +586,9 @@ def kmeans(greyscale: NDArray, greyscale2: NDArray=None, kmeans_clust_nb: int=2,
|
|
|
642
586
|
for i in range(kmeans_clust_nb):
|
|
643
587
|
sum_per_label[i] = (kmeans_image == i).sum()
|
|
644
588
|
new_bio_label = np.argsort(sum_per_label)[-2]
|
|
645
|
-
binary_image
|
|
589
|
+
binary_image += np.isin(kmeans_image, new_bio_label)
|
|
646
590
|
|
|
647
|
-
if logical != 'None' and
|
|
591
|
+
if logical != 'None' and greyscale2 is not None:
|
|
648
592
|
image = greyscale2.reshape((-1, 1))
|
|
649
593
|
image = np.float32(image)
|
|
650
594
|
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 10, 1.0)
|
|
@@ -666,13 +610,13 @@ def kmeans(greyscale: NDArray, greyscale2: NDArray=None, kmeans_clust_nb: int=2,
|
|
|
666
610
|
binary_image2[kmeans_image == bio_label2] = 1
|
|
667
611
|
new_bio_label2 = bio_label2
|
|
668
612
|
else:
|
|
669
|
-
if
|
|
670
|
-
all_labels = kmeans_image[
|
|
613
|
+
if bio_mask is not None:
|
|
614
|
+
all_labels = kmeans_image[bio_mask[0], bio_mask[1]]
|
|
671
615
|
for i in range(kmeans_clust_nb):
|
|
672
616
|
sum_per_label[i] = (all_labels == i).sum()
|
|
673
617
|
new_bio_label2 = np.argsort(sum_per_label)[1]
|
|
674
|
-
elif
|
|
675
|
-
all_labels = kmeans_image[
|
|
618
|
+
elif back_mask is not None:
|
|
619
|
+
all_labels = kmeans_image[back_mask[0], back_mask[1]]
|
|
676
620
|
for i in range(kmeans_clust_nb):
|
|
677
621
|
sum_per_label[i] = (all_labels == i).sum()
|
|
678
622
|
new_bio_label2 = np.argsort(sum_per_label)[-2]
|
|
@@ -646,9 +646,9 @@ def get_line_points(start, end) -> NDArray[int]:
|
|
|
646
646
|
Parameters
|
|
647
647
|
----------
|
|
648
648
|
start : tuple of int
|
|
649
|
-
The starting point coordinates (
|
|
649
|
+
The starting point coordinates (y0, x0).
|
|
650
650
|
end : tuple of int
|
|
651
|
-
The ending point coordinates (
|
|
651
|
+
The ending point coordinates (y1, x1).
|
|
652
652
|
|
|
653
653
|
Returns
|
|
654
654
|
-------
|
|
@@ -1457,7 +1457,7 @@ def dynamically_expand_to_fill_holes(binary_video: NDArray[np.uint8], holes: NDA
|
|
|
1457
1457
|
|
|
1458
1458
|
|
|
1459
1459
|
@njit()
|
|
1460
|
-
def create_ellipse(
|
|
1460
|
+
def create_ellipse(vsize: int, hsize: int, min_size: int=0) -> NDArray[np.uint8]:
|
|
1461
1461
|
"""
|
|
1462
1462
|
Create a 2D array representing an ellipse with given vertical and horizontal sizes.
|
|
1463
1463
|
|
|
@@ -1467,9 +1467,9 @@ def create_ellipse(vsize_in, hsize_in):
|
|
|
1467
1467
|
|
|
1468
1468
|
Parameters
|
|
1469
1469
|
----------
|
|
1470
|
-
|
|
1470
|
+
vsize : int
|
|
1471
1471
|
Vertical size (number of rows) in the output 2D array.
|
|
1472
|
-
|
|
1472
|
+
hsize : int
|
|
1473
1473
|
Horizontal size (number of columns) in the output 2D array.
|
|
1474
1474
|
|
|
1475
1475
|
Returns
|
|
@@ -1477,14 +1477,10 @@ def create_ellipse(vsize_in, hsize_in):
|
|
|
1477
1477
|
NDArray[bool]
|
|
1478
1478
|
A boolean NumPy array of shape `(vsize, hsize)` where `True` indicates that a pixel lies within or on
|
|
1479
1479
|
the boundary of an ellipse centered at the image's center with radii determined by half of the dimensions.
|
|
1480
|
-
|
|
1481
|
-
Notes
|
|
1482
|
-
-----
|
|
1483
|
-
If either vertical or horizontal size is zero, it defaults to 3 as in the original class behavior.
|
|
1484
1480
|
"""
|
|
1485
1481
|
# Use default values if input sizes are zero
|
|
1486
|
-
vsize =
|
|
1487
|
-
hsize =
|
|
1482
|
+
vsize = min_size if vsize == 0 else vsize
|
|
1483
|
+
hsize = min_size if hsize == 0 else hsize
|
|
1488
1484
|
|
|
1489
1485
|
# Compute radii (half of each size)
|
|
1490
1486
|
vr = hsize // 2
|
|
@@ -1499,7 +1495,7 @@ def create_ellipse(vsize_in, hsize_in):
|
|
|
1499
1495
|
lhs = ((x - hr) ** 2 / (hr ** 2)) + ((y - vr) ** 2 / (vr ** 2))
|
|
1500
1496
|
result[i, j] = lhs <= 1
|
|
1501
1497
|
else:
|
|
1502
|
-
result[
|
|
1498
|
+
result[hr, vr] = True
|
|
1503
1499
|
return result
|
|
1504
1500
|
|
|
1505
1501
|
rhombus_55 = create_ellipse(5, 5).astype(np.uint8)
|
|
@@ -1690,7 +1686,7 @@ def get_bb_with_moving_centers(motion_list: list, all_specimens_have_same_direct
|
|
|
1690
1686
|
k_size = 3
|
|
1691
1687
|
if original_shape_hsize is not None:
|
|
1692
1688
|
k_size = int((np.ceil(original_shape_hsize / 5) * 2) + 1)
|
|
1693
|
-
big_kernel = create_ellipse(k_size, k_size).astype(np.uint8)
|
|
1689
|
+
big_kernel = create_ellipse(k_size, k_size, min_size=3).astype(np.uint8)
|
|
1694
1690
|
|
|
1695
1691
|
ordered_stats, ordered_centroids, ordered_image = rank_from_top_to_bottom_from_left_to_right(
|
|
1696
1692
|
binary_image, y_boundaries, get_ordered_image=True)
|