spacr 0.1.64__py3-none-any.whl → 0.1.76__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 -524
- spacr/app_make_masks.py +30 -904
- spacr/core.py +21 -21
- spacr/deep_spacr.py +6 -6
- spacr/gui.py +4 -20
- spacr/gui_core.py +133 -144
- spacr/gui_elements.py +1179 -12
- spacr/gui_utils.py +197 -10
- spacr/gui_wrappers.py +27 -15
- spacr/measure.py +4 -4
- spacr/settings.py +341 -260
- spacr/utils.py +12 -15
- {spacr-0.1.64.dist-info → spacr-0.1.76.dist-info}/METADATA +1 -1
- {spacr-0.1.64.dist-info → spacr-0.1.76.dist-info}/RECORD +18 -18
- {spacr-0.1.64.dist-info → spacr-0.1.76.dist-info}/LICENSE +0 -0
- {spacr-0.1.64.dist-info → spacr-0.1.76.dist-info}/WHEEL +0 -0
- {spacr-0.1.64.dist-info → spacr-0.1.76.dist-info}/entry_points.txt +0 -0
- {spacr-0.1.64.dist-info → spacr-0.1.76.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,15 +70,15 @@ 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()
|
68
78
|
else:
|
69
79
|
proceed_with_app(root, app_name, app_func)
|
70
80
|
|
71
|
-
def
|
81
|
+
def parse_list_v1(value):
|
72
82
|
try:
|
73
83
|
parsed_value = ast.literal_eval(value)
|
74
84
|
if isinstance(parsed_value, list):
|
@@ -78,6 +88,16 @@ def parse_list(value):
|
|
78
88
|
except (ValueError, SyntaxError):
|
79
89
|
raise ValueError("Invalid format for list")
|
80
90
|
|
91
|
+
def parse_list(value):
|
92
|
+
try:
|
93
|
+
parsed_value = ast.literal_eval(value)
|
94
|
+
if isinstance(parsed_value, list):
|
95
|
+
return parsed_value
|
96
|
+
else:
|
97
|
+
raise ValueError(f"Expected a list but got {type(parsed_value).__name__}")
|
98
|
+
except (ValueError, SyntaxError) as e:
|
99
|
+
raise ValueError(f"Invalid format for list: {value}. Error: {e}")
|
100
|
+
|
81
101
|
def create_input_field(frame, label_text, row, var_type='entry', options=None, default_value=None):
|
82
102
|
label_column = 0
|
83
103
|
widget_column = 1
|
@@ -137,3 +157,170 @@ def cancel_after_tasks(frame):
|
|
137
157
|
for task in frame.after_tasks:
|
138
158
|
frame.after_cancel(task)
|
139
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/gui_wrappers.py
CHANGED
@@ -4,15 +4,27 @@ matplotlib.use('Agg')
|
|
4
4
|
|
5
5
|
fig_queue = None
|
6
6
|
|
7
|
-
def
|
7
|
+
def spacrFigShow_v1():
|
8
8
|
"""
|
9
9
|
Replacement for plt.show() that queues figures instead of displaying them.
|
10
10
|
"""
|
11
|
+
global fig_queue
|
11
12
|
fig = plt.gcf()
|
12
13
|
fig_queue.put(fig)
|
13
14
|
plt.close(fig)
|
14
15
|
|
15
|
-
def
|
16
|
+
def spacrFigShow(fig_queue=None):
|
17
|
+
"""
|
18
|
+
Replacement for plt.show() that queues figures instead of displaying them.
|
19
|
+
"""
|
20
|
+
fig = plt.gcf()
|
21
|
+
if fig_queue:
|
22
|
+
fig_queue.put(fig)
|
23
|
+
else:
|
24
|
+
fig.show()
|
25
|
+
plt.close(fig)
|
26
|
+
|
27
|
+
def preprocess_generate_masks_wrapper(settings, q, fig_queue):
|
16
28
|
"""
|
17
29
|
Wraps the measure_crop function to integrate with GUI processes.
|
18
30
|
|
@@ -24,19 +36,18 @@ def measure_crop_wrapper(settings, q, fig_queue):
|
|
24
36
|
|
25
37
|
# Temporarily override plt.show
|
26
38
|
original_show = plt.show
|
27
|
-
plt.show =
|
39
|
+
plt.show = lambda: spacrFigShow(fig_queue)
|
28
40
|
|
29
41
|
try:
|
30
|
-
|
31
|
-
spacr.measure.measure_crop(settings=settings)
|
42
|
+
spacr.core.preprocess_generate_masks(src=settings['src'], settings=settings)
|
32
43
|
except Exception as e:
|
33
44
|
errorMessage = f"Error during processing: {e}"
|
34
45
|
q.put(errorMessage)
|
35
46
|
traceback.print_exc()
|
36
47
|
finally:
|
37
|
-
plt.show = original_show
|
38
|
-
|
39
|
-
def
|
48
|
+
plt.show = original_show
|
49
|
+
|
50
|
+
def measure_crop_wrapper(settings, q, fig_queue):
|
40
51
|
"""
|
41
52
|
Wraps the measure_crop function to integrate with GUI processes.
|
42
53
|
|
@@ -48,22 +59,23 @@ def preprocess_generate_masks_wrapper(settings, q, fig_queue):
|
|
48
59
|
|
49
60
|
# Temporarily override plt.show
|
50
61
|
original_show = plt.show
|
51
|
-
plt.show =
|
62
|
+
plt.show = lambda: spacrFigShow(fig_queue)
|
52
63
|
|
53
64
|
try:
|
54
|
-
|
65
|
+
print('start')
|
66
|
+
spacr.measure.measure_crop(settings=settings)
|
55
67
|
except Exception as e:
|
56
68
|
errorMessage = f"Error during processing: {e}"
|
57
69
|
q.put(errorMessage)
|
58
70
|
traceback.print_exc()
|
59
71
|
finally:
|
60
|
-
plt.show = original_show
|
72
|
+
plt.show = original_show
|
61
73
|
|
62
74
|
def sequencing_wrapper(settings, q, fig_queue):
|
63
75
|
|
64
76
|
# Temporarily override plt.show
|
65
77
|
original_show = plt.show
|
66
|
-
plt.show =
|
78
|
+
plt.show = lambda: spacrFigShow(fig_queue)
|
67
79
|
|
68
80
|
try:
|
69
81
|
spacr.sequencing.analyze_reads(settings=settings)
|
@@ -78,7 +90,7 @@ def umap_wrapper(settings, q, fig_queue):
|
|
78
90
|
|
79
91
|
# Temporarily override plt.show
|
80
92
|
original_show = plt.show
|
81
|
-
plt.show =
|
93
|
+
plt.show = lambda: spacrFigShow(fig_queue)
|
82
94
|
|
83
95
|
try:
|
84
96
|
spacr.core.generate_image_umap(settings=settings)
|
@@ -101,7 +113,7 @@ def train_test_model_wrapper(settings, q, fig_queue):
|
|
101
113
|
|
102
114
|
# Temporarily override plt.show
|
103
115
|
original_show = plt.show
|
104
|
-
plt.show =
|
116
|
+
plt.show = lambda: spacrFigShow(fig_queue)
|
105
117
|
|
106
118
|
try:
|
107
119
|
spacr.core.train_test_model(settings['src'], settings=settings)
|
@@ -125,7 +137,7 @@ def run_multiple_simulations_wrapper(settings, q, fig_queue):
|
|
125
137
|
|
126
138
|
# Temporarily override plt.show
|
127
139
|
original_show = plt.show
|
128
|
-
plt.show =
|
140
|
+
plt.show = lambda: spacrFigShow(fig_queue)
|
129
141
|
|
130
142
|
try:
|
131
143
|
spacr.sim.run_multiple_simulations(settings=settings)
|
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
|
|