cellects 0.1.0.dev1__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.
Files changed (46) hide show
  1. cellects/__init__.py +0 -0
  2. cellects/__main__.py +49 -0
  3. cellects/config/__init__.py +0 -0
  4. cellects/config/all_vars_dict.py +154 -0
  5. cellects/core/__init__.py +0 -0
  6. cellects/core/cellects_paths.py +30 -0
  7. cellects/core/cellects_threads.py +1464 -0
  8. cellects/core/motion_analysis.py +1931 -0
  9. cellects/core/one_image_analysis.py +1065 -0
  10. cellects/core/one_video_per_blob.py +679 -0
  11. cellects/core/program_organizer.py +1347 -0
  12. cellects/core/script_based_run.py +154 -0
  13. cellects/gui/__init__.py +0 -0
  14. cellects/gui/advanced_parameters.py +1258 -0
  15. cellects/gui/cellects.py +189 -0
  16. cellects/gui/custom_widgets.py +789 -0
  17. cellects/gui/first_window.py +449 -0
  18. cellects/gui/if_several_folders_window.py +239 -0
  19. cellects/gui/image_analysis_window.py +1909 -0
  20. cellects/gui/required_output.py +232 -0
  21. cellects/gui/video_analysis_window.py +656 -0
  22. cellects/icons/__init__.py +0 -0
  23. cellects/icons/cellects_icon.icns +0 -0
  24. cellects/icons/cellects_icon.ico +0 -0
  25. cellects/image_analysis/__init__.py +0 -0
  26. cellects/image_analysis/cell_leaving_detection.py +54 -0
  27. cellects/image_analysis/cluster_flux_study.py +102 -0
  28. cellects/image_analysis/extract_exif.py +61 -0
  29. cellects/image_analysis/fractal_analysis.py +184 -0
  30. cellects/image_analysis/fractal_functions.py +108 -0
  31. cellects/image_analysis/image_segmentation.py +272 -0
  32. cellects/image_analysis/morphological_operations.py +867 -0
  33. cellects/image_analysis/network_functions.py +1244 -0
  34. cellects/image_analysis/one_image_analysis_threads.py +289 -0
  35. cellects/image_analysis/progressively_add_distant_shapes.py +246 -0
  36. cellects/image_analysis/shape_descriptors.py +981 -0
  37. cellects/utils/__init__.py +0 -0
  38. cellects/utils/formulas.py +881 -0
  39. cellects/utils/load_display_save.py +1016 -0
  40. cellects/utils/utilitarian.py +516 -0
  41. cellects-0.1.0.dev1.dist-info/LICENSE.odt +0 -0
  42. cellects-0.1.0.dev1.dist-info/METADATA +131 -0
  43. cellects-0.1.0.dev1.dist-info/RECORD +46 -0
  44. cellects-0.1.0.dev1.dist-info/WHEEL +5 -0
  45. cellects-0.1.0.dev1.dist-info/entry_points.txt +2 -0
  46. cellects-0.1.0.dev1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,981 @@
1
+ """
2
+ Shape descriptors: The following names, lists and computes all the variables describing a shape in a binary image.
3
+ If you want to allow the software to compute another variable:
4
+ 1) In the following dicts and list, you need to:
5
+ add the variable name and whether to compute it (True/False) by default
6
+ 2) In the ShapeDescriptors class:
7
+ add a method to compute that variable
8
+ 3) In the init method of the ShapeDescriptors class
9
+ attribute a None value to the variable that store it
10
+ add a if condition in the for loop to compute that variable when its name appear in the wanted_descriptors_list
11
+ """
12
+ import cv2
13
+ import numpy as np
14
+ from copy import deepcopy
15
+ from cellects.utils.utilitarian import translate_dict
16
+ from cellects.utils.formulas import get_inertia_axes, get_standard_deviations, get_skewness, get_kurtosis
17
+ from cellects.image_analysis.morphological_operations import cc
18
+
19
+ descriptors_categories = {'area': True, 'perimeter': False, 'circularity': False, 'rectangularity': False,
20
+ 'total_hole_area': False, 'solidity': False, 'convexity': False, 'eccentricity': False,
21
+ 'euler_number': False, 'standard_deviation_xy': False, 'skewness_xy': False,
22
+ 'kurtosis_xy': False, 'major_axes_len_and_angle': True, 'iso_digi_analysis': False,
23
+ 'oscilacyto_analysis': False, 'network_analysis': False, 'graph_extraction': False,
24
+ 'fractal_analysis': False
25
+ }
26
+
27
+ descriptors_names_to_display = ['Area', 'Perimeter', 'Circularity', 'Rectangularity', 'Total hole area',
28
+ 'Solidity', 'Convexity', 'Eccentricity', 'Euler number', 'Standard deviation xy',
29
+ 'Skewness xy', 'Kurtosis xy', 'Major axes lengths and angle',
30
+ 'Growth transitions', 'Oscillations', 'Network', 'Graph',
31
+ 'Fractals'
32
+ ]#, 'Oscillating cluster nb and size'
33
+
34
+ from_shape_descriptors_class = {'area': True, 'perimeter': False, 'circularity': False, 'rectangularity': False,
35
+ 'total_hole_area': False, 'solidity': False, 'convexity': False, 'eccentricity': False,
36
+ 'euler_number': False, 'standard_deviation_y': False, 'standard_deviation_x': False,
37
+ 'skewness_y': False, 'skewness_x': False, 'kurtosis_y': False, 'kurtosis_x': False,
38
+ 'major_axis_len': True, 'minor_axis_len': True, 'axes_orientation': True
39
+ }
40
+
41
+ descriptors = deepcopy(from_shape_descriptors_class)
42
+ descriptors.update({'cluster_number': False, 'mean_cluster_area': False, 'minkowski_dimension': False,
43
+ 'vertices_number': False, 'edges_number': False})
44
+
45
+
46
+
47
+ class ShapeDescriptors:
48
+ """
49
+ This class takes :
50
+ - a binary image of 0 and 1 drawing one shape
51
+ - a list of descriptors to calculate from that image
52
+ ["area", "perimeter", "circularity", "rectangularity", "total_hole_area", "solidity", "convexity",
53
+ "eccentricity", "euler_number",
54
+
55
+ "standard_deviation_y", "standard_deviation_x", "skewness_y", "skewness_x", "kurtosis_y", "kurtosis_x",
56
+ "major_axis_len", "minor_axis_len", "axes_orientation",
57
+
58
+ "mo", "contours", "min_bounding_rectangle", "convex_hull"]
59
+
60
+ Be careful! mo, contours, min_bounding_rectangle, convex_hull,
61
+ standard_deviations, skewness and kurtosis are not atomics
62
+ https://www.researchgate.net/publication/27343879_Estimators_for_Orientation_and_Anisotropy_in_Digitized_Images
63
+ """
64
+
65
+ def __init__(self, binary_image, wanted_descriptors_list):
66
+ """
67
+ Class to compute various descriptors for a binary image.
68
+
69
+ Parameters
70
+ ----------
71
+ binary_image : ndarray
72
+ Binary image used to compute the descriptors.
73
+ wanted_descriptors_list : list
74
+ List of strings with the names of the wanted descriptors.
75
+
76
+ Attributes
77
+ ----------
78
+ binary_image : ndarray
79
+ The binary image.
80
+ descriptors : dict
81
+ Dictionary containing the computed descriptors.
82
+ mo : float or None, optional
83
+ Moment of inertia (default is `None`).
84
+ area : int or None, optional
85
+ Area of the object (default is `None`).
86
+ contours : ndarray or None, optional
87
+ Contours of the object (default is `None`).
88
+ min_bounding_rectangle : tuple or None, optional
89
+ Minimum bounding rectangle of the object (default is `None`).
90
+ convex_hull : ndarray or None, optional
91
+ Convex hull of the object (default is `None`).
92
+ major_axis_len : float or None, optional
93
+ Major axis length of the object (default is `None`).
94
+ minor_axis_len : float or None, optional
95
+ Minor axis length of the object (default is `None`).
96
+ axes_orientation : float or None, optional
97
+ Orientation of the axes (default is `None`).
98
+ sx : float or None, optional
99
+ Standard deviation in x-axis (default is `None`).
100
+ kx : float or None, optional
101
+ Kurtosis in x-axis (default is `None`).
102
+ skx : float or None, optional
103
+ Skewness in x-axis (default is `None`).
104
+ perimeter : float or None, optional
105
+ Perimeter of the object (default is `None`).
106
+ convexity : float or None, optional
107
+ Convexity of the object (default is `None`).
108
+
109
+ Examples
110
+ --------
111
+ >>> binary_image = np.array([[1, 1, 1], [1, 1, 1], [1, 1, 1]], dtype=np.uint8)
112
+ >>> wanted_descriptors_list = ["area", "perimeter"]
113
+ >>> SD = ShapeDescriptors(binary_image, wanted_descriptors_list)
114
+ >>> SD.descriptors
115
+ {'area': np.uint64(9), 'perimeter': 8.0}
116
+ """
117
+ # Give a None value to each parameters whose presence is assessed before calculation (less calculus for speed)
118
+ self.mo = None
119
+ self.area = None
120
+ self.contours = None
121
+ self.min_bounding_rectangle = None
122
+ self.convex_hull = None
123
+ self.major_axis_len = None
124
+ self.minor_axis_len = None
125
+ self.axes_orientation = None
126
+ self.sx = None
127
+ self.kx = None
128
+ self.skx = None
129
+ self.perimeter = None
130
+ self.convexity = None
131
+
132
+ self.binary_image = binary_image
133
+ if self.binary_image.dtype == 'bool':
134
+ self.binary_image = self.binary_image.astype(np.uint8)
135
+
136
+ self.descriptors = {i: np.empty(0, dtype=np.float64) for i in wanted_descriptors_list}
137
+
138
+ self.get_area()
139
+
140
+ for name in self.descriptors.keys():
141
+ if self.area == 0:
142
+ self.descriptors[name] = 0
143
+ else:
144
+ if name == "mo":
145
+ self.get_mo()
146
+ self.descriptors[name] = self.mo
147
+ elif name == "area":
148
+ self.descriptors[name] = self.area
149
+ elif name == "contours":
150
+ self.get_contours()
151
+ self.descriptors[name] = self.contours
152
+ elif name == "min_bounding_rectangle":
153
+ self.get_min_bounding_rectangle()
154
+ self.descriptors[name] = self.min_bounding_rectangle
155
+ elif name == "major_axis_len":
156
+ self.get_major_axis_len()
157
+ self.descriptors[name] = self.major_axis_len
158
+ elif name == "minor_axis_len":
159
+ self.get_minor_axis_len()
160
+ self.descriptors[name] = self.minor_axis_len
161
+ elif name == "axes_orientation":
162
+ self.get_inertia_axes()
163
+ self.descriptors[name] = self.axes_orientation
164
+ elif name == "standard_deviation_y":
165
+ self.get_standard_deviations()
166
+ self.descriptors[name] = self.sy
167
+ elif name == "standard_deviation_x":
168
+ self.get_standard_deviations()
169
+ self.descriptors[name] = self.sx
170
+ elif name == "skewness_y":
171
+ self.get_skewness()
172
+ self.descriptors[name] = self.sky
173
+ elif name == "skewness_x":
174
+ self.get_skewness()
175
+ self.descriptors[name] = self.skx
176
+ elif name == "kurtosis_y":
177
+ self.get_kurtosis()
178
+ self.descriptors[name] = self.ky
179
+ elif name == "kurtosis_x":
180
+ self.get_kurtosis()
181
+ self.descriptors[name] = self.kx
182
+ elif name == "convex_hull":
183
+ self.get_convex_hull()
184
+ self.descriptors[name] = self.convex_hull
185
+ elif name == "perimeter":
186
+ self.get_perimeter()
187
+ self.descriptors[name] = self.perimeter
188
+ elif name == "circularity":
189
+ self.get_circularity()
190
+ self.descriptors[name] = self.circularity
191
+ elif name == "rectangularity":
192
+ self.get_rectangularity()
193
+ self.descriptors[name] = self.rectangularity
194
+ elif name == "total_hole_area":
195
+ self.get_total_hole_area()
196
+ self.descriptors[name] = self.total_hole_area
197
+ elif name == "solidity":
198
+ self.get_solidity()
199
+ self.descriptors[name] = self.solidity
200
+ elif name == "convexity":
201
+ self.get_convexity()
202
+ self.descriptors[name] = self.convexity
203
+ elif name == "eccentricity":
204
+ self.get_eccentricity()
205
+ self.descriptors[name] = self.eccentricity
206
+ elif name == "euler_number":
207
+ self.get_euler_number()
208
+ self.descriptors[name] = self.euler_number
209
+
210
+ """
211
+ The following methods can be called to compute parameters for descriptors requiring it
212
+ """
213
+
214
+ def get_mo(self):
215
+ """
216
+ Get moments of a binary image.
217
+
218
+ Calculate the image moments for a given binary image using OpenCV's
219
+ `cv2.moments` function and then translate these moments into a formatted
220
+ dictionary.
221
+
222
+ Parameters
223
+ ----------
224
+ self : object
225
+ The instance of the class containing this method.
226
+ binary_image : numpy.ndarray
227
+ A binary image (2D array) where pixels are 0 or 255.
228
+
229
+ Other Parameters
230
+ ----------------
231
+ None
232
+
233
+ Returns
234
+ -------
235
+ dict
236
+ A dictionary containing the translated moments of the binary image.
237
+
238
+ Raises
239
+ ------
240
+ TypeError
241
+ If `binary_image` is not a NumPy array.
242
+
243
+ Notes
244
+ -----
245
+ This function assumes the binary image has already been processed and is in a
246
+ suitable format for moment calculation.
247
+
248
+ Examples
249
+ --------
250
+ >>> SD = ShapeDescriptors(np.array([[1, 1, 1], [1, 1, 1], [1, 1, 1]], dtype=np.uint8), ["mo"])
251
+ >>> print(SD.mo["m00"])
252
+ 9.0
253
+ """
254
+ self.mo = translate_dict(cv2.moments(self.binary_image))
255
+
256
+ def get_area(self):
257
+ """
258
+ Calculate the area of a binary image by summing its pixel values.
259
+
260
+ This function computes the area covered by white pixels (value 1) in a binary image,
261
+ which is equivalent to counting the number of 'on' pixels.
262
+
263
+ Parameters
264
+ ----------
265
+ self : object
266
+ The instance of a class containing the binary_image attribute.
267
+
268
+ Returns
269
+ -------
270
+ int
271
+ The total number of white pixels in the binary image, representing its area.
272
+
273
+ Notes
274
+ -----
275
+ This function assumes the binary_image attribute is a NumPy array containing only 0s and 1s.
276
+ If the image contains other values, this function might not produce accurate results.
277
+
278
+ Examples
279
+ --------
280
+ >>> SD = ShapeDescriptors(np.array([[1, 1, 1], [1, 1, 1], [1, 1, 1]], dtype=np.uint8), ["area"])
281
+ >>> print(SD.area)
282
+ 9.0
283
+ """
284
+ self.area = self.binary_image.sum()
285
+
286
+ def get_contours(self):
287
+ """
288
+ Find and process the largest contour in a binary image.
289
+
290
+ Retrieves contours from a binary image, calculates the Euler number,
291
+ and identifies the largest contour based on its length.
292
+
293
+ Parameters
294
+ ----------
295
+ self : ImageProcessingObject
296
+ The image processing object containing the binary image.
297
+
298
+ Other Parameters
299
+ ----------------
300
+ None
301
+
302
+ Returns
303
+ -------
304
+ None
305
+
306
+ Raises
307
+ ------
308
+ None
309
+
310
+ Notes
311
+ -----
312
+ This function modifies the internal state of the `self` object to store
313
+ the largest contour and Euler number.
314
+
315
+ Examples
316
+ --------
317
+ >>> SD = ShapeDescriptors(np.array([[1, 1, 1], [1, 1, 1], [1, 1, 1]], dtype=np.uint8), ["euler_number"])
318
+ >>> print(len(SD.contours))
319
+ 8
320
+ """
321
+ contours, hierarchy = cv2.findContours(self.binary_image, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)
322
+ nb, shapes = cv2.connectedComponents(self.binary_image, ltype=cv2.CV_16U)
323
+ self.euler_number = (nb - 1) - len(contours)
324
+ self.contours = contours[0]
325
+ if len(contours) > 1:
326
+ all_lengths = np.zeros(len(contours))
327
+ for i, contour in enumerate(contours):
328
+ all_lengths[i] = len(contour)
329
+ self.contours = contours[np.argmax(all_lengths)]
330
+
331
+ def get_min_bounding_rectangle(self):
332
+ """
333
+ Retrieve the minimum bounding rectangle from the contours of an image.
334
+
335
+ This method calculates the smallest area rectangle that can enclose
336
+ the object outlines present in the image, which is useful for
337
+ object detection and analysis tasks.
338
+
339
+ Parameters
340
+ ----------
341
+ None
342
+
343
+ Returns
344
+ -------
345
+ tuple
346
+ A tuple containing the following elements:
347
+ - (cx, cy): The center point of the rectangle.
348
+ - (width, height): Width and height of the bounding rectangle.
349
+ - angle: Angle in degrees describing how much the ellipse is rotated.
350
+
351
+ Raises
352
+ ------
353
+ RuntimeError
354
+ If the contours are not available and cannot be retrieved,
355
+ indicating a problem with the image or preprocessing steps.
356
+
357
+ Notes
358
+ -----
359
+ - The bounding rectangle is calculated only if contours are available.
360
+ If not, they will be retrieved first before calculating the rectangle.
361
+
362
+ Examples
363
+ --------
364
+ >>> SD = ShapeDescriptors(np.array([[1, 1, 1], [1, 1, 1], [1, 1, 1]], dtype=np.uint8), ["rectangularity"])
365
+ >>> print(len(SD.min_bounding_rectangle))
366
+ 3
367
+ """
368
+ if self.contours is None:
369
+ self.get_contours()
370
+ self.min_bounding_rectangle = cv2.minAreaRect(self.contours) # ((cx, cy), (width, height), angle)
371
+
372
+ def get_inertia_axes(self):
373
+ """
374
+ Calculate and set the moments of inertia properties of an object.
375
+
376
+ This function computes the centroid, major axis length,
377
+ minor axis length, and axes orientation for an object. It
378
+ first ensures that the moments of inertia (`mo`) attribute is available,
379
+ computing them if necessary, before using the `get_inertia_axes` function.
380
+
381
+ Returns
382
+ -------
383
+ None
384
+
385
+ This method sets the following attributes:
386
+ - `cx` : float
387
+ The x-coordinate of the centroid.
388
+ - `cy` : float
389
+ The y-coordinate of the centroid.
390
+ - `major_axis_len` : float
391
+ The length of the major axis.
392
+ - `minor_axis_len` : float
393
+ The length of the minor axis.
394
+ - `axes_orientation` : float
395
+ The orientation angle of the axes.
396
+
397
+ Raises
398
+ ------
399
+ ValueError
400
+ If there is an issue with the moments of inertia computation.
401
+
402
+ Notes
403
+ -----
404
+ This function modifies in-place the object's attributes related to its geometry.
405
+
406
+ Examples
407
+ --------
408
+ >>> SD = ShapeDescriptors(np.array([[1, 1, 1], [1, 1, 1], [1, 1, 1]], dtype=np.uint8), ["major_axis_len"])
409
+ >>> print(SD.axes_orientation)
410
+ 0.0
411
+ """
412
+ if self.mo is None:
413
+ self.get_mo()
414
+
415
+ self.cx, self.cy, self.major_axis_len, self.minor_axis_len, self.axes_orientation = get_inertia_axes(self.mo)
416
+
417
+ def get_standard_deviations(self):
418
+ """
419
+ Calculate the standard deviations along x and y.
420
+
421
+ Parameters
422
+ ----------
423
+ moment_order : int
424
+ The order of moments to consider in calculations.
425
+ binary_image : numpy.ndarray
426
+ A 2D binary image where the shape corresponds to `[height, width]`.
427
+ center_x : float
428
+ The x-coordinate of the centroid of the object in the image.
429
+ center_y : float
430
+ The y-coordinate of the centroid of the object in the image.
431
+
432
+ Returns
433
+ -------
434
+ tuple[float, float]
435
+ A tuple containing the standard deviations along x and y (sx, sy).
436
+
437
+ Notes
438
+ -----
439
+ The function calculates the standard deviations of a binary image about its centroid.
440
+
441
+ Examples
442
+ --------
443
+ >>> SD = ShapeDescriptors(np.array([[1, 1, 1], [1, 1, 1], [1, 1, 1]], dtype=np.uint8), ["standard_deviation_x", "standard_deviation_y"])
444
+ >>> print(SD.sx, SD.sy)
445
+ 0.816496580927726 0.816496580927726
446
+ """
447
+ if self.sx is None:
448
+ if self.axes_orientation is None:
449
+ self.get_inertia_axes()
450
+ self.sx, self.sy = get_standard_deviations(self.mo, self.binary_image, self.cx, self.cy)
451
+
452
+ def get_skewness(self):
453
+ """
454
+ Calculate the skewness of an image.
455
+
456
+ This function computes the skewness about the x-axis and y-axis of
457
+ an image. Skewness is a measure of the asymmetry of the probability
458
+ distribution of values in an image.
459
+
460
+ Parameters
461
+ ----------
462
+ binary_image : numpy.ndarray
463
+ A binary image represented as a 2D array of integers, where 0 represents
464
+ background and other values represent foreground.
465
+ mo : dict
466
+ Moments of the image.
467
+ cx, cy : float
468
+ The x and y coordinates of the centroid of the object in the image.
469
+ sx, sy : float
470
+ The standard deviations along the x and y axes.
471
+
472
+ Other Parameters
473
+ ----------------
474
+ None
475
+
476
+ Returns
477
+ -------
478
+ skx, sky : tuple of float
479
+ The skewness about the x-axis and y-axis.
480
+
481
+ Notes
482
+ -----
483
+ This method internally calls `get_standard_deviations` if the standard deviation
484
+ values are not already computed.
485
+
486
+ Examples
487
+ --------
488
+ >>> SD = ShapeDescriptors(np.array([[1, 1, 1], [1, 1, 1], [1, 1, 1]], dtype=np.uint8), ["skewness_x", "skewness_y"])
489
+ >>> print(SD.skx, SD.sky)
490
+ 0.0 0.0
491
+ """
492
+ if self.skx is None:
493
+ if self.sx is None:
494
+ self.get_standard_deviations()
495
+
496
+ self.skx, self.sky = get_skewness(self.mo, self.binary_image, self.cx, self.cy, self.sx, self.sy)
497
+
498
+ def get_kurtosis(self):
499
+ """
500
+ Calculates the kurtosis of the image moments.
501
+
502
+ Kurtosis is a statistical measure that describes the shape of
503
+ a distribution's tails in relation to its overall shape. It is
504
+ used here in the context of image moments analysis.
505
+
506
+ Notes
507
+ -----
508
+ This function first checks if the kurtosis values have already been calculated.
509
+ If not, it calculates them using the `get_kurtosis` function.
510
+
511
+ Examples
512
+ --------
513
+ >>> SD = ShapeDescriptors(np.array([[1, 1, 1], [1, 1, 1], [1, 1, 1]], dtype=np.uint8), ["kurtosis_x", "kurtosis_y"])
514
+ >>> print(SD.kx, SD.ky)
515
+ 1.5 1.5
516
+ """
517
+ if self.kx is None:
518
+ if self.sx is None:
519
+ self.get_standard_deviations()
520
+
521
+ self.kx, self.ky = get_kurtosis(self.mo, self.binary_image, self.cx, self.cy, self.sx, self.sy)
522
+
523
+ def get_convex_hull(self):
524
+ """
525
+ Compute the convex hull of an object's contours.
526
+
527
+ This method calculates the convex hull for the object represented by its
528
+ contours. If the contours are not already computed, it will first compute them.
529
+
530
+ Parameters
531
+ ----------
532
+ self : Object
533
+ The object containing the contours and convex hull attributes.
534
+
535
+ Returns
536
+ -------
537
+ None
538
+
539
+ Notes
540
+ -----
541
+ This method modifies the object in place by setting its `convex_hull`
542
+ attribute to the result of the convex hull computation.
543
+
544
+ Examples
545
+ --------
546
+ >>> SD = ShapeDescriptors(np.array([[1, 1, 1], [1, 1, 1], [1, 1, 1]], dtype=np.uint8), ["solidity"])
547
+ >>> print(len(SD.convex_hull))
548
+ 4
549
+ """
550
+ if self.contours is None:
551
+ self.get_contours()
552
+ self.convex_hull = cv2.convexHull(self.contours)
553
+
554
+ def get_perimeter(self):
555
+ """
556
+ Get the perimeter of a contour.
557
+
558
+ Calculates the perimeter length of the contours present in
559
+ the image after determining them if they are not already available.
560
+
561
+ Parameters
562
+ ----------
563
+ None
564
+
565
+ Returns
566
+ -------
567
+ float
568
+ The perimeter length of the contours in the image.
569
+
570
+ Notes
571
+ -----
572
+ This function retrieves the contours if they have not been determined
573
+ yet using `self.get_contours()`. This is crucial for accurate perimeter
574
+ measurement.
575
+
576
+ Examples
577
+ --------
578
+ >>> SD = ShapeDescriptors(np.array([[1, 1, 1], [1, 1, 1], [1, 1, 1]], dtype=np.uint8), ["perimeter"])
579
+ >>> print(SD.perimeter)
580
+ 8.0
581
+ """
582
+ if self.contours is None:
583
+ self.get_contours()
584
+ self.perimeter = cv2.arcLength(self.contours, True)
585
+
586
+ def get_circularity(self):
587
+ """
588
+ Calculate and set the circularity of a binary image object.
589
+
590
+ Circularity is defined as (4 * pi * area) / perimeter^2.
591
+ If the perimeter has not been calculated yet, it will be computed first.
592
+
593
+ Parameters
594
+ ----------
595
+ self : ShapeObject
596
+ The object which contains the binary image and its properties.
597
+
598
+ Attributes
599
+ ----------
600
+ circularity : float
601
+ The calculated circularity value, set as an attribute of the object.
602
+
603
+ Returns
604
+ -------
605
+ None
606
+
607
+ Notes
608
+ -----
609
+ Circularity is a measure of how closely the shape of an object approximates a circle.
610
+ A perfect circle has a circularity of 1.0.
611
+
612
+ Examples
613
+ --------
614
+ >>> SD = ShapeDescriptors(np.array([[1, 1, 1], [1, 1, 1], [1, 1, 1]], dtype=np.uint8), ["circularity"])
615
+ >>> print(SD.circularity)
616
+ 1.7671458676442586
617
+ """
618
+ if self.perimeter is None:
619
+ self.get_perimeter()
620
+ self.circularity = (4 * np.pi * self.binary_image.sum()) / np.square(self.perimeter)
621
+
622
+ def get_rectangularity(self):
623
+ """
624
+ Calculates the rectangularity of a binary image.
625
+
626
+ Rectangularity is defined as the ratio of the number of pixels in
627
+ the shape to the area of its bounding rectangle. This function
628
+ computes this value by considering the binary image stored in
629
+ `self.binary_image`.
630
+
631
+ Parameters
632
+ ----------
633
+ None
634
+
635
+ Other Parameters
636
+ ----------------
637
+ None
638
+
639
+ Returns
640
+ -------
641
+ float
642
+ The rectangularity of the binary image.
643
+
644
+ Raises
645
+ ------
646
+ None
647
+
648
+ Notes
649
+ -----
650
+ This function uses `self.binary_image` to determine the number of pixels in
651
+ the shape and `self.min_bounding_rectangle` to determine the bounding rectangle area.
652
+ If the minimum bounding rectangle has not been computed yet, it will be calculated
653
+ through `self.get_min_bounding_rectangle()`.
654
+
655
+ Attributes
656
+ ----------
657
+ None
658
+
659
+ Examples
660
+ --------
661
+ >>> SD = ShapeDescriptors(np.array([[1, 1, 1], [1, 1, 1], [1, 1, 1]], dtype=np.uint8), ["rectangularity"])
662
+ >>> print(SD.rectangularity)
663
+ 2.25
664
+ """
665
+ if self.min_bounding_rectangle is None:
666
+ self.get_min_bounding_rectangle()
667
+ bounding_rectangle_area = self.min_bounding_rectangle[1][0] * self.min_bounding_rectangle[1][1]
668
+ if bounding_rectangle_area != 0:
669
+ self.rectangularity = self.binary_image.sum() / bounding_rectangle_area
670
+ else:
671
+ self.rectangularity = 1
672
+
673
+ def get_total_hole_area(self):
674
+ """
675
+ Calculate the total area of holes in a binary image.
676
+
677
+ This function uses connected component labeling to detect and
678
+ measure the area of holes in a binary image.
679
+
680
+ Returns
681
+ -------
682
+ float
683
+ The total area of all detected holes in the binary image.
684
+
685
+ Notes
686
+ -----
687
+ This function assumes that the binary image has been pre-processed
688
+ and that holes are represented as connected components of zero
689
+ pixels within the foreground
690
+
691
+ Examples
692
+ --------
693
+ >>> SD = ShapeDescriptors(np.array([[1, 1, 1], [1, 1, 1], [1, 1, 1]], dtype=np.uint8), ["total_hole_area"])
694
+ >>> print(SD.total_hole_area)
695
+ 0
696
+ """
697
+ new_order, stats, centers = cc(1 - self.binary_image)
698
+ self.total_hole_area = np.sum(stats[2:, 4])
699
+
700
+ def get_solidity(self):
701
+ """
702
+ Calculate the solidity of a contour, which is the ratio of the area
703
+ of the contour to the area of its convex hull.
704
+
705
+ Extended Summary
706
+ ----------------
707
+ The solidity is a dimensionless measure that compares the area of a shape to
708
+ its convex hull. A solidity of 1 means the contour is fully convex, while a
709
+ value less than 1 indicates concavities.
710
+
711
+ Parameters
712
+ ----------
713
+ None
714
+
715
+ Other Parameters
716
+ -----------------
717
+ None
718
+
719
+ Returns
720
+ -------
721
+ float
722
+ The solidity of the contour.
723
+
724
+ Raises
725
+ ------
726
+ None
727
+
728
+ Notes
729
+ -----
730
+ The solidity is computed by dividing the area of the contour by the area of its
731
+ convex hull. If the convex hull is empty, solidity defaults to 1.
732
+
733
+ Attributes
734
+ ----------
735
+ self.convex_hull : array-like or None
736
+ The convex hull of the contour.
737
+ self.solidity : float
738
+ The calculated solidity value of the contour.
739
+
740
+ Examples
741
+ --------
742
+ >>> SD = ShapeDescriptors(np.array([[1, 1, 1], [1, 1, 1], [1, 1, 1]], dtype=np.uint8), ["solidity"])
743
+ >>> print(SD.solidity)
744
+ 1.0
745
+ """
746
+ if self.convex_hull is None:
747
+ self.get_convex_hull()
748
+ conv_h_cont_area = cv2.contourArea(self.convex_hull)
749
+ if conv_h_cont_area != 0:
750
+ self.solidity = cv2.contourArea(self.contours) / cv2.contourArea(self.convex_hull)
751
+ else:
752
+ self.solidity = 1
753
+
754
+ def get_convexity(self):
755
+ """
756
+ Calculate the convexity of a shape.
757
+
758
+ Convexity is defined as the ratio of the length of
759
+ the contour to the perimeter of the convex hull.
760
+
761
+ Parameters
762
+ ----------
763
+ None
764
+
765
+ Returns
766
+ -------
767
+ float
768
+ The convexity ratio of the shape.
769
+
770
+ Raises
771
+ ------
772
+ None
773
+
774
+ Notes
775
+ -----
776
+ This method requires that both `perimeter` and `convex_hull`
777
+ attributes are computed before calling this method.
778
+ Convexity is a dimensionless quantity and should always be in the range [0, 1].
779
+
780
+ Examples
781
+ --------
782
+ >>> SD = ShapeDescriptors(np.array([[1, 1, 1], [1, 1, 1], [1, 1, 1]], dtype=np.uint8), ["convexity"])
783
+ >>> print(SD.convexity)
784
+ 1.0
785
+ """
786
+ if self.perimeter is None:
787
+ self.get_perimeter()
788
+ if self.convex_hull is None:
789
+ self.get_convex_hull()
790
+ self.convexity = cv2.arcLength(self.convex_hull, True) / self.perimeter
791
+
792
+ def get_eccentricity(self):
793
+ """
794
+ Calculate the eccentricity of an ellipsoid based on its major and minor axis lengths.
795
+
796
+ This function computes the eccentricity of an ellipsoid using its major
797
+ and minor axis lengths. The eccentricity is a measure of how much the
798
+ ellipsoid deviates from being circular.
799
+
800
+ Parameters
801
+ ----------
802
+ self : Ellipsoid
803
+ The ellipsoid object containing the major and minor axis lengths.
804
+ These values are assumed to be already set.
805
+
806
+ Other Parameters
807
+ ----------------
808
+ None
809
+
810
+ Returns
811
+ -------
812
+ float
813
+ The calculated eccentricity of the ellipsoid.
814
+
815
+ Raises
816
+ ------
817
+ None
818
+
819
+ Notes
820
+ -----
821
+ - This function assumes that the major and minor axis lengths are already set.
822
+ If not, you must call `self.get_inertia_axes()` to update these values before
823
+ calling this function.
824
+
825
+ Examples
826
+ --------
827
+ >>> SD = ShapeDescriptors(np.array([[1, 1, 1], [1, 1, 1], [1, 1, 1]], dtype=np.uint8), ["eccentricity"])
828
+ >>> print(SD.eccentricity)
829
+ 0.0
830
+ """
831
+ self.get_inertia_axes()
832
+ self.eccentricity = np.sqrt(1 - np.square(self.minor_axis_len / self.major_axis_len))
833
+
834
+ def get_euler_number(self):
835
+ """
836
+ Get Euler number from the contour data.
837
+
838
+ Calculate and return the Euler characteristic of the current contour or
839
+ contours, which is a topological invariant that describes the shape.
840
+
841
+ Parameters
842
+ ----------
843
+ None
844
+
845
+ Returns
846
+ -------
847
+ int or None
848
+ The Euler number of the contour(s). Returns `None` if no contours exist.
849
+
850
+ Raises
851
+ ------
852
+ ValueError
853
+ If the Euler number cannot be calculated due to invalid contour data.
854
+
855
+ Notes
856
+ -----
857
+ The Euler characteristic is computed as: ``vertices - edges + faces``.
858
+ This method handles both single contour and multiple contours cases.
859
+
860
+ Examples
861
+ --------
862
+ >>> contour_object.get_euler_number()
863
+ 1
864
+
865
+ >>> no_contours = Contour() # Object with no contours
866
+ >>> no_contours.get_euler_number()
867
+ None
868
+ """
869
+ if self.contours is None:
870
+ self.get_contours()
871
+
872
+ def get_major_axis_len(self):
873
+ """
874
+ Get the length of the major axis.
875
+
876
+ Calculate or retrieve the length of the major axis, ensuring it is
877
+ computed if not already available.
878
+
879
+ Parameters
880
+ ----------
881
+ None
882
+
883
+ Returns
884
+ -------
885
+ float or None
886
+ The length of the major axis. If the major_axis_len could not be
887
+ computed, returns `None`.
888
+
889
+ Raises
890
+ ------
891
+ AttributeError
892
+ If the major axis length is not available and cannot be computed.
893
+
894
+ Notes
895
+ -----
896
+ - This method may trigger computation of inertia axes if they haven't been
897
+ precomputed to determine the major axis length.
898
+
899
+ Examples
900
+ --------
901
+ >>> SD = ShapeDescriptors(np.array([[0, 1, 0], [0, 1, 0], [0, 1, 0]], dtype=np.uint8), ["major_axis_len"])
902
+ >>> print(SD.major_axis_len)
903
+ 2.8284271247461907
904
+ """
905
+ if self.major_axis_len is None:
906
+ self.get_inertia_axes()
907
+
908
+ def get_minor_axis_len(self):
909
+ """
910
+ Get the length of the minor axis.
911
+
912
+ This method returns the calculated length of the minor axis. If
913
+ `self.minor_axis_len` is `None`, it will first compute the inertia axes.
914
+
915
+ Parameters
916
+ ----------
917
+ none
918
+
919
+ Returns
920
+ -------
921
+ float:
922
+ The length of the minor axis.
923
+
924
+ Raises
925
+ ------
926
+ RuntimeError:
927
+ If the minor axis cannot be calculated. This might happen if there are not enough data points
928
+ to determine the inertia axes.
929
+
930
+ Notes
931
+ -----
932
+ This method will compute the inertia axes if `self.minor_axis_len` is not cached.
933
+
934
+ Examples
935
+ --------
936
+ >>> SD = ShapeDescriptors(np.array([[0, 1, 0], [0, 1, 0], [0, 1, 0]], dtype=np.uint8), ["minor_axis_len"])
937
+ >>> print(SD.minor_axis_len)
938
+ 0.0
939
+ """
940
+ if self.minor_axis_len is None:
941
+ self.get_inertia_axes()
942
+
943
+ def get_axes_orientation(self):
944
+ """
945
+
946
+ Get the orientation of the axes.
947
+
948
+ Extended summary
949
+ ----------------
950
+
951
+ This method retrieves the current orientation of the axes. If the orientation is not already computed, it will compute and store it by calling `get_inertia_axes()`.
952
+
953
+ Parameters
954
+ ----------
955
+ None
956
+
957
+ Returns
958
+ -------
959
+ `np.ndarray`
960
+ A 3x3 matrix representing the orientation of the axes.
961
+
962
+ Raises
963
+ ------
964
+ None
965
+
966
+ Notes
967
+ -----
968
+ This method may trigger a computation of inertia axes if they haven't been computed yet.
969
+
970
+ Examples
971
+ --------
972
+
973
+ Examples
974
+ --------
975
+ >>> SD = ShapeDescriptors(np.array([[0, 1, 0], [0, 1, 0], [0, 1, 0]], dtype=np.uint8), ["axes_orientation"])
976
+ >>> print(SD.axes_orientation)
977
+ 1.5707963267948966
978
+ """
979
+ if self.axes_orientation is None:
980
+ self.get_inertia_axes()
981
+