cellects 0.2.6__py3-none-any.whl → 0.3.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.
- 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.6.dist-info → cellects-0.3.0.dist-info}/METADATA +1 -1
- {cellects-0.2.6.dist-info → cellects-0.3.0.dist-info}/RECORD +20 -20
- {cellects-0.2.6.dist-info → cellects-0.3.0.dist-info}/LICENSE +0 -0
- {cellects-0.2.6.dist-info → cellects-0.3.0.dist-info}/WHEEL +0 -0
- {cellects-0.2.6.dist-info → cellects-0.3.0.dist-info}/entry_points.txt +0 -0
- {cellects-0.2.6.dist-info → cellects-0.3.0.dist-info}/top_level.txt +0 -0
|
@@ -1,24 +1,19 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
|
-
"""Module
|
|
2
|
+
"""Module implementing an image processing pipeline for segmentation and analysis.
|
|
3
3
|
|
|
4
|
-
This module
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
The
|
|
9
|
-
and geometric filtering based on size/shape constraints.
|
|
4
|
+
This module defines functionality to process images using various color space
|
|
5
|
+
combinations, filters, and segmentation techniques like K-means clustering or
|
|
6
|
+
Otsu thresholding. It supports logical operations between images and iterative refinement of
|
|
7
|
+
color space combinations by adding/removing channels based on validation criteria.
|
|
8
|
+
The pipeline evaluates resulting binary masks for blob statistics and spatial relationships.
|
|
10
9
|
|
|
11
10
|
Classes
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
Functions (in ProcessFirstImage)
|
|
16
|
-
shape_selection : Filters shapes by size thresholds and geometric criteria.
|
|
17
|
-
kmeans : Performs clustering-based image segmentation into specified number of clusters.
|
|
18
|
-
process_binary_image : Validates detected shapes against area constraints and spot count targets.
|
|
11
|
+
ProcessImage : Processes an image according to parameterized instructions, performing color space combination,
|
|
12
|
+
filtering, segmentation, and shape validation.
|
|
19
13
|
|
|
20
14
|
Notes
|
|
21
|
-
|
|
15
|
+
Relies on external functions from cellects.image_analysis module for core operations like
|
|
16
|
+
color space combination, filtering, PCA extraction, and segmentation.
|
|
22
17
|
"""
|
|
23
18
|
import threading
|
|
24
19
|
import logging
|
|
@@ -26,145 +21,299 @@ import numpy as np
|
|
|
26
21
|
import cv2
|
|
27
22
|
from numpy.typing import NDArray
|
|
28
23
|
from typing import Tuple
|
|
29
|
-
from
|
|
24
|
+
from numba.typed import Dict, List
|
|
25
|
+
import pandas as pd
|
|
26
|
+
from cellects.image_analysis.image_segmentation import combine_color_spaces, apply_filter, kmeans, extract_first_pc, otsu_thresholding
|
|
30
27
|
from cellects.image_analysis.morphological_operations import shape_selection
|
|
31
28
|
|
|
32
29
|
|
|
33
|
-
class
|
|
30
|
+
class ProcessImage:
|
|
34
31
|
"""
|
|
35
|
-
A class for processing
|
|
32
|
+
A class for processing an image according to a list of parameters.
|
|
36
33
|
"""
|
|
37
34
|
def __init__(self, l):
|
|
38
35
|
"""
|
|
39
36
|
Arguments:
|
|
40
37
|
list : list
|
|
41
|
-
|
|
42
38
|
"""
|
|
39
|
+
self.stats = None
|
|
40
|
+
self.greyscale = None
|
|
43
41
|
self.start_processing(l)
|
|
44
42
|
|
|
45
43
|
def start_processing(self, l: list):
|
|
46
44
|
"""
|
|
45
|
+
Begin processing tasks based on the given process type.
|
|
47
46
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
47
|
+
Parameters
|
|
48
|
+
----------
|
|
49
|
+
l : list
|
|
50
|
+
A list containing parameters and instructions for processing.
|
|
51
|
+
Expected structure:
|
|
52
|
+
- First element: self.parent (object)
|
|
53
|
+
- Second element: params (dict)
|
|
54
|
+
- Third element: process type (str) ('one', 'PCA', 'add', 'subtract', or 'logical')
|
|
55
|
+
- Fourth element (if applicable): additional parameters based on process type.
|
|
53
56
|
|
|
54
|
-
|
|
57
|
+
Notes
|
|
58
|
+
-----
|
|
59
|
+
This function modifies instance attributes directly and performs various operations based on the `process` type.
|
|
55
60
|
"""
|
|
56
61
|
self.parent = l[0]
|
|
57
|
-
|
|
58
|
-
|
|
62
|
+
self.fact = pd.DataFrame(np.zeros((1, len(self.parent.factors)), dtype=np.uint32), columns=self.parent.factors)
|
|
63
|
+
self.params = l[1]
|
|
64
|
+
process = l[2]
|
|
59
65
|
self.all_c_spaces = self.parent.all_c_spaces
|
|
60
|
-
|
|
61
|
-
self.sample_number = l[5]
|
|
62
|
-
self.horizontal_size = l[6]
|
|
63
|
-
self.spot_shape = l[7]
|
|
64
|
-
kmeans_clust_nb = l[8]
|
|
65
|
-
self.biomask = l[9]
|
|
66
|
-
self.backmask = l[10]
|
|
67
|
-
if get_one_channel_result:
|
|
66
|
+
if process == 'one':
|
|
68
67
|
self.csc_dict = l[3]
|
|
69
|
-
self.
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
self.
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
biomask (NDArray[np.uint8]): Optional mask for biological regions. Default is None.
|
|
130
|
-
backmask (NDArray[np.uint8]): Optional mask for background regions. Default is None.
|
|
131
|
-
bio_label (int): The label assigned to the biological region. Default is None.
|
|
132
|
-
|
|
133
|
-
Returns:
|
|
134
|
-
None
|
|
135
|
-
|
|
136
|
-
Note:
|
|
137
|
-
This method modifies the `binary_image` and `bio_label` attributes of the instance.
|
|
138
|
-
|
|
139
|
-
"""
|
|
140
|
-
image = self.image.reshape((-1, 1))
|
|
141
|
-
image = np.float32(image)
|
|
142
|
-
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 10, 1.0)
|
|
143
|
-
compactness, label, center = cv2.kmeans(image, cluster_number, None, criteria, attempts=10, flags=cv2.KMEANS_RANDOM_CENTERS)
|
|
144
|
-
kmeans_image = np.uint8(label.flatten().reshape(self.image.shape[:2]))
|
|
145
|
-
sum_per_label = np.zeros(cluster_number)
|
|
146
|
-
self.binary_image = np.zeros(self.image.shape[:2], np.uint8)
|
|
147
|
-
if bio_label is not None:
|
|
148
|
-
self.binary_image[np.nonzero(kmeans_image == bio_label)] = 1
|
|
149
|
-
self.bio_label = bio_label
|
|
68
|
+
self.combine_and_segment()
|
|
69
|
+
self.evaluate_segmentation()
|
|
70
|
+
elif process == 'PCA':
|
|
71
|
+
self.pca_and_segment()
|
|
72
|
+
self.evaluate_segmentation()
|
|
73
|
+
elif process == 'add':
|
|
74
|
+
self.try_adding_channels(l[3])
|
|
75
|
+
elif process == 'subtract':
|
|
76
|
+
self.try_subtracting_channels()
|
|
77
|
+
elif process == 'logical':
|
|
78
|
+
self.operation = l[3]
|
|
79
|
+
self.apply_logical_operation()
|
|
80
|
+
|
|
81
|
+
def combine_and_segment(self, csc_dict: Dict=None):
|
|
82
|
+
"""
|
|
83
|
+
Combines color spaces and segments images into binary masks using either K-means clustering or Otsu's thresholding based on specified parameters.
|
|
84
|
+
|
|
85
|
+
Parameters
|
|
86
|
+
----------
|
|
87
|
+
csc_dict : dict or None
|
|
88
|
+
Optional dictionary mapping color space abbreviations (e.g., 'bgr', 'hsv') to their relative contribution coefficients for combination. If not provided, uses the instance's `self.csc_dict` attribute instead.
|
|
89
|
+
|
|
90
|
+
"""
|
|
91
|
+
if csc_dict is None:
|
|
92
|
+
csc_dict = self.csc_dict
|
|
93
|
+
self.image = combine_color_spaces(csc_dict, self.all_c_spaces)
|
|
94
|
+
self.apply_filter_and_segment()
|
|
95
|
+
|
|
96
|
+
def pca_and_segment(self):
|
|
97
|
+
"""
|
|
98
|
+
Extract the first principal component and perform segmentation.
|
|
99
|
+
|
|
100
|
+
This method extracts the first principal component from the 'bgr' color space
|
|
101
|
+
and performs k-means clustering or Otsu thresholding to segment the image based on the
|
|
102
|
+
parameters provided.
|
|
103
|
+
"""
|
|
104
|
+
self.image, _, first_pc_vector = extract_first_pc(self.all_c_spaces['bgr'])
|
|
105
|
+
self.csc_dict = Dict()
|
|
106
|
+
self.csc_dict['bgr'] = first_pc_vector
|
|
107
|
+
self.apply_filter_and_segment()
|
|
108
|
+
|
|
109
|
+
def apply_filter_and_segment(self):
|
|
110
|
+
self.greyscale = self.image
|
|
111
|
+
if self.params['filter_spec'] is not None and self.params['filter_spec']["filter1_type"] != "":
|
|
112
|
+
self.greyscale = apply_filter(self.greyscale, self.params['filter_spec']["filter1_type"], self.params['filter_spec']["filter1_param"])
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
if self.params['kmeans_clust_nb'] is not None and (self.params['bio_mask'] is not None or self.params['back_mask'] is not None):
|
|
116
|
+
self.binary_image, _, self.bio_label, _ = kmeans(self.greyscale, None, self.params['kmeans_clust_nb'],
|
|
117
|
+
self.params['bio_mask'], self.params['back_mask'])
|
|
118
|
+
else:
|
|
119
|
+
self.binary_image = otsu_thresholding(self.greyscale)
|
|
120
|
+
self.validated_shapes = self.binary_image
|
|
121
|
+
|
|
122
|
+
def evaluate_segmentation(self):
|
|
123
|
+
"""
|
|
124
|
+
Use the filtering algorithm based on the kind of image to analyse
|
|
125
|
+
"""
|
|
126
|
+
if self.params['is_first_image']:
|
|
127
|
+
self.eval_first_image()
|
|
150
128
|
else:
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
129
|
+
self.eval_any_image()
|
|
130
|
+
|
|
131
|
+
def eval_first_image(self):
|
|
132
|
+
"""
|
|
133
|
+
First image filtering process for binary images.
|
|
134
|
+
|
|
135
|
+
This method processes the first binary image by identifying connected components, computing
|
|
136
|
+
the total area of the image and its potential size limits. If the number of blobs and their
|
|
137
|
+
total area fall within acceptable thresholds, it proceeds with additional processing and saving.
|
|
138
|
+
"""
|
|
139
|
+
self.fact['unaltered_blob_nb'], shapes = cv2.connectedComponents(self.binary_image)
|
|
140
|
+
self.fact['unaltered_blob_nb'] -= 1
|
|
141
|
+
if 1 <= self.fact['unaltered_blob_nb'].values[0] < 10000:
|
|
142
|
+
self.fact['total_area'] = np.sum(self.binary_image)
|
|
143
|
+
inf_lim = np.min((20, np.ceil(self.binary_image.size / 1000)))
|
|
144
|
+
if inf_lim < self.fact['total_area'].values[0] < self.binary_image.size * 0.9:
|
|
145
|
+
self.process_first_binary_image()
|
|
146
|
+
self.save_combination()
|
|
147
|
+
|
|
148
|
+
def eval_any_image(self):
|
|
149
|
+
"""
|
|
150
|
+
|
|
151
|
+
Summarizes the binary image analysis and determines blob characteristics.
|
|
152
|
+
|
|
153
|
+
Evaluates a binary image to determine various attributes like surface area,
|
|
154
|
+
blob number, and their relative positions within specified masks. It also
|
|
155
|
+
checks for common areas with a reference image and saves the combination if
|
|
156
|
+
certain conditions are met.
|
|
157
|
+
"""
|
|
158
|
+
surf = self.binary_image.sum()
|
|
159
|
+
if surf < self.params['total_surface_area']:
|
|
160
|
+
self.fact['unaltered_blob_nb'], shapes = cv2.connectedComponents(self.binary_image)
|
|
161
|
+
self.fact['unaltered_blob_nb'] -= 1
|
|
162
|
+
test: bool = True
|
|
163
|
+
if self.params['arenas_mask'] is not None:
|
|
164
|
+
self.fact['out_of_arenas'] = np.sum(self.binary_image * self.params['out_of_arenas_mask'])
|
|
165
|
+
self.fact['in_arenas'] = np.sum(self.binary_image * self.params['arenas_mask'])
|
|
166
|
+
test = self.fact['out_of_arenas'].values[0] < self.fact['in_arenas'].values[0]
|
|
167
|
+
if test:
|
|
168
|
+
if self.params['con_comp_extent'][0] <= self.fact['unaltered_blob_nb'].values[0] <= self.params['con_comp_extent'][1]:
|
|
169
|
+
if self.params['ref_image'] is not None:
|
|
170
|
+
self.fact['common_with_ref'] = np.sum(self.params['ref_image'] * self.binary_image)
|
|
171
|
+
test = self.fact['common_with_ref'].values[0] > 0
|
|
172
|
+
if test:
|
|
173
|
+
self.fact['blob_nb'], shapes, self.stats, centroids = cv2.connectedComponentsWithStats(self.binary_image)
|
|
174
|
+
self.fact['blob_nb'] -= 1
|
|
175
|
+
if np.all(np.sort(self.stats[:, 4])[:-1] < self.params['max_blob_size']):
|
|
176
|
+
self.save_combination()
|
|
177
|
+
|
|
178
|
+
def apply_logical_operation(self):
|
|
179
|
+
"""
|
|
180
|
+
Apply a logical operation between two saved images.
|
|
181
|
+
|
|
182
|
+
This method applies a specified logical operation ('And' or 'Or')
|
|
183
|
+
between two images stored in the parent's saved_images_list. The result
|
|
184
|
+
is stored as a binary image and validated, with color space information
|
|
185
|
+
updated accordingly.
|
|
186
|
+
|
|
187
|
+
Notes
|
|
188
|
+
-----
|
|
189
|
+
This method modifies the following instance attributes:
|
|
190
|
+
- `binary_image`
|
|
191
|
+
- `validated_shapes`
|
|
192
|
+
- `image`
|
|
193
|
+
- `csc_dict`
|
|
194
|
+
"""
|
|
195
|
+
im1 = self.parent.saved_images_list[self.operation[0]]
|
|
196
|
+
im2 = self.parent.saved_images_list[self.operation[1]]
|
|
197
|
+
if self.operation['logical'] == 'And':
|
|
198
|
+
self.binary_image = np.logical_and(im1, im2).astype(np.uint8)
|
|
199
|
+
elif self.operation['logical'] == 'Or':
|
|
200
|
+
self.binary_image = np.logical_or(im1, im2).astype(np.uint8)
|
|
201
|
+
self.validated_shapes = self.binary_image
|
|
202
|
+
self.greyscale = self.parent.converted_images_list[self.operation[0]]
|
|
203
|
+
csc1 = self.parent.saved_color_space_list[self.operation[0]]
|
|
204
|
+
csc2 = self.parent.saved_color_space_list[self.operation[1]]
|
|
205
|
+
self.csc_dict = {}
|
|
206
|
+
for k, v in csc1.items():
|
|
207
|
+
self.csc_dict[k] = v
|
|
208
|
+
for k, v in csc2.items():
|
|
209
|
+
self.csc_dict[k] = v
|
|
210
|
+
self.csc_dict['logical'] = self.operation['logical']
|
|
211
|
+
self.evaluate_segmentation()
|
|
212
|
+
|
|
213
|
+
def try_adding_channels(self, i: int):
|
|
214
|
+
"""
|
|
215
|
+
Try adding channels to the current color space combination.
|
|
216
|
+
|
|
217
|
+
Extend the functionality of a selected color space combination by attempting
|
|
218
|
+
to add channels from other combinations, evaluating the results based on
|
|
219
|
+
the number of shapes and total area.
|
|
220
|
+
|
|
221
|
+
Parameters
|
|
222
|
+
----------
|
|
223
|
+
i : int
|
|
224
|
+
The index of the saved color space and combination features to start with.
|
|
225
|
+
"""
|
|
226
|
+
saved_color_space_list = self.parent.saved_color_space_list
|
|
227
|
+
combination_features = self.parent.combination_features
|
|
228
|
+
self.csc_dict = saved_color_space_list[i]
|
|
229
|
+
previous_shape_number = combination_features.loc[i, 'blob_nb']
|
|
230
|
+
previous_sum = combination_features.loc[i, 'total_area']
|
|
231
|
+
for j in self.params['possibilities'][::-1]:
|
|
232
|
+
csc_dict2 = saved_color_space_list[j]
|
|
233
|
+
csc_dict = self.csc_dict.copy()
|
|
234
|
+
keys = list(csc_dict.keys())
|
|
235
|
+
|
|
236
|
+
k2 = list(csc_dict2.keys())[0]
|
|
237
|
+
v2 = csc_dict2[k2]
|
|
238
|
+
if np.isin(k2, keys) and np.sum(v2 * csc_dict[k2]) != 0:
|
|
239
|
+
break
|
|
240
|
+
for factor in [2, 1]:
|
|
241
|
+
if np.isin(k2, keys):
|
|
242
|
+
csc_dict[k2] += v2 * factor
|
|
243
|
+
else:
|
|
244
|
+
csc_dict[k2] = v2 * factor
|
|
245
|
+
self.combine_and_segment(csc_dict)
|
|
246
|
+
if self.params['is_first_image']:
|
|
247
|
+
self.process_first_binary_image()
|
|
248
|
+
else:
|
|
249
|
+
self.fact['blob_nb'], shapes, self.stats, centroids = cv2.connectedComponentsWithStats(self.validated_shapes)
|
|
250
|
+
self.fact['blob_nb'] -= 1
|
|
251
|
+
self.fact['total_area'] = self.validated_shapes.sum()
|
|
252
|
+
if self.fact['blob_nb'].values[0] < previous_shape_number and self.fact['total_area'].values[0] > previous_sum * 0.9:
|
|
253
|
+
previous_shape_number = self.fact['blob_nb'].values[0]
|
|
254
|
+
previous_sum = self.fact['total_area'].values[0]
|
|
255
|
+
self.csc_dict = csc_dict.copy()
|
|
256
|
+
self.fact['unaltered_blob_nb'] = combination_features.loc[i, 'unaltered_blob_nb']
|
|
257
|
+
self.save_combination()
|
|
258
|
+
|
|
259
|
+
def try_subtracting_channels(self):
|
|
260
|
+
"""
|
|
261
|
+
Tries to subtract channels to find the optimal color space combination.
|
|
262
|
+
|
|
263
|
+
This method attempts to remove color spaces one by one from the image
|
|
264
|
+
to find a combination that maintains the majority of areas while reducing
|
|
265
|
+
the number of color spaces. This process is repeated until no further
|
|
266
|
+
improvements are possible.
|
|
267
|
+
"""
|
|
268
|
+
potentials = self.parent.all_combined
|
|
269
|
+
# Try to remove color space one by one
|
|
270
|
+
i = 0
|
|
271
|
+
original_length = len(potentials)
|
|
272
|
+
# The while loop until one col space remains or the removal of one implies a strong enough area change
|
|
273
|
+
while np.logical_and(len(potentials) > 1, i < original_length - 1):
|
|
274
|
+
self.combine_and_segment(potentials)
|
|
275
|
+
if self.params['is_first_image']:
|
|
276
|
+
self.process_first_binary_image()
|
|
277
|
+
previous_blob_nb = self.fact['blob_nb'].values[0]
|
|
161
278
|
else:
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
self.
|
|
279
|
+
previous_blob_nb, shapes, self.stats, centroids = cv2.connectedComponentsWithStats(
|
|
280
|
+
self.validated_shapes)
|
|
281
|
+
previous_blob_nb -= 1
|
|
282
|
+
previous_sum = self.validated_shapes.sum()
|
|
283
|
+
color_space_to_remove = List()
|
|
284
|
+
previous_c_space = list(potentials.keys())[-1]
|
|
285
|
+
for c_space in potentials.keys():
|
|
286
|
+
try_potentials = potentials.copy()
|
|
287
|
+
try_potentials.pop(c_space)
|
|
288
|
+
if i > 0:
|
|
289
|
+
try_potentials.pop(previous_c_space)
|
|
290
|
+
self.combine_and_segment(try_potentials)
|
|
291
|
+
if self.params['is_first_image']:
|
|
292
|
+
self.process_first_binary_image()
|
|
293
|
+
else:
|
|
294
|
+
self.fact['blob_nb'], shapes, self.stats, centroids = cv2.connectedComponentsWithStats(
|
|
295
|
+
self.validated_shapes)
|
|
296
|
+
self.fact['blob_nb'] -= 1
|
|
297
|
+
self.fact['total_area'] = self.validated_shapes.sum()
|
|
298
|
+
if self.fact['blob_nb'].values[0] < previous_blob_nb and self.fact['total_area'].values[0] > previous_sum * 0.9:
|
|
299
|
+
previous_blob_nb = self.fact['blob_nb'].values[0]
|
|
300
|
+
previous_sum = self.fact['total_area'].values[0]
|
|
301
|
+
self.csc_dict = try_potentials.copy()
|
|
302
|
+
self.fact['unaltered_blob_nb'] = previous_blob_nb
|
|
303
|
+
self.save_combination()
|
|
304
|
+
# If removing that color space helps, we remove it from potentials
|
|
305
|
+
color_space_to_remove.append(c_space)
|
|
306
|
+
if i > 0:
|
|
307
|
+
color_space_to_remove.append(previous_c_space)
|
|
308
|
+
previous_c_space = c_space
|
|
309
|
+
if len(color_space_to_remove) == 0:
|
|
310
|
+
break
|
|
311
|
+
color_space_to_remove = np.unique(color_space_to_remove)
|
|
312
|
+
for remove_col_space in color_space_to_remove:
|
|
313
|
+
potentials.pop(remove_col_space)
|
|
314
|
+
i += 1
|
|
166
315
|
|
|
167
|
-
def
|
|
316
|
+
def process_first_binary_image(self):
|
|
168
317
|
"""
|
|
169
318
|
Process the binary image to identify and validate shapes.
|
|
170
319
|
|
|
@@ -174,57 +323,38 @@ class ProcessFirstImage:
|
|
|
174
323
|
sample number or applies additional filtering if necessary.
|
|
175
324
|
|
|
176
325
|
"""
|
|
177
|
-
shapes_features = shape_selection(self.binary_image, true_shape_number=self.
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
326
|
+
shapes_features = shape_selection(self.binary_image, true_shape_number=self.params['blob_nb'],
|
|
327
|
+
horizontal_size=self.params['blob_size'], spot_shape=self.params['blob_shape'],
|
|
328
|
+
several_blob_per_arena=self.params['several_blob_per_arena'],
|
|
329
|
+
bio_mask=self.params['bio_mask'], back_mask=self.params['back_mask'])
|
|
330
|
+
self.validated_shapes, self.fact['blob_nb'], self.stats, self.centroids = shapes_features
|
|
182
331
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
332
|
+
def save_combination(self):
|
|
333
|
+
"""
|
|
334
|
+
Saves the calculated features and masks for a combination of shapes.
|
|
186
335
|
|
|
187
|
-
|
|
336
|
+
This method calculates various statistical properties (std) and sums for the
|
|
337
|
+
validated shapes, and optionally computes sums for bio and back masks if they are
|
|
338
|
+
specified in the parameters.
|
|
188
339
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
self.
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
Finally, it increments the saved color space number counter.
|
|
211
|
-
"""
|
|
212
|
-
logging.info(f"Saving results from the color space combination: {self.process_i.csc_dict}. {self.process_i.shape_number} distinct specimen(s) detected.")
|
|
213
|
-
self.parent.saved_images_list.append(self.process_i.validated_shapes)
|
|
214
|
-
self.parent.converted_images_list.append(np.round(self.process_i.image).astype(np.uint8))
|
|
215
|
-
self.parent.saved_color_space_list.append(self.process_i.csc_dict)
|
|
216
|
-
self.parent.combination_features[self.parent.saved_csc_nb, :3] = list(self.process_i.csc_dict.values())[0]
|
|
217
|
-
self.parent.combination_features[self.parent.saved_csc_nb, 3] = self.process_i.unaltered_concomp_nb - 1 # unaltered_cc_nb
|
|
218
|
-
self.parent.combination_features[self.parent.saved_csc_nb, 4] = self.process_i.shape_number # cc_nb
|
|
219
|
-
self.parent.combination_features[self.parent.saved_csc_nb, 5] = self.process_i.total_area # area
|
|
220
|
-
self.parent.combination_features[self.parent.saved_csc_nb, 6] = np.std(self.process_i.stats[1:, 2]) # width_std
|
|
221
|
-
self.parent.combination_features[self.parent.saved_csc_nb, 7] = np.std(self.process_i.stats[1:, 3]) # height_std
|
|
222
|
-
self.parent.combination_features[self.parent.saved_csc_nb, 8] = np.std(self.process_i.stats[1:, 4]) # area_std
|
|
223
|
-
if self.process_i.biomask is not None:
|
|
224
|
-
self.parent.combination_features[self.parent.saved_csc_nb, 9] = np.sum(
|
|
225
|
-
self.process_i.validated_shapes[self.process_i.biomask[0], self.process_i.biomask[1]])
|
|
226
|
-
if self.process_i.backmask is not None:
|
|
227
|
-
self.parent.combination_features[self.parent.saved_csc_nb, 10] = np.sum(
|
|
228
|
-
(1 - self.process_i.validated_shapes)[self.process_i.backmask[0], self.process_i.backmask[1]])
|
|
229
|
-
self.parent.saved_csc_nb += 1
|
|
230
|
-
logging.info("end")
|
|
340
|
+
Parameters
|
|
341
|
+
----------
|
|
342
|
+
self : object
|
|
343
|
+
The instance of the class containing this method. Must have attributes:
|
|
344
|
+
- ``validated_shapes``: (ndarray) Array of validated shapes.
|
|
345
|
+
- ``stats``: (ndarray) Statistics array containing width, height, and area.
|
|
346
|
+
- ``params``: dictionary with parameters including 'bio_mask' and 'back_mask'.
|
|
347
|
+
- ``fact``: dictionary to store the calculated features.
|
|
348
|
+
- ``parent``: The parent object containing the method `save_combination_features`.
|
|
349
|
+
"""
|
|
350
|
+
self.fact['total_area'] = self.validated_shapes.sum()
|
|
351
|
+
self.fact['width_std'] = np.std(self.stats[1:, 2])
|
|
352
|
+
self.fact['height_std'] = np.std(self.stats[1:, 3])
|
|
353
|
+
self.fact['area_std'] = np.std(self.stats[1:, 4])
|
|
354
|
+
if self.params['bio_mask'] is not None:
|
|
355
|
+
self.fact['bio_sum'] = self.validated_shapes[self.params['bio_mask'][0], self.params['bio_mask'][1]].sum()
|
|
356
|
+
if self.params['back_mask'] is not None:
|
|
357
|
+
self.fact['back_sum'] = (1 - self.validated_shapes)[self.params['back_mask'][0], self.params['back_mask'][1]].sum()
|
|
358
|
+
self.parent.save_combination_features(self)
|
|
359
|
+
|
|
360
|
+
|