spacr 0.0.36__py3-none-any.whl → 0.0.61__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/__init__.py +2 -2
- spacr/__main__.py +0 -2
- spacr/alpha.py +514 -2
- spacr/annotate_app.py +112 -116
- spacr/core.py +864 -728
- spacr/deep_spacr.py +696 -0
- spacr/foldseek.py +2 -16
- spacr/graph_learning.py +297 -253
- spacr/gui.py +9 -8
- spacr/gui_2.py +90 -0
- spacr/gui_classify_app.py +3 -4
- spacr/gui_mask_app.py +9 -9
- spacr/gui_measure_app.py +3 -5
- spacr/gui_utils.py +132 -33
- spacr/io.py +308 -464
- spacr/mask_app.py +109 -5
- spacr/measure.py +15 -1
- spacr/models/cp/toxo_pv_lumen.CP_model +0 -0
- spacr/old_code.py +69 -1
- spacr/plot.py +23 -6
- spacr/sequencing.py +1130 -0
- spacr/sim.py +0 -42
- spacr/timelapse.py +0 -1
- spacr/train.py +172 -13
- spacr/umap.py +0 -689
- spacr/utils.py +1322 -75
- {spacr-0.0.36.dist-info → spacr-0.0.61.dist-info}/METADATA +14 -29
- spacr-0.0.61.dist-info/RECORD +39 -0
- {spacr-0.0.36.dist-info → spacr-0.0.61.dist-info}/entry_points.txt +1 -0
- spacr-0.0.36.dist-info/RECORD +0 -35
- {spacr-0.0.36.dist-info → spacr-0.0.61.dist-info}/LICENSE +0 -0
- {spacr-0.0.36.dist-info → spacr-0.0.61.dist-info}/WHEEL +0 -0
- {spacr-0.0.36.dist-info → spacr-0.0.61.dist-info}/top_level.txt +0 -0
spacr/annotate_app.py
CHANGED
@@ -16,35 +16,14 @@ from .logger import log_function_call
|
|
16
16
|
from .gui_utils import ScrollableFrame, set_default_font, set_dark_style, create_dark_mode, style_text_boxes, create_menu_bar
|
17
17
|
|
18
18
|
class ImageApp:
|
19
|
-
|
20
|
-
A class representing an image application.
|
21
|
-
|
22
|
-
Attributes:
|
23
|
-
- root (tkinter.Tk): The root window of the application.
|
24
|
-
- db_path (str): The path to the SQLite database.
|
25
|
-
- index (int): The index of the current page of images.
|
26
|
-
- grid_rows (int): The number of rows in the image grid.
|
27
|
-
- grid_cols (int): The number of columns in the image grid.
|
28
|
-
- image_size (tuple): The size of the displayed images.
|
29
|
-
- annotation_column (str): The column name for image annotations in the database.
|
30
|
-
- image_type (str): The type of images to display.
|
31
|
-
- channels (list): The channels to filter in the images.
|
32
|
-
- images (dict): A dictionary mapping labels to loaded images.
|
33
|
-
- pending_updates (dict): A dictionary of pending image annotation updates.
|
34
|
-
- labels (list): A list of label widgets for displaying images.
|
35
|
-
- terminate (bool): A flag indicating whether the application should terminate.
|
36
|
-
- update_queue (Queue): A queue for storing image annotation updates.
|
37
|
-
- status_label (tkinter.Label): A label widget for displaying status messages.
|
38
|
-
- db_update_thread (threading.Thread): A thread for updating the database.
|
39
|
-
"""
|
40
|
-
|
41
|
-
def __init__(self, root, db_path, image_type=None, channels=None, grid_rows=None, grid_cols=None, image_size=(200, 200), annotation_column='annotate'):
|
19
|
+
def __init__(self, root, db_path, src, image_type=None, channels=None, grid_rows=None, grid_cols=None, image_size=(200, 200), annotation_column='annotate'):
|
42
20
|
"""
|
43
21
|
Initializes an instance of the ImageApp class.
|
44
22
|
|
45
23
|
Parameters:
|
46
24
|
- root (tkinter.Tk): The root window of the application.
|
47
25
|
- db_path (str): The path to the SQLite database.
|
26
|
+
- src (str): The source directory that should be upstream of 'data' in the paths.
|
48
27
|
- image_type (str): The type of images to display.
|
49
28
|
- channels (list): The channels to filter in the images.
|
50
29
|
- grid_rows (int): The number of rows in the image grid.
|
@@ -52,9 +31,9 @@ class ImageApp:
|
|
52
31
|
- image_size (tuple): The size of the displayed images.
|
53
32
|
- annotation_column (str): The column name for image annotations in the database.
|
54
33
|
"""
|
55
|
-
|
56
34
|
self.root = root
|
57
35
|
self.db_path = db_path
|
36
|
+
self.src = src
|
58
37
|
self.index = 0
|
59
38
|
self.grid_rows = grid_rows
|
60
39
|
self.grid_cols = grid_cols
|
@@ -65,7 +44,7 @@ class ImageApp:
|
|
65
44
|
self.images = {}
|
66
45
|
self.pending_updates = {}
|
67
46
|
self.labels = []
|
68
|
-
|
47
|
+
self.adjusted_to_original_paths = {}
|
69
48
|
self.terminate = False
|
70
49
|
self.update_queue = Queue()
|
71
50
|
self.status_label = Label(self.root, text="", font=("Arial", 12))
|
@@ -78,6 +57,68 @@ class ImageApp:
|
|
78
57
|
label = Label(root)
|
79
58
|
label.grid(row=i // grid_cols, column=i % grid_cols)
|
80
59
|
self.labels.append(label)
|
60
|
+
|
61
|
+
def load_images(self):
|
62
|
+
"""
|
63
|
+
Loads and displays images with annotations.
|
64
|
+
|
65
|
+
This method retrieves image paths and annotations from a SQLite database,
|
66
|
+
loads the images using a ThreadPoolExecutor for parallel processing,
|
67
|
+
adds colored borders to images based on their annotations,
|
68
|
+
and displays the images in the corresponding labels.
|
69
|
+
|
70
|
+
Args:
|
71
|
+
None
|
72
|
+
|
73
|
+
Returns:
|
74
|
+
None
|
75
|
+
"""
|
76
|
+
for label in self.labels:
|
77
|
+
label.config(image='')
|
78
|
+
|
79
|
+
self.images = {}
|
80
|
+
|
81
|
+
conn = sqlite3.connect(self.db_path)
|
82
|
+
c = conn.cursor()
|
83
|
+
if self.image_type:
|
84
|
+
c.execute(f"SELECT png_path, {self.annotation_column} FROM png_list WHERE png_path LIKE ? LIMIT ?, ?", (f"%{self.image_type}%", self.index, self.grid_rows * self.grid_cols))
|
85
|
+
else:
|
86
|
+
c.execute(f"SELECT png_path, {self.annotation_column} FROM png_list LIMIT ?, ?", (self.index, self.grid_rows * self.grid_cols))
|
87
|
+
|
88
|
+
paths = c.fetchall()
|
89
|
+
conn.close()
|
90
|
+
|
91
|
+
adjusted_paths = []
|
92
|
+
for path, annotation in paths:
|
93
|
+
if not path.startswith(self.src):
|
94
|
+
parts = path.split('/data/')
|
95
|
+
if len(parts) > 1:
|
96
|
+
new_path = os.path.join(self.src, 'data', parts[1])
|
97
|
+
self.adjusted_to_original_paths[new_path] = path
|
98
|
+
adjusted_paths.append((new_path, annotation))
|
99
|
+
else:
|
100
|
+
adjusted_paths.append((path, annotation))
|
101
|
+
else:
|
102
|
+
adjusted_paths.append((path, annotation))
|
103
|
+
|
104
|
+
with ThreadPoolExecutor() as executor:
|
105
|
+
loaded_images = list(executor.map(self.load_single_image, adjusted_paths))
|
106
|
+
|
107
|
+
for i, (img, annotation) in enumerate(loaded_images):
|
108
|
+
if annotation:
|
109
|
+
border_color = 'teal' if annotation == 1 else 'red'
|
110
|
+
img = self.add_colored_border(img, border_width=5, border_color=border_color)
|
111
|
+
|
112
|
+
photo = ImageTk.PhotoImage(img)
|
113
|
+
label = self.labels[i]
|
114
|
+
self.images[label] = photo
|
115
|
+
label.config(image=photo)
|
116
|
+
|
117
|
+
path = adjusted_paths[i][0]
|
118
|
+
label.bind('<Button-1>', self.get_on_image_click(path, label, img))
|
119
|
+
label.bind('<Button-3>', self.get_on_image_click(path, label, img))
|
120
|
+
|
121
|
+
self.root.update()
|
81
122
|
|
82
123
|
@staticmethod
|
83
124
|
def normalize_image(img):
|
@@ -148,55 +189,6 @@ class ImageApp:
|
|
148
189
|
|
149
190
|
return Image.merge("RGB", (r, g, b))
|
150
191
|
|
151
|
-
def load_images(self):
|
152
|
-
"""
|
153
|
-
Loads and displays images with annotations.
|
154
|
-
|
155
|
-
This method retrieves image paths and annotations from a SQLite database,
|
156
|
-
loads the images using a ThreadPoolExecutor for parallel processing,
|
157
|
-
adds colored borders to images based on their annotations,
|
158
|
-
and displays the images in the corresponding labels.
|
159
|
-
|
160
|
-
Args:
|
161
|
-
None
|
162
|
-
|
163
|
-
Returns:
|
164
|
-
None
|
165
|
-
"""
|
166
|
-
for label in self.labels:
|
167
|
-
label.config(image='')
|
168
|
-
|
169
|
-
self.images = {}
|
170
|
-
|
171
|
-
conn = sqlite3.connect(self.db_path)
|
172
|
-
c = conn.cursor()
|
173
|
-
if self.image_type:
|
174
|
-
c.execute(f"SELECT png_path, {self.annotation_column} FROM png_list WHERE png_path LIKE ? LIMIT ?, ?", (f"%{self.image_type}%", self.index, self.grid_rows * self.grid_cols))
|
175
|
-
else:
|
176
|
-
c.execute(f"SELECT png_path, {self.annotation_column} FROM png_list LIMIT ?, ?", (self.index, self.grid_rows * self.grid_cols))
|
177
|
-
|
178
|
-
paths = c.fetchall()
|
179
|
-
conn.close()
|
180
|
-
|
181
|
-
with ThreadPoolExecutor() as executor:
|
182
|
-
loaded_images = list(executor.map(self.load_single_image, paths))
|
183
|
-
|
184
|
-
for i, (img, annotation) in enumerate(loaded_images):
|
185
|
-
if annotation:
|
186
|
-
border_color = 'teal' if annotation == 1 else 'red'
|
187
|
-
img = self.add_colored_border(img, border_width=5, border_color=border_color)
|
188
|
-
|
189
|
-
photo = ImageTk.PhotoImage(img)
|
190
|
-
label = self.labels[i]
|
191
|
-
self.images[label] = photo
|
192
|
-
label.config(image=photo)
|
193
|
-
|
194
|
-
path = paths[i][0]
|
195
|
-
label.bind('<Button-1>', self.get_on_image_click(path, label, img))
|
196
|
-
label.bind('<Button-3>', self.get_on_image_click(path, label, img))
|
197
|
-
|
198
|
-
self.root.update()
|
199
|
-
|
200
192
|
def load_single_image(self, path_annotation_tuple):
|
201
193
|
"""
|
202
194
|
Loads a single image from the given path and annotation tuple.
|
@@ -230,14 +222,15 @@ class ImageApp:
|
|
230
222
|
function: The callback function for the image click event.
|
231
223
|
"""
|
232
224
|
def on_image_click(event):
|
233
|
-
|
234
225
|
new_annotation = 1 if event.num == 1 else (2 if event.num == 3 else None)
|
235
226
|
|
236
|
-
|
237
|
-
|
227
|
+
original_path = self.adjusted_to_original_paths.get(path, path)
|
228
|
+
|
229
|
+
if original_path in self.pending_updates and self.pending_updates[original_path] == new_annotation:
|
230
|
+
self.pending_updates[original_path] = None
|
238
231
|
new_annotation = None
|
239
232
|
else:
|
240
|
-
self.pending_updates[
|
233
|
+
self.pending_updates[original_path] = new_annotation
|
241
234
|
|
242
235
|
print(f"Image {os.path.split(path)[1]} annotated: {new_annotation}")
|
243
236
|
|
@@ -261,38 +254,38 @@ class ImageApp:
|
|
261
254
|
"""))
|
262
255
|
|
263
256
|
def update_database_worker(self):
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
257
|
+
"""
|
258
|
+
Worker function that continuously updates the database with pending updates from the update queue.
|
259
|
+
It retrieves the pending updates from the queue, updates the corresponding records in the database,
|
260
|
+
and resets the text in the HTML and status label.
|
261
|
+
"""
|
262
|
+
conn = sqlite3.connect(self.db_path)
|
263
|
+
c = conn.cursor()
|
264
|
+
|
265
|
+
display(HTML("<div id='unique_id'>Initial Text</div>"))
|
266
|
+
|
267
|
+
while True:
|
268
|
+
if self.terminate:
|
269
|
+
conn.close()
|
270
|
+
break
|
271
|
+
|
272
|
+
if not self.update_queue.empty():
|
273
|
+
ImageApp.update_html("Do not exit, Updating database...")
|
274
|
+
self.status_label.config(text='Do not exit, Updating database...')
|
275
|
+
|
276
|
+
pending_updates = self.update_queue.get()
|
277
|
+
for path, new_annotation in pending_updates.items():
|
278
|
+
if new_annotation is None:
|
279
|
+
c.execute(f'UPDATE png_list SET {self.annotation_column} = NULL WHERE png_path = ?', (path,))
|
280
|
+
else:
|
281
|
+
c.execute(f'UPDATE png_list SET {self.annotation_column} = ? WHERE png_path = ?', (new_annotation, path))
|
282
|
+
conn.commit()
|
283
|
+
|
284
|
+
# Reset the text
|
285
|
+
ImageApp.update_html('')
|
286
|
+
self.status_label.config(text='')
|
287
|
+
self.root.update()
|
288
|
+
time.sleep(0.1)
|
296
289
|
|
297
290
|
def update_gui_text(self, text):
|
298
291
|
"""
|
@@ -356,12 +349,13 @@ class ImageApp:
|
|
356
349
|
self.root.destroy()
|
357
350
|
print(f'Quit application')
|
358
351
|
|
359
|
-
def annotate(
|
352
|
+
def annotate(src, image_type=None, channels=None, geom="1000x1100", img_size=(200, 200), rows=5, columns=5, annotation_column='annotate'):
|
360
353
|
"""
|
361
354
|
Annotates images in a database using a graphical user interface.
|
362
355
|
|
363
356
|
Args:
|
364
357
|
db (str): The path to the SQLite database.
|
358
|
+
src (str): The source directory that should be upstream of 'data' in the paths.
|
365
359
|
image_type (str, optional): The type of images to load from the database. Defaults to None.
|
366
360
|
channels (str, optional): The channels of the images to load from the database. Defaults to None.
|
367
361
|
geom (str, optional): The geometry of the GUI window. Defaults to "1000x1100".
|
@@ -370,7 +364,10 @@ def annotate(db, image_type=None, channels=None, geom="1000x1100", img_size=(200
|
|
370
364
|
columns (int, optional): The number of columns in the image grid. Defaults to 5.
|
371
365
|
annotation_column (str, optional): The name of the annotation column in the database table. Defaults to 'annotate'.
|
372
366
|
"""
|
373
|
-
|
367
|
+
db = os.path.join(src, 'measurements/measurements.db')
|
368
|
+
#print('src', src)
|
369
|
+
#print('db', db)
|
370
|
+
|
374
371
|
conn = sqlite3.connect(db)
|
375
372
|
c = conn.cursor()
|
376
373
|
c.execute('PRAGMA table_info(png_list)')
|
@@ -382,8 +379,7 @@ def annotate(db, image_type=None, channels=None, geom="1000x1100", img_size=(200
|
|
382
379
|
|
383
380
|
root = tk.Tk()
|
384
381
|
root.geometry(geom)
|
385
|
-
app = ImageApp(root, db, image_type=image_type, channels=channels, image_size=img_size, grid_rows=rows, grid_cols=columns, annotation_column=annotation_column)
|
386
|
-
#app = ImageApp()
|
382
|
+
app = ImageApp(root, db, src, image_type=image_type, channels=channels, image_size=img_size, grid_rows=rows, grid_cols=columns, annotation_column=annotation_column)
|
387
383
|
next_button = tk.Button(root, text="Next", command=app.next_page)
|
388
384
|
next_button.grid(row=app.grid_rows, column=app.grid_cols - 1)
|
389
385
|
back_button = tk.Button(root, text="Back", command=app.previous_page)
|
@@ -394,6 +390,7 @@ def annotate(db, image_type=None, channels=None, geom="1000x1100", img_size=(200
|
|
394
390
|
app.load_images()
|
395
391
|
root.mainloop()
|
396
392
|
|
393
|
+
|
397
394
|
def check_for_duplicates(db):
|
398
395
|
"""
|
399
396
|
Check for duplicates in the given SQLite database.
|
@@ -493,5 +490,4 @@ def gui_annotation():
|
|
493
490
|
root.mainloop()
|
494
491
|
|
495
492
|
if __name__ == "__main__":
|
496
|
-
gui_annotation()
|
497
|
-
|
493
|
+
gui_annotation()
|