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.py ADDED
@@ -0,0 +1,145 @@
1
+ import tkinter as tk
2
+ from tkinter import ttk
3
+ from tkinter import font as tkFont
4
+ from PIL import Image, ImageTk
5
+ import os
6
+ import requests
7
+
8
+ # Import your GUI apps
9
+ from .gui_mask_app import initiate_mask_root
10
+ from .gui_measure_app import initiate_measure_root
11
+ from .annotate_app import initiate_annotation_app_root
12
+ from .mask_app import initiate_mask_app_root
13
+ from .gui_classify_app import initiate_classify_root
14
+
15
+ from .gui_utils import CustomButton, style_text_boxes
16
+
17
+ class MainApp(tk.Tk):
18
+ def __init__(self):
19
+ super().__init__()
20
+ self.title("SpaCr GUI Collection")
21
+ self.geometry("1100x1500")
22
+ self.configure(bg="black")
23
+ #self.attributes('-fullscreen', True)
24
+
25
+ style = ttk.Style()
26
+ style_text_boxes(style)
27
+
28
+ self.gui_apps = {
29
+ "Mask": (initiate_mask_root, "Generate cellpose masks for cells, nuclei and pathogen images."),
30
+ "Measure": (initiate_measure_root, "Measure single object intensity and morphological feature. Crop and save single object image"),
31
+ "Annotate": (initiate_annotation_app_root, "Annotation single object images on a grid. Annotations are saved to database."),
32
+ "Make Masks": (initiate_mask_app_root, "Adjust pre-existing Cellpose models to your specific dataset for improved performance"),
33
+ "Classify": (initiate_classify_root, "Train Torch Convolutional Neural Networks (CNNs) or Transformers to classify single object images.")
34
+ }
35
+
36
+ self.selected_app = tk.StringVar()
37
+ self.create_widgets()
38
+
39
+ def create_widgets(self):
40
+ # Create the menu bar
41
+ #create_menu_bar(self)
42
+ # Create a canvas to hold the selected app and other elements
43
+ self.canvas = tk.Canvas(self, bg="black", highlightthickness=0, width=4000, height=4000)
44
+ self.canvas.grid(row=0, column=0, sticky="nsew")
45
+ self.grid_rowconfigure(0, weight=1)
46
+ self.grid_columnconfigure(0, weight=1)
47
+ # Create a frame inside the canvas to hold the main content
48
+ self.content_frame = tk.Frame(self.canvas, bg="black")
49
+ self.content_frame.pack(fill=tk.BOTH, expand=True)
50
+ # Create startup screen with buttons for each GUI app
51
+ self.create_startup_screen()
52
+
53
+ def create_startup_screen(self):
54
+ self.clear_frame(self.content_frame)
55
+
56
+ # Create a frame for the logo and description
57
+ logo_frame = tk.Frame(self.content_frame, bg="black")
58
+ logo_frame.pack(pady=20, expand=True)
59
+
60
+ # Load the logo image
61
+ if not self.load_logo(logo_frame):
62
+ tk.Label(logo_frame, text="Logo not found", bg="black", fg="white", font=('Arial', 24, tkFont.NORMAL)).pack(padx=10, pady=10)
63
+
64
+ # Add SpaCr text below the logo with padding for sharper text
65
+ tk.Label(logo_frame, text="SpaCr", bg="black", fg="#008080", font=('Arial', 24, tkFont.NORMAL)).pack(padx=10, pady=10)
66
+
67
+ # Create a frame for the buttons and descriptions
68
+ buttons_frame = tk.Frame(self.content_frame, bg="black")
69
+ buttons_frame.pack(pady=10, expand=True, padx=10)
70
+
71
+ for i, (app_name, app_data) in enumerate(self.gui_apps.items()):
72
+ app_func, app_desc = app_data
73
+
74
+ # Create custom button with text
75
+ button = CustomButton(buttons_frame, text=app_name, command=lambda app_name=app_name: self.load_app(app_name))
76
+ button.grid(row=i, column=0, pady=10, padx=10, sticky="w")
77
+
78
+ description_label = tk.Label(buttons_frame, text=app_desc, bg="black", fg="white", wraplength=800, justify="left", font=('Arial', 10, tkFont.NORMAL))
79
+ description_label.grid(row=i, column=1, pady=10, padx=10, sticky="w")
80
+
81
+ # Ensure buttons have a fixed width
82
+ buttons_frame.grid_columnconfigure(0, minsize=150)
83
+ # Ensure descriptions expand as needed
84
+ buttons_frame.grid_columnconfigure(1, weight=1)
85
+
86
+ def load_logo(self, frame):
87
+ def download_image(url, save_path):
88
+ try:
89
+ response = requests.get(url, stream=True)
90
+ response.raise_for_status() # Raise an HTTPError for bad responses
91
+ with open(save_path, 'wb') as f:
92
+ for chunk in response.iter_content(chunk_size=8192):
93
+ f.write(chunk)
94
+ return True
95
+ except requests.exceptions.RequestException as e:
96
+ print(f"Failed to download image from {url}: {e}")
97
+ return False
98
+
99
+ try:
100
+ img_path = os.path.join(os.path.dirname(__file__), 'logo_spacr.png')
101
+ print(f"Trying to load logo from {img_path}")
102
+ logo_image = Image.open(img_path)
103
+ except (FileNotFoundError, Image.UnidentifiedImageError):
104
+ print(f"File {img_path} not found or is not a valid image. Attempting to download from GitHub.")
105
+ if download_image('https://raw.githubusercontent.com/EinarOlafsson/spacr/main/spacr/logo_spacr.png', img_path):
106
+ try:
107
+ print(f"Downloaded file size: {os.path.getsize(img_path)} bytes")
108
+ logo_image = Image.open(img_path)
109
+ except Image.UnidentifiedImageError as e:
110
+ print(f"Downloaded file is not a valid image: {e}")
111
+ return False
112
+ else:
113
+ return False
114
+ except Exception as e:
115
+ print(f"An error occurred while loading the logo: {e}")
116
+ return False
117
+ try:
118
+ logo_image = logo_image.resize((800, 800), Image.Resampling.LANCZOS)
119
+ logo_photo = ImageTk.PhotoImage(logo_image)
120
+ logo_label = tk.Label(frame, image=logo_photo, bg="black")
121
+ logo_label.image = logo_photo # Keep a reference to avoid garbage collection
122
+ logo_label.pack()
123
+ return True
124
+ except Exception as e:
125
+ print(f"An error occurred while processing the logo image: {e}")
126
+ return False
127
+
128
+ def load_app(self, app_name):
129
+ selected_app_func, _ = self.gui_apps[app_name]
130
+ self.clear_frame(self.content_frame)
131
+
132
+ app_frame = tk.Frame(self.content_frame, bg="black")
133
+ app_frame.pack(fill=tk.BOTH, expand=True)
134
+ selected_app_func(app_frame, self.winfo_width(), self.winfo_height())
135
+
136
+ def clear_frame(self, frame):
137
+ for widget in frame.winfo_children():
138
+ widget.destroy()
139
+
140
+ def gui_app():
141
+ app = MainApp()
142
+ app.mainloop()
143
+
144
+ if __name__ == "__main__":
145
+ gui_app()
spacr/gui_2.py ADDED
@@ -0,0 +1,90 @@
1
+ import customtkinter as ctk
2
+ from PIL import Image, ImageTk
3
+ import os
4
+ import requests
5
+
6
+ class MainApp(ctk.CTk):
7
+ def __init__(self):
8
+ super().__init__()
9
+ self.title("SpaCr GUI Collection")
10
+ self.geometry("1200x800")
11
+ ctk.set_appearance_mode("dark") # Modes: "System" (standard), "Dark", "Light"
12
+ ctk.set_default_color_theme("dark-blue") # Themes: "blue" (standard), "green", "dark-blue")
13
+
14
+ # Set scaling factor for high DPI displays; use a floating-point value.
15
+ self.tk.call('tk', 'scaling', 1.5)
16
+
17
+ self.create_widgets()
18
+
19
+ def create_widgets(self):
20
+ self.content_frame = ctk.CTkFrame(self)
21
+ self.content_frame.pack(fill="both", expand=True, padx=20, pady=20)
22
+
23
+ logo_frame = ctk.CTkFrame(self.content_frame)
24
+ logo_frame.pack(pady=20, expand=True)
25
+
26
+ if not self.load_logo(logo_frame):
27
+ ctk.CTkLabel(logo_frame, text="Logo not found", text_color="white", font=('Helvetica', 24)).pack(padx=10, pady=10)
28
+
29
+ ctk.CTkLabel(logo_frame, text="SpaCr", text_color="#00BFFF", font=('Helvetica', 36, "bold")).pack(padx=10, pady=10)
30
+
31
+ button = ctk.CTkButton(
32
+ self.content_frame,
33
+ text="Mask",
34
+ command=self.load_mask_app,
35
+ width=250,
36
+ height=60,
37
+ corner_radius=20,
38
+ fg_color="#1E90FF",
39
+ hover_color="#4682B4",
40
+ text_color="white",
41
+ font=("Helvetica", 18, "bold")
42
+ )
43
+ button.pack(pady=20)
44
+
45
+ def load_logo(self, frame):
46
+ def download_image(url, save_path):
47
+ try:
48
+ response = requests.get(url, stream=True)
49
+ response.raise_for_status()
50
+ with open(save_path, 'wb') as f:
51
+ for chunk in response.iter_content(chunk_size=8192):
52
+ f.write(chunk)
53
+ return True
54
+ except requests.exceptions.RequestException as e:
55
+ print(f"Failed to download image from {url}: {e}")
56
+ return False
57
+
58
+ try:
59
+ img_path = os.path.join(os.path.dirname(__file__), 'logo_spacr.png')
60
+ logo_image = Image.open(img_path)
61
+ except (FileNotFoundError, Image.UnidentifiedImageError):
62
+ if download_image('https://raw.githubusercontent.com/EinarOlafsson/spacr/main/spacr/logo_spacr.png', img_path):
63
+ try:
64
+ logo_image = Image.open(img_path)
65
+ except Image.UnidentifiedImageError as e:
66
+ return False
67
+ else:
68
+ return False
69
+ except Exception as e:
70
+ return False
71
+
72
+ try:
73
+ logo_image = logo_image.resize((200, 200), Image.Resampling.LANCZOS)
74
+ logo_photo = ImageTk.PhotoImage(logo_image)
75
+ logo_label = ctk.CTkLabel(frame, image=logo_photo)
76
+ logo_label.image = logo_photo # Keep a reference to avoid garbage collection
77
+ logo_label.pack()
78
+ return True
79
+ except Exception as e:
80
+ return False
81
+
82
+ def load_mask_app(self):
83
+ print("Mask app loaded.") # Placeholder for mask app loading function
84
+
85
+ def gui_app():
86
+ app = MainApp()
87
+ app.mainloop()
88
+
89
+ if __name__ == "__main__":
90
+ gui_app()
spacr/gui_classify_app.py CHANGED
@@ -1,7 +1,6 @@
1
- import sys, ctypes, csv, matplotlib
1
+ import sys, ctypes, matplotlib
2
2
  import tkinter as tk
3
3
  from tkinter import ttk, scrolledtext
4
- from ttkthemes import ThemedTk
5
4
  from matplotlib.figure import Figure
6
5
  from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
7
6
  from matplotlib.figure import Figure
@@ -16,12 +15,12 @@ except AttributeError:
16
15
  pass
17
16
 
18
17
  from .logger import log_function_call
19
- from .gui_utils import ScrollableFrame, StdoutRedirector, create_dark_mode, set_dark_style, set_default_font, generate_fields, process_stdout_stderr, safe_literal_eval, clear_canvas, main_thread_update_function
20
- from .gui_utils import classify_variables, check_classify_gui_settings, train_test_model_wrapper, read_settings_from_csv, update_settings_from_csv
18
+ from .gui_utils import ScrollableFrame, StdoutRedirector, CustomButton, set_dark_style, set_default_font, generate_fields, process_stdout_stderr, clear_canvas, main_thread_update_function
19
+ from .gui_utils import classify_variables, check_classify_gui_settings, train_test_model_wrapper, read_settings_from_csv, update_settings_from_csv, style_text_boxes, create_menu_bar
21
20
 
22
21
  thread_control = {"run_thread": None, "stop_requested": False}
23
22
 
24
- @log_function_call
23
+ #@log_function_call
25
24
  def initiate_abort():
26
25
  global thread_control
27
26
  if thread_control.get("stop_requested") is not None:
@@ -33,7 +32,7 @@ def initiate_abort():
33
32
  thread_control["run_thread"].terminate()
34
33
  thread_control["run_thread"] = None
35
34
 
36
- @log_function_call
35
+ #@log_function_call
37
36
  def run_classify_gui(q, fig_queue, stop_requested):
38
37
  global vars_dict
39
38
  process_stdout_stderr(q)
@@ -49,7 +48,7 @@ def run_classify_gui(q, fig_queue, stop_requested):
49
48
  finally:
50
49
  stop_requested.value = 1
51
50
 
52
- @log_function_call
51
+ #@log_function_call
53
52
  def start_process(q, fig_queue):
54
53
  global thread_control
55
54
  if thread_control.get("run_thread") is not None:
@@ -69,129 +68,120 @@ def import_settings(scrollable_frame):
69
68
  new_settings = update_settings_from_csv(variables, csv_settings)
70
69
  vars_dict = generate_fields(new_settings, scrollable_frame)
71
70
 
72
- @log_function_call
73
- def initiate_classify_root(width, height):
74
- global root, vars_dict, q, canvas, fig_queue, canvas_widget, thread_control
75
-
76
- theme = 'breeze'
71
+ #@log_function_call
72
+ def initiate_classify_root(parent_frame):#, width, height):
73
+ global vars_dict, q, canvas, fig_queue, canvas_widget, thread_control
77
74
 
78
- if theme in ['clam']:
79
- root = tk.Tk()
80
- style = ttk.Style(root)
81
- style.theme_use(theme) #plastik, clearlooks, elegance, default was clam #alt, breeze, arc
82
- set_dark_style(style)
83
- elif theme in ['breeze']:
84
- root = ThemedTk(theme="breeze")
85
- style = ttk.Style(root)
86
- set_dark_style(style)
87
-
88
- set_default_font(root, font_name="Arial", size=10)
89
- #root.state('zoomed') # For Windows to maximize the window
90
- root.attributes('-fullscreen', True)
91
- root.geometry(f"{width}x{height}")
92
- root.title("SpaCer: generate masks")
75
+ style = ttk.Style(parent_frame)
76
+ set_dark_style(style)
77
+ style_text_boxes(style)
78
+ set_default_font(parent_frame, font_name="Arial", size=8)
79
+
80
+ parent_frame.configure(bg='#333333')
81
+ parent_frame.grid_rowconfigure(0, weight=1)
82
+ parent_frame.grid_columnconfigure(0, weight=1)
93
83
  fig_queue = Queue()
94
-
84
+
95
85
  def _process_fig_queue():
96
86
  global canvas
97
87
  try:
98
88
  while not fig_queue.empty():
99
89
  clear_canvas(canvas)
100
90
  fig = fig_queue.get_nowait()
101
- #set_fig_text_properties(fig, font_size=8)
102
91
  for ax in fig.get_axes():
103
92
  ax.set_xticks([]) # Remove x-axis ticks
104
93
  ax.set_yticks([]) # Remove y-axis ticks
105
94
  ax.xaxis.set_visible(False) # Hide the x-axis
106
95
  ax.yaxis.set_visible(False) # Hide the y-axis
107
- #ax.title.set_fontsize(14)
108
- #disable_interactivity(fig)
109
96
  fig.tight_layout()
110
97
  fig.set_facecolor('#333333')
111
98
  canvas.figure = fig
112
99
  fig_width, fig_height = canvas_widget.winfo_width(), canvas_widget.winfo_height()
113
100
  fig.set_size_inches(fig_width / fig.dpi, fig_height / fig.dpi, forward=True)
114
- canvas.draw_idle()
101
+ canvas.draw_idle()
115
102
  except Exception as e:
116
103
  traceback.print_exc()
117
- #pass
118
104
  finally:
119
105
  canvas_widget.after(100, _process_fig_queue)
120
-
121
- # Process queue for console output
106
+
122
107
  def _process_console_queue():
123
108
  while not q.empty():
124
109
  message = q.get_nowait()
125
110
  console_output.insert(tk.END, message)
126
111
  console_output.see(tk.END)
127
112
  console_output.after(100, _process_console_queue)
128
-
129
- # Vertical container for settings and console
130
- vertical_container = tk.PanedWindow(root, orient=tk.HORIZONTAL) #VERTICAL
131
- vertical_container.pack(fill=tk.BOTH, expand=True)
132
113
 
133
- # Scrollable Frame for user settings
134
- scrollable_frame = ScrollableFrame(vertical_container, bg='#333333')
135
- vertical_container.add(scrollable_frame, stretch="always")
114
+ vertical_container = tk.PanedWindow(parent_frame, orient=tk.HORIZONTAL)
115
+ vertical_container.grid(row=0, column=0, sticky=tk.NSEW)
116
+ parent_frame.grid_rowconfigure(0, weight=1)
117
+ parent_frame.grid_columnconfigure(0, weight=1)
118
+
119
+ # Settings Section
120
+ settings_frame = tk.Frame(vertical_container, bg='#333333')
121
+ vertical_container.add(settings_frame, stretch="always")
122
+ settings_label = ttk.Label(settings_frame, text="Settings", background="#333333", foreground="white")
123
+ settings_label.grid(row=0, column=0, pady=10, padx=10)
124
+ scrollable_frame = ScrollableFrame(settings_frame, width=500)
125
+ scrollable_frame.grid(row=1, column=0, sticky="nsew")
126
+ settings_frame.grid_rowconfigure(1, weight=1)
127
+ settings_frame.grid_columnconfigure(0, weight=1)
136
128
 
137
129
  # Setup for user input fields (variables)
138
130
  variables = classify_variables()
139
131
  vars_dict = generate_fields(variables, scrollable_frame)
140
-
141
- # Horizontal container for Matplotlib figure and the vertical pane (for settings and console)
142
- horizontal_container = tk.PanedWindow(vertical_container, orient=tk.VERTICAL) #HORIZONTAL
143
- vertical_container.add(horizontal_container, stretch="always")
144
132
 
145
- # Matplotlib figure setup
133
+ # Button section
134
+ import_btn = CustomButton(scrollable_frame.scrollable_frame, text="Import Settings", command=lambda: import_settings(scrollable_frame))
135
+ import_btn.grid(row=47, column=0, pady=20, padx=20)
136
+ run_button = CustomButton(scrollable_frame.scrollable_frame, text="Run", command=lambda: start_process(q, fig_queue))
137
+ run_button.grid(row=45, column=0, pady=20, padx=20)
138
+ abort_button = CustomButton(scrollable_frame.scrollable_frame, text="Abort", command=initiate_abort)
139
+ abort_button.grid(row=45, column=1, pady=20, padx=20)
140
+ progress_label = ttk.Label(scrollable_frame.scrollable_frame, text="Processing: 0%", background="black", foreground="white") # Create progress field
141
+ progress_label.grid(row=50, column=0, columnspan=2, sticky="ew", pady=(5, 0), padx=10)
142
+
143
+ # Plot Canvas Section
144
+ plot_frame = tk.PanedWindow(vertical_container, orient=tk.VERTICAL)
145
+ vertical_container.add(plot_frame, stretch="always")
146
146
  figure = Figure(figsize=(30, 4), dpi=100, facecolor='#333333')
147
147
  plot = figure.add_subplot(111)
148
- plot.plot([], []) # This creates an empty plot.
148
+ plot.plot([], [])
149
149
  plot.axis('off')
150
-
151
- # Embedding the Matplotlib figure in the Tkinter window
152
- canvas = FigureCanvasTkAgg(figure, master=horizontal_container)
150
+ canvas = FigureCanvasTkAgg(figure, master=plot_frame)
153
151
  canvas.get_tk_widget().configure(cursor='arrow', background='#333333', highlightthickness=0)
154
- #canvas.get_tk_widget().configure(cursor='arrow')
155
152
  canvas_widget = canvas.get_tk_widget()
156
- horizontal_container.add(canvas_widget, stretch="always")
153
+ plot_frame.add(canvas_widget, stretch="always")
157
154
  canvas.draw()
158
155
  canvas.figure = figure
159
156
 
160
- # Console output setup below the settings
161
- console_output = scrolledtext.ScrolledText(vertical_container, height=10)
162
- vertical_container.add(console_output, stretch="always")
157
+ # Console Section
158
+ console_frame = tk.Frame(vertical_container, bg='#333333')
159
+ vertical_container.add(console_frame, stretch="always")
160
+ console_label = ttk.Label(console_frame, text="Console", background="#333333", foreground="white")
161
+ console_label.grid(row=0, column=0, pady=10, padx=10)
162
+ console_output = scrolledtext.ScrolledText(console_frame, height=10, bg='#333333', fg='white', insertbackground='white')
163
+ console_output.grid(row=1, column=0, sticky="nsew")
164
+ console_frame.grid_rowconfigure(1, weight=1)
165
+ console_frame.grid_columnconfigure(0, weight=1)
163
166
 
164
- # Queue and redirection setup for updating console output safely
165
167
  q = Queue()
166
168
  sys.stdout = StdoutRedirector(console_output)
167
169
  sys.stderr = StdoutRedirector(console_output)
168
-
169
- # This is your GUI setup where you create the Run button
170
- run_button = ttk.Button(scrollable_frame.scrollable_frame, text="Run",command=lambda: start_process(q, fig_queue))
171
- run_button.grid(row=40, column=0, pady=10)
172
-
173
- abort_button = ttk.Button(scrollable_frame.scrollable_frame, text="Abort", command=initiate_abort)
174
- abort_button.grid(row=40, column=1, pady=10)
175
-
176
- progress_label = ttk.Label(scrollable_frame.scrollable_frame, text="Processing: 0%", background="#333333", foreground="white")
177
- progress_label.grid(row=41, column=0, columnspan=2, sticky="ew", pady=(5, 0))
178
-
179
- # Create the Import Settings button
180
- import_btn = tk.Button(root, text="Import Settings", command=lambda: import_settings(scrollable_frame))
181
- import_btn.pack(pady=20)
182
-
170
+
183
171
  _process_console_queue()
184
172
  _process_fig_queue()
185
- create_dark_mode(root, style, console_output)
186
-
187
- root.after(100, lambda: main_thread_update_function(root, q, fig_queue, canvas_widget, progress_label))
188
-
189
- return root, vars_dict
173
+
174
+ parent_frame.after(100, lambda: main_thread_update_function(parent_frame, q, fig_queue, canvas_widget, progress_label))
175
+
176
+ return parent_frame, vars_dict
190
177
 
191
178
  def gui_classify():
192
- global vars_dict, root
193
- root, vars_dict = initiate_classify_root(1000, 1500)
179
+ root = tk.Tk()
180
+ root.geometry("1000x800")
181
+ root.title("SpaCer: generate masks")
182
+ initiate_classify_root(root, 1000, 800)
183
+ create_menu_bar(root)
194
184
  root.mainloop()
195
185
 
196
186
  if __name__ == "__main__":
197
- gui_classify()
187
+ gui_classify()