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.
@@ -21,18 +21,62 @@ from copy import deepcopy
21
21
  import numpy as np
22
22
  import cv2 # named opencv-python
23
23
  import multiprocessing.pool as mp
24
- from numba.typed import List as TList
25
- from numba.typed import Dict as TDict
24
+ from numba.typed import List
25
+ from numba.typed import Dict
26
26
  from numpy.typing import NDArray
27
27
  from typing import Tuple
28
+ import pandas as pd
29
+ from scipy.stats import rankdata
28
30
  from skimage.measure import perimeter
29
31
  from cellects.image_analysis.morphological_operations import cross_33, create_ellipse, spot_size_coefficients
30
- from cellects.image_analysis.image_segmentation import generate_color_space_combination, get_color_spaces, extract_first_pc, combine_color_spaces, apply_filter, otsu_thresholding, get_otsu_threshold, kmeans, windowed_thresholding
31
- from cellects.image_analysis.one_image_analysis_threads import SaveCombinationThread, ProcessFirstImage
32
+ from cellects.image_analysis.image_segmentation import generate_color_space_combination, get_color_spaces, filter_dict, extract_first_pc, combine_color_spaces, apply_filter, otsu_thresholding, get_otsu_threshold, kmeans, windowed_thresholding
33
+ from cellects.image_analysis.one_image_analysis_threads import ProcessImage
32
34
  from cellects.image_analysis.network_functions import NetworkDetection
33
35
  from cellects.utils.formulas import bracket_to_uint8_image_contrast
34
36
  from cellects.utils.utilitarian import split_dict, translate_dict
35
37
 
38
+ def init_params():
39
+ params = {}
40
+ # User set variables:
41
+ params['is_first_image']: bool = False
42
+ params['several_blob_per_arena']: bool = True
43
+ params['blob_nb']: int = None
44
+ params['blob_shape']: str = None
45
+ params['blob_size']: int = None
46
+ params['kmeans_clust_nb']: int = None
47
+ params['arenas_mask']: NDArray = None
48
+ params['ref_image']: NDArray = None
49
+ params['bio_mask']: Tuple = None
50
+ params['back_mask']: Tuple = None
51
+ params['filter_spec']: dict = {'filter1_type': "", 'filter1_param': [.5, 1.], 'filter2_type': "", 'filter2_param': [.5, 1.]}
52
+ # Computed before OneImageAnalysis usage:
53
+
54
+ # Computed in OneImageAnalysis usage:
55
+ params['con_comp_extent']: list = None
56
+ params['max_blob_size']: int = None
57
+ params['total_surface_area']: int = None
58
+ params['out_of_arenas_mask']: NDArray = None
59
+ params['subtract_background']: NDArray = None
60
+ params['are_zigzag']: str = None
61
+ return params
62
+
63
+ def make_one_dict_per_channel():
64
+ colorspace_list = ["bgr", "lab", "hsv", "luv", "hls", "yuv"]
65
+ one_dict_per_channel = List()
66
+ channels = np.array((1, 1, 1), dtype=np.int8)
67
+ csc_dict = Dict()
68
+ csc_dict["bgr"] = channels
69
+ one_dict_per_channel.append(csc_dict)
70
+ for c_space in colorspace_list:
71
+ for j in np.arange(3):
72
+ channels = np.array((0, 0, 0), dtype=np.int8)
73
+ channels[j] = 1
74
+ csc_dict = Dict()
75
+ csc_dict[c_space] = channels
76
+ one_dict_per_channel.append(csc_dict)
77
+ return one_dict_per_channel
78
+
79
+ one_dict_per_channel = make_one_dict_per_channel()
36
80
 
37
81
  class OneImageAnalysis:
38
82
  """
@@ -48,7 +92,7 @@ class OneImageAnalysis:
48
92
 
49
93
  ps: A viewing method displays the image before and after the most advanced modification made in instance
50
94
  """
51
- def __init__(self, image, shape_number=0):
95
+ def __init__(self, image, shape_number: int=1):
52
96
  self.image = image
53
97
  if len(self.image.shape) == 2:
54
98
  self.already_greyscale = True
@@ -72,9 +116,9 @@ class OneImageAnalysis:
72
116
  self.subtract_background2 = None
73
117
  self.im_combinations = None
74
118
  self.bgr = image
75
- self.colorspace_list = TList(("bgr", "lab", "hsv", "luv", "hls", "yuv"))
119
+ self.colorspace_list = List(("bgr", "lab", "hsv", "luv", "hls", "yuv"))
76
120
  self.spot_shapes = None
77
- self.all_c_spaces = TDict()
121
+ self.all_c_spaces = Dict()
78
122
  self.hsv = None
79
123
  self.hls = None
80
124
  self.lab = None
@@ -86,8 +130,8 @@ class OneImageAnalysis:
86
130
  self.drift_mask_coord = None
87
131
  self.saved_csc_nb = 0
88
132
 
89
- def convert_and_segment(self, c_space_dict: dict, color_number=2, biomask: NDArray[np.uint8]=None,
90
- backmask: NDArray[np.uint8]=None, subtract_background: NDArray=None,
133
+ def convert_and_segment(self, c_space_dict: dict, color_number=2, bio_mask: NDArray[np.uint8]=None,
134
+ back_mask: NDArray[np.uint8]=None, subtract_background: NDArray=None,
91
135
  subtract_background2: NDArray=None, rolling_window_segmentation: dict=None,
92
136
  lighter_background: bool=None,
93
137
  allowed_window: NDArray=None, filter_spec: dict=None):
@@ -102,8 +146,8 @@ class OneImageAnalysis:
102
146
 
103
147
  - `c_space_dict` (dict): Dictionary containing color spaces.
104
148
  - `color_number` (int, optional): Number of colors to use in segmentation. Defaults to 2.
105
- - `biomask` (NDArray[np.uint8], optional): Biomask for segmentation. Defaults to None.
106
- - `backmask` (NDArray[np.uint8], optional): Backmask for segmentation. Defaults to None.
149
+ - `bio_mask` (NDArray[np.uint8], optional): Biomask for segmentation. Defaults to None.
150
+ - `back_mask` (NDArray[np.uint8], optional): Backmask for segmentation. Defaults to None.
107
151
  - `subtract_background` (NDArray, optional): Background to subtract. Defaults to None.
108
152
  - `subtract_background2` (NDArray, optional): Second background to subtract. Defaults to None.
109
153
  - rolling_window_segmentation (dict, optional): Flag for grid segmentation. Defaults to None.
@@ -123,13 +167,13 @@ class OneImageAnalysis:
123
167
  if len(all_c_spaces) > len(self.all_c_spaces):
124
168
  self.all_c_spaces = all_c_spaces
125
169
 
126
- self.segmentation(logical=c_space_dict['logical'], color_number=color_number, biomask=biomask,
127
- backmask=backmask, rolling_window_segmentation=rolling_window_segmentation,
170
+ self.segmentation(logical=c_space_dict['logical'], color_number=color_number, bio_mask=bio_mask,
171
+ back_mask=back_mask, rolling_window_segmentation=rolling_window_segmentation,
128
172
  lighter_background=lighter_background, allowed_window=allowed_window, filter_spec=filter_spec)
129
173
 
130
174
 
131
- def segmentation(self, logical: str='None', color_number: int=2, biomask: NDArray[np.uint8]=None,
132
- backmask: NDArray[np.uint8]=None, bio_label=None, bio_label2=None,
175
+ def segmentation(self, logical: str='None', color_number: int=2, bio_mask: NDArray[np.uint8]=None,
176
+ back_mask: NDArray[np.uint8]=None, bio_label=None, bio_label2=None,
133
177
  rolling_window_segmentation: dict=None, lighter_background: bool=None, allowed_window: Tuple=None,
134
178
  filter_spec: dict=None):
135
179
  """
@@ -140,8 +184,8 @@ class OneImageAnalysis:
140
184
  Options are 'Or', 'And', 'Xor'. Default is 'None'.
141
185
  color_number (int): Number of colors to use in segmentation. Must be greater than 2
142
186
  for kmeans clustering. Default is 2.
143
- biomask (NDArray[np.uint8]): Binary mask for biological areas. Default is None.
144
- backmask (NDArray[np.uint8]): Binary mask for background areas. Default is None.
187
+ bio_mask (NDArray[np.uint8]): Binary mask for biological areas. Default is None.
188
+ back_mask (NDArray[np.uint8]): Binary mask for background areas. Default is None.
145
189
  bio_label (Any): Label for biological features. Default is None.
146
190
  bio_label2 (Any): Secondary label for biological features. Default is None.
147
191
  rolling_window_segmentation (dict): Whether to perform grid segmentation. Default is None.
@@ -169,7 +213,7 @@ class OneImageAnalysis:
169
213
 
170
214
  # 3. Do one of the three segmentation algorithms: kmeans, otsu, windowed
171
215
  if color_number > 2:
172
- binary_image, binary_image2, self.bio_label, self.bio_label2 = kmeans(greyscale, greyscale2, color_number, biomask, backmask, logical, bio_label, bio_label2)
216
+ binary_image, binary_image2, self.bio_label, self.bio_label2 = kmeans(greyscale, greyscale2, color_number, bio_mask, back_mask, logical, bio_label, bio_label2)
173
217
  elif rolling_window_segmentation is not None and rolling_window_segmentation['do']:
174
218
  binary_image = windowed_thresholding(greyscale, lighter_background, rolling_window_segmentation['side_len'],
175
219
  rolling_window_segmentation['step'], rolling_window_segmentation['min_int_var'])
@@ -247,8 +291,8 @@ class OneImageAnalysis:
247
291
  # self.adjust_to_drift_correction(c_space_dict['logical'])
248
292
  self.check_if_image_border_attest_drift_correction()
249
293
  self.convert_and_segment(c_space_dict, rolling_window_segmentation=None, allowed_window=self.drift_mask_coord)
250
- disk_size = np.max((3, int(np.floor(np.sqrt(np.min(self.bgr.shape[:2])) / 2))))
251
- disk = create_ellipse(disk_size, disk_size).astype(np.uint8)
294
+ disk_size = int(np.floor(np.sqrt(np.min(self.bgr.shape[:2])) / 2))
295
+ disk = create_ellipse(disk_size, disk_size, min_size=3).astype(np.uint8)
252
296
  self.subtract_background = cv2.morphologyEx(self.image, cv2.MORPH_OPEN, disk)
253
297
  if self.image2 is not None:
254
298
  self.subtract_background2 = cv2.morphologyEx(self.image2, cv2.MORPH_OPEN, disk)
@@ -339,253 +383,169 @@ class OneImageAnalysis:
339
383
  self.binary_image = np.logical_xor(self.binary_image, self.binary_image2)
340
384
  self.binary_image = self.binary_image.astype(np.uint8)
341
385
 
342
- def find_first_im_csc(self, sample_number: int=None, several_blob_per_arena:bool=True, spot_shape: str=None,
343
- spot_size=None, kmeans_clust_nb: int=None, biomask: NDArray[np.uint8]=None,
344
- backmask: NDArray[np.uint8]=None, color_space_dictionaries: TList=None, basic: bool=True):
345
- """
346
- Prepare color space lists, dictionaries and matrices.
347
-
348
- Args:
349
- sample_number: An integer representing the sample number. Defaults to None.
350
- several_blob_per_arena: A boolean indicating whether there are several blobs per arena. Defaults to True.
351
- spot_shape: A string representing the shape of the spot. Defaults to None.
352
- spot_size: An integer representing the size of the spot. Defaults to None.
353
- kmeans_clust_nb: An integer representing the number of clusters for K-means. Defaults to None.
354
- biomask: A 2D numpy array of type np.uint8 representing the bio mask. Defaults to None.
355
- backmask: A 2D numpy array of type np.uint8 representing the background mask. Defaults to None.
356
- color_space_dictionaries: A list of dictionaries containing color space information. Defaults to None.
357
- basic: A boolean indicating whether to process the data basic. Defaults to True.
358
-
359
- Note:
360
- This method processes the input data to find the first image that matches certain criteria, using various color spaces and masks.
361
-
362
- """
363
- logging.info(f"Start automatic detection of the first image")
386
+ def init_combinations_lists(self):
364
387
  self.im_combinations = []
365
- self.saved_images_list = TList()
366
- self.converted_images_list = TList()
388
+ self.saved_images_list = List()
389
+ self.converted_images_list = List()
367
390
  self.saved_color_space_list = list()
368
391
  self.saved_csc_nb = 0
369
392
 
393
+ def find_color_space_combinations(self, params: dict=None, only_bgr: bool = False):
394
+ logging.info(f"Start automatic finding of color space combinations...")
395
+ self.init_combinations_lists()
370
396
  if self.image.any():
371
- self._get_all_color_spaces()
372
- if color_space_dictionaries is None:
373
- if basic:
374
- colorspace_list = ["bgr", "lab", "hsv", "luv", "hls", "yuv"]
397
+ # 1. Set all params
398
+ if params is None:
399
+ params = init_params()
400
+ if params['arenas_mask'] is not None:
401
+ params['out_of_arenas_mask'] = 1 - params['arenas_mask']
402
+ if params['ref_image'] is not None:
403
+ params['ref_image'] = cv2.dilate(params['ref_image'], cross_33)
404
+ if params['several_blob_per_arena']:
405
+ params['con_comp_extent'] = [1, self.binary_image.size // 50]
406
+ else:
407
+ params['con_comp_extent'] = [params['blob_nb'], np.max((params['blob_nb'], self.binary_image.size // 100))]
408
+ im_size = self.image.shape[0] * self.image.shape[1]
409
+
410
+ if not params['several_blob_per_arena'] and params['blob_nb'] is not None and params['blob_nb'] > 1 and params['are_zigzag'] is not None:
411
+ if params['are_zigzag'] == "columns":
412
+ inter_dist = np.mean(np.diff(np.nonzero(self.y_boundaries)))
413
+ elif params['are_zigzag'] == "rows":
414
+ inter_dist = np.mean(np.diff(np.nonzero(self.x_boundaries)))
375
415
  else:
376
- colorspace_list = ["bgr"]
377
- color_space_dictionaries = TList()
378
- for i, c_space in enumerate(colorspace_list):
379
- for i in np.arange(3):
380
- channels = np.array((0, 0, 0), dtype=np.int8)
381
- channels[i] = 1
382
- csc_dict = TDict()
383
- csc_dict[c_space] = channels
384
- color_space_dictionaries.append(csc_dict)
385
-
386
- self.combination_features = np.zeros((len(color_space_dictionaries) + 50, 11), dtype=np.uint32)
387
- unaltered_cc_nb, cc_nb, area, width_std, height_std, area_std, biosum, backsum = 3, 4, 5, 6, 7, 8, 9, 10
388
- self.save_combination_thread = SaveCombinationThread(self)
389
- get_one_channel_result = True
390
- combine_channels = False
391
- logging.info(f"Try detection with each available color space channel, one by one.")
392
- for csc_dict in color_space_dictionaries:
393
- list_args = [self, get_one_channel_result, combine_channels, csc_dict, several_blob_per_arena,
394
- sample_number, spot_size, spot_shape, kmeans_clust_nb, biomask, backmask, None]
395
- ProcessFirstImage(list_args)
396
-
397
- if sample_number is not None and basic:
398
- # Try to add csc together
399
- possibilities = []
400
- if self.saved_csc_nb > 6:
401
- different_color_spaces = np.unique(self.saved_color_space_list)
402
- for color_space in different_color_spaces:
403
- csc_idx = np.nonzero(np.isin(self.saved_color_space_list, color_space))[0]
404
- possibilities.append(csc_idx[0] + np.argmin(self.combination_features[csc_idx, area_std]))
405
- if len(possibilities) <= 6:
406
- remaining_possibilities = np.arange(len(self.saved_color_space_list))
407
- remaining_possibilities = remaining_possibilities[np.logical_not(np.isin(remaining_possibilities, possibilities))]
408
- while len(possibilities) <= 6:
409
- new_possibility = np.argmin(self.combination_features[remaining_possibilities, area_std])
410
- possibilities.append(new_possibility)
411
- remaining_possibilities = remaining_possibilities[remaining_possibilities != new_possibility]
412
-
413
-
414
- pool = mp.ThreadPool(processes=os.cpu_count() - 1)
415
- get_one_channel_result = False
416
- combine_channels = True
417
- list_args = [[self, get_one_channel_result, combine_channels, i, several_blob_per_arena, sample_number,
418
- spot_size, spot_shape, kmeans_clust_nb, biomask, backmask, possibilities] for i in possibilities]
419
- for process_i in pool.imap_unordered(ProcessFirstImage, list_args):
420
- pass
421
-
422
- # Get the most and the least covered images and the 2 best biomask and backmask scores
423
- # To try combinations of those
424
- if self.saved_csc_nb <= 1:
425
- csc_dict = {'bgr': np.array((1, 1, 1))}
426
- list_args = [self, False, False, csc_dict, several_blob_per_arena,
427
- sample_number, spot_size, spot_shape, kmeans_clust_nb, biomask, backmask, None]
428
- process_i = ProcessFirstImage(list_args)
429
- process_i.image = self.bgr.mean(axis=-1)
430
- process_i.binary_image = otsu_thresholding(process_i.image)
431
- process_i.csc_dict = csc_dict
432
- process_i.total_area = process_i.binary_image.sum()
433
- process_i.process_binary_image()
434
- process_i.unaltered_concomp_nb, shapes = cv2.connectedComponents(process_i.validated_shapes)
435
- self.save_combination_features(process_i)
436
- self.combination_features = self.combination_features[:self.saved_csc_nb, :]
437
- fit = np.array([True])
416
+ dist1 = np.mean(np.diff(np.nonzero(self.y_boundaries)))
417
+ dist2 = np.mean(np.diff(np.nonzero(self.x_boundaries)))
418
+ inter_dist = np.max(dist1, dist2)
419
+ if params['blob_shape'] == "rectangle":
420
+ params['max_blob_size'] = np.square(2 * inter_dist)
421
+ else:
422
+ params['max_blob_size'] = np.pi * np.square(inter_dist)
423
+ params['total_surface_area'] = params['max_blob_size'] * self.sample_number
438
424
  else:
439
- coverage = np.argsort(self.combination_features[:self.saved_csc_nb, area])
440
- most1 = coverage[-1]; most2 = coverage[-2]
441
- least1 = coverage[0]; least2 = coverage[1]
442
- if biomask is not None:
443
- bio_sort = np.argsort(self.combination_features[:self.saved_csc_nb, biosum])
444
- bio1 = bio_sort[-1]; bio2 = bio_sort[-2]
445
- if backmask is not None:
446
- back_sort = np.argsort(self.combination_features[:self.saved_csc_nb, backsum])
447
- back1 = back_sort[-1]; back2 = back_sort[-2]
448
-
449
- # Try a logical And between the most covered images
450
- # Should only need one instanciation
451
- process_i = ProcessFirstImage(
452
- [self, False, False, None, several_blob_per_arena, sample_number, spot_size, spot_shape, kmeans_clust_nb, biomask, backmask, None])
453
- process_i.binary_image = np.logical_and(self.saved_images_list[most1], self.saved_images_list[most2]).astype(np.uint8)
454
- process_i.image = self.converted_images_list[most1]
455
- process_i.process_binary_image()
456
- process_i.csc_dict = {list(self.saved_color_space_list[most1].keys())[0]: self.combination_features[most1, :3],
457
- "logical": "And",
458
- list(self.saved_color_space_list[most2].keys())[0] + "2": self.combination_features[most2, :3]}
459
- process_i.unaltered_concomp_nb = np.min(self.combination_features[(most1, most2), unaltered_cc_nb])
460
- process_i.total_area = process_i.binary_image.sum()
461
- self.save_combination_features(process_i)
462
- process_i.image = self.converted_images_list[least1]
463
- process_i.binary_image = np.logical_or(self.saved_images_list[least1], self.saved_images_list[least2]).astype(np.uint8)
464
- process_i.process_binary_image()
465
- process_i.csc_dict = {list(self.saved_color_space_list[least1].keys())[0]: self.combination_features[least1, :3],
466
- "logical": "Or",
467
- list(self.saved_color_space_list[least2].keys())[0] + "2": self.combination_features[least2, :3]}
468
- process_i.unaltered_concomp_nb = np.max(self.combination_features[(least1, least2), unaltered_cc_nb])
469
- process_i.total_area = process_i.binary_image.sum()
470
- self.save_combination_features(process_i)
471
-
472
- # self.save_combination_features(csc_dict, unaltered_concomp_nb, self.binary_image.sum(), biomask, backmask)
473
-
474
- # If most images are very low in biosum or backsum, try to mix them together to improve that score
475
- # Do a logical And between the two best biomasks
476
- if biomask is not None:
477
- if not np.all(np.isin((bio1, bio2), (most1, most2))):
478
- process_i.image = self.converted_images_list[bio1]
479
- process_i.binary_image = np.logical_and(self.saved_images_list[bio1], self.saved_images_list[bio2]).astype(
480
- np.uint8)
481
- process_i.process_binary_image()
482
- process_i.csc_dict = {list(self.saved_color_space_list[bio1].keys())[0]: self.combination_features[bio1, :3],
483
- "logical": "And",
484
- list(self.saved_color_space_list[bio2].keys())[0] + "2": self.combination_features[bio2,:3]}
485
- process_i.unaltered_concomp_nb = np.min(self.combination_features[(bio1, bio2), unaltered_cc_nb])
486
- process_i.total_area = process_i.binary_image.sum()
487
-
488
- self.save_combination_features(process_i)
489
-
490
- # Do a logical And between the two best backmask
491
- if backmask is not None:
492
- if not np.all(np.isin((back1, back2), (most1, most2))):
493
- process_i.image = self.converted_images_list[back1]
494
- process_i.binary_image = np.logical_and(self.saved_images_list[back1], self.saved_images_list[back2]).astype(
495
- np.uint8)
496
- process_i.process_binary_image()
497
- process_i.csc_dict = {list(self.saved_color_space_list[back1].keys())[0]: self.combination_features[back1, :3],
498
- "logical": "And",
499
- list(self.saved_color_space_list[back2].keys())[0] + "2": self.combination_features[back2,:3]}
500
- process_i.unaltered_concomp_nb = np.min(self.combination_features[(back1, back2), unaltered_cc_nb])
501
- process_i.total_area = process_i.binary_image.sum()
502
- self.save_combination_features(process_i)
503
- # Do a logical Or between the best biomask and the best backmask
504
- if biomask is not None and backmask is not None:
505
- if not np.all(np.isin((bio1, back1), (least1, least2))):
506
- process_i.image = self.converted_images_list[bio1]
507
- process_i.binary_image = np.logical_and(self.saved_images_list[bio1], self.saved_images_list[back1]).astype(
508
- np.uint8)
509
- process_i.process_binary_image()
510
- process_i.csc_dict = {list(self.saved_color_space_list[bio1].keys())[0]: self.combination_features[bio1, :3],
511
- "logical": "Or",
512
- list(self.saved_color_space_list[back1].keys())[0] + "2": self.combination_features[back1, :3]}
513
- process_i.unaltered_concomp_nb = np.max(self.combination_features[(bio1, back1), unaltered_cc_nb])
514
- # self.save_combination_features(csc_dict, unaltered_concomp_nb, self.binary_image.sum(), biomask,
515
- # backmask)
516
- process_i.total_area = self.binary_image.sum()
517
- self.save_combination_features(process_i)
518
-
519
- if self.save_combination_thread.is_alive():
520
- self.save_combination_thread.join()
521
- self.combination_features = self.combination_features[:self.saved_csc_nb, :]
522
- # Only keep the row that filled conditions
523
- # Save all combinations if they fulfill the following conditions:
524
- # - Their conncomp number is lower than 3 times the smaller conncomp number.
525
- # - OR The minimal area variations
526
- # - OR The minimal width variations
527
- # - OR The minimal height variations
528
- # - AND/OR their segmentation fits with biomask and backmask
529
- width_std_fit = self.combination_features[:, width_std] == np.min(self.combination_features[:, width_std])
530
- height_std_fit = self.combination_features[:, height_std] == np.min(self.combination_features[:, height_std])
531
- area_std_fit = self.combination_features[:, area_std] < np.min(self.combination_features[:, area_std]) * 10
532
- fit = np.logical_or(np.logical_or(width_std_fit, height_std_fit), area_std_fit)
533
- biomask_fit = np.ones(self.saved_csc_nb, dtype=bool)
534
- backmask_fit = np.ones(self.saved_csc_nb, dtype=bool)
535
- if biomask is not None or backmask is not None:
536
- if biomask is not None:
537
- biomask_fit = self.combination_features[:, biosum] > 0.9 * len(biomask[0])
538
- if backmask is not None:
539
- backmask_fit = self.combination_features[:, backsum] > 0.9 * len(backmask[0])
540
- # First test a logical OR between the precedent options and the mask fits.
541
- fit = np.logical_or(fit, np.logical_and(biomask_fit, backmask_fit))
542
- # If this is not stringent enough, use a logical AND and increase progressively the proportion of pixels that
543
- # must match the biomask and the backmask
544
- if np.sum(fit) > 5:
545
- to_add = 0
546
- while np.sum(fit) > 5 and to_add <= 0.25:
547
- if biomask is not None:
548
- biomask_fit = self.combination_features[:, biosum] > (0.75 + to_add) * len(biomask[0])
549
- if backmask is not None:
550
- backmask_fit = self.combination_features[:, backsum] > (0.75 + to_add) * len(backmask[0])
551
- test_fit = np.logical_and(fit, np.logical_and(biomask_fit, backmask_fit))
552
- if np.sum(test_fit) != 0:
553
- fit = test_fit
554
- to_add += 0.05
555
- # If saved_csc_nb is too low, try bool operators to mix them together to fill holes for instance
556
- # Order the table according to the number of shapes that have been removed by filters
557
- # cc_efficiency_order = np.argsort(self.combination_features[:, unaltered_cc_nb] - self.combination_features[:, cc_nb])
558
- cc_efficiency_order = np.argsort(self.combination_features[:, area_std])
559
- # Save and return a dictionnary containing the selected color space combinations
560
- # and their corresponding binary images
425
+ params['max_blob_size'] = .9 * im_size
426
+ params['total_surface_area'] = .99 * im_size
561
427
 
428
+ # 2. Get color_space_dictionaries
429
+ if only_bgr:
430
+ if not 'bgr' in self.all_c_spaces:
431
+ self.all_c_spaces['bgr'] = self.bgr
432
+ else:
433
+ self._get_all_color_spaces()
434
+
435
+ # 3. Init combination_features table
436
+ unaltered_blob_nb_idx, blob_number_idx, blob_shape_idx, blob_size_idx, total_area_idx, width_std_idx, height_std_idx, area_std_idx, out_of_arenas_idx, in_arena_idx, common_with_ref_idx, bio_sum_idx, back_sum_idx, score_idx = np.arange(3, 17)
437
+ self.factors = ['unaltered_blob_nb', 'blob_nb', 'total_area', 'width_std', 'height_std', 'area_std', 'out_of_arenas', 'in_arenas', 'common_with_ref', 'bio_sum', 'back_sum', 'score']
438
+ self.combination_features = pd.DataFrame(np.zeros((100, len(self.factors)), dtype=np.float64), columns=self.factors)
439
+
440
+ # 4. Test every channel separately
441
+ process = 'one'
442
+ for csc_dict in one_dict_per_channel:
443
+ ProcessImage([self, params, process, csc_dict])
444
+ # If the blob number is known, try applying filters to improve detection
445
+ if params['blob_nb'] is not None and (params['filter_spec'] is None or params['filter_spec']['filter1_type'] == ''):
446
+ if not (self.combination_features['blob_nb'].iloc[:self.saved_csc_nb] == params['blob_nb']).any():
447
+ tested_filters = ['Gaussian', 'Median', 'Mexican hat', 'Laplace', '']
448
+ for tested_filter in tested_filters:
449
+ self.init_combinations_lists()
450
+ params['filter_spec'] = {'filter1_type': tested_filter, 'filter1_param': [.5, 1.], 'filter2_type': "", 'filter2_param': [.5, 1.]}
451
+ if 'Param1' in filter_dict[tested_filter]:
452
+ params['filter_spec']['filter1_param'] = [filter_dict[tested_filter]['Param1']['Default']]
453
+ if 'Param2' in filter_dict[tested_filter]:
454
+ params['filter_spec']['filter1_param'].append(filter_dict[tested_filter]['Param2']['Default'])
455
+ for csc_dict in one_dict_per_channel:
456
+ ProcessImage([self, params, process, csc_dict])
457
+ if (self.combination_features['blob_nb'].iloc[:self.saved_csc_nb] == params['blob_nb']).any():
458
+ break
459
+
460
+ self.score_combination_features()
461
+ # 5. Try adding each valid channel with one another
462
+ # 5.1. Generate an index vector containing, for each color space, the channel maximizing the score
463
+ possibilities = []
464
+ self.all_combined = Dict()
465
+ different_color_spaces = np.unique(self.saved_color_space_list)
466
+ for color_space in different_color_spaces:
467
+ indices = np.nonzero(np.isin(self.saved_color_space_list, color_space))[0]
468
+ csc_idx = indices[0] + np.argmax(self.combination_features.loc[indices, 'score'])
469
+ possibilities.append(csc_idx)
470
+ for k, v in self.saved_color_space_list[csc_idx].items():
471
+ self.all_combined[k] = v
472
+
473
+ # 5.2. Try combining each selected channel with every other in all possible order
474
+ params['possibilities'] = possibilities
475
+ pool = mp.ThreadPool(processes=os.cpu_count() - 1)
476
+ process = 'add'
477
+ list_args = [[self, params, process, i] for i in possibilities]
478
+ for process_i in pool.imap_unordered(ProcessImage, list_args):
479
+ pass
480
+
481
+ # 6. Take a combination of all selected channels and try to remove each color space one by one
482
+ ProcessImage([self, params, 'subtract', 0])
483
+
484
+ # 7. Add PCA:
485
+ ProcessImage([self, params, 'PCA', None])
486
+
487
+ # 8. Make logical operations between pairs of segmentation result
488
+ coverage = np.argsort(self.combination_features['total_area'].iloc[:self.saved_csc_nb])
489
+
490
+ # 8.1 Try a logical And between the most covered images
491
+ most1, most2 = coverage.values[-1], coverage.values[-2]
492
+ operation = {0: most1, 1: most2, 'logical': 'And'}
493
+ ProcessImage([self, params, 'logical', operation])
494
+
495
+ # 8.2 Try a logical Or between the least covered images
496
+ least1, least2 = coverage.values[0], coverage.values[1]
497
+ operation = {0: least1, 1: least2, 'logical': 'Or'}
498
+ ProcessImage([self, params, 'logical', operation])
499
+
500
+
501
+ # 8.3 Try a logical And between the best bio_mask images
502
+ if params['bio_mask'] is not None:
503
+ bio_sort = np.argsort(self.combination_features['bio_sum'].iloc[:self.saved_csc_nb])
504
+ bio1, bio2 = bio_sort.values[-1], bio_sort.values[-2]
505
+ operation = {0: bio1, 1: bio2, 'logical': 'And'}
506
+ ProcessImage([self, params, 'logical', operation])
507
+
508
+ # 8.4 Try a logical And between the best back_mask images
509
+ if params['back_mask'] is not None:
510
+ back_sort = np.argsort(self.combination_features['back_sum'].iloc[:self.saved_csc_nb])
511
+ back1, back2 = back_sort.values[-1], back_sort.values[-2]
512
+ operation = {0: back1, 1: back2, 'logical': 'And'}
513
+ ProcessImage([self, params, 'logical', operation])
514
+
515
+ # 8.5 Try a logical Or between the best bio_mask and the best back_mask images
516
+ if params['bio_mask'] is not None and params['back_mask'] is not None:
517
+ operation = {0: bio1, 1: back1, 'logical': 'Or'}
518
+ ProcessImage([self, params, 'logical', operation])
519
+
520
+ # 9. Order all saved features
521
+ self.combination_features = self.combination_features.iloc[:self.saved_csc_nb, :]
522
+ self.score_combination_features()
523
+ if params['is_first_image'] and params['blob_nb'] is not None:
524
+ distances = np.abs(self.combination_features['blob_nb'] - params['blob_nb'])
525
+ cc_efficiency_order = np.argsort(distances)
526
+ else:
527
+ cc_efficiency_order = np.argsort(self.combination_features['score'])
528
+ cc_efficiency_order = cc_efficiency_order.max() - cc_efficiency_order
529
+
530
+ # 7. Save and return a dictionary containing the selected color space combinations
531
+ # and their corresponding binary images
532
+ self.im_combinations = []
562
533
  for saved_csc in cc_efficiency_order:
563
- if fit[saved_csc]:
534
+ if len(self.saved_color_space_list[saved_csc]) > 0:
564
535
  self.im_combinations.append({})
565
- # self.im_combinations.append({})
566
- # self.im_combinations[len(self.im_combinations) - 1]["csc"] = self.saved_color_space_list[saved_csc]
567
536
  self.im_combinations[len(self.im_combinations) - 1]["csc"] = {}
568
537
  self.im_combinations[len(self.im_combinations) - 1]["csc"]['logical'] = 'None'
569
538
  for k, v in self.saved_color_space_list[saved_csc].items():
570
539
  self.im_combinations[len(self.im_combinations) - 1]["csc"][k] = v
571
- if backmask is not None:
572
- shape_number, shapes = cv2.connectedComponents(self.saved_images_list[saved_csc], connectivity=8)
573
- if np.any(shapes[backmask]):
574
- shapes[np.isin(shapes, np.unique(shapes[backmask]))] = 0
575
- self.saved_images_list[saved_csc] = (shapes > 0).astype(np.uint8)
576
- if biomask is not None:
577
- self.saved_images_list[saved_csc][biomask] = 1
578
- if backmask is not None or biomask is not None:
579
- self.combination_features[saved_csc, cc_nb], shapes = cv2.connectedComponents(self.saved_images_list[saved_csc], connectivity=8)
580
- self.combination_features[saved_csc, cc_nb] -= 1
581
540
  self.im_combinations[len(self.im_combinations) - 1]["binary_image"] = self.saved_images_list[saved_csc]
582
- self.im_combinations[len(self.im_combinations) - 1]["shape_number"] = self.combination_features[saved_csc, cc_nb]
583
- self.im_combinations[len(self.im_combinations) - 1]["converted_image"] = self.converted_images_list[saved_csc]
584
-
541
+ self.im_combinations[len(self.im_combinations) - 1]["converted_image"] = np.round(self.converted_images_list[
542
+ saved_csc]).astype(np.uint8)
543
+ self.im_combinations[len(self.im_combinations) - 1]["shape_number"] = int(self.combination_features['blob_nb'].iloc[saved_csc])
544
+ self.im_combinations[len(self.im_combinations) - 1]['filter_spec']= params['filter_spec']
585
545
  self.saved_color_space_list = []
586
- self.saved_images_list = None
587
- self.converted_images_list = None
588
- self.combination_features = None
546
+ del self.saved_images_list
547
+ del self.converted_images_list
548
+ del self.all_combined
589
549
 
590
550
  def save_combination_features(self, process_i: object):
591
551
  """
@@ -594,7 +554,7 @@ class OneImageAnalysis:
594
554
  Args:
595
555
  process_i (object): The processed image object containing various attributes
596
556
  such as validated_shapes, image, csc_dict, unaltered_concomp_nb,
597
- shape_number, total_area, stats, biomask, and backmask.
557
+ shape_number, total_area, stats, bio_mask, and back_mask.
598
558
 
599
559
  Attributes:
600
560
  processed image object
@@ -603,24 +563,20 @@ class OneImageAnalysis:
603
563
  csc_dict (dict): Color space conversion dictionary
604
564
  """
605
565
  if process_i.validated_shapes.any():
566
+ saved_csc_nb = self.saved_csc_nb
567
+ self.saved_csc_nb += 1
606
568
  self.saved_images_list.append(process_i.validated_shapes)
607
- self.converted_images_list.append(np.round(process_i.image).astype(np.uint8))
569
+ self.converted_images_list.append(bracket_to_uint8_image_contrast(process_i.greyscale))
608
570
  self.saved_color_space_list.append(process_i.csc_dict)
609
- self.combination_features[self.saved_csc_nb, :3] = list(process_i.csc_dict.values())[0]
610
- self.combination_features[
611
- self.saved_csc_nb, 3] = process_i.unaltered_concomp_nb - 1 # unaltered_cc_nb
612
- self.combination_features[self.saved_csc_nb, 4] = process_i.shape_number # cc_nb
613
- self.combination_features[self.saved_csc_nb, 5] = process_i.total_area # area
614
- self.combination_features[self.saved_csc_nb, 6] = np.std(process_i.stats[1:, 2]) # width_std
615
- self.combination_features[self.saved_csc_nb, 7] = np.std(process_i.stats[1:, 3]) # height_std
616
- self.combination_features[self.saved_csc_nb, 8] = np.std(process_i.stats[1:, 4]) # area_std
617
- if process_i.biomask is not None:
618
- self.combination_features[self.saved_csc_nb, 9] = np.sum(
619
- process_i.validated_shapes[process_i.biomask[0], process_i.biomask[1]])
620
- if process_i.backmask is not None:
621
- self.combination_features[self.saved_csc_nb, 10] = np.sum(
622
- (1 - process_i.validated_shapes)[process_i.backmask[0], process_i.backmask[1]])
623
- self.saved_csc_nb += 1
571
+ self.combination_features.iloc[saved_csc_nb, :] = process_i.fact
572
+
573
+ def score_combination_features(self):
574
+ for to_minimize in ['unaltered_blob_nb', 'blob_nb', 'area_std', 'width_std', 'height_std', 'back_sum', 'out_of_arenas']:
575
+ values = rankdata(self.combination_features[to_minimize], method='dense')
576
+ self.combination_features['score'] += values.max() - values
577
+ for to_maximize in ['bio_sum', 'in_arenas', 'common_with_ref']:
578
+ values = rankdata(self.combination_features[to_maximize], method='dense') - 1
579
+ self.combination_features['score'] += values
624
580
 
625
581
  def update_current_images(self, current_combination_id: int):
626
582
  """
@@ -641,262 +597,7 @@ class OneImageAnalysis:
641
597
  self.image = self.im_combinations[current_combination_id]["converted_image"]
642
598
  self.validated_shapes = self.im_combinations[current_combination_id]["binary_image"]
643
599
 
644
- def find_last_im_csc(self, concomp_nb: int, total_surfarea: int, max_shape_size: int, arenas_mask: NDArray=None,
645
- ref_image: NDArray=None, subtract_background: NDArray=None, kmeans_clust_nb: int=None,
646
- biomask: NDArray[np.uint8]=None, backmask: NDArray[np.uint8]=None,
647
- color_space_dictionaries: dict=None, basic: bool=True):
648
- """
649
- Find the last image color space configurations that meets given criteria.
650
-
651
- Args:
652
- concomp_nb (int): A tuple of two integers representing the minimum and maximum number of connected components.
653
- total_surfarea (int): The total surface area required for the image.
654
- max_shape_size (int): The maximum shape size allowed in the image.
655
- arenas_mask (NDArray, optional): A numpy array representing areas inside the field of interest.
656
- ref_image (NDArray, optional): A reference image for comparison.
657
- subtract_background (NDArray, optional): A numpy array representing the background to be subtracted.
658
- kmeans_clust_nb (int, optional): The number of clusters for k-means clustering.
659
- biomask (NDArray[np.uint8], optional): A binary mask for biological structures.
660
- backmask (NDArray[np.uint8], optional): A binary mask for background areas.
661
- color_space_dictionaries (dict, optional): Dictionaries of color space configurations.
662
- basic (bool, optional): A flag indicating whether to process colorspaces basic.
663
-
664
- """
665
- logging.info(f"Start automatic detection of the last image")
666
- self.im_combinations = []
667
- self.saved_images_list = TList()
668
- self.converted_images_list = TList()
669
- self.saved_color_space_list = list()
670
- self.saved_csc_nb = 0
671
-
672
- if self.image.any():
673
- if arenas_mask is None:
674
- arenas_mask = np.ones_like(self.binary_image)
675
- out_of_arenas = 1 - arenas_mask
676
- self._get_all_color_spaces()
677
- if color_space_dictionaries is None:
678
- if basic:
679
- colorspace_list = TList(("bgr", "lab", "hsv", "luv", "hls", "yuv"))
680
- else:
681
- colorspace_list = TList(("lab", "hsv"))
682
- color_space_dictionaries = TList()
683
- channels = np.array((1, 1, 1), dtype=np.int8)
684
- csc_dict = TDict()
685
- csc_dict["bgr"] = channels
686
- color_space_dictionaries.append(csc_dict)
687
- for i, c_space in enumerate(colorspace_list):
688
- for i in np.arange(3):
689
- channels = np.array((0, 0, 0), dtype=np.int8)
690
- channels[i] = 1
691
- csc_dict = TDict()
692
- csc_dict[c_space] = channels
693
- color_space_dictionaries.append(csc_dict)
694
- if ref_image is not None:
695
- ref_image = cv2.dilate(ref_image, cross_33)
696
- else:
697
- ref_image = np.ones(self.bgr.shape[:2], dtype=np.uint8)
698
- out_of_arenas_threshold = 0.01 * out_of_arenas.sum()
699
- self.combination_features = np.zeros((len(color_space_dictionaries) + 50, 10), dtype=np.uint32)
700
- cc_nb_idx, area_idx, out_of_arenas_idx, in_arena_idx, surf_in_common_idx, biosum_idx, backsum_idx = 3, 4, 5, 6, 7, 8, 9
701
- self.save_combination_thread = SaveCombinationThread(self)
702
-
703
- # Start with a PCA:
704
- pca_dict = TDict()
705
- pca_dict['PCA'] = np.array([1, 1, 1], dtype=np.int8)
706
- self.image, explained_variance_ratio, first_pc_vector = extract_first_pc(self.bgr)
707
- self.binary_image = otsu_thresholding(self.image)
708
- nb, shapes = cv2.connectedComponents(self.binary_image)
709
- nb -= 1
710
- surf = self.binary_image.sum()
711
- outside_pixels = np.sum(self.binary_image * out_of_arenas)
712
- inside_pixels = np.sum(self.binary_image * arenas_mask)
713
- in_common = np.sum(ref_image * self.binary_image)
714
- self.converted_images_list.append(self.image)
715
- self.saved_images_list.append(self.binary_image)
716
- self.saved_color_space_list.append(pca_dict)
717
- self.combination_features[self.saved_csc_nb, :3] = list(pca_dict.values())[0]
718
- self.combination_features[self.saved_csc_nb, cc_nb_idx] = nb
719
- self.combination_features[self.saved_csc_nb, area_idx] = surf
720
- self.combination_features[self.saved_csc_nb, out_of_arenas_idx] = outside_pixels
721
- self.combination_features[self.saved_csc_nb, in_arena_idx] = inside_pixels
722
- self.combination_features[self.saved_csc_nb, surf_in_common_idx] = in_common
723
- if biomask is not None:
724
- self.combination_features[self.saved_csc_nb, biosum_idx] = np.sum(
725
- self.binary_image[biomask[0], biomask[1]])
726
- if backmask is not None:
727
- self.combination_features[self.saved_csc_nb, backsum_idx] = np.sum(
728
- (1 - self.binary_image)[backmask[0], backmask[1]])
729
- self.saved_csc_nb += 1
730
-
731
- potentials = TDict()
732
- # One channel processing
733
- for csc_dict in color_space_dictionaries:
734
- self.image = combine_color_spaces(csc_dict, self.all_c_spaces, subtract_background)
735
- if kmeans_clust_nb is not None and (biomask is not None or backmask is not None):
736
- self.binary_image, self.binary_image2, self.bio_label, self.bio_label2 = kmeans(self.image, self.image2, kmeans_clust_nb, biomask, backmask)
737
- else:
738
- self.binary_image = otsu_thresholding(self.image)
739
- surf = np.sum(self.binary_image)
740
- if surf < total_surfarea:
741
- nb, shapes = cv2.connectedComponents(self.binary_image)
742
- outside_pixels = np.sum(self.binary_image * out_of_arenas)
743
- inside_pixels = np.sum(self.binary_image * arenas_mask)
744
- if outside_pixels < inside_pixels:
745
- if (nb > concomp_nb[0] - 1) and (nb < concomp_nb[1]):
746
- in_common = np.sum(ref_image * self.binary_image)
747
- if in_common > 0:
748
- nb, shapes, stats, centroids = cv2.connectedComponentsWithStats(self.binary_image)
749
- nb -= 1
750
- if np.all(np.sort(stats[:, 4])[:-1] < max_shape_size):
751
- c_space = list(csc_dict.keys())[0]
752
- self.converted_images_list.append(self.image)
753
- self.saved_images_list.append(self.binary_image)
754
- self.saved_color_space_list.append(csc_dict)
755
- self.combination_features[self.saved_csc_nb, :3] = csc_dict[c_space]
756
- self.combination_features[self.saved_csc_nb, cc_nb_idx] = nb
757
- self.combination_features[self.saved_csc_nb, area_idx] = surf
758
- self.combination_features[self.saved_csc_nb, out_of_arenas_idx] = outside_pixels
759
- self.combination_features[self.saved_csc_nb, in_arena_idx] = inside_pixels
760
- self.combination_features[self.saved_csc_nb, surf_in_common_idx] = in_common
761
- if biomask is not None:
762
- self.combination_features[self.saved_csc_nb, biosum_idx] = np.sum(
763
- self.binary_image[biomask[0], biomask[1]])
764
- if backmask is not None:
765
- self.combination_features[self.saved_csc_nb, backsum_idx] = np.sum(
766
- (1 - self.binary_image)[backmask[0], backmask[1]])
767
- if np.isin(c_space, list(potentials.keys())):
768
- potentials[c_space] += csc_dict[c_space]
769
- else:
770
- potentials[c_space] = csc_dict[c_space]
771
- self.saved_csc_nb += 1
772
- if len(potentials) > 0:
773
- # All combination processing
774
-
775
- # Add a combination of all selected channels :
776
- self.saved_color_space_list.append(potentials)
777
- self.image = combine_color_spaces(potentials, self.all_c_spaces, subtract_background)
778
- if kmeans_clust_nb is not None and (biomask is not None or backmask is not None):
779
- self.binary_image, self.binary_image2, self.bio_label, self.bio_label2 = kmeans(self.image, kmeans_clust_nb=kmeans_clust_nb, biomask=biomask, backmask=backmask)
780
- else:
781
- self.binary_image = otsu_thresholding(self.image)
782
- surf = self.binary_image.sum()
783
- nb, shapes = cv2.connectedComponents(self.binary_image)
784
- nb -= 1
785
- outside_pixels = np.sum(self.binary_image * out_of_arenas)
786
- inside_pixels = np.sum(self.binary_image * arenas_mask)
787
- in_common = np.sum(ref_image * self.binary_image)
788
- self.converted_images_list.append(self.image)
789
- self.saved_images_list.append(self.binary_image)
790
- self.saved_color_space_list.append(potentials)
791
- self.combination_features[self.saved_csc_nb, :3] = list(potentials.values())[0]
792
- self.combination_features[self.saved_csc_nb, cc_nb_idx] = nb
793
- self.combination_features[self.saved_csc_nb, area_idx] = surf
794
- self.combination_features[self.saved_csc_nb, out_of_arenas_idx] = outside_pixels
795
- self.combination_features[self.saved_csc_nb, in_arena_idx] = inside_pixels
796
- self.combination_features[self.saved_csc_nb, surf_in_common_idx] = in_common
797
- if biomask is not None:
798
- self.combination_features[self.saved_csc_nb, biosum_idx] = np.sum(
799
- self.binary_image[biomask[0], biomask[1]])
800
- if backmask is not None:
801
- self.combination_features[self.saved_csc_nb, backsum_idx] = np.sum(
802
- (1 - self.binary_image)[backmask[0], backmask[1]])
803
- self.saved_csc_nb += 1
804
- # All combination processing
805
- # Try to remove color space one by one
806
- i = 0
807
- original_length = len(potentials)
808
- while np.logical_and(len(potentials) > 1, i < original_length // 2):
809
- color_space_to_remove = TList()
810
- # The while loop until one col space remains or the removal of one implies a strong enough area change
811
- previous_c_space = list(potentials.keys())[-1]
812
- for c_space in potentials.keys():
813
- try_potentials = potentials.copy()
814
- try_potentials.pop(c_space)
815
- if i > 0:
816
- try_potentials.pop(previous_c_space)
817
- self.image = combine_color_spaces(try_potentials, self.all_c_spaces, subtract_background)
818
- if kmeans_clust_nb is not None and (biomask is not None or backmask is not None):
819
- self.binary_image, self.binary_image2, self.bio_label, self.bio_label2 = kmeans(self.image, kmeans_clust_nb=kmeans_clust_nb, biomask=biomask, backmask=backmask)
820
- else:
821
- self.binary_image = otsu_thresholding(self.image)
822
- surf = np.sum(self.binary_image)
823
- if surf < total_surfarea:
824
- nb, shapes = cv2.connectedComponents(self.binary_image)
825
- outside_pixels = np.sum(self.binary_image * out_of_arenas)
826
- inside_pixels = np.sum(self.binary_image * arenas_mask)
827
- if outside_pixels < inside_pixels:
828
- if (nb > concomp_nb[0] - 1) and (nb < concomp_nb[1]):
829
- in_common = np.sum(ref_image * self.binary_image)
830
- if in_common > 0:
831
- nb, shapes, stats, centroids = cv2.connectedComponentsWithStats(self.binary_image)
832
- nb -= 1
833
- if np.all(np.sort(stats[:, 4])[:-1] < max_shape_size):
834
- # If a color space remove fits in the requirements, we store its values
835
- self.converted_images_list.append(self.image)
836
- self.saved_images_list.append(self.binary_image)
837
- self.saved_color_space_list.append(try_potentials)
838
- self.combination_features[self.saved_csc_nb, cc_nb_idx] = nb
839
- self.combination_features[self.saved_csc_nb, area_idx] = surf
840
- self.combination_features[self.saved_csc_nb, out_of_arenas_idx] = outside_pixels
841
- self.combination_features[self.saved_csc_nb, in_arena_idx] = inside_pixels
842
- self.combination_features[self.saved_csc_nb, surf_in_common_idx] = in_common
843
- if biomask is not None:
844
- self.combination_features[self.saved_csc_nb, biosum_idx] = np.sum(
845
- self.binary_image[biomask[0], biomask[1]])
846
- if backmask is not None:
847
- self.combination_features[self.saved_csc_nb, backsum_idx] = np.sum(
848
- (1 - self.binary_image)[backmask[0], backmask[1]])
849
- self.saved_csc_nb += 1
850
- color_space_to_remove.append(c_space)
851
- if i > 0:
852
- color_space_to_remove.append(previous_c_space)
853
- # If it does not (if it did not pass every "if" layers), we definitely remove that color space
854
- previous_c_space = c_space
855
- color_space_to_remove = np.unique(color_space_to_remove)
856
- for remove_col_space in color_space_to_remove:
857
- potentials.pop(remove_col_space)
858
- i += 1
859
- if np.logical_and(len(potentials) > 0, i > 1):
860
- self.converted_images_list.append(self.image)
861
- self.saved_images_list.append(self.binary_image)
862
- self.saved_color_space_list.append(potentials)
863
- self.combination_features[self.saved_csc_nb, :3] = list(potentials.values())[0]
864
- self.combination_features[self.saved_csc_nb, cc_nb_idx] = nb
865
- self.combination_features[self.saved_csc_nb, area_idx] = surf
866
- self.combination_features[self.saved_csc_nb, out_of_arenas_idx] = outside_pixels
867
- self.combination_features[self.saved_csc_nb, in_arena_idx] = inside_pixels
868
- self.combination_features[self.saved_csc_nb, surf_in_common_idx] = in_common
869
- if biomask is not None:
870
- self.combination_features[self.saved_csc_nb, biosum_idx] = np.sum(
871
- self.binary_image[biomask[0], biomask[1]])
872
- if backmask is not None:
873
- self.combination_features[self.saved_csc_nb, backsum_idx] = np.sum(
874
- (1 - self.binary_image)[backmask[0], backmask[1]])
875
- self.saved_csc_nb += 1
876
-
877
- self.combination_features = self.combination_features[:self.saved_csc_nb, :]
878
- # Among all potentials, select the best one, according to criterion decreasing in importance
879
- cc_efficiency_order = np.argsort(self.combination_features[:, surf_in_common_idx] + self.combination_features[:, in_arena_idx] - self.combination_features[:, out_of_arenas_idx])
880
-
881
- # Save and return a dictionnary containing the selected color space combinations
882
- # and their corresponding binary images
883
- self.im_combinations = []
884
- for saved_csc in cc_efficiency_order:
885
- if len(self.saved_color_space_list[saved_csc]) > 0:
886
- self.im_combinations.append({})
887
- self.im_combinations[len(self.im_combinations) - 1]["csc"] = {}
888
- self.im_combinations[len(self.im_combinations) - 1]["csc"]['logical'] = 'None'
889
- for k, v in self.saved_color_space_list[saved_csc].items():
890
- self.im_combinations[len(self.im_combinations) - 1]["csc"][k] = v
891
- self.im_combinations[len(self.im_combinations) - 1]["binary_image"] = self.saved_images_list[saved_csc]
892
- self.im_combinations[len(self.im_combinations) - 1]["converted_image"] = np.round(self.converted_images_list[
893
- saved_csc]).astype(np.uint8)
894
- self.saved_color_space_list = []
895
- self.saved_images_list = None
896
- self.converted_images_list = None
897
- self.combination_features = None
898
-
899
- def network_detection(self, arenas_mask: NDArray=None, pseudopod_min_size: int=50, csc_dict: dict=None, biomask=None, backmask=None):
600
+ def network_detection(self, arenas_mask: NDArray=None, pseudopod_min_size: int=50, csc_dict: dict=None, lighter_background: bool= None, bio_mask=None, back_mask=None):
900
601
  """
901
602
  Network Detection Function
902
603
 
@@ -911,9 +612,11 @@ class OneImageAnalysis:
911
612
  csc_dict : dict, optional
912
613
  A dictionary containing color space conversion parameters. If None,
913
614
  defaults to {'bgr': np.array((1, 1, 1), np.int8), 'logical': 'None'}
914
- biomask : NDArray, optional
615
+ lighter_background : bool, optional
616
+ Whether the background is lighter or not
617
+ bio_mask : NDArray, optional
915
618
  The mask for biological objects in the image.
916
- backmask : NDArray, optional
619
+ back_mask : NDArray, optional
917
620
  The background mask.
918
621
 
919
622
  Notes
@@ -935,7 +638,10 @@ class OneImageAnalysis:
935
638
  greyscale = self.image
936
639
  NetDet = NetworkDetection(greyscale, possibly_filled_pixels=arenas_mask)
937
640
  NetDet.get_best_network_detection_method()
938
- lighter_background = NetDet.greyscale_image[arenas_mask > 0].mean() < NetDet.greyscale_image[arenas_mask== 0].mean()
641
+ if lighter_background is None:
642
+ lighter_background = True
643
+ if arenas_mask.any() and not arenas_mask.all():
644
+ lighter_background = NetDet.greyscale_image[arenas_mask > 0].mean() < NetDet.greyscale_image[arenas_mask == 0].mean()
939
645
  NetDet.detect_pseudopods(lighter_background, pseudopod_min_size=pseudopod_min_size, only_one_connected_component=False)
940
646
  NetDet.merge_network_with_pseudopods()
941
647
  cc_efficiency_order = np.argsort(NetDet.quality_metrics)
@@ -1079,4 +785,3 @@ class OneImageAnalysis:
1079
785
  self.y_boundaries, y_max_sum = self.projection_to_get_peaks_boundaries(axis=1)
1080
786
  self.x_boundaries, x_max_sum = self.projection_to_get_peaks_boundaries(axis=0)
1081
787
 
1082
-