spacr 0.0.2__py3-none-any.whl → 0.0.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.
spacr/gui_utils.py CHANGED
@@ -1,18 +1,284 @@
1
- import spacr, inspect, traceback, io, sys, ast, ctypes, matplotlib, re, csv
1
+ import os, spacr, inspect, traceback, io, sys, ast, ctypes, matplotlib, re, csv, requests
2
2
  import matplotlib.pyplot as plt
3
3
  matplotlib.use('Agg')
4
4
  import numpy as np
5
5
  import tkinter as tk
6
6
  from tkinter import ttk, messagebox
7
- from tkinter.font import nametofont
7
+ import tkinter.font as tkFont
8
8
  from torchvision import models
9
+ #from ttf_opensans import opensans
10
+
11
+ from tkinter import font as tkFont
12
+
13
+
14
+ from .logger import log_function_call
9
15
 
10
16
  try:
11
17
  ctypes.windll.shcore.SetProcessDpiAwareness(True)
12
18
  except AttributeError:
13
19
  pass
14
20
 
15
- from .logger import log_function_call
21
+ class ToolTip:
22
+ def __init__(self, widget, text):
23
+ self.widget = widget
24
+ self.text = text
25
+ self.tooltip_window = None
26
+ widget.bind("<Enter>", self.show_tooltip)
27
+ widget.bind("<Leave>", self.hide_tooltip)
28
+
29
+ def show_tooltip(self, event):
30
+ x = event.x_root + 20
31
+ y = event.y_root + 10
32
+ self.tooltip_window = tk.Toplevel(self.widget)
33
+ self.tooltip_window.wm_overrideredirect(True)
34
+ self.tooltip_window.wm_geometry(f"+{x}+{y}")
35
+ label = tk.Label(self.tooltip_window, text=self.text, background="yellow", relief='solid', borderwidth=1)
36
+ label.pack()
37
+
38
+ def hide_tooltip(self, event):
39
+ if self.tooltip_window:
40
+ self.tooltip_window.destroy()
41
+ self.tooltip_window = None
42
+
43
+
44
+ def load_app(root, app_name, app_func):
45
+ # Destroy the current window
46
+ root.destroy()
47
+ # Create a new window for the app
48
+ app_window = tk.Tk()
49
+ app_window.title(f"SpaCr - {app_name}")
50
+ app_window.geometry("1200x800")
51
+ #app_window.attributes('-fullscreen', True)
52
+ app_window.configure(bg="black")
53
+ create_menu_bar(app_window) # Add menu to the new window
54
+ app_func(app_window, app_window.winfo_width(), app_window.winfo_height())
55
+
56
+ def create_menu_bar(root):
57
+
58
+ from .gui_mask_app import initiate_mask_root
59
+ from .gui_measure_app import initiate_measure_root
60
+ from .annotate_app import initiate_annotation_app_root
61
+ from .mask_app import initiate_mask_app_root
62
+ from .gui_classify_app import initiate_classify_root
63
+
64
+ gui_apps = {
65
+ "Mask": initiate_mask_root,
66
+ "Measure": initiate_measure_root,
67
+ "Annotate": initiate_annotation_app_root,
68
+ "Make Masks": initiate_mask_app_root,
69
+ "Classify": initiate_classify_root
70
+ }
71
+ # Create the menu bar
72
+ menu_bar = tk.Menu(root, bg="#008080", fg="white")
73
+ # Create a "SpaCr Applications" menu
74
+ app_menu = tk.Menu(menu_bar, tearoff=0, bg="#008080", fg="white")
75
+ menu_bar.add_cascade(label="SpaCr Applications", menu=app_menu)
76
+ # Add options to the "SpaCr Applications" menu
77
+ for app_name, app_func in gui_apps.items():
78
+ app_menu.add_command(label=app_name, command=lambda app_name=app_name, app_func=app_func: load_app(root, app_name, app_func))
79
+ # Add a separator and an exit option
80
+ app_menu.add_separator()
81
+ app_menu.add_command(label="Exit", command=root.quit)
82
+ # Configure the menu for the root window
83
+ root.config(menu=menu_bar)
84
+
85
+ class CustomButton(tk.Frame):
86
+ def __init__(self, parent, text="", command=None, *args, **kwargs):
87
+ super().__init__(parent, *args, **kwargs)
88
+ self.text = text
89
+ self.command = command
90
+
91
+ self.canvas = tk.Canvas(self, width=150, height=50, highlightthickness=0, bg="black")
92
+ self.canvas.grid(row=0, column=0)
93
+
94
+ self.button_bg = self.create_rounded_rectangle(0, 0, 150, 50, radius=20, fill="#800080")
95
+
96
+ # Create a Tkinter font object using OpenSans
97
+ self.font_style = tkFont.Font(family="Arial", size=12, weight=tkFont.NORMAL)
98
+ self.button_text = self.canvas.create_text(75, 25, text=self.text, fill="white", font=self.font_style)
99
+
100
+ self.bind("<Enter>", self.on_enter)
101
+ self.bind("<Leave>", self.on_leave)
102
+ self.bind("<Button-1>", self.on_click)
103
+ self.canvas.bind("<Enter>", self.on_enter)
104
+ self.canvas.bind("<Leave>", self.on_leave)
105
+ self.canvas.bind("<Button-1>", self.on_click)
106
+
107
+ def on_enter(self, event=None):
108
+ self.canvas.itemconfig(self.button_bg, fill="#993399")
109
+
110
+ def on_leave(self, event=None):
111
+ self.canvas.itemconfig(self.button_bg, fill="#800080")
112
+
113
+ def on_click(self, event=None):
114
+ if self.command:
115
+ self.command()
116
+
117
+ def create_rounded_rectangle(self, x1, y1, x2, y2, radius=20, **kwargs):
118
+ points = [x1 + radius, y1,
119
+ x1 + radius, y1,
120
+ x2 - radius, y1,
121
+ x2 - radius, y1,
122
+ x2, y1,
123
+ x2, y1 + radius,
124
+ x2, y1 + radius,
125
+ x2, y2 - radius,
126
+ x2, y2 - radius,
127
+ x2, y2,
128
+ x2 - radius, y2,
129
+ x2 - radius, y2,
130
+ x1 + radius, y2,
131
+ x1 + radius, y2,
132
+ x1, y2,
133
+ x1, y2 - radius,
134
+ x1, y2 - radius,
135
+ x1, y1 + radius,
136
+ x1, y1 + radius,
137
+ x1, y1]
138
+
139
+ return self.canvas.create_polygon(points, **kwargs, smooth=True)
140
+
141
+ class ToggleSwitch(ttk.Frame):
142
+ def __init__(self, parent, text="", variable=None, command=None, *args, **kwargs):
143
+ super().__init__(parent, *args, **kwargs)
144
+ self.text = text
145
+ self.variable = variable if variable else tk.BooleanVar()
146
+ self.command = command
147
+
148
+ self.canvas = tk.Canvas(self, width=40, height=20, highlightthickness=0, bd=0, bg="black")
149
+ self.canvas.grid(row=0, column=1, padx=(10, 0))
150
+
151
+ # Background rounded rectangle with smaller dimensions and no outline
152
+ self.switch_bg = self.create_rounded_rectangle(2, 2, 38, 18, radius=9, outline="", fill="#fff")
153
+
154
+ # Switch ball with no outline
155
+ self.switch = self.canvas.create_oval(4, 4, 16, 16, outline="", fill="#800080") # Purple initially
156
+
157
+ self.label = ttk.Label(self, text=self.text, background="black", foreground="white")
158
+ self.label.grid(row=0, column=0, padx=(0, 10))
159
+
160
+ self.bind("<Button-1>", self.toggle)
161
+ self.canvas.bind("<Button-1>", self.toggle)
162
+ self.label.bind("<Button-1>", self.toggle)
163
+
164
+ self.update_switch()
165
+
166
+ def toggle(self, event=None):
167
+ self.variable.set(not self.variable.get())
168
+ self.animate_switch()
169
+ if self.command:
170
+ self.command()
171
+
172
+ def update_switch(self):
173
+ if self.variable.get():
174
+ self.canvas.itemconfig(self.switch, fill="#008080") # Teal
175
+ self.canvas.coords(self.switch, 24, 4, 36, 16) # Move switch to the right
176
+ else:
177
+ self.canvas.itemconfig(self.switch, fill="#800080") # Purple
178
+ self.canvas.coords(self.switch, 4, 4, 16, 16) # Move switch to the left
179
+
180
+ def animate_switch(self):
181
+ if self.variable.get():
182
+ start_x, end_x = 4, 24
183
+ final_color = "#008080" # Teal
184
+ else:
185
+ start_x, end_x = 24, 4
186
+ final_color = "#800080" # Purple
187
+
188
+ self.animate_movement(start_x, end_x, final_color)
189
+
190
+ def animate_movement(self, start_x, end_x, final_color):
191
+ step = 1 if start_x < end_x else -1
192
+ for i in range(start_x, end_x, step):
193
+ self.canvas.coords(self.switch, i, 4, i + 12, 16)
194
+ self.canvas.update()
195
+ self.after(10) # Small delay for smooth animation
196
+ self.canvas.itemconfig(self.switch, fill=final_color)
197
+
198
+ def get(self):
199
+ return self.variable.get()
200
+
201
+ def set(self, value):
202
+ self.variable.set(value)
203
+ self.update_switch()
204
+
205
+ def create_rounded_rectangle(self, x1, y1, x2, y2, radius=9, **kwargs): # Smaller radius for smaller switch
206
+ points = [x1 + radius, y1,
207
+ x1 + radius, y1,
208
+ x2 - radius, y1,
209
+ x2 - radius, y1,
210
+ x2, y1,
211
+ x2, y1 + radius,
212
+ x2, y1 + radius,
213
+ x2, y2 - radius,
214
+ x2, y2 - radius,
215
+ x2, y2,
216
+ x2 - radius, y2,
217
+ x2 - radius, y2,
218
+ x1 + radius, y2,
219
+ x1 + radius, y2,
220
+ x1, y2,
221
+ x1, y2 - radius,
222
+ x1, y2 - radius,
223
+ x1, y1 + radius,
224
+ x1, y1 + radius,
225
+ x1, y1]
226
+
227
+ return self.canvas.create_polygon(points, **kwargs, smooth=True)
228
+
229
+ def set_default_font(root, font_name="Arial", size=12):
230
+ default_font = (font_name, size)
231
+ root.option_add("*Font", default_font)
232
+ root.option_add("*TButton.Font", default_font)
233
+ root.option_add("*TLabel.Font", default_font)
234
+ root.option_add("*TEntry.Font", default_font)
235
+
236
+ def check_and_download_font():
237
+ font_name = "Arial"
238
+ font_dir = "fonts"
239
+ font_path = os.path.join(font_dir, "OpenSans-Regular.ttf")
240
+
241
+ # Check if the font is already available
242
+ available_fonts = list(tkFont.families())
243
+ if font_name not in available_fonts:
244
+ print(f"Font '{font_name}' not found. Downloading...")
245
+ if not os.path.exists(font_dir):
246
+ os.makedirs(font_dir)
247
+
248
+ if not os.path.exists(font_path):
249
+ url = "https://github.com/google/fonts/blob/main/apache/opensans/OpenSans-Regular.ttf?raw=true"
250
+ response = requests.get(url)
251
+ with open(font_path, "wb") as f:
252
+ f.write(response.content)
253
+
254
+ # Load the font
255
+ try:
256
+ tkFont.nametofont("TkDefaultFont").configure(family=font_name, size=10)
257
+ tkFont.nametofont("TkTextFont").configure(family=font_name, size=10)
258
+ tkFont.nametofont("TkHeadingFont").configure(family=font_name, size=12)
259
+ except tk.TclError:
260
+ tkFont.nametofont("TkDefaultFont").configure(family="Arial", size=10)
261
+ tkFont.nametofont("TkTextFont").configure(family="Arial", size=10)
262
+ tkFont.nametofont("TkHeadingFont").configure(family="Arial", size=12)
263
+ else:
264
+ tkFont.nametofont("TkDefaultFont").configure(family=font_name, size=10)
265
+ tkFont.nametofont("TkTextFont").configure(family=font_name, size=10)
266
+ tkFont.nametofont("TkHeadingFont").configure(family=font_name, size=12)
267
+
268
+ def style_text_boxes(style):
269
+ check_and_download_font()
270
+ font_style = tkFont.Font(family="Arial", size=10) # Define the Arial font
271
+ style.configure('TEntry', padding='5 5 5 5', borderwidth=1, relief='solid', fieldbackground='#000000', foreground='#ffffff', font=font_style)
272
+ style.configure('TCombobox', fieldbackground='#000000', background='#000000', foreground='#ffffff', font=font_style)
273
+ style.configure('Custom.TButton', padding='10 10 10 10', borderwidth=1, relief='solid', background='#008080', foreground='#ffffff', font=font_style)
274
+ style.map('Custom.TButton',
275
+ background=[('active', '#66b2b2'), ('disabled', '#004d4d'), ('!disabled', '#008080')],
276
+ foreground=[('active', '#ffffff'), ('disabled', '#888888')])
277
+ style.configure('Custom.TLabel', padding='5 5 5 5', borderwidth=1, relief='flat', background='#000000', foreground='#ffffff', font=font_style)
278
+ style.configure('TCheckbutton', background='#333333', foreground='#ffffff', indicatoron=False, relief='flat', font=font_style)
279
+ style.map('TCheckbutton', background=[('selected', '#555555'), ('active', '#555555')])
280
+
281
+
16
282
 
17
283
  def read_settings_from_csv(csv_file_path):
18
284
  settings = {}
@@ -51,17 +317,8 @@ def disable_interactivity(fig):
51
317
  for handler_id in list(handlers.keys()):
52
318
  fig.canvas.mpl_disconnect(handler_id)
53
319
 
54
- def set_default_font(app, font_name="Arial Bold", size=10):
55
- default_font = nametofont("TkDefaultFont")
56
- text_font = nametofont("TkTextFont")
57
- fixed_font = nametofont("TkFixedFont")
58
-
59
- # Set the family to Open Sans and size as desired
60
- for font in (default_font, text_font, fixed_font):
61
- font.config(family=font_name, size=size)
62
-
63
320
  class ScrollableFrame(ttk.Frame):
64
- def __init__(self, container, *args, bg='#333333', **kwargs):
321
+ def __init__(self, container, *args, bg='black', **kwargs):
65
322
  super().__init__(container, *args, **kwargs)
66
323
  self.configure(style='TFrame') # Ensure this uses the styled frame from dark mode
67
324
 
@@ -366,32 +623,31 @@ def classify_variables():
366
623
  }
367
624
  return variables
368
625
 
369
-
370
- #@log_function_call
371
626
  def create_input_field(frame, label_text, row, var_type='entry', options=None, default_value=None):
372
- label = ttk.Label(frame, text=label_text, style='TLabel') # Assuming you have a dark mode style for labels too
627
+ label = ttk.Label(frame, text=label_text, style='Custom.TLabel') # Apply Custom.TLabel style for labels
373
628
  label.grid(column=0, row=row, sticky=tk.W, padx=5, pady=5)
374
629
 
375
630
  if var_type == 'entry':
376
631
  var = tk.StringVar(value=default_value) # Set default value
377
- entry = ttk.Entry(frame, textvariable=var, style='TEntry') # Assuming you have a dark mode style for entries
632
+ entry = ttk.Entry(frame, textvariable=var, style='TEntry') # Apply TEntry style for entries
378
633
  entry.grid(column=1, row=row, sticky=tk.EW, padx=5)
634
+ return (label, entry, var) # Return both the label and the entry, and the variable
379
635
  elif var_type == 'check':
380
636
  var = tk.BooleanVar(value=default_value) # Set default value (True/False)
381
- # Use the custom style for Checkbutton
382
- check = ttk.Checkbutton(frame, variable=var, style='Dark.TCheckbutton')
637
+ check = ToggleSwitch(frame, text=label_text, variable=var) # Use ToggleSwitch class
383
638
  check.grid(column=1, row=row, sticky=tk.W, padx=5)
639
+ return (label, check, var) # Return both the label and the checkbutton, and the variable
384
640
  elif var_type == 'combo':
385
641
  var = tk.StringVar(value=default_value) # Set default value
386
- combo = ttk.Combobox(frame, textvariable=var, values=options, style='TCombobox') # Assuming you have a dark mode style for comboboxes
642
+ combo = ttk.Combobox(frame, textvariable=var, values=options, style='TCombobox') # Apply TCombobox style
387
643
  combo.grid(column=1, row=row, sticky=tk.EW, padx=5)
388
644
  if default_value:
389
645
  combo.set(default_value)
646
+ return (label, combo, var) # Return both the label and the combobox, and the variable
390
647
  else:
391
648
  var = None # Placeholder in case of an undefined var_type
392
-
393
- return var
394
-
649
+ return (label, None, var)
650
+
395
651
  def mask_variables():
396
652
  variables = {
397
653
  'src': ('entry', None, '/mnt/data/CellVoyager/40x/einar/mitotrackerHeLaToxoDsRed_20240224_123156/test_gui'),
@@ -457,10 +713,98 @@ def add_mask_gui_defaults(settings):
457
713
  def generate_fields(variables, scrollable_frame):
458
714
  vars_dict = {}
459
715
  row = 0
716
+ tooltips = {
717
+ "src": "Path to the folder containing the images.",
718
+ "metadata_type": "Type of metadata to expect in the images. This will determine how the images are processed. If 'custom' is selected, you can provide a custom regex pattern to extract metadata from the image names",
719
+ "custom_regex": "Custom regex pattern to extract metadata from the image names. This will only be used if 'custom' is selected for 'metadata_type'.",
720
+ "experiment": "Name of the experiment. This will be used to name the output files.",
721
+ "channels": "List of channels to use for the analysis. The first channel is 0, the second is 1, and so on. For example, [0,1,2] will use channels 0, 1, and 2.",
722
+ "magnification": "At what magnification the images were taken. This will be used to determine the size of the objects in the images.",
723
+ "nucleus_channel": "The channel to use for the nucleus. If None, the nucleus will not be segmented.",
724
+ "nucleus_background": "The background intensity for the nucleus channel. This will be used to remove background noise.",
725
+ "nucleus_Signal_to_noise": "The signal-to-noise ratio for the nucleus channel. This will be used to determine the range of intensities to normalize images to for nucleus segmentation.",
726
+ "nucleus_CP_prob": "The cellpose probability threshold for the nucleus channel. This will be used to segment the nucleus.",
727
+ "cell_channel": "The channel to use for the cell. If None, the cell will not be segmented.",
728
+ "cell_background": "The background intensity for the cell channel. This will be used to remove background noise.",
729
+ "cell_Signal_to_noise": "The signal-to-noise ratio for the cell channel. This will be used to determine the range of intensities to normalize images to for cell segmentation.",
730
+ "cell_CP_prob": "The cellpose probability threshold for the cell channel. This will be used to segment the cell.",
731
+ "pathogen_channel": "The channel to use for the pathogen. If None, the pathogen will not be segmented.",
732
+ "pathogen_background": "The background intensity for the pathogen channel. This will be used to remove background noise.",
733
+ "pathogen_Signal_to_noise": "The signal-to-noise ratio for the pathogen channel. This will be used to determine the range of intensities to normalize images to for pathogen segmentation.",
734
+ "pathogen_CP_prob": "The cellpose probability threshold for the pathogen channel. This will be used to segment the pathogen.",
735
+ "preprocess": "Whether to preprocess the images before segmentation. This includes background removal and normalization. Set to False only if this step has already been done.",
736
+ "masks": "Whether to generate masks for the segmented objects. If True, masks will be generated for the nucleus, cell, and pathogen.",
737
+ "examples_to_plot": "The number of images to plot for each segmented object. This will be used to visually inspect the segmentation results and normalization .",
738
+ "randomize": "Whether to randomize the order of the images before processing. Recommended to avoid bias in the segmentation.",
739
+ "batch_size": "The batch size to use for processing the images. This will determine how many images are processed at once. Images are normalized and segmented in batches. Lower if application runs out of RAM or VRAM.",
740
+ "timelapse": "Whether to process the images as a timelapse.",
741
+ "timelapse_displacement": "The displacement between frames in the timelapse. This will be used to align the frames before processing.",
742
+ "timelapse_memory": "The number of frames to in tandem objects must be present in to be considered the same object in the timelapse.",
743
+ "timelapse_frame_limits": "The frame limits to use for the timelapse. This will determine which frames are processed. For example, [5,20] will process frames 5 to 20.",
744
+ "timelapse_remove_transient": "Whether to remove transient objects in the timelapse. Transient objects are present in fewer than all frames.",
745
+ "timelapse_mode": "The mode to use for processing the timelapse. 'trackpy' uses the trackpy library for tracking objects, while 'btrack' uses the btrack library.",
746
+ "timelapse_objects": "The objects to track in the timelapse (cell, nucleus or pathogen). This will determine which objects are tracked over time. If None, all objects will be tracked.",
747
+ "fps": "Frames per second of the automatically generated timelapse movies.",
748
+ "remove_background": "Whether to remove background noise from the images. This will help improve the quality of the segmentation.",
749
+ "lower_quantile": "The lower quantile to use for normalizing the images. This will be used to determine the range of intensities to normalize images to.",
750
+ "merge_pathogens": "Whether to merge pathogen objects that share more than 75% of their perimiter.",
751
+ "normalize_plots": "Whether to normalize the plots.",
752
+ "all_to_mip": "Whether to convert all images to maximum intensity projections before processing.",
753
+ "pick_slice": "Whether to pick a single slice from the z-stack images. If False, the maximum intensity projection will be used.",
754
+ "skip_mode": "The mode to use for skipping images. This will determine how to handle images that cannot be processed.",
755
+ "save": "Whether to save the results to disk.",
756
+ "plot": "Whether to plot the results.",
757
+ "workers": "The number of workers to use for processing the images. This will determine how many images are processed in parallel. Increase to speed up processing.",
758
+ "verbose": "Whether to print verbose output during processing.",
759
+ "input_folder": "Path to the folder containing the images.",
760
+ "cell_mask_dim": "The dimension of the array the cell mask is saved in.",
761
+ "cell_min_size": "The minimum size of cell objects in pixels2.",
762
+ "cytoplasm_min_size": "The minimum size of cytoplasm objects in pixels2.",
763
+ "nucleus_mask_dim": "The dimension of the array the nucleus mask is saved in.",
764
+ "nucleus_min_size": "The minimum size of nucleus objects in pixels2.",
765
+ "pathogen_mask_dim": "The dimension of the array the pathogen mask is saved in.",
766
+ "pathogen_min_size": "The minimum size of pathogen objects in pixels2.",
767
+ "save_png": "Whether to save the segmented objects as PNG images.",
768
+ "crop_mode": "The mode to use for cropping the images. This will determine which objects are cropped from the images (cell, nucleus, pathogen, cytoplasm).",
769
+ "use_bounding_box": "Whether to use the bounding box of the objects for cropping. If False, only the object itself will be cropped.",
770
+ "png_size": "The size of the PNG images to save. This will determine the size of the saved images.",
771
+ "normalize": "The percentiles to use for normalizing the images. This will be used to determine the range of intensities to normalize images to., if None, no normalization is done.",
772
+ "png_dims": "The dimensions of the PNG images to save. This will determine the dimensions of the saved images. Maximum of 3 dimensions e.g. [1,2,3].",
773
+ "normalize_by": "Whether to normalize the images by field of view (fov) or by PNG image (png).",
774
+ "save_measurements": "Whether to save the measurements to disk.",
775
+ "representative_images": "Whether to save representative images of the segmented objects (Not working yet).",
776
+ "plot": "Whether to plot results.",
777
+ "plot_filtration": "Whether to plot the filtration steps.",
778
+ "include_uninfected": "Whether to include uninfected cells in the analysis.",
779
+ "dialate_pngs": "Whether to dialate the PNG images before saving.",
780
+ "dialate_png_ratios": "The ratios to use for dialating the PNG images. This will determine the amount of dialation applied to the images before cropping.",
781
+ "timelapse_objects": "The objects to track in the timelapse (cell, nucleus or pathogen). This will determine which objects are tracked over time. If None, all objects will be tracked.",
782
+ "max_workers": "The number of workers to use for processing the images. This will determine how many images are processed in parallel. Increase to speed up processing.",
783
+ "cells: ": "The cell types to include in the analysis.",
784
+ "cell_loc": "The locations of the cell types in the images.",
785
+ "pathogens": "The pathogen types to include in the analysis.",
786
+ "pathogen_loc": "The locations of the pathogen types in the images.",
787
+ "treatments": "The treatments to include in the analysis.",
788
+ "treatment_loc": "The locations of the treatments in the images.",
789
+ "channel_of_interest": "The channel of interest to use for the analysis.",
790
+ "compartments": "The compartments to measure in the images.",
791
+ "measurement": "The measurement to use for the analysis.",
792
+ "nr_imgs": "The number of images to plot.",
793
+ "um_per_pixel": "The micrometers per pixel for the images.",
794
+ }
795
+
460
796
  for key, (var_type, options, default_value) in variables.items():
461
- vars_dict[key] = create_input_field(scrollable_frame.scrollable_frame, key, row, var_type, options, default_value)
797
+ label, widget, var = create_input_field(scrollable_frame.scrollable_frame, key, row, var_type, options, default_value)
798
+ vars_dict[key] = (label, widget, var) # Store the label, widget, and variable
799
+
800
+ # Add tooltip to the label if it exists in the tooltips dictionary
801
+ if key in tooltips:
802
+ ToolTip(label, tooltips[key])
803
+
462
804
  row += 1
463
805
  return vars_dict
806
+
807
+
464
808
 
465
809
  class TextRedirector(object):
466
810
  def __init__(self, widget, queue):
@@ -474,7 +818,7 @@ class TextRedirector(object):
474
818
  pass
475
819
 
476
820
  def create_dark_mode(root, style, console_output):
477
- dark_bg = '#333333'
821
+ dark_bg = 'black'
478
822
  light_text = 'white'
479
823
  dark_text = 'black'
480
824
  input_bg = '#555555' # Slightly lighter background for input fields
@@ -494,12 +838,12 @@ def create_dark_mode(root, style, console_output):
494
838
  root.configure(bg=dark_bg)
495
839
 
496
840
  def set_dark_style(style):
497
- style.configure('TFrame', background='#333333')
498
- style.configure('TLabel', background='#333333', foreground='white')
499
- style.configure('TEntry', background='#333333', foreground='white')
500
- style.configure('TCheckbutton', background='#333333', foreground='white')
841
+ style.configure('TFrame', background='black')
842
+ style.configure('TLabel', background='black', foreground='white')
843
+ style.configure('TEntry', background='black', foreground='white')
844
+ style.configure('TCheckbutton', background='black', foreground='white')
501
845
 
502
- #@log_function_call
846
+ ##@log_function_call
503
847
  def main_thread_update_function(root, q, fig_queue, canvas_widget, progress_label):
504
848
  try:
505
849
  ansi_escape_pattern = re.compile(r'\x1B\[[0-?]*[ -/]*[@-~]')
@@ -580,7 +924,7 @@ def measure_crop_wrapper(settings, q, fig_queue):
580
924
 
581
925
  try:
582
926
  print('start')
583
- spacr.measure.measure_crop(settings=settings, annotation_settings={}, advanced_settings={})
927
+ spacr.measure.measure_crop(settings=settings)
584
928
  except Exception as e:
585
929
  errorMessage = f"Error during processing: {e}"
586
930
  q.put(errorMessage) # Send the error message to the GUI via the queue
@@ -588,7 +932,7 @@ def measure_crop_wrapper(settings, q, fig_queue):
588
932
  finally:
589
933
  plt.show = original_show # Restore the original plt.show function
590
934
 
591
- @log_function_call
935
+ #@log_function_call
592
936
  def preprocess_generate_masks_wrapper(settings, q, fig_queue):
593
937
  """
594
938
  Wraps the measure_crop function to integrate with GUI processes.