cellects 0.1.3__py3-none-any.whl → 0.2.6__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 (38) hide show
  1. cellects/__main__.py +65 -25
  2. cellects/config/all_vars_dict.py +18 -17
  3. cellects/core/cellects_threads.py +1034 -396
  4. cellects/core/motion_analysis.py +1664 -2010
  5. cellects/core/one_image_analysis.py +1082 -1061
  6. cellects/core/program_organizer.py +1687 -1316
  7. cellects/core/script_based_run.py +80 -76
  8. cellects/gui/advanced_parameters.py +365 -326
  9. cellects/gui/cellects.py +102 -91
  10. cellects/gui/custom_widgets.py +4 -3
  11. cellects/gui/first_window.py +226 -104
  12. cellects/gui/if_several_folders_window.py +117 -68
  13. cellects/gui/image_analysis_window.py +841 -450
  14. cellects/gui/required_output.py +100 -56
  15. cellects/gui/ui_strings.py +840 -0
  16. cellects/gui/video_analysis_window.py +317 -135
  17. cellects/image_analysis/cell_leaving_detection.py +64 -4
  18. cellects/image_analysis/image_segmentation.py +451 -22
  19. cellects/image_analysis/morphological_operations.py +2166 -1635
  20. cellects/image_analysis/network_functions.py +616 -253
  21. cellects/image_analysis/one_image_analysis_threads.py +94 -153
  22. cellects/image_analysis/oscillations_functions.py +131 -0
  23. cellects/image_analysis/progressively_add_distant_shapes.py +2 -3
  24. cellects/image_analysis/shape_descriptors.py +517 -466
  25. cellects/utils/formulas.py +169 -6
  26. cellects/utils/load_display_save.py +362 -105
  27. cellects/utils/utilitarian.py +86 -9
  28. cellects-0.2.6.dist-info/LICENSE +675 -0
  29. cellects-0.2.6.dist-info/METADATA +829 -0
  30. cellects-0.2.6.dist-info/RECORD +44 -0
  31. cellects/core/one_video_per_blob.py +0 -540
  32. cellects/image_analysis/cluster_flux_study.py +0 -102
  33. cellects-0.1.3.dist-info/LICENSE.odt +0 -0
  34. cellects-0.1.3.dist-info/METADATA +0 -176
  35. cellects-0.1.3.dist-info/RECORD +0 -44
  36. {cellects-0.1.3.dist-info → cellects-0.2.6.dist-info}/WHEEL +0 -0
  37. {cellects-0.1.3.dist-info → cellects-0.2.6.dist-info}/entry_points.txt +0 -0
  38. {cellects-0.1.3.dist-info → cellects-0.2.6.dist-info}/top_level.txt +0 -0
@@ -1,29 +1,78 @@
1
1
  #!/usr/bin/env python3
2
- """This module creates the First window of the user interface of Cellects"""
2
+ """First window of the Cellects graphical user interface (GUI).
3
+
4
+ This module implements the initial setup UI for Cellects data processing. It provides
5
+ widgets for selecting image/video inputs, configuring folder paths, arena numbers,
6
+ and prefixes/extensions. Threaded operations ensure UI responsiveness during background tasks.
7
+
8
+ Main Components
9
+ FirstWindow : QWidget subclass implementing the first GUI window with tabs and interactive widgets
10
+ """
3
11
 
4
12
  import os
5
13
  import logging
6
14
  from pathlib import Path
7
15
  import numpy as np
8
- import cv2
9
16
  from PySide6 import QtWidgets, QtCore
10
-
11
17
  from cellects.core.cellects_threads import (
12
18
  GetFirstImThread, GetExifDataThread, RunAllThread, LookForDataThreadInFirstW, LoadDataToRunCellectsQuicklyThread)
13
19
  from cellects.gui.custom_widgets import (
14
20
  MainTabsType, InsertImage, FullScreenImage, PButton, Spinbox,
15
21
  Combobox, FixedText, EditText, LineWidget)
22
+ from cellects.gui.ui_strings import FW
16
23
 
17
24
 
18
25
  class FirstWindow(MainTabsType):
26
+ """
27
+ First window of the Cellects GUI.
28
+ """
19
29
  def __init__(self, parent, night_mode):
30
+ """
31
+ Initialize the First window with a parent widget and night mode setting.
32
+
33
+ Parameters
34
+ ----------
35
+ parent : QWidget
36
+ The parent widget to which this window will be attached.
37
+ night_mode : bool
38
+ A boolean indicating whether the night mode should be enabled.
39
+
40
+
41
+ Examples
42
+ --------
43
+ >>> from PySide6 import QtWidgets
44
+ >>> from cellects.gui.cellects import CellectsMainWidget
45
+ >>> from cellects.gui.first_window import FirstWindow
46
+ >>> import sys
47
+ >>> app = QtWidgets.QApplication([])
48
+ >>> parent = CellectsMainWidget()
49
+ >>> session = FirstWindow(parent, False)
50
+ >>> session.true_init()
51
+ >>> parent.insertWidget(0, session)
52
+ >>> parent.show()
53
+ >>> sys.exit(app.exec())
54
+ """
20
55
  super().__init__(parent, night_mode)
21
56
  logging.info("Initialize first window")
22
57
  self.setParent(parent)
58
+
59
+ self.true_init()
60
+
61
+ def true_init(self):
62
+ """
63
+ Initialize the FirstWindow components and setup its layout.
64
+
65
+ Sets up various widgets, layouts, and threading components for the Cellects GUI,
66
+ including image or video selection, folder path input, arena number management,
67
+ and display setup.
68
+
69
+ Notes
70
+ -----
71
+ This method assumes that the parent widget has a 'po' attribute with specific settings and variables.
72
+ """
23
73
  self.data_tab.set_in_use()
24
74
  self.image_tab.set_not_usable()
25
75
  self.video_tab.set_not_usable()
26
- # self.night_mode_switch(False)
27
76
  self.thread = {}
28
77
  self.thread["LookForData"] = LookForDataThreadInFirstW(self.parent())
29
78
  self.thread["RunAll"] = RunAllThread(self.parent())
@@ -31,15 +80,11 @@ class FirstWindow(MainTabsType):
31
80
  self.thread["GetFirstIm"] = GetFirstImThread(self.parent())
32
81
  self.thread["GetExifDataThread"] = GetExifDataThread(self.parent())
33
82
  self.instantiate: bool = True
34
- ##
35
83
  self.title_label = FixedText('Cellects', police=60, night_mode=self.parent().po.all['night_mode'])
36
84
  self.title_label.setAlignment(QtCore.Qt.AlignHCenter)
37
- # self.subtitle_label = FixedText('A Cell Expansion Computer Tracking Software', police=18, night_mode=self.parent().po.all['night_mode'])
38
- # self.subtitle_label.setAlignment(QtCore.Qt.AlignHCenter)
39
85
  self.subtitle_line = LineWidget(size=[1, 50], night_mode=self.parent().po.all['night_mode'])
40
86
 
41
87
  self.Vlayout.addWidget(self.title_label)
42
- # self.Vlayout.addWidget(self.subtitle_label)
43
88
  self.Vlayout.addWidget(self.subtitle_line)
44
89
  self.Vlayout.addItem(self.vertical_space)
45
90
 
@@ -47,7 +92,9 @@ class FirstWindow(MainTabsType):
47
92
  # Open the layout:
48
93
  self.second_row_widget = QtWidgets.QWidget()
49
94
  self.second_row_layout = QtWidgets.QHBoxLayout()
50
- self.im_or_vid_label = FixedText('Image list or Videos:', tip="What type of data do(es) contain(s) folder(s)?", night_mode=self.parent().po.all['night_mode'])
95
+ self.im_or_vid_label = FixedText(FW['Image_list_or_videos']['label'], tip=FW['Image_list_or_videos']['tips'],
96
+ night_mode=self.parent().po.all['night_mode'])
97
+ # self.im_or_vid_label = FixedText('Image list or Videos:', tip="What type of data do(es) contain(s) folder(s)?", night_mode=self.parent().po.all['night_mode'])
51
98
  self.im_or_vid = Combobox(["Image list", "Videos"], self.parent().po.all['im_or_vid'], night_mode=self.parent().po.all['night_mode'])
52
99
  self.im_or_vid.setFixedWidth(150)
53
100
  # Set their positions on layout
@@ -67,28 +114,28 @@ class FirstWindow(MainTabsType):
67
114
  if self.parent().po.all['extension'] == '.mp4':
68
115
  self.parent().po.all['radical'] = 'IMG_'
69
116
  self.parent().po.all['extension'] = '.JPG'
70
- self.arena_number_label = FixedText('Arena number per folder:', tip="If this number is not always the same (depending on the folder), it can be changed later",
117
+ self.arena_number_label = FixedText('Arena number per folder:',
118
+ tip=FW["Arena_number_per_folder"]["tips"] , #"If this number is not always the same (depending on the folder), it can be changed later",
71
119
  night_mode=self.parent().po.all['night_mode'])
72
-
73
120
  else:
74
121
  if self.parent().po.all['extension'] == '.JPG':
75
122
  self.parent().po.all['radical'] = ''
76
123
  self.parent().po.all['extension'] = '.mp4'
77
124
  self.arena_number_label = FixedText('Arena number per video:',
78
- tip="If this number is not always the same (depending on the video), it can be changed later",
125
+ tip=FW["Arena_number_per_folder"]["tips"], #"If this number is not always the same (depending on the video), it can be changed later",
79
126
  night_mode=self.parent().po.all['night_mode'])
80
127
  what = 'Videos'
81
128
  self.arena_number_label.setAlignment(QtCore.Qt.AlignVCenter)
82
129
  self.arena_number = Spinbox(min=0, max=255, val=self.parent().po.all['first_folder_sample_number'],
83
130
  decimals=0, night_mode=self.parent().po.all['night_mode'])
84
131
  self.arena_number.valueChanged.connect(self.re_instantiate_widgets)
85
- self.radical_label = FixedText(what + ' prefix:', tip="Inform the prefix common to each name, if it exists", night_mode=self.parent().po.all['night_mode'])
132
+ self.radical_label = FixedText(what + ' prefix:', tip=FW["Image_prefix_and_extension"]["tips"], night_mode=self.parent().po.all['night_mode'])
86
133
  self.radical_label.setAlignment(QtCore.Qt.AlignVCenter)
87
134
  self.radical = EditText(self.parent().po.all['radical'],
88
135
  night_mode=self.parent().po.all['night_mode'])
89
136
  self.radical.textChanged.connect(self.re_instantiate_widgets)
90
137
 
91
- self.extension_label = FixedText(what + ' extension:', tip="Caps sensitive", night_mode=self.parent().po.all['night_mode'])
138
+ self.extension_label = FixedText(what + ' extension:', tip=FW["Image_prefix_and_extension"]["tips"], night_mode=self.parent().po.all['night_mode'])
92
139
  self.extension_label.setAlignment(QtCore.Qt.AlignVCenter)
93
140
  self.extension = EditText(self.parent().po.all['extension'],
94
141
  night_mode=self.parent().po.all['night_mode'])
@@ -112,15 +159,16 @@ class FirstWindow(MainTabsType):
112
159
  self.first_row_widget = QtWidgets.QWidget()
113
160
  self.first_row_layout = QtWidgets.QHBoxLayout()
114
161
 
115
- self.folder_label = FixedText('Folder:',
116
- tip="Path to the folder containing images or videos\nThe selected folder may also contain several folders of data",
162
+ self.folder_label = FixedText(FW["Folder"]["label"] + ':',
163
+ tip=FW["Folder"]["tips"],#"Path to the folder containing images or videos\nThe selected folder may also contain several folders of data",
117
164
  night_mode=self.parent().po.all['night_mode'])
118
165
  self.folder_label.setAlignment(QtCore.Qt.AlignVCenter)
119
166
  self.global_pathway = EditText(self.parent().po.all['global_pathway'],
120
167
  night_mode=self.parent().po.all['night_mode'])
121
168
  self.global_pathway.setSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Maximum)
122
169
  self.global_pathway.textChanged.connect(self.pathway_changed)
123
- self.browse = PButton('Browse', night_mode=self.parent().po.all['night_mode'])
170
+ self.browse = PButton(FW["Browse"]["label"], tip=FW["Browse"]["tips"],
171
+ night_mode=self.parent().po.all['night_mode'])
124
172
  self.browse.clicked.connect(self.browse_is_clicked)
125
173
 
126
174
  # Set their positions on layout
@@ -147,36 +195,29 @@ class FirstWindow(MainTabsType):
147
195
  self.display_image.setVisible(False)
148
196
  self.display_image.mousePressEvent = self.full_screen_display
149
197
 
150
- # Add the display shortcuts option
151
- #self.shortcut_cb = Checkbox(self.parent().po.all['display_shortcuts'])
152
- #self.shortcut_cb.stateChanged.connect(self.display_shortcuts_checked)
153
- #self.shortcut_label = FixedText('Display shortcuts', night_mode=self.parent().po.all['night_mode'])
154
- #self.shortcut_label.setAlignment(QtCore.Qt.AlignVCenter)
155
-
156
198
  # 4) Create the shortcuts row
157
199
  self.shortcuts_widget = QtWidgets.QWidget()
158
200
  self.shortcuts_layout = QtWidgets.QHBoxLayout()
159
201
  # Add shortcuts: Video_analysis and Run directly
160
202
  # Shortcut 1 : Advanced Parameters
161
- self.advanced_parameters = PButton('Advanced Parameters', night_mode=self.parent().po.all['night_mode'])
203
+ self.advanced_parameters = PButton(FW["Advanced_parameters"]["label"], tip=FW["Advanced_parameters"]["tips"],
204
+ night_mode=self.parent().po.all['night_mode'])
162
205
  self.advanced_parameters.clicked.connect(self.advanced_parameters_is_clicked)
163
206
  # Shortcut 2 : Required Outputs
164
- self.required_outputs = PButton('Required Outputs', night_mode=self.parent().po.all['night_mode'])
207
+ self.required_outputs = PButton(FW["Required_outputs"]["label"], tip=FW["Required_outputs"]["tips"],
208
+ night_mode=self.parent().po.all['night_mode'])
165
209
  self.required_outputs.clicked.connect(self.required_outputs_is_clicked)
166
210
  # Shortcut 3 :
167
- # self.Video_analysis_window = PButton("Video tracking window", night_mode=self.parent().po.all['night_mode'])
168
- # self.Video_analysis_window.clicked.connect(self.video_analysis_window_is_clicked)
169
211
  self.video_tab.clicked.connect(self.video_analysis_window_is_clicked)
170
212
  # Shortcut 4 :
171
- self.Run_all_directly = PButton("Run all directly", night_mode=self.parent().po.all['night_mode'])
213
+ self.Run_all_directly = PButton(FW["Run_all_directly"]["label"], tip=FW["Run_all_directly"]["tips"],
214
+ night_mode=self.parent().po.all['night_mode'])
172
215
  self.Run_all_directly.clicked.connect(self.Run_all_directly_is_clicked)
173
- # self.Video_analysis_window.setVisible(False)
174
216
  self.Run_all_directly.setVisible(False)
175
217
 
176
218
  self.shortcuts_layout.addItem(self.horizontal_space)
177
219
  self.shortcuts_layout.addWidget(self.advanced_parameters)
178
220
  self.shortcuts_layout.addWidget(self.required_outputs)
179
- # self.shortcuts_layout.addWidget(self.Video_analysis_window)
180
221
  self.shortcuts_layout.addWidget(self.Run_all_directly)
181
222
  self.shortcuts_layout.addItem(self.horizontal_space)
182
223
  self.shortcuts_widget.setLayout(self.shortcuts_layout)
@@ -190,12 +231,11 @@ class FirstWindow(MainTabsType):
190
231
  self.message = FixedText('', halign='r', night_mode=self.parent().po.all['night_mode'])
191
232
  self.message.setStyleSheet("color: rgb(230, 145, 18)")
192
233
  # Next button
193
- self.next = PButton('Next', night_mode=self.parent().po.all['night_mode'])
234
+ self.next = PButton(FW['Next']['label'], tip=FW['Next']['tips'],
235
+ night_mode=self.parent().po.all['night_mode'])
194
236
  self.image_tab.clicked.connect(self.next_is_clicked)
195
237
  self.next.clicked.connect(self.next_is_clicked)
196
238
  # Add widgets to the last_row_layout
197
- #self.last_row_layout.addWidget(self.shortcut_cb)
198
- #self.last_row_layout.addWidget(self.shortcut_label)
199
239
  self.last_row_layout.addItem(self.horizontal_space)
200
240
  self.last_row_layout.addWidget(self.message)
201
241
  self.last_row_layout.addWidget(self.next)
@@ -208,10 +248,41 @@ class FirstWindow(MainTabsType):
208
248
  self.pathway_changed()
209
249
 
210
250
  def full_screen_display(self, event):
251
+ """
252
+ Display an image in full screen.
253
+
254
+ Displays the current `image_to_display` of the parent window
255
+ in a separate full-screen window.
256
+
257
+ Parameters
258
+ ----------
259
+ event : QEvent
260
+ The event that triggers the full-screen display.
261
+
262
+ Other Parameters
263
+ ----------------
264
+ popup_img : FullScreenImage
265
+ The instance of `FullScreenImage` created to display the image.
266
+
267
+ Notes
268
+ -----
269
+ The method creates a new instance of `FullScreenImage` and displays it.
270
+ This is intended to provide a full-screen view of the image currently
271
+ displayed in the parent window.
272
+ """
211
273
  self.popup_img = FullScreenImage(self.parent().image_to_display, self.parent().screen_width, self.parent().screen_height)
212
274
  self.popup_img.show()
213
275
 
214
276
  def browse_is_clicked(self):
277
+ """
278
+ Handles the logic for when a "Browse" button is clicked in the interface.
279
+
280
+ Opens a file dialog to select a directory and updates the global pathway.
281
+
282
+ Notes
283
+ -----
284
+ This function assumes that `self.parent().po.all` is a dictionary with a key `'global_pathway'`.
285
+ """
215
286
  dialog = QtWidgets.QFileDialog()
216
287
  dialog.setDirectory(str(self.parent().po.all['global_pathway']))
217
288
  self.parent().po.all['global_pathway'] = dialog.getExistingDirectory(self,
@@ -219,6 +290,9 @@ class FirstWindow(MainTabsType):
219
290
  self.global_pathway.setText(self.parent().po.all['global_pathway'])
220
291
 
221
292
  def im2vid(self):
293
+ """
294
+ Toggle between processing images or videos based on UI selection.
295
+ """
222
296
  if self.im_or_vid.currentText() == "Image list":
223
297
  what = 'Images'
224
298
  if self.parent().po.all['extension'] == '.mp4':
@@ -234,20 +308,43 @@ class FirstWindow(MainTabsType):
234
308
  self.radical.setText(self.parent().po.all['radical'])
235
309
  self.extension.setText(self.parent().po.all['extension'])
236
310
 
237
- def display_message_from_thread(self, text_from_thread):
311
+ def display_message_from_thread(self, text_from_thread: str):
312
+ """
313
+ Updates the message displayed in the UI with text from a thread.
314
+
315
+ Parameters
316
+ ----------
317
+ text_from_thread : str
318
+ The text to be displayed in the UI message.
319
+ """
238
320
  self.message.setText(text_from_thread)
239
321
 
240
- def display_image_during_thread(self, dictionary):
322
+ def display_image_during_thread(self, dictionary: dict):
323
+ """
324
+ Display an image and set a message during a thread operation.
325
+
326
+ Parameters
327
+ ----------
328
+ dictionary : dict
329
+ A dictionary containing the 'message' and 'current_image'.
330
+ The message is a string to display.
331
+ The current_image is the image data that will be displayed.
332
+ """
241
333
  self.message.setText(dictionary['message'])
242
334
  self.parent().image_to_display = dictionary['current_image']
243
335
  self.display_image.update_image(dictionary['current_image'])
244
336
 
245
337
  def next_is_clicked(self):
338
+ """
339
+ Handles the logic for when a "Next" button is clicked in the interface.
340
+
341
+ Checks if certain threads are running, updates parent object's attributes,
342
+ and starts a data-looking thread if conditions are met.
343
+ """
246
344
  if not self.thread["LookForData"].isRunning() and not self.thread["RunAll"].isRunning():
247
345
  self.parent().po.all['im_or_vid'] = self.im_or_vid.currentIndex()
248
346
  self.parent().po.all['radical'] = self.radical.text()
249
347
  self.parent().po.all['extension'] = self.extension.text()
250
- #self.parent().po.all['display_shortcuts'] = self.shortcut_cb.isChecked()
251
348
  self.parent().po.sample_number = int(self.arena_number.value())
252
349
  self.parent().po.all['first_folder_sample_number'] = self.parent().po.sample_number
253
350
  self.parent().po.all['sample_number_per_folder'] = [self.parent().po.sample_number]
@@ -260,11 +357,6 @@ class FirstWindow(MainTabsType):
260
357
  self.message.setText('The folder selected is not valid')
261
358
  else:
262
359
  self.message.setText('')
263
- # self.parent().po.all['im_or_vid'] = self.im_or_vid.currentIndex()
264
- # self.parent().po.all['radical'] = self.radical.text()
265
- # self.parent().po.all['extension'] = self.extension.text()
266
- # self.parent().po.all['display_shortcuts'] = self.shortcut_cb.isChecked()
267
-
268
360
  self.message.setText(f"Looking for {self.parent().po.all['radical']}***{self.parent().po.all['extension']} Wait...")
269
361
  self.message.setStyleSheet("color: rgb(230, 145, 18)")
270
362
  self.thread["LookForData"].start()
@@ -273,27 +365,23 @@ class FirstWindow(MainTabsType):
273
365
  self.message.setText('Analysis has already begun, wait or restart Cellects.')
274
366
 
275
367
  def when_look_for_data_finished(self):
368
+ """
369
+ Check if there are any data items left in the selected folder and its sub-folders.
370
+ Display appropriate error messages or proceed with further actions based on the data availability.
371
+
372
+ Notes
373
+ -----
374
+ This function checks if there are any data items (images or videos) left in the selected folder and its sub-folders.
375
+ If no data is found, it displays an error message. Otherwise, it proceeds with instantiating widgets or starting a thread.
376
+ """
276
377
  if len(self.parent().po.all['folder_list']) == 0 and len(self.parent().po.data_list) == 0:
277
378
  if self.parent().po.all['im_or_vid'] == 1:
278
379
  error_message = f"There is no videos ({self.parent().po.all['extension']})in the selected folder and its sub-folders"
279
380
  else:
280
381
  error_message = f"There is no images ({self.parent().po.all['extension']}) in the selected folder and its sub-folders"
281
382
  self.message.setText(error_message)
282
- # = FixedText(error_message, align='r')
283
- # self.message.setStyleSheet("color: rgb(230, 145, 18)")
284
- # self.layout.addWidget(self.message, 12, 0, 12, 3)
285
383
  else:
286
384
  self.message.setText('')
287
- # if len(self.parent().po.all['folder_list']) > 0:
288
- # self.parent().po.update_folder_id(self.parent().po.all['first_folder_sample_number'],
289
- # self.parent().po.all['folder_list'][0])
290
- # if self.instantiate: # not self.parent().imageanalysiswindow.initialized:
291
- # self.thread["GetFirstIm"].start()
292
- # self.thread["GetFirstIm"].message_when_thread_finished.connect(self.first_im_read)
293
- # else:
294
- # self.first_im_read(True)
295
- # if isinstance(self.parent().po.all['sample_number_per_folder'], int):
296
- # self.parent().po.all['folder_number'] = 1
297
385
  if self.parent().po.all['folder_number'] > 1:
298
386
  self.parent().instantiate_widgets()
299
387
  self.parent().ifseveralfolderswindow.true_init()
@@ -304,10 +392,18 @@ class FirstWindow(MainTabsType):
304
392
  self.thread["GetFirstIm"].message_when_thread_finished.connect(self.first_im_read)
305
393
 
306
394
  def first_im_read(self, greyscale):
395
+ """
396
+ Initialize the image analysis window and prepare for reading images.
397
+
398
+ Notes
399
+ -----
400
+ This function prepares the image analysis window and sets it to be ready for
401
+ reading images. It also ensures that certain tabs are set as not in use.
402
+ """
307
403
  self.parent().instantiate_widgets()
308
404
  self.parent().imageanalysiswindow.true_init()
309
405
  self.instantiate = False
310
- if self.parent().po.first_exp_ready_to_run:
406
+ if self.parent().po.first_exp_ready_to_run and (self.parent().po.all["im_or_vid"] == 1 or len(self.parent().po.data_list) > 1):
311
407
  self.parent().imageanalysiswindow.video_tab.set_not_in_use()
312
408
  self.parent().change_widget(2) # imageanalysiswindow
313
409
  # From now on, image analysis will be available from video analysis:
@@ -315,10 +411,27 @@ class FirstWindow(MainTabsType):
315
411
  self.thread["GetExifDataThread"].start()
316
412
 
317
413
  def required_outputs_is_clicked(self):
414
+ """
415
+ Handle the click event for switching to required outputs.
416
+
417
+ This function sets the `last_is_first` attribute of the parent to True
418
+ and changes the widget to the Required Outputs view.
419
+ """
318
420
  self.parent().last_is_first = True
319
421
  self.parent().change_widget(4) # RequiredOutput
320
422
 
321
423
  def advanced_parameters_is_clicked(self):
424
+ """
425
+ Handle the click event for switching to advanced parameters.
426
+
427
+ Checks if an Exif data reading thread is running and acts accordingly.
428
+ If not, it updates the display for advanced parameters.
429
+
430
+ Notes
431
+ -----
432
+ This function updates the display for advanced parameters only if no Exif data reading thread is running.
433
+ If a thread is active, it informs the user to wait or restart Cellects.
434
+ """
322
435
  if self.thread["GetExifDataThread"].isRunning():
323
436
  self.message.setText("Reading data, wait or restart Cellects")
324
437
  else:
@@ -327,26 +440,61 @@ class FirstWindow(MainTabsType):
327
440
  self.parent().change_widget(5) # AdvancedParameters
328
441
 
329
442
  def video_analysis_window_is_clicked(self):
443
+ """
444
+ Handles the logic for when the "Video tracking" button is clicked in the interface,
445
+ leading to the video analysis window.
446
+
447
+ Notes
448
+ -----
449
+ This function displays an error message when a thread relative to the current window is running.
450
+ This function also save the id of the following window for later use.
451
+ """
330
452
  if self.video_tab.state != "not_usable":
331
453
  if self.thread["LookForData"].isRunning() or self.thread["LoadDataToRunCellectsQuickly"].isRunning() or self.thread["GetFirstIm"].isRunning() or self.thread["RunAll"].isRunning():
332
454
  self.message.setText("Wait for the analysis to end, or restart Cellects")
333
455
  else:
334
456
  self.parent().last_tab = "data_specifications"
335
- # self.parent().po.first_exp_ready_to_run = False
336
457
  self.parent().change_widget(3) # Should be VideoAnalysisW
337
458
 
338
459
  def Run_all_directly_is_clicked(self):
460
+ """
461
+ Run_all_directly_is_clicked
462
+
463
+ This method initiates a complete analysis process by starting the `RunAll` thread
464
+ after ensuring no other relevant threads are currently running.
465
+
466
+ Notes
467
+ -----
468
+ - This method ensures that the `LookForData` and `RunAll` threads are not running
469
+ before initiating a new analysis.
470
+ - The method updates the UI to indicate that an analysis has started and displays
471
+ progress messages.
472
+ """
339
473
  if not self.thread["LookForData"].isRunning() and not self.thread["RunAll"].isRunning():
340
474
  self.parent().po.motion = None
341
475
  self.message.setText("Complete analysis has started, wait until this message disappear...")
342
- # if not self.parent().po.first_exp_ready_to_run:
343
- # self.parent().po.use_data_to_run_cellects_quickly = True
344
476
  self.thread["RunAll"].start()
345
477
  self.thread["RunAll"].message_from_thread.connect(self.display_message_from_thread)
346
478
  self.thread["RunAll"].image_from_thread.connect(self.display_image_during_thread)
347
479
  self.display_image.setVisible(True)
348
480
 
349
481
  def pathway_changed(self):
482
+ """
483
+ Method for handling pathway changes in the application.
484
+
485
+ This method performs several operations when a new global pathway is set:
486
+ 1. Waits for any running thread to complete.
487
+ 2. Updates the global pathway if a valid directory is found.
488
+ 3. Changes the current working directory to the new global pathway.
489
+ 4. Hides various widgets associated with advanced options and outputs.
490
+ 5. Starts a background thread to load data quickly.
491
+ 6. If the provided pathway is invalid, it hides relevant tabs and outputs an error message.
492
+
493
+ Notes
494
+ -----
495
+ This method performs actions to prepare the application for loading data from a new pathway.
496
+ It ensures that certain widgets are hidden and starts necessary background processes.
497
+ """
350
498
  if self.thread["LoadDataToRunCellectsQuickly"].isRunning():
351
499
  self.thread["LoadDataToRunCellectsQuickly"].wait()
352
500
  if os.path.isdir(Path(self.global_pathway.text())):
@@ -360,7 +508,6 @@ class FirstWindow(MainTabsType):
360
508
  self.im_or_vid.setVisible(False)
361
509
  self.advanced_parameters.setVisible(False)
362
510
  self.required_outputs.setVisible(False)
363
- # self.Video_analysis_window.setVisible(False)
364
511
  self.Run_all_directly.setVisible(False)
365
512
  self.next.setVisible(False)
366
513
  # 2) Load the dict
@@ -368,13 +515,25 @@ class FirstWindow(MainTabsType):
368
515
  self.thread["LoadDataToRunCellectsQuickly"].message_from_thread.connect(self.load_data_quickly_finished)
369
516
  # 3) go to another func to change, put visible and re_instantiate
370
517
  else:
371
- # self.Video_analysis_window.setVisible(False)
372
518
  self.Run_all_directly.setVisible(False)
373
519
  self.image_tab.set_not_usable()
374
520
  self.video_tab.set_not_usable()
375
521
  self.message.setText("Please, enter a valid path")
376
522
 
377
- def load_data_quickly_finished(self, message):
523
+ def load_data_quickly_finished(self, message: str):
524
+ """
525
+ Set up the UI components for a new experiment.
526
+
527
+ Parameters
528
+ ----------
529
+ message : str
530
+ The message to be displayed on the UI component.
531
+
532
+ Notes
533
+ -----
534
+ This function sets several visibility flags and values for UI components
535
+ in preparation for starting an experiment.
536
+ """
378
537
  self.image_tab.set_not_in_use()
379
538
  self.message.setText(message)
380
539
  self.radical.setVisible(True)
@@ -392,58 +551,21 @@ class FirstWindow(MainTabsType):
392
551
  self.im_or_vid.setCurrentIndex(self.parent().po.all['im_or_vid'])
393
552
  self.radical.setText(self.parent().po.all['radical'])
394
553
  self.extension.setText(self.parent().po.all['extension'])
395
- #self.shortcut_cb.setChecked(self.parent().po.all['display_shortcuts'])
396
- #self.display_shortcuts_checked()
397
- # self.Video_analysis_window.setVisible(True)
398
554
  self.Run_all_directly.setVisible(True)
399
- self.video_tab.set_not_in_use()
555
+ if self.parent().po.all["im_or_vid"] == 1 or len(self.parent().po.data_list) > 1:
556
+ self.video_tab.set_not_in_use()
400
557
 
401
558
 
402
559
  def re_instantiate_widgets(self):
403
560
  """
404
-
405
- :return:
561
+ Reinstantiate the videoanalysis window from the parent of the current window.
406
562
  """
407
563
  self.instantiate = True
408
564
  # Since we re-instantiate everything, image analysis will no longer be available from video analysis:
409
565
  self.parent().videoanalysiswindow.image_tab.set_not_usable()
410
566
 
411
- # self.parent().po.all['radical'] = self.radical.text()
412
- # self.parent().po.all['extension'] = self.extension.text()
413
- # self.parent().po.sample_number = int(self.arena_number.value())
414
- # self.parent().po.all['first_folder_sample_number'] = self.parent().po.sample_number
415
- # self.parent().po.all['sample_number_per_folder'] = [self.parent().po.sample_number]
416
-
417
-
418
- # Mettre ça en thread ? PB : conflict entre all et vars
419
- # if os.path.isfile('Data to run Cellects quickly.pkl'):
420
- # try:
421
- # with open('Data to run Cellects quickly.pkl', 'rb') as fileopen:
422
- # data_to_run_cellects_quickly = pickle.load(fileopen)
423
- # if 'vars' in data_to_run_cellects_quickly:
424
- # self.vars = data_to_run_cellects_quickly['vars']
425
- # except EOFError:
426
- # print("Pickle error: could not load vars from the data folder")
427
- # self.instantiate = True
428
- # else:
429
- # self.instantiate = True
430
- # if self.instantiate:
431
- # self.parent().po.all['radical'] = self.radical.text()
432
- # self.parent().po.all['extension'] = self.extension.text()
433
- # self.parent().po.sample_number = int(self.arena_number.value())
434
- # self.parent().po.all['first_folder_sample_number'] = self.parent().po.sample_number
435
- # self.parent().po.all['sample_number_per_folder'] = [self.parent().po.sample_number]
436
-
437
567
  def closeEvent(self, event):
568
+ """
569
+ Handle the close event for a QWidget.
570
+ """
438
571
  event.accept
439
-
440
-
441
- # if __name__ == "__main__":
442
- # from cellects.gui.cellects import CellectsMainWidget
443
- # import sys
444
- # app = QtWidgets.QApplication([])
445
- # parent = CellectsMainWidget()
446
- # session = FirstWindow(parent, False)
447
- # parent.insertWidget(0, session)
448
- # parent.show()
449
- # sys.exit(app.exec())