celldetective 1.4.2__py3-none-any.whl → 1.5.0b0__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 (151) hide show
  1. celldetective/__init__.py +25 -0
  2. celldetective/__main__.py +62 -43
  3. celldetective/_version.py +1 -1
  4. celldetective/extra_properties.py +477 -399
  5. celldetective/filters.py +192 -97
  6. celldetective/gui/InitWindow.py +541 -411
  7. celldetective/gui/__init__.py +0 -15
  8. celldetective/gui/about.py +44 -39
  9. celldetective/gui/analyze_block.py +120 -84
  10. celldetective/gui/base/__init__.py +0 -0
  11. celldetective/gui/base/channel_norm_generator.py +335 -0
  12. celldetective/gui/base/components.py +249 -0
  13. celldetective/gui/base/feature_choice.py +92 -0
  14. celldetective/gui/base/figure_canvas.py +52 -0
  15. celldetective/gui/base/list_widget.py +133 -0
  16. celldetective/gui/{styles.py → base/styles.py} +92 -36
  17. celldetective/gui/base/utils.py +33 -0
  18. celldetective/gui/base_annotator.py +900 -767
  19. celldetective/gui/classifier_widget.py +6 -22
  20. celldetective/gui/configure_new_exp.py +777 -671
  21. celldetective/gui/control_panel.py +635 -524
  22. celldetective/gui/dynamic_progress.py +449 -0
  23. celldetective/gui/event_annotator.py +2023 -1662
  24. celldetective/gui/generic_signal_plot.py +1292 -944
  25. celldetective/gui/gui_utils.py +899 -1289
  26. celldetective/gui/interactions_block.py +658 -0
  27. celldetective/gui/interactive_timeseries_viewer.py +447 -0
  28. celldetective/gui/json_readers.py +48 -15
  29. celldetective/gui/layouts/__init__.py +5 -0
  30. celldetective/gui/layouts/background_model_free_layout.py +537 -0
  31. celldetective/gui/layouts/channel_offset_layout.py +134 -0
  32. celldetective/gui/layouts/local_correction_layout.py +91 -0
  33. celldetective/gui/layouts/model_fit_layout.py +372 -0
  34. celldetective/gui/layouts/operation_layout.py +68 -0
  35. celldetective/gui/layouts/protocol_designer_layout.py +96 -0
  36. celldetective/gui/pair_event_annotator.py +3130 -2435
  37. celldetective/gui/plot_measurements.py +586 -267
  38. celldetective/gui/plot_signals_ui.py +724 -506
  39. celldetective/gui/preprocessing_block.py +395 -0
  40. celldetective/gui/process_block.py +1678 -1831
  41. celldetective/gui/seg_model_loader.py +580 -473
  42. celldetective/gui/settings/__init__.py +0 -7
  43. celldetective/gui/settings/_cellpose_model_params.py +181 -0
  44. celldetective/gui/settings/_event_detection_model_params.py +95 -0
  45. celldetective/gui/settings/_segmentation_model_params.py +159 -0
  46. celldetective/gui/settings/_settings_base.py +77 -65
  47. celldetective/gui/settings/_settings_event_model_training.py +752 -526
  48. celldetective/gui/settings/_settings_measurements.py +1133 -964
  49. celldetective/gui/settings/_settings_neighborhood.py +574 -488
  50. celldetective/gui/settings/_settings_segmentation_model_training.py +779 -564
  51. celldetective/gui/settings/_settings_signal_annotator.py +329 -305
  52. celldetective/gui/settings/_settings_tracking.py +1304 -1094
  53. celldetective/gui/settings/_stardist_model_params.py +98 -0
  54. celldetective/gui/survival_ui.py +422 -312
  55. celldetective/gui/tableUI.py +1665 -1701
  56. celldetective/gui/table_ops/_maths.py +295 -0
  57. celldetective/gui/table_ops/_merge_groups.py +140 -0
  58. celldetective/gui/table_ops/_merge_one_hot.py +95 -0
  59. celldetective/gui/table_ops/_query_table.py +43 -0
  60. celldetective/gui/table_ops/_rename_col.py +44 -0
  61. celldetective/gui/thresholds_gui.py +382 -179
  62. celldetective/gui/viewers/__init__.py +0 -0
  63. celldetective/gui/viewers/base_viewer.py +700 -0
  64. celldetective/gui/viewers/channel_offset_viewer.py +331 -0
  65. celldetective/gui/viewers/contour_viewer.py +394 -0
  66. celldetective/gui/viewers/size_viewer.py +153 -0
  67. celldetective/gui/viewers/spot_detection_viewer.py +341 -0
  68. celldetective/gui/viewers/threshold_viewer.py +309 -0
  69. celldetective/gui/workers.py +304 -126
  70. celldetective/log_manager.py +92 -0
  71. celldetective/measure.py +1895 -1478
  72. celldetective/napari/__init__.py +0 -0
  73. celldetective/napari/utils.py +1025 -0
  74. celldetective/neighborhood.py +1914 -1448
  75. celldetective/preprocessing.py +1620 -1220
  76. celldetective/processes/__init__.py +0 -0
  77. celldetective/processes/background_correction.py +271 -0
  78. celldetective/processes/compute_neighborhood.py +894 -0
  79. celldetective/processes/detect_events.py +246 -0
  80. celldetective/processes/measure_cells.py +565 -0
  81. celldetective/processes/segment_cells.py +760 -0
  82. celldetective/processes/track_cells.py +435 -0
  83. celldetective/processes/train_segmentation_model.py +694 -0
  84. celldetective/processes/train_signal_model.py +265 -0
  85. celldetective/processes/unified_process.py +292 -0
  86. celldetective/regionprops/_regionprops.py +358 -317
  87. celldetective/relative_measurements.py +987 -710
  88. celldetective/scripts/measure_cells.py +313 -212
  89. celldetective/scripts/measure_relative.py +90 -46
  90. celldetective/scripts/segment_cells.py +165 -104
  91. celldetective/scripts/segment_cells_thresholds.py +96 -68
  92. celldetective/scripts/track_cells.py +198 -149
  93. celldetective/scripts/train_segmentation_model.py +324 -201
  94. celldetective/scripts/train_signal_model.py +87 -45
  95. celldetective/segmentation.py +844 -749
  96. celldetective/signals.py +3514 -2861
  97. celldetective/tracking.py +30 -15
  98. celldetective/utils/__init__.py +0 -0
  99. celldetective/utils/cellpose_utils/__init__.py +133 -0
  100. celldetective/utils/color_mappings.py +42 -0
  101. celldetective/utils/data_cleaning.py +630 -0
  102. celldetective/utils/data_loaders.py +450 -0
  103. celldetective/utils/dataset_helpers.py +207 -0
  104. celldetective/utils/downloaders.py +197 -0
  105. celldetective/utils/event_detection/__init__.py +8 -0
  106. celldetective/utils/experiment.py +1782 -0
  107. celldetective/utils/image_augmenters.py +308 -0
  108. celldetective/utils/image_cleaning.py +74 -0
  109. celldetective/utils/image_loaders.py +926 -0
  110. celldetective/utils/image_transforms.py +335 -0
  111. celldetective/utils/io.py +62 -0
  112. celldetective/utils/mask_cleaning.py +348 -0
  113. celldetective/utils/mask_transforms.py +5 -0
  114. celldetective/utils/masks.py +184 -0
  115. celldetective/utils/maths.py +351 -0
  116. celldetective/utils/model_getters.py +325 -0
  117. celldetective/utils/model_loaders.py +296 -0
  118. celldetective/utils/normalization.py +380 -0
  119. celldetective/utils/parsing.py +465 -0
  120. celldetective/utils/plots/__init__.py +0 -0
  121. celldetective/utils/plots/regression.py +53 -0
  122. celldetective/utils/resources.py +34 -0
  123. celldetective/utils/stardist_utils/__init__.py +104 -0
  124. celldetective/utils/stats.py +90 -0
  125. celldetective/utils/types.py +21 -0
  126. {celldetective-1.4.2.dist-info → celldetective-1.5.0b0.dist-info}/METADATA +1 -1
  127. celldetective-1.5.0b0.dist-info/RECORD +187 -0
  128. {celldetective-1.4.2.dist-info → celldetective-1.5.0b0.dist-info}/WHEEL +1 -1
  129. tests/gui/test_new_project.py +129 -117
  130. tests/gui/test_project.py +127 -79
  131. tests/test_filters.py +39 -15
  132. tests/test_notebooks.py +8 -0
  133. tests/test_tracking.py +232 -13
  134. tests/test_utils.py +123 -77
  135. celldetective/gui/base_components.py +0 -23
  136. celldetective/gui/layouts.py +0 -1602
  137. celldetective/gui/processes/compute_neighborhood.py +0 -594
  138. celldetective/gui/processes/measure_cells.py +0 -360
  139. celldetective/gui/processes/segment_cells.py +0 -499
  140. celldetective/gui/processes/track_cells.py +0 -303
  141. celldetective/gui/processes/train_segmentation_model.py +0 -270
  142. celldetective/gui/processes/train_signal_model.py +0 -108
  143. celldetective/gui/table_ops/merge_groups.py +0 -118
  144. celldetective/gui/viewers.py +0 -1354
  145. celldetective/io.py +0 -3663
  146. celldetective/utils.py +0 -3108
  147. celldetective-1.4.2.dist-info/RECORD +0 -123
  148. /celldetective/{gui/processes → processes}/downloader.py +0 -0
  149. {celldetective-1.4.2.dist-info → celldetective-1.5.0b0.dist-info}/entry_points.txt +0 -0
  150. {celldetective-1.4.2.dist-info → celldetective-1.5.0b0.dist-info}/licenses/LICENSE +0 -0
  151. {celldetective-1.4.2.dist-info → celldetective-1.5.0b0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,435 @@
1
+ from multiprocessing import Process
2
+ import time
3
+ from pathlib import Path, PurePath
4
+ from glob import glob
5
+ from tqdm import tqdm
6
+ import numpy as np
7
+ import gc
8
+ import concurrent.futures
9
+ import datetime
10
+ import os
11
+ import json
12
+ from celldetective.measure import drop_tonal_features, measure_features
13
+ from celldetective.tracking import track
14
+ import pandas as pd
15
+ from natsort import natsorted
16
+ from art import tprint
17
+ from celldetective.log_manager import get_logger
18
+ import traceback
19
+ from skimage.io import imread
20
+
21
+ from celldetective.utils.data_cleaning import _mask_intensity_measurements
22
+ from celldetective.utils.data_loaders import interpret_tracking_configuration
23
+ from celldetective.utils.experiment import extract_experiment_channels
24
+ from celldetective.utils.image_loaders import (
25
+ _get_img_num_per_channel,
26
+ auto_load_number_of_frames,
27
+ _load_frames_to_measure,
28
+ )
29
+ from celldetective.utils.io import remove_file_if_exists
30
+ from celldetective.utils.parsing import config_section_to_dict
31
+
32
+ logger = get_logger(__name__)
33
+
34
+
35
+ class TrackingProcess(Process):
36
+
37
+ def __init__(self, queue=None, process_args=None, *args, **kwargs):
38
+
39
+ super().__init__(*args, **kwargs)
40
+
41
+ self.queue = queue
42
+
43
+ if process_args is not None:
44
+ for key, value in process_args.items():
45
+ setattr(self, key, value)
46
+
47
+ self.timestep_dataframes = []
48
+
49
+ self.sum_done = 0
50
+ self.t0 = time.time()
51
+
52
+ def read_tracking_instructions(self):
53
+
54
+ instr_path = PurePath(self.exp_dir, Path(f"{self.instruction_file}"))
55
+ if os.path.exists(instr_path):
56
+ logger.info(
57
+ f"Tracking instructions for the {self.mode} population have been successfully loaded..."
58
+ )
59
+ with open(instr_path, "r") as f:
60
+ self.instructions = json.load(f)
61
+
62
+ self.btrack_config = interpret_tracking_configuration(
63
+ self.instructions["btrack_config_path"]
64
+ )
65
+
66
+ if "features" in self.instructions:
67
+ self.features = self.instructions["features"]
68
+ else:
69
+ self.features = None
70
+
71
+ if "mask_channels" in self.instructions:
72
+ self.mask_channels = self.instructions["mask_channels"]
73
+ else:
74
+ self.mask_channels = None
75
+
76
+ if "haralick_options" in self.instructions:
77
+ self.haralick_options = self.instructions["haralick_options"]
78
+ else:
79
+ self.haralick_options = None
80
+
81
+ if "post_processing_options" in self.instructions:
82
+ self.post_processing_options = self.instructions[
83
+ "post_processing_options"
84
+ ]
85
+ else:
86
+ self.post_processing_options = None
87
+
88
+ self.btrack_option = True
89
+ if "btrack_option" in self.instructions:
90
+ self.btrack_option = self.instructions["btrack_option"]
91
+ self.search_range = None
92
+ if "search_range" in self.instructions:
93
+ self.search_range = self.instructions["search_range"]
94
+ self.memory = None
95
+ if "memory" in self.instructions:
96
+ self.memory = self.instructions["memory"]
97
+ else:
98
+ logger.info(
99
+ "Tracking instructions could not be located... Using a standard bTrack motion model instead..."
100
+ )
101
+ self.btrack_config = interpret_tracking_configuration(None)
102
+ self.features = None
103
+ self.mask_channels = None
104
+ self.haralick_options = None
105
+ self.post_processing_options = None
106
+ self.btrack_option = True
107
+ self.memory = None
108
+ self.search_range = None
109
+
110
+ if self.features is None:
111
+ self.features = []
112
+
113
+ def detect_channels(self):
114
+ self.img_num_channels = _get_img_num_per_channel(
115
+ self.channel_indices, self.len_movie, self.nbr_channels
116
+ )
117
+
118
+ def write_log(self):
119
+
120
+ features_log = f"features: {self.features}"
121
+ mask_channels_log = f"mask_channels: {self.mask_channels}"
122
+ haralick_option_log = f"haralick_options: {self.haralick_options}"
123
+ post_processing_option_log = (
124
+ f"post_processing_options: {self.post_processing_options}"
125
+ )
126
+ log_list = [
127
+ features_log,
128
+ mask_channels_log,
129
+ haralick_option_log,
130
+ post_processing_option_log,
131
+ ]
132
+ log = "\n".join(log_list)
133
+
134
+ with open(self.pos + f"log_{self.mode}.txt", "a") as f:
135
+ f.write(f"{datetime.datetime.now()} TRACK \n")
136
+ f.write(log + "\n")
137
+
138
+ def prepare_folders(self):
139
+
140
+ if not os.path.exists(self.pos + "output"):
141
+ os.mkdir(self.pos + "output")
142
+
143
+ if not os.path.exists(self.pos + os.sep.join(["output", "tables"])):
144
+ os.mkdir(self.pos + os.sep.join(["output", "tables"]))
145
+
146
+ if self.mode.lower() == "target" or self.mode.lower() == "targets":
147
+ self.label_folder = "labels_targets"
148
+ self.instruction_file = os.sep.join(
149
+ ["configs", "tracking_instructions_targets.json"]
150
+ )
151
+ self.napari_name = "napari_target_trajectories.npy"
152
+ self.table_name = "trajectories_targets.csv"
153
+
154
+ elif self.mode.lower() == "effector" or self.mode.lower() == "effectors":
155
+ self.label_folder = "labels_effectors"
156
+ self.instruction_file = os.sep.join(
157
+ ["configs", "tracking_instructions_effectors.json"]
158
+ )
159
+ self.napari_name = "napari_effector_trajectories.npy"
160
+ self.table_name = "trajectories_effectors.csv"
161
+
162
+ else:
163
+ self.label_folder = f"labels_{self.mode}"
164
+ self.instruction_file = os.sep.join(
165
+ ["configs", f"tracking_instructions_{self.mode}.json"]
166
+ )
167
+ self.napari_name = f"napari_{self.mode}_trajectories.npy"
168
+ self.table_name = f"trajectories_{self.mode}.csv"
169
+
170
+ def extract_experiment_parameters(self):
171
+
172
+ self.movie_prefix = config_section_to_dict(self.config, "MovieSettings")[
173
+ "movie_prefix"
174
+ ]
175
+ self.spatial_calibration = float(
176
+ config_section_to_dict(self.config, "MovieSettings")["pxtoum"]
177
+ )
178
+ self.time_calibration = float(
179
+ config_section_to_dict(self.config, "MovieSettings")["frametomin"]
180
+ )
181
+ self.len_movie = float(
182
+ config_section_to_dict(self.config, "MovieSettings")["len_movie"]
183
+ )
184
+ self.shape_x = int(
185
+ config_section_to_dict(self.config, "MovieSettings")["shape_x"]
186
+ )
187
+ self.shape_y = int(
188
+ config_section_to_dict(self.config, "MovieSettings")["shape_y"]
189
+ )
190
+
191
+ self.channel_names, self.channel_indices = extract_experiment_channels(
192
+ self.exp_dir
193
+ )
194
+ self.nbr_channels = len(self.channel_names)
195
+
196
+ def locate_experiment_config(self):
197
+
198
+ parent1 = Path(self.pos).parent
199
+ self.exp_dir = parent1.parent
200
+ self.config = PurePath(self.exp_dir, Path("config.ini"))
201
+
202
+ if not os.path.exists(self.config):
203
+ logger.info("The configuration file for the experiment was not found...")
204
+ self.abort_process()
205
+
206
+ def detect_movie_and_labels(self):
207
+
208
+ self.label_path = natsorted(
209
+ glob(self.pos + f"{self.label_folder}" + os.sep + "*.tif")
210
+ )
211
+ if len(self.label_path) > 0:
212
+ logger.info(f"Found {len(self.label_path)} segmented frames...")
213
+ # Optimize: Create a map of frame index to file path
214
+ self.label_map = {}
215
+ for path in self.label_path:
216
+ try:
217
+ # Assumes format ####.tif, e.g., 0001.tif
218
+ frame_idx = int(os.path.basename(path).split(".")[0])
219
+ self.label_map[frame_idx] = path
220
+ except ValueError:
221
+ continue
222
+ else:
223
+ logger.error(
224
+ f"No segmented frames have been found. Please run segmentation first. Abort..."
225
+ )
226
+ self.abort_process()
227
+
228
+ try:
229
+ self.file = glob(self.pos + f"movie/{self.movie_prefix}*.tif")[0]
230
+ except IndexError:
231
+ self.file = None
232
+ self.haralick_option = None
233
+ self.features = drop_tonal_features(self.features)
234
+ logger.warning("Movie could not be found. Check the prefix.")
235
+
236
+ len_movie_auto = auto_load_number_of_frames(self.file)
237
+ if len_movie_auto is not None:
238
+ self.len_movie = len_movie_auto
239
+
240
+ def parallel_job(self, indices):
241
+
242
+ props = []
243
+
244
+ try:
245
+
246
+ for t in tqdm(indices, desc="frame"):
247
+
248
+ # Load channels at time t
249
+ img = _load_frames_to_measure(
250
+ self.file, indices=self.img_num_channels[:, t]
251
+ )
252
+ # Optimize: Direct lookup instead of glob
253
+ if t in self.label_map:
254
+ try:
255
+ # Load image from path in map
256
+ lbl = np.array(imread(self.label_map[t]))
257
+ except Exception as e:
258
+ logger.error(f"Failed to load label for frame {t}: {e}")
259
+ continue
260
+ else:
261
+ # Fallback or skip if not in map
262
+ continue
263
+
264
+ df_props = measure_features(
265
+ img,
266
+ lbl,
267
+ features=self.features + ["centroid"],
268
+ border_dist=None,
269
+ channels=self.channel_names,
270
+ haralick_options=self.haralick_options,
271
+ verbose=False,
272
+ )
273
+ df_props.rename(
274
+ columns={"centroid-1": "x", "centroid-0": "y"}, inplace=True
275
+ )
276
+ df_props["t"] = int(t)
277
+
278
+ props.append(df_props)
279
+
280
+ # Progress Update
281
+ self.loop_count += 1
282
+
283
+ data = {}
284
+
285
+ # Frame Progress
286
+ frame_progress = (self.loop_count / self.len_movie) * 100
287
+ if frame_progress > 100:
288
+ frame_progress = 100
289
+ data["frame_progress"] = frame_progress
290
+
291
+ # Frame Time Estimation
292
+ elapsed = time.time() - getattr(self, "t0_frame", time.time())
293
+ if self.loop_count > 0:
294
+ avg = elapsed / self.loop_count
295
+ rem = self.len_movie - self.loop_count
296
+ rem_t = rem * avg
297
+ mins = int(rem_t // 60)
298
+ secs = int(rem_t % 60)
299
+ data["frame_time"] = f"Tracking: {mins} m {secs} s"
300
+ else:
301
+ data["frame_time"] = "Tracking..."
302
+
303
+ self.queue.put(data)
304
+
305
+ except Exception as e:
306
+ logger.error(e)
307
+ traceback.print_exc()
308
+
309
+ return props
310
+
311
+ def setup_for_position(self, pos):
312
+
313
+ self.pos = pos
314
+ # Experiment
315
+ self.prepare_folders()
316
+ self.locate_experiment_config()
317
+ self.extract_experiment_parameters()
318
+ self.read_tracking_instructions()
319
+ self.detect_movie_and_labels()
320
+ self.detect_channels()
321
+ self.write_log()
322
+
323
+ def process_position(self):
324
+
325
+ tprint("Track")
326
+
327
+ self.indices = list(range(self.img_num_channels.shape[1]))
328
+ chunks = np.array_split(self.indices, self.n_threads)
329
+
330
+ self.timestep_dataframes = []
331
+ self.t0_frame = time.time()
332
+ self.loop_count = 0
333
+
334
+ with concurrent.futures.ThreadPoolExecutor(
335
+ max_workers=self.n_threads
336
+ ) as executor:
337
+ results = executor.map(self.parallel_job, chunks)
338
+ try:
339
+ for i, return_value in enumerate(results):
340
+ logger.info(f"Thread {i} completed...")
341
+ self.timestep_dataframes.extend(return_value)
342
+ except Exception as e:
343
+ logger.error("Exception: ", e)
344
+
345
+ logger.info("Features successfully measured...")
346
+
347
+ if not self.timestep_dataframes:
348
+ logger.warning("No cells detected in any frame. Skipping position.")
349
+ return
350
+
351
+ df = pd.concat(self.timestep_dataframes)
352
+ if df.empty:
353
+ logger.warning("Dataframe is empty. Skipping position.")
354
+ return
355
+
356
+ df = df.replace([np.inf, -np.inf], np.nan)
357
+
358
+ df.reset_index(inplace=True, drop=True)
359
+ df = _mask_intensity_measurements(df, self.mask_channels)
360
+
361
+ # do tracking
362
+ if self.btrack_option:
363
+ tracker = "bTrack"
364
+ else:
365
+ tracker = "trackpy"
366
+
367
+ try:
368
+ trajectories, napari_data = track(
369
+ None,
370
+ configuration=self.btrack_config,
371
+ objects=df,
372
+ spatial_calibration=self.spatial_calibration,
373
+ channel_names=self.channel_names,
374
+ return_napari_data=True,
375
+ optimizer_options={"tm_lim": int(12e4)},
376
+ track_kwargs={"step_size": 100},
377
+ clean_trajectories_kwargs=self.post_processing_options,
378
+ volume=(self.shape_x, self.shape_y),
379
+ btrack_option=self.btrack_option,
380
+ search_range=self.search_range,
381
+ memory=self.memory,
382
+ )
383
+ except Exception as e:
384
+ logger.error(f"Tracking failed: {e}")
385
+ if "search_range" in str(e) or "SubnetOversizeException" in str(e):
386
+ logger.error(
387
+ "Suggestion: Try reducing the 'search_range' (maxdisp) in your tracking configuration. Skipping tracking for this position."
388
+ )
389
+ return
390
+ raise e
391
+
392
+ logger.info("Tracking successfully performed...")
393
+
394
+ # out trajectory table, create POSITION_X_um, POSITION_Y_um, TIME_min (new ones)
395
+ # Save napari data
396
+ np.save(
397
+ self.pos + os.sep.join(["output", "tables", self.napari_name]),
398
+ napari_data,
399
+ allow_pickle=True,
400
+ )
401
+
402
+ trajectories.to_csv(
403
+ self.pos + os.sep.join(["output", "tables", self.table_name]), index=False
404
+ )
405
+ logger.info(
406
+ f"Trajectory table successfully exported in {os.sep.join(['output', 'tables'])}..."
407
+ )
408
+
409
+ remove_file_if_exists(
410
+ self.pos
411
+ + os.sep.join(["output", "tables", self.table_name.replace(".csv", ".pkl")])
412
+ )
413
+
414
+ del trajectories
415
+ del napari_data
416
+ gc.collect()
417
+
418
+ def run(self):
419
+
420
+ self.setup_for_position(self.pos)
421
+ self.process_position()
422
+
423
+ # Send end signal
424
+ self.queue.put("finished")
425
+ self.queue.close()
426
+
427
+ def end_process(self):
428
+
429
+ self.terminate()
430
+ self.queue.put("finished")
431
+
432
+ def abort_process(self):
433
+
434
+ self.terminate()
435
+ self.queue.put("error")