spacr 0.2.68__py3-none-any.whl → 0.3.0__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 (77) hide show
  1. spacr/__init__.py +2 -1
  2. spacr/core.py +107 -12
  3. spacr/gui.py +3 -2
  4. spacr/gui_core.py +160 -109
  5. spacr/gui_elements.py +190 -18
  6. spacr/gui_utils.py +4 -1
  7. spacr/io.py +1 -1
  8. spacr/measure.py +4 -4
  9. spacr/mediar.py +366 -0
  10. spacr/plot.py +4 -1
  11. spacr/resources/MEDIAR/.git +1 -0
  12. spacr/resources/MEDIAR/.gitignore +18 -0
  13. spacr/resources/MEDIAR/LICENSE +21 -0
  14. spacr/resources/MEDIAR/README.md +189 -0
  15. spacr/resources/MEDIAR/SetupDict.py +39 -0
  16. spacr/resources/MEDIAR/config/baseline.json +60 -0
  17. spacr/resources/MEDIAR/config/mediar_example.json +72 -0
  18. spacr/resources/MEDIAR/config/pred/pred_mediar.json +17 -0
  19. spacr/resources/MEDIAR/config/step1_pretraining/phase1.json +55 -0
  20. spacr/resources/MEDIAR/config/step1_pretraining/phase2.json +58 -0
  21. spacr/resources/MEDIAR/config/step2_finetuning/finetuning1.json +66 -0
  22. spacr/resources/MEDIAR/config/step2_finetuning/finetuning2.json +66 -0
  23. spacr/resources/MEDIAR/config/step3_prediction/base_prediction.json +16 -0
  24. spacr/resources/MEDIAR/config/step3_prediction/ensemble_tta.json +23 -0
  25. spacr/resources/MEDIAR/core/BasePredictor.py +120 -0
  26. spacr/resources/MEDIAR/core/BaseTrainer.py +240 -0
  27. spacr/resources/MEDIAR/core/Baseline/Predictor.py +59 -0
  28. spacr/resources/MEDIAR/core/Baseline/Trainer.py +113 -0
  29. spacr/resources/MEDIAR/core/Baseline/__init__.py +2 -0
  30. spacr/resources/MEDIAR/core/Baseline/utils.py +80 -0
  31. spacr/resources/MEDIAR/core/MEDIAR/EnsemblePredictor.py +105 -0
  32. spacr/resources/MEDIAR/core/MEDIAR/Predictor.py +234 -0
  33. spacr/resources/MEDIAR/core/MEDIAR/Trainer.py +172 -0
  34. spacr/resources/MEDIAR/core/MEDIAR/__init__.py +3 -0
  35. spacr/resources/MEDIAR/core/MEDIAR/utils.py +429 -0
  36. spacr/resources/MEDIAR/core/__init__.py +2 -0
  37. spacr/resources/MEDIAR/core/utils.py +40 -0
  38. spacr/resources/MEDIAR/evaluate.py +71 -0
  39. spacr/resources/MEDIAR/generate_mapping.py +121 -0
  40. spacr/resources/MEDIAR/image/examples/img1.tiff +0 -0
  41. spacr/resources/MEDIAR/image/examples/img2.tif +0 -0
  42. spacr/resources/MEDIAR/image/failure_cases.png +0 -0
  43. spacr/resources/MEDIAR/image/mediar_framework.png +0 -0
  44. spacr/resources/MEDIAR/image/mediar_model.PNG +0 -0
  45. spacr/resources/MEDIAR/image/mediar_results.png +0 -0
  46. spacr/resources/MEDIAR/main.py +125 -0
  47. spacr/resources/MEDIAR/predict.py +70 -0
  48. spacr/resources/MEDIAR/requirements.txt +14 -0
  49. spacr/resources/MEDIAR/train_tools/__init__.py +3 -0
  50. spacr/resources/MEDIAR/train_tools/data_utils/__init__.py +1 -0
  51. spacr/resources/MEDIAR/train_tools/data_utils/custom/CellAware.py +88 -0
  52. spacr/resources/MEDIAR/train_tools/data_utils/custom/LoadImage.py +161 -0
  53. spacr/resources/MEDIAR/train_tools/data_utils/custom/NormalizeImage.py +77 -0
  54. spacr/resources/MEDIAR/train_tools/data_utils/custom/__init__.py +3 -0
  55. spacr/resources/MEDIAR/train_tools/data_utils/custom/modalities.pkl +0 -0
  56. spacr/resources/MEDIAR/train_tools/data_utils/datasetter.py +208 -0
  57. spacr/resources/MEDIAR/train_tools/data_utils/transforms.py +148 -0
  58. spacr/resources/MEDIAR/train_tools/data_utils/utils.py +84 -0
  59. spacr/resources/MEDIAR/train_tools/measures.py +200 -0
  60. spacr/resources/MEDIAR/train_tools/models/MEDIARFormer.py +102 -0
  61. spacr/resources/MEDIAR/train_tools/models/__init__.py +1 -0
  62. spacr/resources/MEDIAR/train_tools/utils.py +70 -0
  63. spacr/resources/MEDIAR_weights/.DS_Store +0 -0
  64. spacr/resources/icons/.DS_Store +0 -0
  65. spacr/resources/icons/plaque.png +0 -0
  66. spacr/resources/images/plate1_E01_T0001F001L01A01Z01C02.tif +0 -0
  67. spacr/resources/images/plate1_E01_T0001F001L01A02Z01C01.tif +0 -0
  68. spacr/resources/images/plate1_E01_T0001F001L01A03Z01C03.tif +0 -0
  69. spacr/sequencing.py +234 -422
  70. spacr/settings.py +16 -10
  71. spacr/utils.py +14 -11
  72. {spacr-0.2.68.dist-info → spacr-0.3.0.dist-info}/METADATA +10 -2
  73. {spacr-0.2.68.dist-info → spacr-0.3.0.dist-info}/RECORD +77 -18
  74. {spacr-0.2.68.dist-info → spacr-0.3.0.dist-info}/LICENSE +0 -0
  75. {spacr-0.2.68.dist-info → spacr-0.3.0.dist-info}/WHEEL +0 -0
  76. {spacr-0.2.68.dist-info → spacr-0.3.0.dist-info}/entry_points.txt +0 -0
  77. {spacr-0.2.68.dist-info → spacr-0.3.0.dist-info}/top_level.txt +0 -0
spacr/__init__.py CHANGED
@@ -23,9 +23,9 @@ from . import app_measure
23
23
  from . import app_classify
24
24
  from . import app_sequencing
25
25
  from . import app_umap
26
+ from . import mediar
26
27
  from . import logger
27
28
 
28
-
29
29
  __all__ = [
30
30
  "core",
31
31
  "io",
@@ -48,6 +48,7 @@ __all__ = [
48
48
  "app_classify",
49
49
  "app_sequencing",
50
50
  "app_umap",
51
+ "mediar",
51
52
  "logger"
52
53
  ]
53
54
 
spacr/core.py CHANGED
@@ -50,8 +50,6 @@ import random
50
50
  from PIL import Image
51
51
  from torchvision.transforms import ToTensor
52
52
 
53
-
54
-
55
53
  def analyze_plaques(folder):
56
54
  summary_data = []
57
55
  details_data = []
@@ -1638,7 +1636,7 @@ def preprocess_generate_masks(src, settings={}):
1638
1636
 
1639
1637
  settings = set_default_settings_preprocess_generate_masks(src, settings)
1640
1638
  settings['src'] = src
1641
- save_settings(settings)
1639
+ save_settings(settings, name='gen_mask')
1642
1640
 
1643
1641
  if not settings['pathogen_channel'] is None:
1644
1642
  custom_model_ls = ['toxo_pv_lumen','toxo_cyto']
@@ -1674,7 +1672,10 @@ def preprocess_generate_masks(src, settings={}):
1674
1672
  time_ls=[]
1675
1673
  if check_mask_folder(src, 'cell_mask_stack'):
1676
1674
  start = time.time()
1677
- generate_cellpose_masks(mask_src, settings, 'cell')
1675
+ if settings['segmantation_model'] == 'cellpose':
1676
+ generate_cellpose_masks(mask_src, settings, 'cell')
1677
+ elif settings['segmantation_model'] == 'mediar':
1678
+ generate_mediar_masks(mask_src, settings, 'cell')
1678
1679
  stop = time.time()
1679
1680
  duration = (stop - start)
1680
1681
  time_ls.append(duration)
@@ -1685,7 +1686,10 @@ def preprocess_generate_masks(src, settings={}):
1685
1686
  time_ls=[]
1686
1687
  if check_mask_folder(src, 'nucleus_mask_stack'):
1687
1688
  start = time.time()
1688
- generate_cellpose_masks(mask_src, settings, 'nucleus')
1689
+ if settings['segmantation_model'] == 'cellpose':
1690
+ generate_cellpose_masks(mask_src, settings, 'nucleus')
1691
+ elif settings['segmantation_model'] == 'mediar':
1692
+ generate_mediar_masks(mask_src, settings, 'nucleus')
1689
1693
  stop = time.time()
1690
1694
  duration = (stop - start)
1691
1695
  time_ls.append(duration)
@@ -1696,7 +1700,10 @@ def preprocess_generate_masks(src, settings={}):
1696
1700
  time_ls=[]
1697
1701
  if check_mask_folder(src, 'pathogen_mask_stack'):
1698
1702
  start = time.time()
1699
- generate_cellpose_masks(mask_src, settings, 'pathogen')
1703
+ if settings['segmantation_model'] == 'cellpose':
1704
+ generate_cellpose_masks(mask_src, settings, 'pathogen')
1705
+ elif settings['segmantation_model'] == 'mediar':
1706
+ generate_mediar_masks(mask_src, settings, 'pathogen')
1700
1707
  stop = time.time()
1701
1708
  duration = (stop - start)
1702
1709
  time_ls.append(duration)
@@ -1898,7 +1905,7 @@ def all_elements_match(list1, list2):
1898
1905
  # Check if all elements in list1 are in list2
1899
1906
  return all(element in list2 for element in list1)
1900
1907
 
1901
- def prepare_batch_for_cellpose(batch):
1908
+ def prepare_batch_for_segmentation(batch):
1902
1909
  # Ensure the batch is of dtype float32
1903
1910
  if batch.dtype != np.float32:
1904
1911
  batch = batch.astype(np.float32)
@@ -2021,7 +2028,7 @@ def generate_cellpose_masks(src, settings, object_type):
2021
2028
  if batch.size == 0:
2022
2029
  continue
2023
2030
 
2024
- batch = prepare_batch_for_cellpose(batch)
2031
+ batch = prepare_batch_for_segmentation(batch)
2025
2032
 
2026
2033
  if timelapse:
2027
2034
  movie_path = os.path.join(os.path.dirname(src), 'movies')
@@ -2180,7 +2187,7 @@ def generate_masks_from_imgs(src, model, model_name, batch_size, diameter, cellp
2180
2187
  image_files = all_image_files[i:i+batch_size]
2181
2188
 
2182
2189
  if normalize:
2183
- images, _, image_names, _ = _load_normalized_images_and_labels(image_files, None, channels, percentiles, circular, invert, plot, remove_background, background, Signal_to_noise)
2190
+ images, _, image_names, _, orig_dims = _load_normalized_images_and_labels(image_files, None, channels, percentiles, circular, invert, plot, remove_background, background, Signal_to_noise, target_height, target_width)
2184
2191
  images = [np.squeeze(img) if img.shape[-1] == 1 else img for img in images]
2185
2192
  orig_dims = [(image.shape[0], image.shape[1]) for image in images]
2186
2193
  else:
@@ -2300,7 +2307,7 @@ def compare_cellpose_masks(src, verbose=False, processes=None, save=True):
2300
2307
  from .io import _read_mask
2301
2308
 
2302
2309
  dirs = [os.path.join(src, d) for d in os.listdir(src) if os.path.isdir(os.path.join(src, d)) and d != 'results']
2303
- dirs.sort() # Optional: sort directories if needed
2310
+ dirs.sort()
2304
2311
  conditions = [os.path.basename(d) for d in dirs]
2305
2312
 
2306
2313
  # Get common files in all directories
@@ -2316,7 +2323,7 @@ def compare_cellpose_masks(src, verbose=False, processes=None, save=True):
2316
2323
 
2317
2324
  # Filter out None results (from skipped files)
2318
2325
  results = [res for res in results if res is not None]
2319
- #print(results)
2326
+ print(results)
2320
2327
  if verbose:
2321
2328
  for result in results:
2322
2329
  filename = result['filename']
@@ -3102,4 +3109,92 @@ def reducer_hyperparameter_search(settings={}, reduction_params=None, dbscan_par
3102
3109
  else:
3103
3110
  plt.show()
3104
3111
 
3105
- return
3112
+ return
3113
+
3114
+ def generate_mediar_masks(src, settings, object_type):
3115
+ """
3116
+ Generates masks using the MEDIARPredictor.
3117
+
3118
+ :param src: Source folder containing images or npz files.
3119
+ :param settings: Dictionary of settings for generating masks.
3120
+ :param object_type: Type of object to detect (e.g., 'cell', 'nucleus', etc.).
3121
+ """
3122
+ from .mediar import MEDIARPredictor
3123
+ from .io import _create_database, _save_object_counts_to_database
3124
+ from .plot import plot_masks
3125
+ from .settings import set_default_settings_preprocess_generate_masks, _get_object_settings
3126
+
3127
+ # Clear CUDA cache and check if CUDA is available
3128
+ gc.collect()
3129
+ if not torch.cuda.is_available():
3130
+ print(f'Torch CUDA is not available, using CPU')
3131
+
3132
+ # Preprocess settings
3133
+ settings = set_default_settings_preprocess_generate_masks(src, settings)
3134
+
3135
+ if settings['verbose']:
3136
+ settings_df = pd.DataFrame(list(settings.items()), columns=['setting_key', 'setting_value'])
3137
+ settings_df['setting_value'] = settings_df['setting_value'].apply(str)
3138
+ display(settings_df)
3139
+
3140
+ figuresize = 10
3141
+ timelapse = settings['timelapse']
3142
+ batch_size = settings['batch_size']
3143
+
3144
+ # Get object settings and initialize MEDIARPredictor
3145
+ mediar_predictor = MEDIARPredictor(input_path=None, output_path=None, normalize=settings['normalize'], use_tta=False)
3146
+
3147
+ # Paths to input npz files
3148
+ paths = [os.path.join(src, file) for file in os.listdir(src) if file.endswith('.npz')]
3149
+
3150
+ # Initialize a database for saving measurements
3151
+ count_loc = os.path.join(os.path.dirname(src), 'measurements', 'measurements.db')
3152
+ os.makedirs(os.path.dirname(src) + '/measurements', exist_ok=True)
3153
+ _create_database(count_loc)
3154
+
3155
+ for file_index, path in enumerate(paths):
3156
+ name = os.path.basename(path)
3157
+ name, ext = os.path.splitext(name)
3158
+ output_folder = os.path.join(os.path.dirname(path), f'{object_type}_mask_stack')
3159
+ os.makedirs(output_folder, exist_ok=True)
3160
+
3161
+ with np.load(path) as data:
3162
+ stack = data['data']
3163
+ filenames = data['filenames']
3164
+
3165
+ for i, filename in enumerate(filenames):
3166
+ output_path = os.path.join(output_folder, filename)
3167
+ if os.path.exists(output_path):
3168
+ print(f"File {filename} already exists. Skipping...")
3169
+ continue
3170
+
3171
+ # Process each batch of images in the stack
3172
+ for i in range(0, stack.shape[0], batch_size):
3173
+ batch = stack[i: i + batch_size]
3174
+ batch_filenames = filenames[i: i + batch_size]
3175
+
3176
+ # Prepare batch for MEDIARPredictor (optional)
3177
+ batch = prepare_batch_for_segmentation(batch)
3178
+
3179
+ # Predict masks using MEDIARPredictor
3180
+ predicted_masks = mediar_predictor.predict_batch(batch)
3181
+
3182
+ # Save predicted masks
3183
+ for j, mask in enumerate(predicted_masks):
3184
+ output_filename = os.path.join(output_folder, batch_filenames[j])
3185
+ mask = mask.astype(np.uint16)
3186
+ np.save(output_filename, mask)
3187
+
3188
+ # Optional: Plot the masks
3189
+ if settings['plot']:
3190
+ for idx, mask in enumerate(predicted_masks):
3191
+ plot_masks(batch[idx], mask, cmap='inferno', figuresize=figuresize)
3192
+
3193
+ # Save object counts to database
3194
+ _save_object_counts_to_database(predicted_masks, object_type, batch_filenames, count_loc)
3195
+
3196
+ # Clear CUDA cache after each file
3197
+ gc.collect()
3198
+ torch.cuda.empty_cache()
3199
+
3200
+ print("Mask generation completed.")
spacr/gui.py CHANGED
@@ -32,7 +32,7 @@ class MainApp(tk.Tk):
32
32
  # Set the window size to the dimensions of the monitor where it is located
33
33
  self.geometry(f"{width}x{height}")
34
34
  self.title("SpaCr GUI Collection")
35
- self.configure(bg='#333333') # Set window background to dark gray
35
+ self.configure(bg='#333333')
36
36
 
37
37
  style = ttk.Style()
38
38
  self.color_settings = set_dark_style(style, parent_frame=self)
@@ -55,7 +55,8 @@ class MainApp(tk.Tk):
55
55
  "Cellpose All": (lambda frame: initiate_root(self, 'cellpose_all'), "Run Cellpose on all images."),
56
56
  "Map Barcodes": (lambda frame: initiate_root(self, 'map_barcodes'), "Map barcodes to data."),
57
57
  "Regression": (lambda frame: initiate_root(self, 'regression'), "Perform regression analysis."),
58
- "Recruitment": (lambda frame: initiate_root(self, 'recruitment'), "Analyze recruitment data.")
58
+ "Recruitment": (lambda frame: initiate_root(self, 'recruitment'), "Analyze recruitment data."),
59
+ "Plaque": (lambda frame: initiate_root(self, 'analyze_plaques'), "Analyze plaque data.")
59
60
  }
60
61
 
61
62
  self.selected_app = tk.StringVar()
spacr/gui_core.py CHANGED
@@ -1,4 +1,4 @@
1
- import traceback, ctypes, csv, re
1
+ import traceback, ctypes, csv, re, platform, time
2
2
  import tkinter as tk
3
3
  from tkinter import ttk
4
4
  from tkinter import filedialog
@@ -19,7 +19,7 @@ try:
19
19
  except AttributeError:
20
20
  pass
21
21
 
22
- from .gui_elements import spacrProgressBar, spacrButton, spacrLabel, spacrFrame, spacrDropdownMenu , set_dark_style
22
+ from .gui_elements import spacrProgressBar, spacrButton, spacrLabel, spacrFrame, spacrDropdownMenu , spacrSlider, set_dark_style, standardize_figure
23
23
 
24
24
  # Define global variables
25
25
  q = None
@@ -35,8 +35,6 @@ figures = None
35
35
  figure_index = None
36
36
  progress_bar = None
37
37
  usage_bars = None
38
- fig_memory_limit = None
39
- figure_current_memory_usage = None
40
38
 
41
39
  thread_control = {"run_thread": None, "stop_requested": False}
42
40
 
@@ -80,35 +78,10 @@ def toggle_settings(button_scrollable_frame):
80
78
  category_dropdown.grid(row=0, column=4, sticky="ew", pady=2, padx=2)
81
79
  vars_dict = hide_all_settings(vars_dict, categories)
82
80
 
83
- def process_fig_queue():
84
- global canvas, fig_queue, canvas_widget, parent_frame, uppdate_frequency, figures, figure_index
85
-
86
- from .gui_elements import standardize_figure
87
- try:
88
- while not fig_queue.empty():
89
- fig = fig_queue.get_nowait()
90
-
91
- if fig is None:
92
- print("Warning: Retrieved a None figure from fig_queue.")
93
- continue # Skip processing if the figure is None
94
-
95
- # Standardize the figure appearance before adding it to the list
96
- standardize_figure(fig)
97
-
98
- figures.append(fig)
99
- if figure_index == len(figures) - 2:
100
- figure_index += 1
101
- display_figure(fig)
102
- except Exception as e:
103
- traceback.print_exc()
104
- finally:
105
- after_id = canvas_widget.after(uppdate_frequency, process_fig_queue)
106
- parent_frame.after_tasks.append(after_id)
107
-
108
81
  def display_figure(fig):
109
82
  global canvas, canvas_widget
110
83
 
111
- from .gui_elements import modify_figure_properties, save_figure_as_format, modify_figure
84
+ from .gui_elements import save_figure_as_format, modify_figure
112
85
 
113
86
  # Apply the dark style to the context menu
114
87
  style_out = set_dark_style(ttk.Style())
@@ -197,7 +170,7 @@ def display_figure(fig):
197
170
  #flash_feedback("right")
198
171
  show_next_figure()
199
172
 
200
- def zoom(event):
173
+ def zoom_v1(event):
201
174
  nonlocal scale_factor
202
175
 
203
176
  zoom_speed = 0.1 # Adjust the zoom speed for smoother experience
@@ -230,16 +203,55 @@ def display_figure(fig):
230
203
  # Redraw the figure
231
204
  fig.canvas.draw_idle()
232
205
 
206
+ def zoom(event):
207
+ nonlocal scale_factor
208
+
209
+ zoom_speed = 0.1 # Adjust the zoom speed for smoother experience
210
+
211
+ # Determine the zoom direction based on the scroll event
212
+ if event.num == 4 or event.delta > 0: # Scroll up (zoom in)
213
+ scale_factor /= (1 + zoom_speed) # Divide to zoom in
214
+ elif event.num == 5 or event.delta < 0: # Scroll down (zoom out)
215
+ scale_factor *= (1 + zoom_speed) # Multiply to zoom out
216
+
217
+ # Adjust the axes limits based on the new scale factor
218
+ for ax in canvas.figure.get_axes():
219
+ xlim = ax.get_xlim()
220
+ ylim = ax.get_ylim()
221
+
222
+ x_center = (xlim[1] + xlim[0]) / 2
223
+ y_center = (ylim[1] + ylim[0]) / 2
224
+
225
+ x_range = (xlim[1] - xlim[0]) * scale_factor
226
+ y_range = (ylim[1] - ylim[0]) * scale_factor
227
+
228
+ # Set the new limits
229
+ ax.set_xlim([x_center - x_range / 2, x_center + x_range / 2])
230
+ ax.set_ylim([y_center - y_range / 2, y_center + y_range / 2])
231
+
232
+ # Redraw the figure efficiently
233
+ canvas.draw_idle()
234
+
235
+
233
236
  # Bind events for hover, click interactions, and zoom
234
237
  canvas_widget.bind("<Motion>", on_hover)
235
238
  canvas_widget.bind("<Leave>", on_leave)
236
239
  canvas_widget.bind("<Button-1>", on_click)
237
240
  canvas_widget.bind("<Button-3>", on_right_click)
238
241
 
239
- # Bind mouse wheel for zooming (cross-platform)
240
- canvas_widget.bind("<MouseWheel>", zoom) # Windows
241
- canvas_widget.bind("<Button-4>", zoom) # Linux/macOS Scroll Up
242
- canvas_widget.bind("<Button-5>", zoom) # Linux/macOS Scroll Down
242
+
243
+ # Detect the operating system and bind the appropriate mouse wheel events
244
+ current_os = platform.system()
245
+
246
+ if current_os == "Windows":
247
+ canvas_widget.bind("<MouseWheel>", zoom) # Windows
248
+ elif current_os == "Darwin": # macOS
249
+ canvas_widget.bind("<MouseWheel>", zoom)
250
+ canvas_widget.bind("<Button-4>", zoom) # Scroll up
251
+ canvas_widget.bind("<Button-5>", zoom) # Scroll down
252
+ elif current_os == "Linux":
253
+ canvas_widget.bind("<Button-4>", zoom) # Linux Scroll up
254
+ canvas_widget.bind("<Button-5>", zoom) # Linux Scroll down
243
255
 
244
256
  def clear_unused_figures():
245
257
  global figures, figure_index
@@ -253,6 +265,7 @@ def clear_unused_figures():
253
265
 
254
266
  def show_previous_figure():
255
267
  global figure_index, figures, fig_queue
268
+
256
269
  if figure_index is not None and figure_index > 0:
257
270
  figure_index -= 1
258
271
  display_figure(figures[figure_index])
@@ -270,8 +283,104 @@ def show_next_figure():
270
283
  figure_index += 1
271
284
  display_figure(fig)
272
285
 
273
- def set_globals(thread_control_var, q_var, console_output_var, parent_frame_var, vars_dict_var, canvas_var, canvas_widget_var, scrollable_frame_var, fig_queue_var, figures_var, figure_index_var, progress_bar_var, usage_bars_var, fig_memory_limit_var, figure_current_memory_usage_var):
274
- global thread_control, q, console_output, parent_frame, vars_dict, canvas, canvas_widget, scrollable_frame, fig_queue, figures, figure_index, progress_bar, usage_bars, fig_memory_limit, figure_current_memory_usage
286
+ def process_fig_queue():
287
+ global canvas, fig_queue, canvas_widget, parent_frame, uppdate_frequency, figures, figure_index, index_control
288
+
289
+ from .gui_elements import standardize_figure
290
+ try:
291
+ while not fig_queue.empty():
292
+ fig = fig_queue.get_nowait()
293
+
294
+ if fig is None:
295
+ print("Warning: Retrieved a None figure from fig_queue.")
296
+ continue # Skip processing if the figure is None
297
+
298
+ # Standardize the figure appearance before adding it to the list
299
+ fig = standardize_figure(fig)
300
+
301
+ figures.append(fig)
302
+
303
+ # Update the slider range and set the value to the latest figure index
304
+ index_control.set_to(len(figures) - 1)
305
+
306
+ if figure_index == -1:
307
+ figure_index += 1
308
+ display_figure(figures[figure_index])
309
+ index_control.set(figure_index)
310
+
311
+ except Exception as e:
312
+ traceback.print_exc()
313
+ finally:
314
+ after_id = canvas_widget.after(uppdate_frequency, process_fig_queue)
315
+ parent_frame.after_tasks.append(after_id)
316
+
317
+ def update_figure(value):
318
+ global figure_index, figures
319
+
320
+ # Convert the value to an integer
321
+ index = int(value)
322
+
323
+ # Check if the index is valid
324
+ if 0 <= index < len(figures):
325
+ figure_index = index
326
+ display_figure(figures[figure_index])
327
+
328
+ # Update the index control widget's range and value
329
+ index_control.set_to(len(figures) - 1)
330
+ index_control.set(figure_index)
331
+
332
+ def setup_plot_section(vertical_container):
333
+ global canvas, canvas_widget, figures, figure_index, index_control
334
+
335
+ # Initialize deque for storing figures and the current index
336
+ figures = deque()
337
+
338
+ # Create a frame for the plot section
339
+ plot_frame = tk.Frame(vertical_container)
340
+ vertical_container.add(plot_frame, stretch="always")
341
+
342
+ # Set up the plot
343
+ figure = Figure(figsize=(30, 4), dpi=100)
344
+ plot = figure.add_subplot(111)
345
+ plot.plot([], [])
346
+ plot.axis('off')
347
+
348
+ canvas = FigureCanvasTkAgg(figure, master=plot_frame)
349
+ canvas.get_tk_widget().configure(cursor='arrow', highlightthickness=0)
350
+ canvas_widget = canvas.get_tk_widget()
351
+ canvas_widget.grid(row=0, column=0, sticky="nsew")
352
+
353
+ plot_frame.grid_rowconfigure(0, weight=1)
354
+ plot_frame.grid_columnconfigure(0, weight=1)
355
+
356
+ canvas.draw()
357
+ canvas.figure = figure # Ensure that the figure is linked to the canvas
358
+ style_out = set_dark_style(ttk.Style())
359
+ bg = style_out['bg_color']
360
+ fg = style_out['fg_color']
361
+
362
+ figure.patch.set_facecolor(bg)
363
+ plot.set_facecolor(bg)
364
+ containers = [plot_frame]
365
+
366
+ # Create slider
367
+ control_frame = tk.Frame(plot_frame, height=15*2, bg=bg) # Fixed height based on knob_radius
368
+ control_frame.grid(row=1, column=0, sticky="ew", padx=10, pady=5)
369
+ control_frame.grid_propagate(False) # Prevent the frame from resizing
370
+
371
+ # Pass the update_figure function as the command to spacrSlider
372
+ index_control = spacrSlider(control_frame, from_=0, to=0, value=0, thickness=2, knob_radius=10, position="center", show_index=True, command=update_figure)
373
+ index_control.grid(row=0, column=0, sticky="ew")
374
+ control_frame.grid_columnconfigure(0, weight=1)
375
+
376
+ widgets = [canvas_widget, index_control]
377
+ style = ttk.Style(vertical_container)
378
+ _ = set_dark_style(style, containers=containers, widgets=widgets)
379
+
380
+ return canvas, canvas_widget
381
+
382
+ def set_globals(thread_control_var, q_var, console_output_var, parent_frame_var, vars_dict_var, canvas_var, canvas_widget_var, scrollable_frame_var, fig_queue_var, figures_var, figure_index_var, index_control_var, progress_bar_var, usage_bars_var):
383
+ global thread_control, q, console_output, parent_frame, vars_dict, canvas, canvas_widget, scrollable_frame, fig_queue, figures, figure_index, progress_bar, usage_bars, index_control
275
384
  thread_control = thread_control_var
276
385
  q = q_var
277
386
  console_output = console_output_var
@@ -285,8 +394,7 @@ def set_globals(thread_control_var, q_var, console_output_var, parent_frame_var,
285
394
  figure_index = figure_index_var
286
395
  progress_bar = progress_bar_var
287
396
  usage_bars = usage_bars_var
288
- fig_memory_limit = fig_memory_limit_var
289
- figure_current_memory_usage = figure_current_memory_usage_var
397
+ index_control = index_control_var
290
398
 
291
399
  def import_settings(settings_type='mask'):
292
400
  from .gui_utils import convert_settings_dict_for_gui, hide_all_settings
@@ -332,6 +440,8 @@ def import_settings(settings_type='mask'):
332
440
  settings = set_default_umap_image_settings(settings={})
333
441
  elif settings_type == 'recruitment':
334
442
  settings = get_analyze_recruitment_default_settings(settings={})
443
+ elif settings_type == 'analyze_plaques':
444
+ settings = {}
335
445
  else:
336
446
  raise ValueError(f"Invalid settings type: {settings_type}")
337
447
 
@@ -385,6 +495,8 @@ def setup_settings_panel(vertical_container, settings_type='mask'):
385
495
  settings = get_perform_regression_default_settings(settings={})
386
496
  elif settings_type == 'recruitment':
387
497
  settings = get_analyze_recruitment_default_settings(settings={})
498
+ elif settings_type == 'analyze_plaques':
499
+ settings = {}
388
500
  else:
389
501
  raise ValueError(f"Invalid settings type: {settings_type}")
390
502
 
@@ -400,46 +512,6 @@ def setup_settings_panel(vertical_container, settings_type='mask'):
400
512
  print("Settings panel setup complete")
401
513
  return scrollable_frame, vars_dict
402
514
 
403
- def setup_plot_section(vertical_container):
404
- global canvas, canvas_widget, figures, figure_index
405
-
406
- from .gui_elements import set_element_size
407
-
408
- # Initialize deque for storing figures and the current index
409
- figures = deque()
410
- figure_index = -1
411
-
412
- # Create a frame for the plot section
413
- plot_frame = tk.Frame(vertical_container)
414
- vertical_container.add(plot_frame, stretch="always")
415
-
416
- # Set up the plot
417
- figure = Figure(figsize=(30, 4), dpi=100)
418
- plot = figure.add_subplot(111)
419
- plot.plot([], [])
420
- plot.axis('off')
421
-
422
- canvas = FigureCanvasTkAgg(figure, master=plot_frame)
423
- canvas.get_tk_widget().configure(cursor='arrow', highlightthickness=0)
424
- canvas_widget = canvas.get_tk_widget()
425
- canvas_widget.grid(row=0, column=0, sticky="nsew")
426
-
427
- plot_frame.grid_rowconfigure(0, weight=1)
428
- plot_frame.grid_columnconfigure(0, weight=1)
429
-
430
- canvas.draw()
431
- canvas.figure = figure # Ensure that the figure is linked to the canvas
432
- style_out = set_dark_style(ttk.Style())
433
-
434
- figure.patch.set_facecolor(style_out['bg_color'])
435
- plot.set_facecolor(style_out['bg_color'])
436
- containers = [plot_frame]
437
- widgets = [canvas_widget]
438
- style = ttk.Style(vertical_container)
439
- _ = set_dark_style(style, containers=containers, widgets=widgets)
440
-
441
- return canvas, canvas_widget
442
-
443
515
  def setup_console(vertical_container):
444
516
  global console_output
445
517
  from .gui_elements import set_dark_style
@@ -479,27 +551,6 @@ def setup_console(vertical_container):
479
551
 
480
552
  return console_output, console_frame
481
553
 
482
- def setup_progress_frame(vertical_container):
483
- global progress_output
484
- style_out = set_dark_style(ttk.Style())
485
- font_loader = style_out['font_loader']
486
- font_size = style_out['font_size']
487
- progress_frame = tk.Frame(vertical_container)
488
- vertical_container.add(progress_frame, stretch="always")
489
- label_frame = tk.Frame(progress_frame)
490
- label_frame.grid(row=0, column=0, sticky="ew", pady=(5, 0), padx=10)
491
- progress_label = spacrLabel(label_frame, text="Processing: 0%", font=font_loader.get_font(size=font_size), anchor='w', justify='left', align="left")
492
- progress_label.grid(row=0, column=0, sticky="w")
493
- progress_output = scrolledtext.ScrolledText(progress_frame, height=10)
494
- progress_output.grid(row=1, column=0, sticky="nsew")
495
- progress_frame.grid_rowconfigure(1, weight=1)
496
- progress_frame.grid_columnconfigure(0, weight=1)
497
- containers = [progress_frame, label_frame]
498
- widgets = [progress_label, progress_output]
499
- style = ttk.Style(vertical_container)
500
- _ = set_dark_style(style, containers=containers, widgets=widgets)
501
- return progress_output
502
-
503
554
  def setup_button_section(horizontal_container, settings_type='mask', run=True, abort=True, download=True, import_btn=True):
504
555
  global thread_control, parent_frame, button_frame, button_scrollable_frame, run_button, abort_button, download_dataset_button, import_button, q, fig_queue, vars_dict, progress_bar
505
556
  from .gui_utils import download_hug_dataset
@@ -598,7 +649,7 @@ def setup_usage_panel(horizontal_container, btn_col, uppdate_frequency):
598
649
  widgets = [usage_scrollable_frame.scrollable_frame]
599
650
 
600
651
  usage_bars = []
601
- max_elements_per_column = 6
652
+ max_elements_per_column = 5
602
653
  row = 0
603
654
  col = 0
604
655
 
@@ -726,9 +777,9 @@ def start_process(q=None, fig_queue=None, settings_type='mask'):
726
777
  initialize_cuda()
727
778
 
728
779
  process_args = (settings_type, settings, q, fig_queue, stop_requested)
729
- if settings_type in ['mask', 'umap', 'measure', 'simulation', 'sequencing',
730
- 'classify', 'cellpose_dataset', 'train_cellpose', 'ml_analyze', 'cellpose_masks', 'cellpose_all', 'map_barcodes',
731
- 'regression', 'recruitment', 'plaques', 'cellpose_compare', 'vision_scores', 'vision_dataset']:
780
+ if settings_type in ['mask', 'umap', 'measure', 'simulation', 'sequencing', 'classify', 'analyze_plaques',
781
+ 'cellpose_dataset', 'train_cellpose', 'ml_analyze', 'cellpose_masks', 'cellpose_all', 'map_barcodes',
782
+ 'regression', 'recruitment', 'cellpose_compare', 'vision_scores', 'vision_dataset']:
732
783
 
733
784
  # Start the process
734
785
  process = Process(target=run_function_gui, args=process_args)
@@ -836,7 +887,7 @@ def initiate_root(parent, settings_type='mask'):
836
887
  tuple: A tuple containing the parent frame and the dictionary of variables used in the GUI.
837
888
  """
838
889
 
839
- global q, fig_queue, thread_control, parent_frame, scrollable_frame, button_frame, vars_dict, canvas, canvas_widget, button_scrollable_frame, progress_bar, uppdate_frequency, figures, figure_index, fig_memory_limit, figure_current_memory_usage
890
+ global q, fig_queue, thread_control, parent_frame, scrollable_frame, button_frame, vars_dict, canvas, canvas_widget, button_scrollable_frame, progress_bar, uppdate_frequency, figures, figure_index, index_control, usage_bars
840
891
 
841
892
  from .gui_utils import setup_frame
842
893
  from .settings import descriptions
@@ -853,8 +904,6 @@ def initiate_root(parent, settings_type='mask'):
853
904
  # Initialize global variables
854
905
  figures = deque()
855
906
  figure_index = -1
856
- fig_memory_limit = 200 * 1024 * 1024 # 200 MB limit
857
- figure_current_memory_usage = 0
858
907
 
859
908
  parent_frame = parent
860
909
 
@@ -886,7 +935,7 @@ def initiate_root(parent, settings_type='mask'):
886
935
  button_scrollable_frame, btn_col = setup_button_section(horizontal_container, settings_type)
887
936
  _, usage_bars, btn_col = setup_usage_panel(horizontal_container, btn_col, uppdate_frequency)
888
937
 
889
- set_globals(thread_control, q, console_output, parent_frame, vars_dict, canvas, canvas_widget, scrollable_frame, fig_queue, figures, figure_index, progress_bar, usage_bars, fig_memory_limit, figure_current_memory_usage)
938
+ set_globals(thread_control, q, console_output, parent_frame, vars_dict, canvas, canvas_widget, scrollable_frame, fig_queue, figures, figure_index, index_control, progress_bar, usage_bars)
890
939
  description_text = descriptions.get(settings_type, "No description available for this module.")
891
940
 
892
941
  q.put(f"Console")
@@ -899,4 +948,6 @@ def initiate_root(parent, settings_type='mask'):
899
948
  parent_window.after_tasks.append(after_id)
900
949
 
901
950
  print("Root initialization complete")
902
- return parent_frame, vars_dict
951
+ return parent_frame, vars_dict
952
+
953
+