spacr 0.1.1__tar.gz → 0.1.12__tar.gz
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-0.1.1/spacr.egg-info → spacr-0.1.12}/PKG-INFO +5 -1
- {spacr-0.1.1 → spacr-0.1.12}/setup.py +9 -5
- {spacr-0.1.1 → spacr-0.1.12}/spacr/__init__.py +12 -12
- spacr-0.1.1/spacr/annotate_app_v2.py → spacr-0.1.12/spacr/app_annotate.py +183 -155
- spacr-0.1.1/spacr/gui_classify_app.py → spacr-0.1.12/spacr/app_classify.py +29 -15
- spacr-0.1.1/spacr/mask_app.py → spacr-0.1.12/spacr/app_make_masks.py +75 -71
- spacr-0.1.12/spacr/app_make_masks_v2.py +688 -0
- spacr-0.1.1/spacr/gui_mask_app.py → spacr-0.1.12/spacr/app_mask.py +10 -4
- spacr-0.1.1/spacr/gui_measure_app.py → spacr-0.1.12/spacr/app_measure.py +17 -5
- {spacr-0.1.1 → spacr-0.1.12}/spacr/gui.py +23 -20
- {spacr-0.1.1 → spacr-0.1.12}/spacr/gui_utils.py +110 -20
- {spacr-0.1.1 → spacr-0.1.12/spacr.egg-info}/PKG-INFO +5 -1
- {spacr-0.1.1 → spacr-0.1.12}/spacr.egg-info/SOURCES.txt +7 -8
- {spacr-0.1.1 → spacr-0.1.12}/spacr.egg-info/entry_points.txt +3 -3
- {spacr-0.1.1 → spacr-0.1.12}/spacr.egg-info/requires.txt +4 -0
- {spacr-0.1.1 → spacr-0.1.12}/tests/test_annotate_app.py +1 -1
- {spacr-0.1.1 → spacr-0.1.12}/tests/test_gui_classify_app.py +1 -1
- {spacr-0.1.1 → spacr-0.1.12}/tests/test_gui_mask_app.py +1 -1
- {spacr-0.1.1 → spacr-0.1.12}/tests/test_gui_measure_app.py +1 -1
- {spacr-0.1.1 → spacr-0.1.12}/tests/test_gui_sim_app.py +1 -1
- {spacr-0.1.1 → spacr-0.1.12}/tests/test_mask_app.py +1 -1
- spacr-0.1.1/spacr/annotate_app.py +0 -511
- spacr-0.1.1/spacr/gui_2.py +0 -160
- {spacr-0.1.1 → spacr-0.1.12}/LICENSE +0 -0
- {spacr-0.1.1 → spacr-0.1.12}/MANIFEST.in +0 -0
- {spacr-0.1.1 → spacr-0.1.12}/README.rst +0 -0
- {spacr-0.1.1 → spacr-0.1.12}/setup.cfg +0 -0
- {spacr-0.1.1 → spacr-0.1.12}/spacr/__main__.py +0 -0
- {spacr-0.1.1 → spacr-0.1.12}/spacr/chris.py +0 -0
- {spacr-0.1.1 → spacr-0.1.12}/spacr/core.py +0 -0
- {spacr-0.1.1 → spacr-0.1.12}/spacr/deep_spacr.py +0 -0
- {spacr-0.1.1 → spacr-0.1.12}/spacr/graph_learning.py +0 -0
- {spacr-0.1.1 → spacr-0.1.12}/spacr/io.py +0 -0
- {spacr-0.1.1 → spacr-0.1.12}/spacr/logger.py +0 -0
- {spacr-0.1.1 → spacr-0.1.12}/spacr/measure.py +0 -0
- {spacr-0.1.1 → spacr-0.1.12}/spacr/models/cp/toxo_plaque_cyto_e25000_X1120_Y1120.CP_model +0 -0
- {spacr-0.1.1 → spacr-0.1.12}/spacr/models/cp/toxo_plaque_cyto_e25000_X1120_Y1120.CP_model_settings.csv +0 -0
- {spacr-0.1.1 → spacr-0.1.12}/spacr/models/cp/toxo_pv_lumen.CP_model +0 -0
- {spacr-0.1.1 → spacr-0.1.12}/spacr/plot.py +0 -0
- {spacr-0.1.1 → spacr-0.1.12}/spacr/sequencing.py +0 -0
- {spacr-0.1.1 → spacr-0.1.12}/spacr/settings.py +0 -0
- {spacr-0.1.1 → spacr-0.1.12}/spacr/sim.py +0 -0
- /spacr-0.1.1/spacr/gui_sim_app.py → /spacr-0.1.12/spacr/sim_app.py +0 -0
- {spacr-0.1.1 → spacr-0.1.12}/spacr/timelapse.py +0 -0
- {spacr-0.1.1 → spacr-0.1.12}/spacr/utils.py +0 -0
- {spacr-0.1.1 → spacr-0.1.12}/spacr/version.py +0 -0
- {spacr-0.1.1 → spacr-0.1.12}/spacr.egg-info/dependency_links.txt +0 -0
- {spacr-0.1.1 → spacr-0.1.12}/spacr.egg-info/top_level.txt +0 -0
- {spacr-0.1.1 → spacr-0.1.12}/tests/test_core.py +0 -0
- {spacr-0.1.1 → spacr-0.1.12}/tests/test_gui_utils.py +0 -0
- {spacr-0.1.1 → spacr-0.1.12}/tests/test_io.py +0 -0
- {spacr-0.1.1 → spacr-0.1.12}/tests/test_measure.py +0 -0
- {spacr-0.1.1 → spacr-0.1.12}/tests/test_plot.py +0 -0
- {spacr-0.1.1 → spacr-0.1.12}/tests/test_sim.py +0 -0
- {spacr-0.1.1 → spacr-0.1.12}/tests/test_timelapse.py +0 -0
- {spacr-0.1.1 → spacr-0.1.12}/tests/test_train.py +0 -0
- {spacr-0.1.1 → spacr-0.1.12}/tests/test_umap.py +0 -0
- {spacr-0.1.1 → spacr-0.1.12}/tests/test_utils.py +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: spacr
|
3
|
-
Version: 0.1.
|
3
|
+
Version: 0.1.12
|
4
4
|
Summary: Spatial phenotype analysis of crisp screens (SpaCr)
|
5
5
|
Home-page: https://github.com/EinarOlafsson/spacr
|
6
6
|
Author: Einar Birnir Olafsson
|
@@ -40,6 +40,10 @@ Requires-Dist: ttf_opensans>=2020.10.30
|
|
40
40
|
Requires-Dist: customtkinter<6.0,>=5.2.2
|
41
41
|
Requires-Dist: biopython<2.0,>=1.80
|
42
42
|
Requires-Dist: lxml<6.0,>=5.1.0
|
43
|
+
Requires-Dist: qtpy<2.5,>=2.4.1
|
44
|
+
Requires-Dist: superqt<0.7,>=0.6.7
|
45
|
+
Requires-Dist: pyqt6<6.8,>=6.7.1
|
46
|
+
Requires-Dist: pyqtgraph<0.14,>=0.13.7
|
43
47
|
Provides-Extra: dev
|
44
48
|
Requires-Dist: pytest>=3.9; extra == "dev"
|
45
49
|
Provides-Extra: headless
|
@@ -52,12 +52,16 @@ dependencies = [
|
|
52
52
|
'ttf_opensans>=2020.10.30',
|
53
53
|
'customtkinter>=5.2.2,<6.0',
|
54
54
|
'biopython>=1.80,<2.0',
|
55
|
-
'lxml>=5.1.0,<6.0'
|
55
|
+
'lxml>=5.1.0,<6.0',
|
56
|
+
'qtpy>=2.4.1,<2.5',
|
57
|
+
'superqt>=0.6.7,<0.7',
|
58
|
+
'pyqt6>=6.7.1,<6.8',
|
59
|
+
'pyqtgraph>=0.13.7,<0.14'
|
56
60
|
]
|
57
61
|
|
58
62
|
setup(
|
59
63
|
name="spacr",
|
60
|
-
version="0.1.
|
64
|
+
version="0.1.12",
|
61
65
|
author="Einar Birnir Olafsson",
|
62
66
|
author_email="olafsson@med.umich.com",
|
63
67
|
description="Spatial phenotype analysis of crisp screens (SpaCr)",
|
@@ -71,12 +75,12 @@ setup(
|
|
71
75
|
'console_scripts': [
|
72
76
|
'mask=spacr.gui_mask_app:gui_mask',
|
73
77
|
'measure=spacr.gui_measure_app:gui_measure',
|
74
|
-
'make_masks=spacr.
|
75
|
-
'
|
78
|
+
'make_masks=spacr.gui_make_mask_app:gui_make_masks',
|
79
|
+
'make_masks2=spacr.gui_make_mask_app_v2:gui_make_masks',
|
80
|
+
'annotate=spacr.annotate_app_v2:gui_annotate',
|
76
81
|
'classify=spacr.gui_classify_app:gui_classify',
|
77
82
|
'sim=spacr.gui_sim_app:gui_sim',
|
78
83
|
'gui=spacr.gui:gui_app',
|
79
|
-
'gui2=spacr.gui_2:gui_app',
|
80
84
|
],
|
81
85
|
},
|
82
86
|
extras_require={
|
@@ -12,13 +12,13 @@ from . import sim
|
|
12
12
|
from . import sequencing
|
13
13
|
from . import timelapse
|
14
14
|
from . import deep_spacr
|
15
|
-
from . import
|
16
|
-
from . import annotate_app_v2
|
15
|
+
from . import app_annotate
|
17
16
|
from . import gui_utils
|
18
|
-
from . import
|
19
|
-
from . import
|
20
|
-
from . import
|
21
|
-
from . import
|
17
|
+
from . import app_make_masks
|
18
|
+
from . import app_make_masks_v2
|
19
|
+
from . import app_mask
|
20
|
+
from . import app_measure
|
21
|
+
from . import app_classify
|
22
22
|
from . import logger
|
23
23
|
|
24
24
|
|
@@ -33,13 +33,13 @@ __all__ = [
|
|
33
33
|
"sequencing"
|
34
34
|
"timelapse",
|
35
35
|
"deep_spacr",
|
36
|
-
"
|
37
|
-
"annotate_app_v2",
|
36
|
+
"app_annotate",
|
38
37
|
"gui_utils",
|
39
|
-
"
|
40
|
-
"
|
41
|
-
"
|
42
|
-
"
|
38
|
+
"app_make_masks",
|
39
|
+
"app_make_masks_v2",
|
40
|
+
"app_mask",
|
41
|
+
"app_measure",
|
42
|
+
"app_classify",
|
43
43
|
"logger"
|
44
44
|
]
|
45
45
|
|
@@ -1,37 +1,22 @@
|
|
1
|
+
import sqlite3
|
1
2
|
from queue import Queue
|
2
|
-
from tkinter import Label
|
3
|
+
from tkinter import Label
|
3
4
|
import tkinter as tk
|
5
|
+
from tkinter import ttk
|
4
6
|
import os, threading, time, sqlite3
|
5
7
|
import numpy as np
|
6
8
|
from PIL import Image, ImageOps
|
7
9
|
from concurrent.futures import ThreadPoolExecutor
|
8
10
|
from PIL import ImageTk
|
9
|
-
import pandas as pd
|
10
11
|
from skimage.exposure import rescale_intensity
|
11
|
-
import cv2
|
12
|
-
import matplotlib.pyplot as plt
|
13
12
|
from IPython.display import display, HTML
|
13
|
+
from tkinter import font as tkFont
|
14
|
+
from tkinter import TclError
|
15
|
+
|
16
|
+
from .gui_utils import ScrollableFrame, CustomButton, set_dark_style, set_default_font, style_text_boxes, create_menu_bar
|
14
17
|
|
15
18
|
class ImageApp:
|
16
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', normalize=False, percentiles=(1,99), measurement=None, threshold=None):
|
17
|
-
"""
|
18
|
-
Initializes an instance of the ImageApp class.
|
19
|
-
|
20
|
-
Parameters:
|
21
|
-
- root (tkinter.Tk): The root window of the application.
|
22
|
-
- db_path (str): The path to the SQLite database.
|
23
|
-
- src (str): The source directory that should be upstream of 'data' in the paths.
|
24
|
-
- image_type (str): The type of images to display.
|
25
|
-
- channels (list): The channels to filter in the 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
|
-
- normalize (bool): Whether to normalize images to their 2nd and 98th percentiles. Defaults to False.
|
31
|
-
- measurement (str): The measurement column to filter by.
|
32
|
-
- threshold (float): The threshold value for filtering the measurement column.
|
33
|
-
"""
|
34
|
-
|
35
20
|
self.root = root
|
36
21
|
self.db_path = db_path
|
37
22
|
self.src = src
|
@@ -67,9 +52,6 @@ class ImageApp:
|
|
67
52
|
self.labels.append(label)
|
68
53
|
|
69
54
|
def prefilter_paths_annotations(self):
|
70
|
-
"""
|
71
|
-
Pre-filters the paths and annotations based on the specified measurement and threshold.
|
72
|
-
"""
|
73
55
|
from .io import _read_and_join_tables
|
74
56
|
from .utils import is_list_of_lists
|
75
57
|
|
@@ -157,21 +139,6 @@ class ImageApp:
|
|
157
139
|
conn.close()
|
158
140
|
|
159
141
|
def load_images(self):
|
160
|
-
"""
|
161
|
-
Loads and displays images with annotations.
|
162
|
-
|
163
|
-
This method retrieves image paths and annotations from a pre-filtered list,
|
164
|
-
loads the images using a ThreadPoolExecutor for parallel processing,
|
165
|
-
adds colored borders to images based on their annotations,
|
166
|
-
and displays the images in the corresponding labels.
|
167
|
-
|
168
|
-
Args:
|
169
|
-
None
|
170
|
-
|
171
|
-
Returns:
|
172
|
-
None
|
173
|
-
"""
|
174
|
-
|
175
142
|
for label in self.labels:
|
176
143
|
label.config(image='')
|
177
144
|
|
@@ -211,16 +178,6 @@ class ImageApp:
|
|
211
178
|
self.root.update()
|
212
179
|
|
213
180
|
def load_single_image(self, path_annotation_tuple):
|
214
|
-
"""
|
215
|
-
Loads a single image from the given path and annotation tuple.
|
216
|
-
|
217
|
-
Args:
|
218
|
-
path_annotation_tuple (tuple): A tuple containing the image path and its annotation.
|
219
|
-
|
220
|
-
Returns:
|
221
|
-
img (PIL.Image.Image): The loaded image.
|
222
|
-
annotation: The annotation associated with the image.
|
223
|
-
"""
|
224
181
|
path, annotation = path_annotation_tuple
|
225
182
|
img = Image.open(path)
|
226
183
|
img = self.normalize_image(img, self.normalize, self.percentiles)
|
@@ -231,18 +188,6 @@ class ImageApp:
|
|
231
188
|
|
232
189
|
@staticmethod
|
233
190
|
def normalize_image(img, normalize=False, percentiles=(1, 99)):
|
234
|
-
"""
|
235
|
-
Normalize the pixel values of an image based on the 2nd and 98th percentiles or the image min and max values,
|
236
|
-
and ensure the image is exported as 8-bit.
|
237
|
-
|
238
|
-
Parameters:
|
239
|
-
- img: PIL.Image.Image. The input image to be normalized.
|
240
|
-
- normalize: bool. Whether to normalize based on the 2nd and 98th percentiles.
|
241
|
-
- percentiles: tuple. The percentiles to use for normalization.
|
242
|
-
|
243
|
-
Returns:
|
244
|
-
- PIL.Image.Image. The normalized and 8-bit converted image.
|
245
|
-
"""
|
246
191
|
img_array = np.array(img)
|
247
192
|
|
248
193
|
if normalize:
|
@@ -259,17 +204,6 @@ class ImageApp:
|
|
259
204
|
return Image.fromarray(img_array)
|
260
205
|
|
261
206
|
def add_colored_border(self, img, border_width, border_color):
|
262
|
-
"""
|
263
|
-
Adds a colored border to an image.
|
264
|
-
|
265
|
-
Args:
|
266
|
-
img (PIL.Image.Image): The input image.
|
267
|
-
border_width (int): The width of the border in pixels.
|
268
|
-
border_color (str): The color of the border in RGB format.
|
269
|
-
|
270
|
-
Returns:
|
271
|
-
PIL.Image.Image: The image with the colored border.
|
272
|
-
"""
|
273
207
|
top_border = Image.new('RGB', (img.width, border_width), color=border_color)
|
274
208
|
bottom_border = Image.new('RGB', (img.width, border_width), color=border_color)
|
275
209
|
left_border = Image.new('RGB', (border_width, img.height), color=border_color)
|
@@ -285,15 +219,6 @@ class ImageApp:
|
|
285
219
|
return bordered_img
|
286
220
|
|
287
221
|
def filter_channels(self, img):
|
288
|
-
"""
|
289
|
-
Filters the channels of an image based on the specified channels.
|
290
|
-
|
291
|
-
Args:
|
292
|
-
img (PIL.Image.Image): The input image.
|
293
|
-
|
294
|
-
Returns:
|
295
|
-
PIL.Image.Image: The filtered image.
|
296
|
-
"""
|
297
222
|
r, g, b = img.split()
|
298
223
|
if self.channels:
|
299
224
|
if 'r' not in self.channels:
|
@@ -310,17 +235,6 @@ class ImageApp:
|
|
310
235
|
return Image.merge("RGB", (r, g, b))
|
311
236
|
|
312
237
|
def get_on_image_click(self, path, label, img):
|
313
|
-
"""
|
314
|
-
Returns a callback function that handles the click event on an image.
|
315
|
-
|
316
|
-
Parameters:
|
317
|
-
path (str): The path of the image file.
|
318
|
-
label (tkinter.Label): The label widget to update with the annotated image.
|
319
|
-
img (PIL.Image.Image): The image object.
|
320
|
-
|
321
|
-
Returns:
|
322
|
-
function: The callback function for the image click event.
|
323
|
-
"""
|
324
238
|
def on_image_click(event):
|
325
239
|
new_annotation = 1 if event.num == 1 else (2 if event.num == 3 else None)
|
326
240
|
|
@@ -354,11 +268,6 @@ class ImageApp:
|
|
354
268
|
"""))
|
355
269
|
|
356
270
|
def update_database_worker(self):
|
357
|
-
"""
|
358
|
-
Worker function that continuously updates the database with pending updates from the update queue.
|
359
|
-
It retrieves the pending updates from the queue, updates the corresponding records in the database,
|
360
|
-
and resets the text in the HTML and status label.
|
361
|
-
"""
|
362
271
|
conn = sqlite3.connect(self.db_path)
|
363
272
|
c = conn.cursor()
|
364
273
|
|
@@ -381,51 +290,24 @@ class ImageApp:
|
|
381
290
|
c.execute(f'UPDATE png_list SET {self.annotation_column} = ? WHERE png_path = ?', (new_annotation, path))
|
382
291
|
conn.commit()
|
383
292
|
|
384
|
-
# Reset the text
|
385
293
|
ImageApp.update_html('')
|
386
294
|
self.status_label.config(text='')
|
387
295
|
self.root.update()
|
388
296
|
time.sleep(0.1)
|
389
297
|
|
390
298
|
def update_gui_text(self, text):
|
391
|
-
"""
|
392
|
-
Update the text of the status label in the GUI.
|
393
|
-
|
394
|
-
Args:
|
395
|
-
text (str): The new text to be displayed in the status label.
|
396
|
-
|
397
|
-
Returns:
|
398
|
-
None
|
399
|
-
"""
|
400
299
|
self.status_label.config(text=text)
|
401
300
|
self.root.update()
|
402
301
|
|
403
302
|
def next_page(self):
|
404
|
-
|
405
|
-
Moves to the next page of images in the grid.
|
406
|
-
|
407
|
-
If there are pending updates in the dictionary, they are added to the update queue.
|
408
|
-
The pending updates dictionary is then cleared.
|
409
|
-
The index is incremented by the number of rows multiplied by the number of columns in the grid.
|
410
|
-
Finally, the images are loaded for the new page.
|
411
|
-
"""
|
412
|
-
if self.pending_updates: # Check if the dictionary is not empty
|
303
|
+
if self.pending_updates:
|
413
304
|
self.update_queue.put(self.pending_updates.copy())
|
414
305
|
self.pending_updates.clear()
|
415
306
|
self.index += self.grid_rows * self.grid_cols
|
416
307
|
self.load_images()
|
417
308
|
|
418
309
|
def previous_page(self):
|
419
|
-
|
420
|
-
Move to the previous page in the grid.
|
421
|
-
|
422
|
-
If there are pending updates in the dictionary, they are added to the update queue.
|
423
|
-
The dictionary of pending updates is then cleared.
|
424
|
-
The index is decremented by the number of rows multiplied by the number of columns in the grid.
|
425
|
-
If the index becomes negative, it is set to 0.
|
426
|
-
Finally, the images are loaded for the new page.
|
427
|
-
"""
|
428
|
-
if self.pending_updates: # Check if the dictionary is not empty
|
310
|
+
if self.pending_updates:
|
429
311
|
self.update_queue.put(self.pending_updates.copy())
|
430
312
|
self.pending_updates.clear()
|
431
313
|
self.index -= self.grid_rows * self.grid_cols
|
@@ -434,23 +316,15 @@ class ImageApp:
|
|
434
316
|
self.load_images()
|
435
317
|
|
436
318
|
def shutdown(self):
|
437
|
-
|
438
|
-
Shuts down the application.
|
439
|
-
|
440
|
-
This method sets the terminate flag to True, clears the pending updates,
|
441
|
-
updates the database, and quits the application.
|
442
|
-
|
443
|
-
"""
|
444
|
-
self.terminate = True # Set terminate first
|
319
|
+
self.terminate = True
|
445
320
|
self.update_queue.put(self.pending_updates.copy())
|
446
321
|
self.pending_updates.clear()
|
447
|
-
self.db_update_thread.join()
|
322
|
+
self.db_update_thread.join()
|
448
323
|
self.root.quit()
|
449
324
|
self.root.destroy()
|
450
325
|
print(f'Quit application')
|
451
326
|
|
452
327
|
def get_annotate_default_settings(settings):
|
453
|
-
|
454
328
|
settings.setdefault('image_type', 'cell_png')
|
455
329
|
settings.setdefault('channels', ['r', 'g', 'b'])
|
456
330
|
settings.setdefault('geom', "3200x2000")
|
@@ -466,24 +340,6 @@ def get_annotate_default_settings(settings):
|
|
466
340
|
return settings
|
467
341
|
|
468
342
|
def annotate(settings):
|
469
|
-
"""
|
470
|
-
Annotates images in a database using a graphical user interface.
|
471
|
-
|
472
|
-
Args:
|
473
|
-
db (str): The path to the SQLite database.
|
474
|
-
src (str): The source directory that should be upstream of 'data' in the paths.
|
475
|
-
image_type (str, optional): The type of images to load from the database. Defaults to None.
|
476
|
-
channels (str, optional): The channels of the images to load from the database. Defaults to None.
|
477
|
-
geom (str, optional): The geometry of the GUI window. Defaults to "1000x1100".
|
478
|
-
img_size (tuple, optional): The size of the images to display in the GUI. Defaults to (200, 200).
|
479
|
-
rows (int, optional): The number of rows in the image grid. Defaults to 5.
|
480
|
-
columns (int, optional): The number of columns in the image grid. Defaults to 5.
|
481
|
-
annotation_column (str, optional): The name of the annotation column in the database table. Defaults to 'annotate'.
|
482
|
-
normalize (bool, optional): Whether to normalize images to their 2nd and 98th percentiles. Defaults to False.
|
483
|
-
measurement (str, optional): The measurement column to filter by.
|
484
|
-
threshold (float, optional): The threshold value for filtering the measurement column.
|
485
|
-
"""
|
486
|
-
|
487
343
|
settings = get_annotate_default_settings(settings)
|
488
344
|
src = settings['src']
|
489
345
|
|
@@ -509,3 +365,175 @@ def annotate(settings):
|
|
509
365
|
|
510
366
|
app.load_images()
|
511
367
|
root.mainloop()
|
368
|
+
|
369
|
+
# Global list to keep references to PhotoImage objects
|
370
|
+
global_image_refs = []
|
371
|
+
|
372
|
+
def initiate_annotation_app_root(parent_frame):
|
373
|
+
style = ttk.Style(parent_frame)
|
374
|
+
set_dark_style(style)
|
375
|
+
style_text_boxes(style)
|
376
|
+
set_default_font(parent_frame, font_name="Arial", size=8)
|
377
|
+
|
378
|
+
parent_frame.configure(bg='black')
|
379
|
+
|
380
|
+
container = tk.PanedWindow(parent_frame, orient=tk.HORIZONTAL, bg='black')
|
381
|
+
container.pack(fill=tk.BOTH, expand=True)
|
382
|
+
|
383
|
+
scrollable_frame = ScrollableFrame(container, bg='black')
|
384
|
+
container.add(scrollable_frame, stretch="always")
|
385
|
+
|
386
|
+
# Setup input fields
|
387
|
+
vars_dict = {
|
388
|
+
'src': ttk.Entry(scrollable_frame.scrollable_frame),
|
389
|
+
'image_type': ttk.Entry(scrollable_frame.scrollable_frame),
|
390
|
+
'channels': ttk.Entry(scrollable_frame.scrollable_frame),
|
391
|
+
'geom': ttk.Entry(scrollable_frame.scrollable_frame),
|
392
|
+
'img_size': ttk.Entry(scrollable_frame.scrollable_frame),
|
393
|
+
'rows': ttk.Entry(scrollable_frame.scrollable_frame),
|
394
|
+
'columns': ttk.Entry(scrollable_frame.scrollable_frame),
|
395
|
+
'annotation_column': ttk.Entry(scrollable_frame.scrollable_frame),
|
396
|
+
'normalize': ttk.Entry(scrollable_frame.scrollable_frame),
|
397
|
+
'percentiles': ttk.Entry(scrollable_frame.scrollable_frame),
|
398
|
+
'measurement': ttk.Entry(scrollable_frame.scrollable_frame),
|
399
|
+
'threshold': ttk.Entry(scrollable_frame.scrollable_frame),
|
400
|
+
}
|
401
|
+
|
402
|
+
default_settings = {
|
403
|
+
'src': 'path',
|
404
|
+
'image_type': 'cell_png',
|
405
|
+
'channels': 'r,g,b',
|
406
|
+
'geom': "3200x2000",
|
407
|
+
'img_size': '(200, 200)',
|
408
|
+
'rows': '10',
|
409
|
+
'columns': '18',
|
410
|
+
'annotation_column': 'recruited_test',
|
411
|
+
'normalize': 'False',
|
412
|
+
'percentiles': '(2,98)',
|
413
|
+
'measurement': 'None',
|
414
|
+
'threshold': 'None'
|
415
|
+
}
|
416
|
+
|
417
|
+
# Arrange input fields and labels
|
418
|
+
row = 0
|
419
|
+
for name, entry in vars_dict.items():
|
420
|
+
ttk.Label(scrollable_frame.scrollable_frame, text=f"{name.replace('_', ' ').capitalize()}:",
|
421
|
+
background="black", foreground="white").grid(row=row, column=0)
|
422
|
+
entry.insert(0, default_settings[name])
|
423
|
+
entry.grid(row=row, column=1)
|
424
|
+
row += 1
|
425
|
+
|
426
|
+
# Function to be called when "Run" button is clicked
|
427
|
+
def run_app():
|
428
|
+
settings = {key: entry.get() for key, entry in vars_dict.items()}
|
429
|
+
settings['channels'] = settings['channels'].split(',')
|
430
|
+
settings['img_size'] = tuple(map(int, settings['img_size'][1:-1].split(',')))
|
431
|
+
settings['percentiles'] = tuple(map(int, settings['percentiles'][1:-1].split(',')))
|
432
|
+
settings['normalize'] = settings['normalize'].lower() == 'true'
|
433
|
+
settings['rows'] = int(settings['rows'])
|
434
|
+
settings['columns'] = int(settings['columns'])
|
435
|
+
if settings['measurement'].lower() == 'none':
|
436
|
+
settings['measurement'] = None
|
437
|
+
if settings['threshold'].lower() == 'none':
|
438
|
+
settings['threshold'] = None
|
439
|
+
|
440
|
+
# Clear previous content instead of destroying the root
|
441
|
+
if hasattr(parent_frame, 'winfo_children'):
|
442
|
+
for widget in parent_frame.winfo_children():
|
443
|
+
widget.destroy()
|
444
|
+
|
445
|
+
# Start the annotate application in the same root window
|
446
|
+
annotate_app(parent_frame, settings)
|
447
|
+
|
448
|
+
run_button = CustomButton(scrollable_frame.scrollable_frame, text="Run", command=run_app,
|
449
|
+
font=tkFont.Font(family="Arial", size=12, weight=tkFont.NORMAL))
|
450
|
+
run_button.grid(row=row, column=0, columnspan=2, pady=10, padx=10)
|
451
|
+
|
452
|
+
return parent_frame
|
453
|
+
|
454
|
+
def annotate_app(parent_frame, settings):
|
455
|
+
global global_image_refs
|
456
|
+
global_image_refs.clear()
|
457
|
+
root = parent_frame.winfo_toplevel()
|
458
|
+
annotate_with_image_refs(settings, root, lambda: load_next_app(root))
|
459
|
+
|
460
|
+
def annotate_with_image_refs(settings, root, shutdown_callback):
|
461
|
+
from .gui_utils import proceed_with_app
|
462
|
+
from .gui import gui_app
|
463
|
+
|
464
|
+
settings = get_annotate_default_settings(settings)
|
465
|
+
src = settings['src']
|
466
|
+
|
467
|
+
db = os.path.join(src, 'measurements/measurements.db')
|
468
|
+
conn = sqlite3.connect(db)
|
469
|
+
c = conn.cursor()
|
470
|
+
c.execute('PRAGMA table_info(png_list)')
|
471
|
+
cols = c.fetchall()
|
472
|
+
if settings['annotation_column'] not in [col[1] for col in cols]:
|
473
|
+
c.execute(f"ALTER TABLE png_list ADD COLUMN {settings['annotation_column']} integer")
|
474
|
+
conn.commit()
|
475
|
+
conn.close()
|
476
|
+
|
477
|
+
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'])
|
478
|
+
|
479
|
+
# Set the canvas background to black
|
480
|
+
root.configure(bg='black')
|
481
|
+
|
482
|
+
next_button = tk.Button(root, text="Next", command=app.next_page, background='black', foreground='white')
|
483
|
+
next_button.grid(row=app.grid_rows, column=app.grid_cols - 1)
|
484
|
+
back_button = tk.Button(root, text="Back", command=app.previous_page, background='black', foreground='white')
|
485
|
+
back_button.grid(row=app.grid_rows, column=app.grid_cols - 2)
|
486
|
+
exit_button = tk.Button(root, text="Exit", command=lambda: [app.shutdown(), shutdown_callback()], background='black', foreground='white')
|
487
|
+
exit_button.grid(row=app.grid_rows, column=app.grid_cols - 3)
|
488
|
+
|
489
|
+
app.load_images()
|
490
|
+
|
491
|
+
# Store the shutdown function and next app details in the root
|
492
|
+
root.current_app_exit_func = lambda: [app.shutdown(), shutdown_callback()]
|
493
|
+
root.next_app_func = proceed_with_app
|
494
|
+
root.next_app_args = ("Main App", gui_app) # Specify the main app function
|
495
|
+
|
496
|
+
def load_next_app(root):
|
497
|
+
# Get the next app function and arguments
|
498
|
+
next_app_func = root.next_app_func
|
499
|
+
next_app_args = root.next_app_args
|
500
|
+
|
501
|
+
if next_app_func:
|
502
|
+
try:
|
503
|
+
if not root.winfo_exists():
|
504
|
+
raise tk.TclError
|
505
|
+
next_app_func(root, *next_app_args)
|
506
|
+
except tk.TclError:
|
507
|
+
# Reinitialize root if it has been destroyed
|
508
|
+
new_root = tk.Tk()
|
509
|
+
width = new_root.winfo_screenwidth()
|
510
|
+
height = new_root.winfo_screenheight()
|
511
|
+
new_root.geometry(f"{width}x{height}")
|
512
|
+
new_root.title("SpaCr Application")
|
513
|
+
next_app_func(new_root, *next_app_args)
|
514
|
+
|
515
|
+
|
516
|
+
def gui_annotate():
|
517
|
+
root = tk.Tk()
|
518
|
+
width = root.winfo_screenwidth()
|
519
|
+
height = root.winfo_screenheight()
|
520
|
+
root.geometry(f"{width}x{height}")
|
521
|
+
root.title("Annotate Application")
|
522
|
+
|
523
|
+
# Clear previous content if any
|
524
|
+
if hasattr(root, 'content_frame'):
|
525
|
+
for widget in root.content_frame.winfo_children():
|
526
|
+
widget.destroy()
|
527
|
+
root.content_frame.grid_forget()
|
528
|
+
else:
|
529
|
+
root.content_frame = tk.Frame(root)
|
530
|
+
root.content_frame.grid(row=1, column=0, sticky="nsew")
|
531
|
+
root.grid_rowconfigure(1, weight=1)
|
532
|
+
root.grid_columnconfigure(0, weight=1)
|
533
|
+
|
534
|
+
initiate_annotation_app_root(root.content_frame)
|
535
|
+
create_menu_bar(root)
|
536
|
+
root.mainloop()
|
537
|
+
|
538
|
+
if __name__ == "__main__":
|
539
|
+
gui_annotate()
|
@@ -77,7 +77,7 @@ def initiate_classify_root(parent_frame):
|
|
77
77
|
style_text_boxes(style)
|
78
78
|
set_default_font(parent_frame, font_name="Helvetica", size=8)
|
79
79
|
|
80
|
-
parent_frame.configure(bg='
|
80
|
+
parent_frame.configure(bg='black')
|
81
81
|
parent_frame.grid_rowconfigure(0, weight=1)
|
82
82
|
parent_frame.grid_columnconfigure(0, weight=1)
|
83
83
|
fig_queue = Queue()
|
@@ -94,7 +94,7 @@ def initiate_classify_root(parent_frame):
|
|
94
94
|
ax.xaxis.set_visible(False) # Hide the x-axis
|
95
95
|
ax.yaxis.set_visible(False) # Hide the y-axis
|
96
96
|
fig.tight_layout()
|
97
|
-
fig.set_facecolor('
|
97
|
+
fig.set_facecolor('black')
|
98
98
|
canvas.figure = fig
|
99
99
|
fig_width, fig_height = canvas_widget.winfo_width(), canvas_widget.winfo_height()
|
100
100
|
fig.set_size_inches(fig_width / fig.dpi, fig_height / fig.dpi, forward=True)
|
@@ -117,9 +117,9 @@ def initiate_classify_root(parent_frame):
|
|
117
117
|
parent_frame.grid_columnconfigure(0, weight=1)
|
118
118
|
|
119
119
|
# Settings Section
|
120
|
-
settings_frame = tk.Frame(vertical_container, bg='
|
120
|
+
settings_frame = tk.Frame(vertical_container, bg='black')
|
121
121
|
vertical_container.add(settings_frame, stretch="always")
|
122
|
-
settings_label = ttk.Label(settings_frame, text="Settings", background="
|
122
|
+
settings_label = ttk.Label(settings_frame, text="Settings", background="black", foreground="white")
|
123
123
|
settings_label.grid(row=0, column=0, pady=10, padx=10)
|
124
124
|
scrollable_frame = ScrollableFrame(settings_frame, width=500)
|
125
125
|
scrollable_frame.grid(row=1, column=0, sticky="nsew")
|
@@ -131,11 +131,11 @@ def initiate_classify_root(parent_frame):
|
|
131
131
|
vars_dict = generate_fields(variables, scrollable_frame)
|
132
132
|
|
133
133
|
# Button section
|
134
|
-
import_btn = CustomButton(scrollable_frame.scrollable_frame, text="Import
|
134
|
+
import_btn = CustomButton(scrollable_frame.scrollable_frame, text="Import", command=lambda: import_settings(scrollable_frame), font=('Helvetica', 10))
|
135
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))
|
136
|
+
run_button = CustomButton(scrollable_frame.scrollable_frame, text="Run", command=lambda: start_process(q, fig_queue), font=('Helvetica', 10))
|
137
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)
|
138
|
+
abort_button = CustomButton(scrollable_frame.scrollable_frame, text="Abort", command=initiate_abort, font=('Helvetica', 10))
|
139
139
|
abort_button.grid(row=45, column=1, pady=20, padx=20)
|
140
140
|
progress_label = ttk.Label(scrollable_frame.scrollable_frame, text="Processing: 0%", background="black", foreground="white") # Create progress field
|
141
141
|
progress_label.grid(row=50, column=0, columnspan=2, sticky="ew", pady=(5, 0), padx=10)
|
@@ -143,23 +143,23 @@ def initiate_classify_root(parent_frame):
|
|
143
143
|
# Plot Canvas Section
|
144
144
|
plot_frame = tk.PanedWindow(vertical_container, orient=tk.VERTICAL)
|
145
145
|
vertical_container.add(plot_frame, stretch="always")
|
146
|
-
figure = Figure(figsize=(30, 4), dpi=100, facecolor='
|
146
|
+
figure = Figure(figsize=(30, 4), dpi=100, facecolor='black')
|
147
147
|
plot = figure.add_subplot(111)
|
148
148
|
plot.plot([], [])
|
149
149
|
plot.axis('off')
|
150
150
|
canvas = FigureCanvasTkAgg(figure, master=plot_frame)
|
151
|
-
canvas.get_tk_widget().configure(cursor='arrow', background='
|
151
|
+
canvas.get_tk_widget().configure(cursor='arrow', background='black', highlightthickness=0)
|
152
152
|
canvas_widget = canvas.get_tk_widget()
|
153
153
|
plot_frame.add(canvas_widget, stretch="always")
|
154
154
|
canvas.draw()
|
155
155
|
canvas.figure = figure
|
156
156
|
|
157
157
|
# Console Section
|
158
|
-
console_frame = tk.Frame(vertical_container, bg='
|
158
|
+
console_frame = tk.Frame(vertical_container, bg='black')
|
159
159
|
vertical_container.add(console_frame, stretch="always")
|
160
|
-
console_label = ttk.Label(console_frame, text="Console", background="
|
160
|
+
console_label = ttk.Label(console_frame, text="Console", background="black", foreground="white")
|
161
161
|
console_label.grid(row=0, column=0, pady=10, padx=10)
|
162
|
-
console_output = scrolledtext.ScrolledText(console_frame, height=10, bg='
|
162
|
+
console_output = scrolledtext.ScrolledText(console_frame, height=10, bg='black', fg='white', insertbackground='white')
|
163
163
|
console_output.grid(row=1, column=0, sticky="nsew")
|
164
164
|
console_frame.grid_rowconfigure(1, weight=1)
|
165
165
|
console_frame.grid_columnconfigure(0, weight=1)
|
@@ -177,9 +177,23 @@ def initiate_classify_root(parent_frame):
|
|
177
177
|
|
178
178
|
def gui_classify():
|
179
179
|
root = tk.Tk()
|
180
|
-
root.
|
181
|
-
root.
|
182
|
-
|
180
|
+
width = root.winfo_screenwidth()
|
181
|
+
height = root.winfo_screenheight()
|
182
|
+
root.geometry(f"{width}x{height}")
|
183
|
+
root.title("SpaCr: classify objects")
|
184
|
+
|
185
|
+
# Clear previous content if any
|
186
|
+
if hasattr(root, 'content_frame'):
|
187
|
+
for widget in root.content_frame.winfo_children():
|
188
|
+
widget.destroy()
|
189
|
+
root.content_frame.grid_forget()
|
190
|
+
else:
|
191
|
+
root.content_frame = tk.Frame(root)
|
192
|
+
root.content_frame.grid(row=1, column=0, sticky="nsew")
|
193
|
+
root.grid_rowconfigure(1, weight=1)
|
194
|
+
root.grid_columnconfigure(0, weight=1)
|
195
|
+
|
196
|
+
initiate_classify_root(root.content_frame)
|
183
197
|
create_menu_bar(root)
|
184
198
|
root.mainloop()
|
185
199
|
|