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.
Files changed (65) hide show
  1. {cellects-0.2.6 → cellects-0.3.4}/PKG-INFO +10 -10
  2. {cellects-0.2.6 → cellects-0.3.4}/README.md +9 -9
  3. {cellects-0.2.6 → cellects-0.3.4}/pyproject.toml +1 -1
  4. {cellects-0.2.6 → cellects-0.3.4}/src/cellects/core/cellects_paths.py +1 -1
  5. {cellects-0.2.6 → cellects-0.3.4}/src/cellects/core/cellects_threads.py +46 -184
  6. {cellects-0.2.6 → cellects-0.3.4}/src/cellects/core/motion_analysis.py +74 -45
  7. cellects-0.3.4/src/cellects/core/one_image_analysis.py +787 -0
  8. {cellects-0.2.6 → cellects-0.3.4}/src/cellects/core/program_organizer.py +157 -98
  9. {cellects-0.2.6 → cellects-0.3.4}/src/cellects/core/script_based_run.py +18 -24
  10. {cellects-0.2.6 → cellects-0.3.4}/src/cellects/gui/advanced_parameters.py +21 -32
  11. {cellects-0.2.6 → cellects-0.3.4}/src/cellects/gui/cellects.py +10 -4
  12. {cellects-0.2.6 → cellects-0.3.4}/src/cellects/gui/custom_widgets.py +4 -103
  13. {cellects-0.2.6 → cellects-0.3.4}/src/cellects/gui/first_window.py +44 -47
  14. {cellects-0.2.6 → cellects-0.3.4}/src/cellects/gui/if_several_folders_window.py +11 -18
  15. {cellects-0.2.6 → cellects-0.3.4}/src/cellects/gui/image_analysis_window.py +142 -148
  16. {cellects-0.2.6 → cellects-0.3.4}/src/cellects/gui/required_output.py +7 -16
  17. {cellects-0.2.6 → cellects-0.3.4}/src/cellects/gui/ui_strings.py +3 -2
  18. {cellects-0.2.6 → cellects-0.3.4}/src/cellects/gui/video_analysis_window.py +50 -65
  19. {cellects-0.2.6 → cellects-0.3.4}/src/cellects/image_analysis/image_segmentation.py +21 -77
  20. {cellects-0.2.6 → cellects-0.3.4}/src/cellects/image_analysis/morphological_operations.py +9 -13
  21. cellects-0.3.4/src/cellects/image_analysis/one_image_analysis_threads.py +360 -0
  22. {cellects-0.2.6 → cellects-0.3.4}/src/cellects/image_analysis/shape_descriptors.py +1068 -1067
  23. {cellects-0.2.6 → cellects-0.3.4}/src/cellects/utils/formulas.py +3 -1
  24. {cellects-0.2.6 → cellects-0.3.4}/src/cellects/utils/load_display_save.py +3 -3
  25. {cellects-0.2.6 → cellects-0.3.4}/src/cellects.egg-info/PKG-INFO +10 -10
  26. {cellects-0.2.6 → cellects-0.3.4}/tests/test_based_run.py +1 -1
  27. {cellects-0.2.6 → cellects-0.3.4}/tests/test_formulas.py +4 -4
  28. {cellects-0.2.6 → cellects-0.3.4}/tests/test_image_segmentation.py +20 -14
  29. {cellects-0.2.6 → cellects-0.3.4}/tests/test_motion_analysis.py +10 -22
  30. {cellects-0.2.6 → cellects-0.3.4}/tests/test_network_functions.py +0 -4
  31. cellects-0.3.4/tests/test_one_image_analysis.py +401 -0
  32. {cellects-0.2.6 → cellects-0.3.4}/tests/test_program_organizer.py +14 -11
  33. {cellects-0.2.6 → cellects-0.3.4}/tests/test_shape_descriptors.py +0 -1
  34. cellects-0.2.6/src/cellects/core/one_image_analysis.py +0 -1082
  35. cellects-0.2.6/src/cellects/image_analysis/one_image_analysis_threads.py +0 -230
  36. cellects-0.2.6/tests/test_one_image_analysis.py +0 -259
  37. {cellects-0.2.6 → cellects-0.3.4}/LICENSE +0 -0
  38. {cellects-0.2.6 → cellects-0.3.4}/setup.cfg +0 -0
  39. {cellects-0.2.6 → cellects-0.3.4}/src/cellects/__init__.py +0 -0
  40. {cellects-0.2.6 → cellects-0.3.4}/src/cellects/__main__.py +0 -0
  41. {cellects-0.2.6 → cellects-0.3.4}/src/cellects/config/__init__.py +0 -0
  42. {cellects-0.2.6 → cellects-0.3.4}/src/cellects/config/all_vars_dict.py +0 -0
  43. {cellects-0.2.6 → cellects-0.3.4}/src/cellects/core/__init__.py +0 -0
  44. {cellects-0.2.6 → cellects-0.3.4}/src/cellects/gui/__init__.py +0 -0
  45. {cellects-0.2.6 → cellects-0.3.4}/src/cellects/icons/__init__.py +0 -0
  46. {cellects-0.2.6 → cellects-0.3.4}/src/cellects/icons/cellects_icon.icns +0 -0
  47. {cellects-0.2.6 → cellects-0.3.4}/src/cellects/icons/cellects_icon.ico +0 -0
  48. {cellects-0.2.6 → cellects-0.3.4}/src/cellects/image_analysis/__init__.py +0 -0
  49. {cellects-0.2.6 → cellects-0.3.4}/src/cellects/image_analysis/cell_leaving_detection.py +0 -0
  50. {cellects-0.2.6 → cellects-0.3.4}/src/cellects/image_analysis/network_functions.py +0 -0
  51. {cellects-0.2.6 → cellects-0.3.4}/src/cellects/image_analysis/oscillations_functions.py +0 -0
  52. {cellects-0.2.6 → cellects-0.3.4}/src/cellects/image_analysis/progressively_add_distant_shapes.py +0 -0
  53. {cellects-0.2.6 → cellects-0.3.4}/src/cellects/utils/__init__.py +0 -0
  54. {cellects-0.2.6 → cellects-0.3.4}/src/cellects/utils/decorators.py +0 -0
  55. {cellects-0.2.6 → cellects-0.3.4}/src/cellects/utils/utilitarian.py +0 -0
  56. {cellects-0.2.6 → cellects-0.3.4}/src/cellects.egg-info/SOURCES.txt +0 -0
  57. {cellects-0.2.6 → cellects-0.3.4}/src/cellects.egg-info/dependency_links.txt +0 -0
  58. {cellects-0.2.6 → cellects-0.3.4}/src/cellects.egg-info/entry_points.txt +0 -0
  59. {cellects-0.2.6 → cellects-0.3.4}/src/cellects.egg-info/requires.txt +0 -0
  60. {cellects-0.2.6 → cellects-0.3.4}/src/cellects.egg-info/top_level.txt +0 -0
  61. {cellects-0.2.6 → cellects-0.3.4}/tests/test_cell_leaving_detection.py +0 -0
  62. {cellects-0.2.6 → cellects-0.3.4}/tests/test_load_display_save.py +0 -0
  63. {cellects-0.2.6 → cellects-0.3.4}/tests/test_morphological_operations.py +0 -0
  64. {cellects-0.2.6 → cellects-0.3.4}/tests/test_progressively_add_distant_shapes.py +0 -0
  65. {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.2.6
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
  [![Python versions](https://img.shields.io/pypi/pyversions/cellects.svg?style=flat-square)](https://pypi.org/project/cellects/)
731
731
  [![License](https://img.shields.io/pypi/l/cellects.svg?style=flat-square)](https://github.com/Aurele-B/cellects/blob/main/LICENSE)
732
732
  [![Stars](https://img.shields.io/github/stars/Aurele-B/cellects.svg?style=flat-square)](https://github.com/Aurele-B/cellects/stargazers)
733
- ![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/Aurele-B/Cellects/.github%2Fworkflows%2Fci.yml)
733
+ ![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/Aurele-B/Cellects/.github%2Fworkflows%2Frelease.yml)
734
734
  ![Coverage](https://raw.githubusercontent.com/Aurele-B/cellects/gh-pages/badges/coverage.svg)
735
735
 
736
736
  Description
@@ -748,7 +748,7 @@ easy installation and user-friendly interface.
748
748
 
749
749
  ---
750
750
 
751
- ## 🚀 Installation (Short version)
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
- ## 🎯 Quick Start
762
+ ## Quick Start
763
763
  Run in terminal:
764
764
  ```bash
765
- Cellects
765
+ cellects
766
766
  ```
767
767
 
768
768
  ---
769
769
 
770
- ## 📚 Documentation
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
- ## 🧪 Use Cases
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
- ## 🛠 Contributing
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
- ## 📌 License & Citation
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
- ## 🧪 Testing
819
+ ## Testing
820
820
 
821
821
  Run unit tests with:
822
822
  ```bash
@@ -10,7 +10,7 @@
10
10
  [![Python versions](https://img.shields.io/pypi/pyversions/cellects.svg?style=flat-square)](https://pypi.org/project/cellects/)
11
11
  [![License](https://img.shields.io/pypi/l/cellects.svg?style=flat-square)](https://github.com/Aurele-B/cellects/blob/main/LICENSE)
12
12
  [![Stars](https://img.shields.io/github/stars/Aurele-B/cellects.svg?style=flat-square)](https://github.com/Aurele-B/cellects/stargazers)
13
- ![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/Aurele-B/Cellects/.github%2Fworkflows%2Fci.yml)
13
+ ![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/Aurele-B/Cellects/.github%2Fworkflows%2Frelease.yml)
14
14
  ![Coverage](https://raw.githubusercontent.com/Aurele-B/cellects/gh-pages/badges/coverage.svg)
15
15
 
16
16
  Description
@@ -28,7 +28,7 @@ easy installation and user-friendly interface.
28
28
 
29
29
  ---
30
30
 
31
- ## 🚀 Installation (Short version)
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
- ## 🎯 Quick Start
42
+ ## Quick Start
43
43
  Run in terminal:
44
44
  ```bash
45
- Cellects
45
+ cellects
46
46
  ```
47
47
 
48
48
  ---
49
49
 
50
- ## 📚 Documentation
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
- ## 🧪 Use Cases
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
- ## 🛠 Contributing
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
- ## 📌 License & Citation
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
- ## 🧪 Testing
99
+ ## Testing
100
100
 
101
101
  Run unit tests with:
102
102
  ```bash
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "cellects"
7
- version = "0.2.6"
7
+ version = "0.3.4"
8
8
  description = "Cell Expansion Computer Tracking Software."
9
9
  readme = "README.md"
10
10
  license = { file = "LICENSE" }
@@ -24,7 +24,7 @@ REPO_ROOT = CELLECTS_DIR.parent.parent
24
24
 
25
25
  # Repo-level dirs
26
26
  DATA_DIR = REPO_ROOT / "data"
27
- EXPERIMENTS_DIR = DATA_DIR / "experiment"
27
+ EXPERIMENTS_DIR = DATA_DIR / "single_experiment"
28
28
  TEST_DIR = REPO_ROOT / "tests"
29
29
 
30
30
  # Example packaged file
@@ -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 contour_i in range(len(self.parent().po.top)):
359
- min_cy = self.parent().po.top[contour_i]
360
- max_cy = self.parent().po.bot[contour_i]
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'], (138, 95, 18), True, contour_width)
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.all['arena_shape']
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
- biomask = None
459
- backmask = None
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 analysis options, wait...")
484
- if self.parent().po.vars["color_number"] > 2:
485
- kmeans_clust_nb = self.parent().po.vars["color_number"]
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
- arenas_mask = None
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(bool)
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(str)
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
- self.message_from_thread.emit("Image analysis failed to detect the right cell(s) number: restart the analysis.")
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
- self.message_from_thread.emit("Automatic arena delineation cannot work if one cell touches the image border.")
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
- else:
708
- logging.info("Start automatic video delineation")
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
- self.message_when_thread_finished.emit(analysis_status["message"])
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.arange(self.parent().po.sample_number)
742
- self.parent().po.right = np.arange(self.parent().po.sample_number)
743
- self.parent().po.top = np.arange(self.parent().po.sample_number)
744
- self.parent().po.bot = np.arange(self.parent().po.sample_number)
745
- for arena in np.arange(1, self.parent().po.sample_number + 1):
746
- y, x = np.nonzero(self.parent().imageanalysiswindow.arena_mask == arena)
747
- self.parent().po.left[arena - 1] = np.min(x)
748
- self.parent().po.right[arena - 1] = np.max(x)
749
- self.parent().po.top[arena - 1] = np.min(y)
750
- self.parent().po.bot[arena - 1] = np.max(y)
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.get_origin_shape()
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
- logging.info(f"sum: {video_mask.sum()}")
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(1 / 50)
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']: