cellects 0.2.6__tar.gz → 0.3.4__tar.gz
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-0.2.6 → cellects-0.3.4}/PKG-INFO +10 -10
- {cellects-0.2.6 → cellects-0.3.4}/README.md +9 -9
- {cellects-0.2.6 → cellects-0.3.4}/pyproject.toml +1 -1
- {cellects-0.2.6 → cellects-0.3.4}/src/cellects/core/cellects_paths.py +1 -1
- {cellects-0.2.6 → cellects-0.3.4}/src/cellects/core/cellects_threads.py +46 -184
- {cellects-0.2.6 → cellects-0.3.4}/src/cellects/core/motion_analysis.py +74 -45
- cellects-0.3.4/src/cellects/core/one_image_analysis.py +787 -0
- {cellects-0.2.6 → cellects-0.3.4}/src/cellects/core/program_organizer.py +157 -98
- {cellects-0.2.6 → cellects-0.3.4}/src/cellects/core/script_based_run.py +18 -24
- {cellects-0.2.6 → cellects-0.3.4}/src/cellects/gui/advanced_parameters.py +21 -32
- {cellects-0.2.6 → cellects-0.3.4}/src/cellects/gui/cellects.py +10 -4
- {cellects-0.2.6 → cellects-0.3.4}/src/cellects/gui/custom_widgets.py +4 -103
- {cellects-0.2.6 → cellects-0.3.4}/src/cellects/gui/first_window.py +44 -47
- {cellects-0.2.6 → cellects-0.3.4}/src/cellects/gui/if_several_folders_window.py +11 -18
- {cellects-0.2.6 → cellects-0.3.4}/src/cellects/gui/image_analysis_window.py +142 -148
- {cellects-0.2.6 → cellects-0.3.4}/src/cellects/gui/required_output.py +7 -16
- {cellects-0.2.6 → cellects-0.3.4}/src/cellects/gui/ui_strings.py +3 -2
- {cellects-0.2.6 → cellects-0.3.4}/src/cellects/gui/video_analysis_window.py +50 -65
- {cellects-0.2.6 → cellects-0.3.4}/src/cellects/image_analysis/image_segmentation.py +21 -77
- {cellects-0.2.6 → cellects-0.3.4}/src/cellects/image_analysis/morphological_operations.py +9 -13
- cellects-0.3.4/src/cellects/image_analysis/one_image_analysis_threads.py +360 -0
- {cellects-0.2.6 → cellects-0.3.4}/src/cellects/image_analysis/shape_descriptors.py +1068 -1067
- {cellects-0.2.6 → cellects-0.3.4}/src/cellects/utils/formulas.py +3 -1
- {cellects-0.2.6 → cellects-0.3.4}/src/cellects/utils/load_display_save.py +3 -3
- {cellects-0.2.6 → cellects-0.3.4}/src/cellects.egg-info/PKG-INFO +10 -10
- {cellects-0.2.6 → cellects-0.3.4}/tests/test_based_run.py +1 -1
- {cellects-0.2.6 → cellects-0.3.4}/tests/test_formulas.py +4 -4
- {cellects-0.2.6 → cellects-0.3.4}/tests/test_image_segmentation.py +20 -14
- {cellects-0.2.6 → cellects-0.3.4}/tests/test_motion_analysis.py +10 -22
- {cellects-0.2.6 → cellects-0.3.4}/tests/test_network_functions.py +0 -4
- cellects-0.3.4/tests/test_one_image_analysis.py +401 -0
- {cellects-0.2.6 → cellects-0.3.4}/tests/test_program_organizer.py +14 -11
- {cellects-0.2.6 → cellects-0.3.4}/tests/test_shape_descriptors.py +0 -1
- cellects-0.2.6/src/cellects/core/one_image_analysis.py +0 -1082
- cellects-0.2.6/src/cellects/image_analysis/one_image_analysis_threads.py +0 -230
- cellects-0.2.6/tests/test_one_image_analysis.py +0 -259
- {cellects-0.2.6 → cellects-0.3.4}/LICENSE +0 -0
- {cellects-0.2.6 → cellects-0.3.4}/setup.cfg +0 -0
- {cellects-0.2.6 → cellects-0.3.4}/src/cellects/__init__.py +0 -0
- {cellects-0.2.6 → cellects-0.3.4}/src/cellects/__main__.py +0 -0
- {cellects-0.2.6 → cellects-0.3.4}/src/cellects/config/__init__.py +0 -0
- {cellects-0.2.6 → cellects-0.3.4}/src/cellects/config/all_vars_dict.py +0 -0
- {cellects-0.2.6 → cellects-0.3.4}/src/cellects/core/__init__.py +0 -0
- {cellects-0.2.6 → cellects-0.3.4}/src/cellects/gui/__init__.py +0 -0
- {cellects-0.2.6 → cellects-0.3.4}/src/cellects/icons/__init__.py +0 -0
- {cellects-0.2.6 → cellects-0.3.4}/src/cellects/icons/cellects_icon.icns +0 -0
- {cellects-0.2.6 → cellects-0.3.4}/src/cellects/icons/cellects_icon.ico +0 -0
- {cellects-0.2.6 → cellects-0.3.4}/src/cellects/image_analysis/__init__.py +0 -0
- {cellects-0.2.6 → cellects-0.3.4}/src/cellects/image_analysis/cell_leaving_detection.py +0 -0
- {cellects-0.2.6 → cellects-0.3.4}/src/cellects/image_analysis/network_functions.py +0 -0
- {cellects-0.2.6 → cellects-0.3.4}/src/cellects/image_analysis/oscillations_functions.py +0 -0
- {cellects-0.2.6 → cellects-0.3.4}/src/cellects/image_analysis/progressively_add_distant_shapes.py +0 -0
- {cellects-0.2.6 → cellects-0.3.4}/src/cellects/utils/__init__.py +0 -0
- {cellects-0.2.6 → cellects-0.3.4}/src/cellects/utils/decorators.py +0 -0
- {cellects-0.2.6 → cellects-0.3.4}/src/cellects/utils/utilitarian.py +0 -0
- {cellects-0.2.6 → cellects-0.3.4}/src/cellects.egg-info/SOURCES.txt +0 -0
- {cellects-0.2.6 → cellects-0.3.4}/src/cellects.egg-info/dependency_links.txt +0 -0
- {cellects-0.2.6 → cellects-0.3.4}/src/cellects.egg-info/entry_points.txt +0 -0
- {cellects-0.2.6 → cellects-0.3.4}/src/cellects.egg-info/requires.txt +0 -0
- {cellects-0.2.6 → cellects-0.3.4}/src/cellects.egg-info/top_level.txt +0 -0
- {cellects-0.2.6 → cellects-0.3.4}/tests/test_cell_leaving_detection.py +0 -0
- {cellects-0.2.6 → cellects-0.3.4}/tests/test_load_display_save.py +0 -0
- {cellects-0.2.6 → cellects-0.3.4}/tests/test_morphological_operations.py +0 -0
- {cellects-0.2.6 → cellects-0.3.4}/tests/test_progressively_add_distant_shapes.py +0 -0
- {cellects-0.2.6 → cellects-0.3.4}/tests/test_utilitarian.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.2
|
|
2
2
|
Name: cellects
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.4
|
|
4
4
|
Summary: Cell Expansion Computer Tracking Software.
|
|
5
5
|
Author: Aurèle Boussard
|
|
6
6
|
License: GNU GENERAL PUBLIC LICENSE
|
|
@@ -730,7 +730,7 @@ Requires-Dist: mkdocs-jupyter; extra == "doc"
|
|
|
730
730
|
[](https://pypi.org/project/cellects/)
|
|
731
731
|
[](https://github.com/Aurele-B/cellects/blob/main/LICENSE)
|
|
732
732
|
[](https://github.com/Aurele-B/cellects/stargazers)
|
|
733
|
-

|
|
734
734
|

|
|
735
735
|
|
|
736
736
|
Description
|
|
@@ -748,7 +748,7 @@ easy installation and user-friendly interface.
|
|
|
748
748
|
|
|
749
749
|
---
|
|
750
750
|
|
|
751
|
-
##
|
|
751
|
+
## Installation (Short version)
|
|
752
752
|
Install using our Windows installer: [Cellects_installer.exe](https://github.com/Aurele-B/Cellects/releases/)
|
|
753
753
|
|
|
754
754
|
Or, install via pip:
|
|
@@ -759,15 +759,15 @@ Any difficulties? follow our [complete installation tutorial](https://aurele-b.g
|
|
|
759
759
|
|
|
760
760
|
---
|
|
761
761
|
|
|
762
|
-
##
|
|
762
|
+
## Quick Start
|
|
763
763
|
Run in terminal:
|
|
764
764
|
```bash
|
|
765
|
-
|
|
765
|
+
cellects
|
|
766
766
|
```
|
|
767
767
|
|
|
768
768
|
---
|
|
769
769
|
|
|
770
|
-
##
|
|
770
|
+
## Documentation
|
|
771
771
|
|
|
772
772
|
Cellects' workflow is described in a [complete documentation](https://aurele-b.github.io/Cellects/). It includes:
|
|
773
773
|
- [**What is Cellects**](https://aurele-b.github.io/Cellects/what-is-cellects/): Purpose of the software, usable data and introduction of its user manual
|
|
@@ -779,7 +779,7 @@ Cellects' workflow is described in a [complete documentation](https://aurele-b.g
|
|
|
779
779
|
|
|
780
780
|
---
|
|
781
781
|
|
|
782
|
-
##
|
|
782
|
+
## Use Cases
|
|
783
783
|
|
|
784
784
|
See [use cases](https://aurele-b.github.io/Cellects/use-cases/) for real-world examples:
|
|
785
785
|
- Automated Physarum polycephalum tracking using GUI
|
|
@@ -788,7 +788,7 @@ See [use cases](https://aurele-b.github.io/Cellects/use-cases/) for real-world e
|
|
|
788
788
|
|
|
789
789
|
---
|
|
790
790
|
|
|
791
|
-
##
|
|
791
|
+
## Contributing
|
|
792
792
|
|
|
793
793
|
We welcome contributions!
|
|
794
794
|
1. Fork the repository and create a new branch.
|
|
@@ -798,7 +798,7 @@ For developer workflows, see [**Contributing**](https://aurele-b.github.io/Celle
|
|
|
798
798
|
|
|
799
799
|
---
|
|
800
800
|
|
|
801
|
-
##
|
|
801
|
+
## License & Citation
|
|
802
802
|
|
|
803
803
|
GNU GPL3 License (see [LICENSE](https://github.com/Aurele-B/cellects/blob/main/LICENSE)).
|
|
804
804
|
|
|
@@ -816,7 +816,7 @@ To cite Cellects, use:
|
|
|
816
816
|
|
|
817
817
|
---
|
|
818
818
|
|
|
819
|
-
##
|
|
819
|
+
## Testing
|
|
820
820
|
|
|
821
821
|
Run unit tests with:
|
|
822
822
|
```bash
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
[](https://pypi.org/project/cellects/)
|
|
11
11
|
[](https://github.com/Aurele-B/cellects/blob/main/LICENSE)
|
|
12
12
|
[](https://github.com/Aurele-B/cellects/stargazers)
|
|
13
|
-

|
|
14
14
|

|
|
15
15
|
|
|
16
16
|
Description
|
|
@@ -28,7 +28,7 @@ easy installation and user-friendly interface.
|
|
|
28
28
|
|
|
29
29
|
---
|
|
30
30
|
|
|
31
|
-
##
|
|
31
|
+
## Installation (Short version)
|
|
32
32
|
Install using our Windows installer: [Cellects_installer.exe](https://github.com/Aurele-B/Cellects/releases/)
|
|
33
33
|
|
|
34
34
|
Or, install via pip:
|
|
@@ -39,15 +39,15 @@ Any difficulties? follow our [complete installation tutorial](https://aurele-b.g
|
|
|
39
39
|
|
|
40
40
|
---
|
|
41
41
|
|
|
42
|
-
##
|
|
42
|
+
## Quick Start
|
|
43
43
|
Run in terminal:
|
|
44
44
|
```bash
|
|
45
|
-
|
|
45
|
+
cellects
|
|
46
46
|
```
|
|
47
47
|
|
|
48
48
|
---
|
|
49
49
|
|
|
50
|
-
##
|
|
50
|
+
## Documentation
|
|
51
51
|
|
|
52
52
|
Cellects' workflow is described in a [complete documentation](https://aurele-b.github.io/Cellects/). It includes:
|
|
53
53
|
- [**What is Cellects**](https://aurele-b.github.io/Cellects/what-is-cellects/): Purpose of the software, usable data and introduction of its user manual
|
|
@@ -59,7 +59,7 @@ Cellects' workflow is described in a [complete documentation](https://aurele-b.g
|
|
|
59
59
|
|
|
60
60
|
---
|
|
61
61
|
|
|
62
|
-
##
|
|
62
|
+
## Use Cases
|
|
63
63
|
|
|
64
64
|
See [use cases](https://aurele-b.github.io/Cellects/use-cases/) for real-world examples:
|
|
65
65
|
- Automated Physarum polycephalum tracking using GUI
|
|
@@ -68,7 +68,7 @@ See [use cases](https://aurele-b.github.io/Cellects/use-cases/) for real-world e
|
|
|
68
68
|
|
|
69
69
|
---
|
|
70
70
|
|
|
71
|
-
##
|
|
71
|
+
## Contributing
|
|
72
72
|
|
|
73
73
|
We welcome contributions!
|
|
74
74
|
1. Fork the repository and create a new branch.
|
|
@@ -78,7 +78,7 @@ For developer workflows, see [**Contributing**](https://aurele-b.github.io/Celle
|
|
|
78
78
|
|
|
79
79
|
---
|
|
80
80
|
|
|
81
|
-
##
|
|
81
|
+
## License & Citation
|
|
82
82
|
|
|
83
83
|
GNU GPL3 License (see [LICENSE](https://github.com/Aurele-B/cellects/blob/main/LICENSE)).
|
|
84
84
|
|
|
@@ -96,7 +96,7 @@ To cite Cellects, use:
|
|
|
96
96
|
|
|
97
97
|
---
|
|
98
98
|
|
|
99
|
-
##
|
|
99
|
+
## Testing
|
|
100
100
|
|
|
101
101
|
Run unit tests with:
|
|
102
102
|
```bash
|
|
@@ -354,26 +354,18 @@ class UpdateImageThread(QtCore.QThread):
|
|
|
354
354
|
image = self.parent().imageanalysiswindow.drawn_image.copy()
|
|
355
355
|
# 3) The automatically detected video contours
|
|
356
356
|
if self.parent().imageanalysiswindow.delineation_done: # add a mask of the video contour
|
|
357
|
+
if self.parent().po.vars['contour_color'] == 255:
|
|
358
|
+
arena_contour_col = (240, 232, 202)
|
|
359
|
+
else:
|
|
360
|
+
arena_contour_col = (138, 95, 18)
|
|
357
361
|
# Draw the delineation mask of each arena
|
|
358
|
-
for
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
min_cx = self.parent().po.left[contour_i]
|
|
362
|
-
max_cx = self.parent().po.right[contour_i]
|
|
363
|
-
text = f"{contour_i + 1}"
|
|
364
|
-
position = (self.parent().po.left[contour_i] + 25, self.parent().po.top[contour_i] + (self.parent().po.bot[contour_i] - self.parent().po.top[contour_i]) // 2)
|
|
365
|
-
image = cv2.putText(image, # numpy array on which text is written
|
|
366
|
-
text, # text
|
|
367
|
-
position, # position at which writing has to start
|
|
368
|
-
cv2.FONT_HERSHEY_SIMPLEX, # font family
|
|
369
|
-
1, # font size
|
|
370
|
-
(138, 95, 18, 255),
|
|
371
|
-
# (209, 80, 0, 255), # font color
|
|
372
|
-
2) # font stroke
|
|
362
|
+
for _i, (min_cy, max_cy, min_cx, max_cx) in enumerate(zip(self.parent().po.top, self.parent().po.bot, self.parent().po.left, self.parent().po.right)):
|
|
363
|
+
position = (min_cx + 25, min_cy + (max_cy - min_cy) // 2)
|
|
364
|
+
image = cv2.putText(image, f"{_i + 1}", position, cv2.FONT_HERSHEY_SIMPLEX, 1, arena_contour_col + (255,),2)
|
|
373
365
|
if (max_cy - min_cy) < 0 or (max_cx - min_cx) < 0:
|
|
374
366
|
self.parent().imageanalysiswindow.message.setText("Error: the shape number or the detection is wrong")
|
|
375
367
|
image = draw_img_with_mask(image, dims, (min_cy, max_cy - 1, min_cx, max_cx - 1),
|
|
376
|
-
self.parent().po.vars['arena_shape'],
|
|
368
|
+
self.parent().po.vars['arena_shape'], arena_contour_col, True, contour_width)
|
|
377
369
|
else: #load
|
|
378
370
|
if user_input:
|
|
379
371
|
# III/ If this thread runs from user input: update the drawn_image according to the current user input
|
|
@@ -391,7 +383,7 @@ class UpdateImageThread(QtCore.QThread):
|
|
|
391
383
|
mask_shape = "rectangle"
|
|
392
384
|
else:
|
|
393
385
|
color = (0, 0, 0)
|
|
394
|
-
mask_shape = self.parent().po.
|
|
386
|
+
mask_shape = self.parent().po.vars['arena_shape']
|
|
395
387
|
image = draw_img_with_mask(image, dims, minmax, mask_shape, color)
|
|
396
388
|
self.parent().imageanalysiswindow.display_image.update_image(image)
|
|
397
389
|
self.message_when_thread_finished.emit(True)
|
|
@@ -455,67 +447,12 @@ class FirstImageAnalysisThread(QtCore.QThread):
|
|
|
455
447
|
pixel sizes, and updates various state attributes on the parent object.
|
|
456
448
|
"""
|
|
457
449
|
tic = default_timer()
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
if self.parent().imageanalysiswindow.bio_masks_number != 0:
|
|
461
|
-
shape_nb, ordered_image = cv2.connectedComponents((self.parent().imageanalysiswindow.bio_mask > 0).astype(np.uint8))
|
|
462
|
-
shape_nb -= 1
|
|
463
|
-
biomask = np.nonzero(self.parent().imageanalysiswindow.bio_mask)
|
|
464
|
-
else:
|
|
465
|
-
shape_nb = 0
|
|
466
|
-
if self.parent().imageanalysiswindow.back_masks_number != 0:
|
|
467
|
-
backmask = np.nonzero(self.parent().imageanalysiswindow.back_mask)
|
|
468
|
-
if self.parent().po.visualize or len(self.parent().po.first_im.shape) == 2 or shape_nb == self.parent().po.sample_number:
|
|
469
|
-
self.message_from_thread.emit("Image segmentation, wait")
|
|
470
|
-
if not self.parent().imageanalysiswindow.asking_first_im_parameters_flag and self.parent().po.all['scale_with_image_or_cells'] == 0 and self.parent().po.all["set_spot_size"]:
|
|
471
|
-
self.parent().po.get_average_pixel_size()
|
|
472
|
-
else:
|
|
473
|
-
self.parent().po.starting_blob_hsize_in_pixels = None
|
|
474
|
-
self.parent().po.all["bio_mask"] = biomask
|
|
475
|
-
self.parent().po.all["back_mask"] = backmask
|
|
476
|
-
self.parent().po.fast_first_image_segmentation()
|
|
477
|
-
if shape_nb == self.parent().po.sample_number and self.parent().po.first_image.im_combinations[self.parent().po.current_combination_id]['shape_number'] != self.parent().po.sample_number:
|
|
478
|
-
self.parent().po.first_image.im_combinations[self.parent().po.current_combination_id]['shape_number'] = shape_nb
|
|
479
|
-
self.parent().po.first_image.shape_number = shape_nb
|
|
480
|
-
self.parent().po.first_image.validated_shapes = (self.parent().imageanalysiswindow.bio_mask > 0).astype(np.uint8)
|
|
481
|
-
self.parent().po.first_image.im_combinations[self.parent().po.current_combination_id]['binary_image'] = self.parent().po.first_image.validated_shapes
|
|
450
|
+
if self.parent().po.visualize or len(self.parent().po.first_im.shape) == 2:
|
|
451
|
+
self.message_from_thread.emit("Image segmentation, wait...")
|
|
482
452
|
else:
|
|
483
|
-
self.message_from_thread.emit("Generating
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
if self.parent().po.basic:
|
|
487
|
-
self.message_from_thread.emit("Generating analysis options, wait less than 30 minutes")
|
|
488
|
-
else:
|
|
489
|
-
self.message_from_thread.emit("Generating analysis options, a few minutes")
|
|
490
|
-
else:
|
|
491
|
-
kmeans_clust_nb = None
|
|
492
|
-
if self.parent().po.basic:
|
|
493
|
-
self.message_from_thread.emit("Generating analysis options, wait a few minutes")
|
|
494
|
-
else:
|
|
495
|
-
self.message_from_thread.emit("Generating analysis options, around 1 minute")
|
|
496
|
-
if self.parent().imageanalysiswindow.asking_first_im_parameters_flag:
|
|
497
|
-
self.parent().po.first_image.find_first_im_csc(sample_number=self.parent().po.sample_number,
|
|
498
|
-
several_blob_per_arena=None,
|
|
499
|
-
spot_shape=None, spot_size=None,
|
|
500
|
-
kmeans_clust_nb=kmeans_clust_nb,
|
|
501
|
-
biomask=self.parent().po.all["bio_mask"],
|
|
502
|
-
backmask=self.parent().po.all["back_mask"],
|
|
503
|
-
color_space_dictionaries=None,
|
|
504
|
-
basic=self.parent().po.basic)
|
|
505
|
-
else:
|
|
506
|
-
if self.parent().po.all['scale_with_image_or_cells'] == 0:
|
|
507
|
-
self.parent().po.get_average_pixel_size()
|
|
508
|
-
else:
|
|
509
|
-
self.parent().po.starting_blob_hsize_in_pixels = None
|
|
510
|
-
self.parent().po.first_image.find_first_im_csc(sample_number=self.parent().po.sample_number,
|
|
511
|
-
several_blob_per_arena=self.parent().po.vars['several_blob_per_arena'],
|
|
512
|
-
spot_shape=self.parent().po.all['starting_blob_shape'],
|
|
513
|
-
spot_size=self.parent().po.starting_blob_hsize_in_pixels,
|
|
514
|
-
kmeans_clust_nb=kmeans_clust_nb,
|
|
515
|
-
biomask=self.parent().po.all["bio_mask"],
|
|
516
|
-
backmask=self.parent().po.all["back_mask"],
|
|
517
|
-
color_space_dictionaries=None,
|
|
518
|
-
basic=self.parent().po.basic)
|
|
453
|
+
self.message_from_thread.emit("Generating segmentation options, wait...")
|
|
454
|
+
self.parent().po.full_first_image_segmentation(not self.parent().imageanalysiswindow.asking_first_im_parameters_flag,
|
|
455
|
+
self.parent().imageanalysiswindow.bio_mask, self.parent().imageanalysiswindow.back_mask)
|
|
519
456
|
|
|
520
457
|
logging.info(f" image analysis lasted {np.floor((default_timer() - tic) / 60).astype(int)} minutes {np.round((default_timer() - tic) % 60).astype(int)} secondes")
|
|
521
458
|
self.message_when_thread_finished.emit(True)
|
|
@@ -582,58 +519,11 @@ class LastImageAnalysisThread(QtCore.QThread):
|
|
|
582
519
|
message_when_thread_finished.emit(success : bool) : signal
|
|
583
520
|
Signal to indicate the completion of the thread.
|
|
584
521
|
"""
|
|
585
|
-
self.parent().po.cropping(False)
|
|
586
|
-
self.parent().po.get_background_to_subtract()
|
|
587
|
-
biomask = None
|
|
588
|
-
backmask = None
|
|
589
|
-
if self.parent().imageanalysiswindow.bio_masks_number != 0:
|
|
590
|
-
biomask = np.nonzero(self.parent().imageanalysiswindow.bio_mask)
|
|
591
|
-
if self.parent().imageanalysiswindow.back_masks_number != 0:
|
|
592
|
-
backmask = np.nonzero(self.parent().imageanalysiswindow.back_mask)
|
|
593
522
|
if self.parent().po.visualize or (len(self.parent().po.first_im.shape) == 2 and not self.parent().po.network_shaped):
|
|
594
523
|
self.message_from_thread.emit("Image segmentation, wait...")
|
|
595
|
-
self.parent().po.fast_last_image_segmentation(biomask=biomask, backmask=backmask)
|
|
596
524
|
else:
|
|
597
525
|
self.message_from_thread.emit("Generating analysis options, wait...")
|
|
598
|
-
|
|
599
|
-
if self.parent().po.all['are_gravity_centers_moving'] != 1:
|
|
600
|
-
cr = [self.parent().po.top, self.parent().po.bot, self.parent().po.left, self.parent().po.right]
|
|
601
|
-
arenas_mask = np.zeros_like(self.parent().po.first_image.validated_shapes)
|
|
602
|
-
for _i in np.arange(len(self.parent().po.vars['analyzed_individuals'])):
|
|
603
|
-
if self.parent().po.vars['arena_shape'] == 'circle':
|
|
604
|
-
ellipse = create_ellipse(cr[1][_i] - cr[0][_i], cr[3][_i] - cr[2][_i])
|
|
605
|
-
arenas_mask[cr[0][_i]: cr[1][_i], cr[2][_i]:cr[3][_i]] = ellipse
|
|
606
|
-
else:
|
|
607
|
-
arenas_mask[cr[0][_i]: cr[1][_i], cr[2][_i]:cr[3][_i]] = 1
|
|
608
|
-
if self.parent().po.network_shaped:
|
|
609
|
-
self.parent().po.last_image.network_detection(arenas_mask, csc_dict=self.parent().po.vars["convert_for_motion"], biomask=biomask, backmask=backmask)
|
|
610
|
-
else:
|
|
611
|
-
if self.parent().po.vars['several_blob_per_arena']:
|
|
612
|
-
concomp_nb = [self.parent().po.sample_number, self.parent().po.first_image.size // 50]
|
|
613
|
-
max_shape_size = .75 * self.parent().po.first_image.size
|
|
614
|
-
total_surfarea = .99 * self.parent().po.first_image.size
|
|
615
|
-
else:
|
|
616
|
-
concomp_nb = [self.parent().po.sample_number, self.parent().po.sample_number * 200]
|
|
617
|
-
if self.parent().po.all['are_zigzag'] == "columns":
|
|
618
|
-
inter_dist = np.mean(np.diff(np.nonzero(self.parent().po.first_image.y_boundaries)))
|
|
619
|
-
elif self.parent().po.all['are_zigzag'] == "rows":
|
|
620
|
-
inter_dist = np.mean(np.diff(np.nonzero(self.parent().po.first_image.x_boundaries)))
|
|
621
|
-
else:
|
|
622
|
-
dist1 = np.mean(np.diff(np.nonzero(self.parent().po.first_image.y_boundaries)))
|
|
623
|
-
dist2 = np.mean(np.diff(np.nonzero(self.parent().po.first_image.x_boundaries)))
|
|
624
|
-
inter_dist = np.max(dist1, dist2)
|
|
625
|
-
if self.parent().po.all['starting_blob_shape'] == "rectangle":
|
|
626
|
-
max_shape_size = np.square(2 * inter_dist)
|
|
627
|
-
else:
|
|
628
|
-
max_shape_size = np.pi * np.square(inter_dist)
|
|
629
|
-
total_surfarea = max_shape_size * self.parent().po.sample_number
|
|
630
|
-
ref_image = self.parent().po.first_image.validated_shapes
|
|
631
|
-
self.parent().po.first_image.generate_subtract_background(self.parent().po.vars['convert_for_motion'], self.parent().po.vars['drift_already_corrected'])
|
|
632
|
-
kmeans_clust_nb = None
|
|
633
|
-
self.parent().po.last_image.find_last_im_csc(concomp_nb, total_surfarea, max_shape_size, arenas_mask,
|
|
634
|
-
ref_image, self.parent().po.first_image.subtract_background,
|
|
635
|
-
kmeans_clust_nb, biomask, backmask, color_space_dictionaries=None,
|
|
636
|
-
basic=self.parent().po.basic)
|
|
526
|
+
self.parent().po.full_last_image_segmentation(self.parent().imageanalysiswindow.bio_mask, self.parent().imageanalysiswindow.back_mask)
|
|
637
527
|
self.message_when_thread_finished.emit(True)
|
|
638
528
|
|
|
639
529
|
|
|
@@ -645,7 +535,7 @@ class CropScaleSubtractDelineateThread(QtCore.QThread):
|
|
|
645
535
|
-------
|
|
646
536
|
message_from_thread : Signal(str)
|
|
647
537
|
Signal emitted when progress messages are available.
|
|
648
|
-
message_when_thread_finished : Signal(
|
|
538
|
+
message_when_thread_finished : Signal(dict)
|
|
649
539
|
Signal emitted upon completion of the thread's task.
|
|
650
540
|
|
|
651
541
|
Notes
|
|
@@ -653,7 +543,7 @@ class CropScaleSubtractDelineateThread(QtCore.QThread):
|
|
|
653
543
|
This class uses `QThread` to manage the process asynchronously.
|
|
654
544
|
"""
|
|
655
545
|
message_from_thread = QtCore.Signal(str)
|
|
656
|
-
message_when_thread_finished = QtCore.Signal(
|
|
546
|
+
message_when_thread_finished = QtCore.Signal(dict)
|
|
657
547
|
|
|
658
548
|
def __init__(self, parent=None):
|
|
659
549
|
"""
|
|
@@ -686,8 +576,8 @@ class CropScaleSubtractDelineateThread(QtCore.QThread):
|
|
|
686
576
|
to perform necessary image processing tasks.
|
|
687
577
|
"""
|
|
688
578
|
logging.info("Start cropping if required")
|
|
579
|
+
analysis_status = {"continue": True, "message": ""}
|
|
689
580
|
self.parent().po.cropping(is_first_image=True)
|
|
690
|
-
self.parent().po.cropping(is_first_image=False)
|
|
691
581
|
self.parent().po.get_average_pixel_size()
|
|
692
582
|
if os.path.isfile('Data to run Cellects quickly.pkl'):
|
|
693
583
|
os.remove('Data to run Cellects quickly.pkl')
|
|
@@ -700,18 +590,22 @@ class CropScaleSubtractDelineateThread(QtCore.QThread):
|
|
|
700
590
|
nb, shapes, stats, centroids = cv2.connectedComponentsWithStats(self.parent().po.first_image.validated_shapes)
|
|
701
591
|
y_lim = self.parent().po.first_image.y_boundaries
|
|
702
592
|
if ((nb - 1) != self.parent().po.sample_number or np.any(stats[:, 4] == 1)):
|
|
703
|
-
|
|
593
|
+
analysis_status["message"] = "Image analysis failed to detect the right cell(s) number: restart the analysis."
|
|
594
|
+
analysis_status['continue'] = False
|
|
595
|
+
elif y_lim is None:
|
|
596
|
+
analysis_status["message"] = "The shapes detected in the image did not allow automatic arena delineation."
|
|
597
|
+
analysis_status['continue'] = False
|
|
704
598
|
elif (y_lim == - 1).sum() != (y_lim == 1).sum():
|
|
705
|
-
|
|
599
|
+
analysis_status["message"] = "Automatic arena delineation cannot work if one cell touches the image border."
|
|
706
600
|
self.parent().po.first_image.y_boundaries = None
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
analysis_status = self.parent().po.delineate_each_arena()
|
|
710
|
-
self.message_when_thread_finished.emit(analysis_status["message"])
|
|
711
|
-
else:
|
|
601
|
+
analysis_status['continue'] = False
|
|
602
|
+
if analysis_status['continue']:
|
|
712
603
|
logging.info("Start automatic video delineation")
|
|
713
604
|
analysis_status = self.parent().po.delineate_each_arena()
|
|
714
|
-
|
|
605
|
+
else:
|
|
606
|
+
self.parent().po.first_image.validated_shapes = np.zeros(self.parent().po.first_image.image.shape[:2], dtype=np.uint8)
|
|
607
|
+
logging.info(analysis_status["message"])
|
|
608
|
+
self.message_when_thread_finished.emit(analysis_status)
|
|
715
609
|
|
|
716
610
|
|
|
717
611
|
class SaveManualDelineationThread(QtCore.QThread):
|
|
@@ -738,21 +632,18 @@ class SaveManualDelineationThread(QtCore.QThread):
|
|
|
738
632
|
"""
|
|
739
633
|
Do save the coordinates.
|
|
740
634
|
"""
|
|
741
|
-
self.parent().po.left = np.
|
|
742
|
-
self.parent().po.right = np.
|
|
743
|
-
self.parent().po.top = np.
|
|
744
|
-
self.parent().po.bot = np.
|
|
745
|
-
for
|
|
746
|
-
y, x = np.nonzero(self.parent().imageanalysiswindow.arena_mask ==
|
|
747
|
-
self.parent().po.left[
|
|
748
|
-
self.parent().po.right[
|
|
749
|
-
self.parent().po.top[
|
|
750
|
-
self.parent().po.bot[
|
|
751
|
-
|
|
752
|
-
logging.info("Save data to run Cellects quickly")
|
|
753
|
-
self.parent().po.data_to_save['coordinates'] = True
|
|
635
|
+
self.parent().po.left = np.zeros(self.parent().po.sample_number)
|
|
636
|
+
self.parent().po.right = np.zeros(self.parent().po.sample_number)
|
|
637
|
+
self.parent().po.top = np.zeros(self.parent().po.sample_number)
|
|
638
|
+
self.parent().po.bot = np.zeros(self.parent().po.sample_number)
|
|
639
|
+
for arena_i in np.arange(self.parent().po.sample_number):
|
|
640
|
+
y, x = np.nonzero(self.parent().imageanalysiswindow.arena_mask == arena_i + 1)
|
|
641
|
+
self.parent().po.left[arena_i] = np.min(x)
|
|
642
|
+
self.parent().po.right[arena_i] = np.max(x)
|
|
643
|
+
self.parent().po.top[arena_i] = np.min(y)
|
|
644
|
+
self.parent().po.bot[arena_i] = np.max(y)
|
|
645
|
+
self.parent().po.list_coordinates()
|
|
754
646
|
self.parent().po.save_data_to_run_cellects_quickly()
|
|
755
|
-
self.parent().po.data_to_save['coordinates'] = False
|
|
756
647
|
|
|
757
648
|
logging.info("Save manual video delineation")
|
|
758
649
|
self.parent().po.vars['analyzed_individuals'] = np.arange(self.parent().po.sample_number) + 1
|
|
@@ -816,7 +707,6 @@ class CompleteImageAnalysisThread(QtCore.QThread):
|
|
|
816
707
|
def run(self):
|
|
817
708
|
self.parent().po.get_background_to_subtract()
|
|
818
709
|
self.parent().po.get_origins_and_backgrounds_lists()
|
|
819
|
-
self.parent().po.data_to_save['coordinates'] = True
|
|
820
710
|
self.parent().po.data_to_save['exif'] = True
|
|
821
711
|
self.parent().po.save_data_to_run_cellects_quickly()
|
|
822
712
|
self.parent().po.all['bio_mask'] = None
|
|
@@ -867,10 +757,8 @@ class PrepareVideoAnalysisThread(QtCore.QThread):
|
|
|
867
757
|
self.parent().po.find_if_lighter_background()
|
|
868
758
|
logging.info("The current (or the first) folder is ready to run")
|
|
869
759
|
self.parent().po.first_exp_ready_to_run = True
|
|
870
|
-
self.parent().po.data_to_save['coordinates'] = True
|
|
871
760
|
self.parent().po.data_to_save['exif'] = True
|
|
872
761
|
self.parent().po.save_data_to_run_cellects_quickly()
|
|
873
|
-
self.parent().po.data_to_save['coordinates'] = False
|
|
874
762
|
self.parent().po.data_to_save['exif'] = False
|
|
875
763
|
|
|
876
764
|
|
|
@@ -1096,7 +984,7 @@ class OneArenaThread(QtCore.QThread):
|
|
|
1096
984
|
"""
|
|
1097
985
|
arena = self.parent().po.all['arena']
|
|
1098
986
|
i = np.nonzero(self.parent().po.vars['analyzed_individuals'] == arena)[0][0]
|
|
1099
|
-
true_frame_width = self.parent().po.vars['origin_list'][i].shape[1]
|
|
987
|
+
true_frame_width = self.parent().po.right[i] - self.parent().po.left[i]# self.parent().po.vars['origin_list'][i].shape[1]
|
|
1100
988
|
if self.parent().po.all['overwrite_unaltered_videos'] and os.path.isfile(f'ind_{arena}.npy'):
|
|
1101
989
|
os.remove(f'ind_{arena}.npy')
|
|
1102
990
|
background = None
|
|
@@ -1173,16 +1061,7 @@ class OneArenaThread(QtCore.QThread):
|
|
|
1173
1061
|
self.parent().po.converted_video = deepcopy(self.parent().po.motion.converted_video)
|
|
1174
1062
|
if self.parent().po.vars['convert_for_motion']['logical'] != 'None':
|
|
1175
1063
|
self.parent().po.converted_video2 = deepcopy(self.parent().po.motion.converted_video2)
|
|
1176
|
-
self.parent().po.motion.
|
|
1177
|
-
|
|
1178
|
-
if self.parent().po.motion.dims[0] >= 40:
|
|
1179
|
-
step = self.parent().po.motion.dims[0] // 20
|
|
1180
|
-
else:
|
|
1181
|
-
step = 1
|
|
1182
|
-
if self.parent().po.motion.start >= (self.parent().po.motion.dims[0] - step - 1):
|
|
1183
|
-
self.parent().po.motion.start = None
|
|
1184
|
-
else:
|
|
1185
|
-
self.parent().po.motion.get_covering_duration(step)
|
|
1064
|
+
self.parent().po.motion.assess_motion_detection()
|
|
1186
1065
|
self.when_loading_finished.emit(save_loaded_video)
|
|
1187
1066
|
|
|
1188
1067
|
if self.parent().po.motion.visu is None:
|
|
@@ -1323,8 +1202,7 @@ class OneArenaThread(QtCore.QThread):
|
|
|
1323
1202
|
|
|
1324
1203
|
while self._isRunning and analysis_i.t < analysis_i.binary.shape[0]:
|
|
1325
1204
|
analysis_i.update_shape(False)
|
|
1326
|
-
contours = np.nonzero(
|
|
1327
|
-
cv2.morphologyEx(analysis_i.binary[analysis_i.t - 1, :, :], cv2.MORPH_GRADIENT, cross_33))
|
|
1205
|
+
contours = np.nonzero(get_contours(analysis_i.binary[analysis_i.t - 1, :, :]))
|
|
1328
1206
|
current_image = deepcopy(self.parent().po.motion.visu[analysis_i.t - 1, :, :, :])
|
|
1329
1207
|
current_image[contours[0], contours[1], :] = self.parent().po.vars['contour_color']
|
|
1330
1208
|
self.image_from_thread.emit(
|
|
@@ -1355,7 +1233,6 @@ class OneArenaThread(QtCore.QThread):
|
|
|
1355
1233
|
self.when_detection_finished.emit("Post processing done, read to see the result")
|
|
1356
1234
|
|
|
1357
1235
|
|
|
1358
|
-
|
|
1359
1236
|
class VideoReaderThread(QtCore.QThread):
|
|
1360
1237
|
"""
|
|
1361
1238
|
Thread for reading a video in the GUI.
|
|
@@ -1427,7 +1304,7 @@ class VideoReaderThread(QtCore.QThread):
|
|
|
1427
1304
|
video_mask = np.cumsum(video_mask.astype(np.uint32), axis=0)
|
|
1428
1305
|
video_mask[video_mask > 0] = 1
|
|
1429
1306
|
video_mask = video_mask.astype(np.uint8)
|
|
1430
|
-
|
|
1307
|
+
frame_delay = (8 + np.log10(self.parent().po.motion.dims[0])) / self.parent().po.motion.dims[0]
|
|
1431
1308
|
for t in np.arange(self.parent().po.motion.dims[0]):
|
|
1432
1309
|
mask = cv2.morphologyEx(video_mask[t, ...], cv2.MORPH_GRADIENT, cross_33)
|
|
1433
1310
|
mask = np.stack((mask, mask, mask), axis=2)
|
|
@@ -1435,7 +1312,7 @@ class VideoReaderThread(QtCore.QThread):
|
|
|
1435
1312
|
current_image[mask > 0] = self.parent().po.vars['contour_color']
|
|
1436
1313
|
self.message_from_thread.emit(
|
|
1437
1314
|
{"current_image": current_image, "message": f"Reading in progress... Image number: {t}"}) #, "time": timings[t]
|
|
1438
|
-
time.sleep(
|
|
1315
|
+
time.sleep(frame_delay)
|
|
1439
1316
|
self.message_from_thread.emit({"current_image": current_image, "message": ""})#, "time": timings[t]
|
|
1440
1317
|
|
|
1441
1318
|
|
|
@@ -1552,26 +1429,11 @@ class WriteVideoThread(QtCore.QThread):
|
|
|
1552
1429
|
|
|
1553
1430
|
already_greyscale : bool
|
|
1554
1431
|
Flag indicating if the video is already in greyscale format.
|
|
1555
|
-
This parameter must be set as a variable named 'already_greyscale' in the instance
|
|
1556
|
-
variables of the parent object.
|
|
1557
|
-
|
|
1558
|
-
Returns
|
|
1559
|
-
-------
|
|
1560
|
-
None
|
|
1561
1432
|
|
|
1562
1433
|
Raises
|
|
1563
1434
|
------
|
|
1564
1435
|
FileNotFoundError
|
|
1565
1436
|
When the path to write the video is not specified.
|
|
1566
|
-
|
|
1567
|
-
Examples
|
|
1568
|
-
--------
|
|
1569
|
-
>>> self.parent().po.vars['already_greyscale'] = False
|
|
1570
|
-
>>> self.run()
|
|
1571
|
-
>>> # Expects to write a visualization video as 'ind_arena.npy'
|
|
1572
|
-
>>> self.parent().po.vars['already_greyscale'] = True
|
|
1573
|
-
>>> self.run()
|
|
1574
|
-
>>> # Expects to write a converted video as 'ind_arena.npy'
|
|
1575
1437
|
"""
|
|
1576
1438
|
arena = self.parent().po.all['arena']
|
|
1577
1439
|
if not self.parent().po.vars['already_greyscale']:
|