spacr 0.1.7__py3-none-any.whl → 0.1.8__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/app_annotate.py +38 -527
- spacr/app_make_masks.py +30 -904
- spacr/core.py +21 -21
- spacr/deep_spacr.py +6 -6
- spacr/gui.py +38 -26
- spacr/gui_core.py +203 -109
- spacr/gui_elements.py +1185 -10
- spacr/gui_utils.py +186 -9
- spacr/measure.py +4 -4
- spacr/settings.py +282 -96
- spacr/utils.py +12 -15
- {spacr-0.1.7.dist-info → spacr-0.1.8.dist-info}/METADATA +1 -1
- {spacr-0.1.7.dist-info → spacr-0.1.8.dist-info}/RECORD +17 -17
- {spacr-0.1.7.dist-info → spacr-0.1.8.dist-info}/LICENSE +0 -0
- {spacr-0.1.7.dist-info → spacr-0.1.8.dist-info}/WHEEL +0 -0
- {spacr-0.1.7.dist-info → spacr-0.1.8.dist-info}/entry_points.txt +0 -0
- {spacr-0.1.7.dist-info → spacr-0.1.8.dist-info}/top_level.txt +0 -0
spacr/gui_utils.py
CHANGED
@@ -1,9 +1,9 @@
|
|
1
|
-
import io, sys, ast, ctypes, re,
|
1
|
+
import os, io, sys, ast, ctypes, re, ast, sqlite3
|
2
2
|
import tkinter as tk
|
3
3
|
from tkinter import ttk
|
4
4
|
|
5
5
|
from . gui_core import initiate_root
|
6
|
-
from .gui_elements import spacrLabel, spacrCheckbutton,
|
6
|
+
from .gui_elements import spacrLabel, spacrCheckbutton, ImageApp
|
7
7
|
|
8
8
|
try:
|
9
9
|
ctypes.windll.shcore.SetProcessDpiAwareness(True)
|
@@ -17,9 +17,7 @@ def set_default_font(root, font_name="Helvetica", size=12):
|
|
17
17
|
root.option_add("*TLabel.Font", default_font)
|
18
18
|
root.option_add("*TEntry.Font", default_font)
|
19
19
|
|
20
|
-
def
|
21
|
-
from .app_annotate import gui_annotate
|
22
|
-
from .app_make_masks import gui_make_masks
|
20
|
+
def proceed_with_app_v1(root, app_name, app_func):
|
23
21
|
from .gui import gui_app
|
24
22
|
|
25
23
|
# Clear the current content frame
|
@@ -47,11 +45,23 @@ def proceed_with_app(root, app_name, app_func):
|
|
47
45
|
elif app_name == "Umap":
|
48
46
|
initiate_root(root.content_frame, 'umap')
|
49
47
|
elif app_name == "Annotate":
|
50
|
-
|
48
|
+
initiate_root(root.content_frame, 'annotate')
|
51
49
|
elif app_name == "Make Masks":
|
52
|
-
|
50
|
+
initiate_root(root.content_frame, 'make_masks')
|
53
51
|
else:
|
54
52
|
raise ValueError(f"Invalid app name: {app_name}")
|
53
|
+
|
54
|
+
def proceed_with_app(root, app_name, app_func):
|
55
|
+
# Clear the current content frame
|
56
|
+
if hasattr(root, 'content_frame'):
|
57
|
+
for widget in root.content_frame.winfo_children():
|
58
|
+
try:
|
59
|
+
widget.destroy()
|
60
|
+
except tk.TclError as e:
|
61
|
+
print(f"Error destroying widget: {e}")
|
62
|
+
|
63
|
+
# Initialize the new app in the content frame
|
64
|
+
app_func(root.content_frame)
|
55
65
|
|
56
66
|
def load_app(root, app_name, app_func):
|
57
67
|
# Cancel all scheduled after tasks
|
@@ -60,8 +70,8 @@ def load_app(root, app_name, app_func):
|
|
60
70
|
root.after_cancel(task)
|
61
71
|
root.after_tasks = []
|
62
72
|
|
63
|
-
# Exit functionality only for the annotation
|
64
|
-
if app_name
|
73
|
+
# Exit functionality only for the annotation and make_masks apps
|
74
|
+
if app_name not in ["Annotate", "make_masks"] and hasattr(root, 'current_app_exit_func'):
|
65
75
|
root.next_app_func = proceed_with_app
|
66
76
|
root.next_app_args = (app_name, app_func) # Ensure correct arguments
|
67
77
|
root.current_app_exit_func()
|
@@ -147,3 +157,170 @@ def cancel_after_tasks(frame):
|
|
147
157
|
for task in frame.after_tasks:
|
148
158
|
frame.after_cancel(task)
|
149
159
|
frame.after_tasks.clear()
|
160
|
+
|
161
|
+
def main_thread_update_function(root, q, fig_queue, canvas_widget, progress_label):
|
162
|
+
try:
|
163
|
+
ansi_escape_pattern = re.compile(r'\x1B\[[0-?]*[ -/]*[@-~]')
|
164
|
+
while not q.empty():
|
165
|
+
message = q.get_nowait()
|
166
|
+
clean_message = ansi_escape_pattern.sub('', message)
|
167
|
+
if clean_message.startswith("Progress"):
|
168
|
+
progress_label.config(text=clean_message)
|
169
|
+
if clean_message.startswith("\rProgress"):
|
170
|
+
progress_label.config(text=clean_message)
|
171
|
+
elif clean_message.startswith("Successfully"):
|
172
|
+
progress_label.config(text=clean_message)
|
173
|
+
elif clean_message.startswith("Processing"):
|
174
|
+
progress_label.config(text=clean_message)
|
175
|
+
elif clean_message.startswith("scale"):
|
176
|
+
pass
|
177
|
+
elif clean_message.startswith("plot_cropped_arrays"):
|
178
|
+
pass
|
179
|
+
elif clean_message == "" or clean_message == "\r" or clean_message.strip() == "":
|
180
|
+
pass
|
181
|
+
else:
|
182
|
+
print(clean_message)
|
183
|
+
except Exception as e:
|
184
|
+
print(f"Error updating GUI canvas: {e}")
|
185
|
+
finally:
|
186
|
+
root.after(100, lambda: main_thread_update_function(root, q, fig_queue, canvas_widget, progress_label))
|
187
|
+
|
188
|
+
def annotate(settings):
|
189
|
+
from .settings import set_annotate_default_settings
|
190
|
+
settings = set_annotate_default_settings(settings)
|
191
|
+
src = settings['src']
|
192
|
+
|
193
|
+
db = os.path.join(src, 'measurements/measurements.db')
|
194
|
+
conn = sqlite3.connect(db)
|
195
|
+
c = conn.cursor()
|
196
|
+
c.execute('PRAGMA table_info(png_list)')
|
197
|
+
cols = c.fetchall()
|
198
|
+
if settings['annotation_column'] not in [col[1] for col in cols]:
|
199
|
+
c.execute(f"ALTER TABLE png_list ADD COLUMN {settings['annotation_column']} integer")
|
200
|
+
conn.commit()
|
201
|
+
conn.close()
|
202
|
+
|
203
|
+
root = tk.Tk()
|
204
|
+
root.geometry(settings['geom'])
|
205
|
+
app = ImageApp(root, db, src, image_type=settings['image_type'], channels=settings['channels'], image_size=settings['img_size'], grid_rows=settings['rows'], grid_cols=settings['columns'], annotation_column=settings['annotation_column'], normalize=settings['normalize'], percentiles=settings['percentiles'], measurement=settings['measurement'], threshold=settings['threshold'])
|
206
|
+
next_button = tk.Button(root, text="Next", command=app.next_page)
|
207
|
+
next_button.grid(row=app.grid_rows, column=app.grid_cols - 1)
|
208
|
+
back_button = tk.Button(root, text="Back", command=app.previous_page)
|
209
|
+
back_button.grid(row=app.grid_rows, column=app.grid_cols - 2)
|
210
|
+
exit_button = tk.Button(root, text="Exit", command=app.shutdown)
|
211
|
+
exit_button.grid(row=app.grid_rows, column=app.grid_cols - 3)
|
212
|
+
|
213
|
+
app.load_images()
|
214
|
+
root.mainloop()
|
215
|
+
|
216
|
+
def generate_annotate_fields(frame):
|
217
|
+
from .settings import set_annotate_default_settings
|
218
|
+
vars_dict = {}
|
219
|
+
settings = set_annotate_default_settings(settings={})
|
220
|
+
|
221
|
+
for setting in settings:
|
222
|
+
vars_dict[setting] = {
|
223
|
+
'entry': ttk.Entry(frame),
|
224
|
+
'value': settings[setting]
|
225
|
+
}
|
226
|
+
|
227
|
+
# Arrange input fields and labels
|
228
|
+
for row, (name, data) in enumerate(vars_dict.items()):
|
229
|
+
ttk.Label(frame, text=f"{name.replace('_', ' ').capitalize()}:",
|
230
|
+
background="black", foreground="white").grid(row=row, column=0)
|
231
|
+
if isinstance(data['value'], list):
|
232
|
+
# Convert lists to comma-separated strings
|
233
|
+
data['entry'].insert(0, ','.join(map(str, data['value'])))
|
234
|
+
else:
|
235
|
+
data['entry'].insert(0, data['value'])
|
236
|
+
data['entry'].grid(row=row, column=1)
|
237
|
+
|
238
|
+
return vars_dict
|
239
|
+
|
240
|
+
def run_annotate_app(vars_dict, parent_frame):
|
241
|
+
settings = {key: data['entry'].get() for key, data in vars_dict.items()}
|
242
|
+
settings['channels'] = settings['channels'].split(',')
|
243
|
+
settings['img_size'] = list(map(int, settings['img_size'].split(','))) # Convert string to list of integers
|
244
|
+
settings['percentiles'] = list(map(int, settings['percentiles'].split(','))) # Convert string to list of integers
|
245
|
+
settings['normalize'] = settings['normalize'].lower() == 'true'
|
246
|
+
settings['rows'] = int(settings['rows'])
|
247
|
+
settings['columns'] = int(settings['columns'])
|
248
|
+
settings['measurement'] = settings['measurement'].split(',')
|
249
|
+
settings['threshold'] = None if settings['threshold'].lower() == 'none' else int(settings['threshold'])
|
250
|
+
|
251
|
+
# Clear previous content instead of destroying the root
|
252
|
+
if hasattr(parent_frame, 'winfo_children'):
|
253
|
+
for widget in parent_frame.winfo_children():
|
254
|
+
widget.destroy()
|
255
|
+
|
256
|
+
# Start the annotate application in the same root window
|
257
|
+
annotate_app(parent_frame, settings)
|
258
|
+
|
259
|
+
# Global list to keep references to PhotoImage objects
|
260
|
+
global_image_refs = []
|
261
|
+
|
262
|
+
def annotate_app(parent_frame, settings):
|
263
|
+
global global_image_refs
|
264
|
+
global_image_refs.clear()
|
265
|
+
root = parent_frame.winfo_toplevel()
|
266
|
+
annotate_with_image_refs(settings, root, lambda: load_next_app(root))
|
267
|
+
|
268
|
+
def load_next_app(root):
|
269
|
+
# Get the next app function and arguments
|
270
|
+
next_app_func = root.next_app_func
|
271
|
+
next_app_args = root.next_app_args
|
272
|
+
|
273
|
+
if next_app_func:
|
274
|
+
try:
|
275
|
+
if not root.winfo_exists():
|
276
|
+
raise tk.TclError
|
277
|
+
next_app_func(root, *next_app_args)
|
278
|
+
except tk.TclError:
|
279
|
+
# Reinitialize root if it has been destroyed
|
280
|
+
new_root = tk.Tk()
|
281
|
+
width = new_root.winfo_screenwidth()
|
282
|
+
height = new_root.winfo_screenheight()
|
283
|
+
new_root.geometry(f"{width}x{height}")
|
284
|
+
new_root.title("SpaCr Application")
|
285
|
+
next_app_func(new_root, *next_app_args)
|
286
|
+
|
287
|
+
def annotate_with_image_refs(settings, root, shutdown_callback):
|
288
|
+
#from .gui_utils import proceed_with_app
|
289
|
+
from .gui import gui_app
|
290
|
+
from .settings import set_annotate_default_settings
|
291
|
+
|
292
|
+
settings = set_annotate_default_settings(settings)
|
293
|
+
src = settings['src']
|
294
|
+
|
295
|
+
db = os.path.join(src, 'measurements/measurements.db')
|
296
|
+
conn = sqlite3.connect(db)
|
297
|
+
c = conn.cursor()
|
298
|
+
c.execute('PRAGMA table_info(png_list)')
|
299
|
+
cols = c.fetchall()
|
300
|
+
if settings['annotation_column'] not in [col[1] for col in cols]:
|
301
|
+
c.execute(f"ALTER TABLE png_list ADD COLUMN {settings['annotation_column']} integer")
|
302
|
+
conn.commit()
|
303
|
+
conn.close()
|
304
|
+
|
305
|
+
app = ImageApp(root, db, src, image_type=settings['image_type'], channels=settings['channels'], image_size=settings['img_size'], grid_rows=settings['rows'], grid_cols=settings['columns'], annotation_column=settings['annotation_column'], normalize=settings['normalize'], percentiles=settings['percentiles'], measurement=settings['measurement'], threshold=settings['threshold'])
|
306
|
+
|
307
|
+
# Set the canvas background to black
|
308
|
+
root.configure(bg='black')
|
309
|
+
|
310
|
+
next_button = tk.Button(root, text="Next", command=app.next_page, background='black', foreground='white')
|
311
|
+
next_button.grid(row=app.grid_rows, column=app.grid_cols - 1)
|
312
|
+
back_button = tk.Button(root, text="Back", command=app.previous_page, background='black', foreground='white')
|
313
|
+
back_button.grid(row=app.grid_rows, column=app.grid_cols - 2)
|
314
|
+
exit_button = tk.Button(root, text="Exit", command=lambda: [app.shutdown(), shutdown_callback()], background='black', foreground='white')
|
315
|
+
exit_button.grid(row=app.grid_rows, column=app.grid_cols - 3)
|
316
|
+
|
317
|
+
#app.load_images()
|
318
|
+
|
319
|
+
# Store the shutdown function and next app details in the root
|
320
|
+
root.current_app_exit_func = lambda: [app.shutdown(), shutdown_callback()]
|
321
|
+
root.next_app_func = proceed_with_app
|
322
|
+
root.next_app_args = ("Main App", gui_app)
|
323
|
+
|
324
|
+
# Call load_images after setting up the root window
|
325
|
+
app.load_images()
|
326
|
+
|
spacr/measure.py
CHANGED
@@ -998,12 +998,12 @@ def measure_crop(settings):
|
|
998
998
|
_save_settings_to_db(settings)
|
999
999
|
|
1000
1000
|
files = [f for f in os.listdir(settings['input_folder']) if f.endswith('.npy')]
|
1001
|
-
|
1002
|
-
print(f'using {
|
1001
|
+
n_jobs = settings['n_jobs'] or mp.cpu_count()-4
|
1002
|
+
print(f'using {n_jobs} cpu cores')
|
1003
1003
|
|
1004
1004
|
with mp.Manager() as manager:
|
1005
1005
|
time_ls = manager.list()
|
1006
|
-
with mp.Pool(
|
1006
|
+
with mp.Pool(n_jobs) as pool:
|
1007
1007
|
result = pool.starmap_async(_measure_crop_core, [(index, time_ls, file, settings) for index, file in enumerate(files)])
|
1008
1008
|
|
1009
1009
|
# Track progress in the main process
|
@@ -1012,7 +1012,7 @@ def measure_crop(settings):
|
|
1012
1012
|
files_processed = len(time_ls)
|
1013
1013
|
files_to_process = len(files)
|
1014
1014
|
average_time = np.mean(time_ls) if len(time_ls) > 0 else 0
|
1015
|
-
time_left = (((files_to_process-files_processed)*average_time)/
|
1015
|
+
time_left = (((files_to_process-files_processed)*average_time)/n_jobs)/60
|
1016
1016
|
print(f'Progress: {files_processed}/{files_to_process} Time/img {average_time:.3f}sec, Time Remaining {time_left:.3f} min.', end='\r', flush=True)
|
1017
1017
|
result.get()
|
1018
1018
|
|