spacr 1.0.5__py3-none-any.whl → 1.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/app_annotate.py +1 -1
- spacr/core.py +1 -0
- spacr/deep_spacr.py +1 -3
- spacr/gui.py +39 -17
- spacr/gui_elements.py +7 -7
- spacr/io.py +14 -4
- spacr/plot.py +57 -0
- spacr/resources/icons/activation.png +0 -0
- spacr/resources/icons/activation_heatmap.png +0 -0
- spacr/resources/icons/logo_spacr.png +0 -0
- spacr/resources/icons/logo_spacr_v1.png +0 -0
- spacr/resources/icons/logo_v1.pdf +2786 -6
- spacr/resources/icons/logo_v2.pdf +274 -0
- spacr/resources/icons/logo_v2_dark.pdf +327 -0
- spacr/resources/icons/logo_v2_dark.png +0 -0
- spacr/resources/icons/logo_v2_white.pdf +386 -0
- spacr/resources/icons/logo_v2_white.png +0 -0
- spacr/settings.py +2 -2
- spacr/spacr_cellpose.py +0 -1
- spacr/utils.py +51 -1
- {spacr-1.0.5.dist-info → spacr-1.0.6.dist-info}/METADATA +1 -1
- {spacr-1.0.5.dist-info → spacr-1.0.6.dist-info}/RECORD +26 -17
- {spacr-1.0.5.dist-info → spacr-1.0.6.dist-info}/entry_points.txt +1 -1
- {spacr-1.0.5.dist-info → spacr-1.0.6.dist-info}/LICENSE +0 -0
- {spacr-1.0.5.dist-info → spacr-1.0.6.dist-info}/WHEEL +0 -0
- {spacr-1.0.5.dist-info → spacr-1.0.6.dist-info}/top_level.txt +0 -0
spacr/app_annotate.py
CHANGED
@@ -11,7 +11,7 @@ def convert_to_number(value):
|
|
11
11
|
return float(value)
|
12
12
|
except ValueError:
|
13
13
|
raise ValueError(f"Unable to convert '{value}' to an integer or float.")
|
14
|
-
|
14
|
+
|
15
15
|
def initiate_annotation_app(parent_frame):
|
16
16
|
from .gui_utils import generate_annotate_fields, annotate_app, convert_to_number
|
17
17
|
# Set up the settings window
|
spacr/core.py
CHANGED
@@ -238,6 +238,7 @@ def generate_cellpose_masks(src, settings, object_type):
|
|
238
238
|
model_name = object_settings['model_name']
|
239
239
|
|
240
240
|
cellpose_channels = _get_cellpose_channels(src, settings['nucleus_channel'], settings['pathogen_channel'], settings['cell_channel'])
|
241
|
+
|
241
242
|
if settings['verbose']:
|
242
243
|
print(cellpose_channels)
|
243
244
|
|
spacr/deep_spacr.py
CHANGED
@@ -1,10 +1,8 @@
|
|
1
|
-
import os, torch, time, gc, datetime
|
1
|
+
import os, torch, time, gc, datetime
|
2
2
|
torch.backends.cudnn.benchmark = True
|
3
|
-
|
4
3
|
import numpy as np
|
5
4
|
import pandas as pd
|
6
5
|
from torch.optim import Adagrad, AdamW
|
7
|
-
from torch.autograd import grad
|
8
6
|
from torch.optim.lr_scheduler import StepLR
|
9
7
|
import torch.nn.functional as F
|
10
8
|
import matplotlib.pyplot as plt
|
spacr/gui.py
CHANGED
@@ -4,6 +4,7 @@ from multiprocessing import set_start_method
|
|
4
4
|
from .gui_elements import spacrButton, create_menu_bar, set_dark_style
|
5
5
|
from .gui_core import initiate_root
|
6
6
|
from screeninfo import get_monitors
|
7
|
+
import webbrowser
|
7
8
|
|
8
9
|
class MainApp(tk.Tk):
|
9
10
|
def __init__(self, default_app=None):
|
@@ -22,6 +23,7 @@ class MainApp(tk.Tk):
|
|
22
23
|
for monitor in get_monitors():
|
23
24
|
if monitor.x <= x < monitor.x + monitor.width and monitor.y <= y < monitor.y + monitor.height:
|
24
25
|
width = monitor.width
|
26
|
+
self.width = width
|
25
27
|
height = monitor.height
|
26
28
|
break
|
27
29
|
else:
|
@@ -42,22 +44,22 @@ class MainApp(tk.Tk):
|
|
42
44
|
self.main_gui_apps = {
|
43
45
|
"Mask": (lambda frame: initiate_root(self, 'mask'), "Generate cellpose masks for cells, nuclei and pathogen images."),
|
44
46
|
"Measure": (lambda frame: initiate_root(self, 'measure'), "Measure single object intensity and morphological feature. Crop and save single object image"),
|
45
|
-
"Annotate": (lambda frame: initiate_root(self, 'annotate'), "Annotation single object images on a grid. Annotations are saved to database."),
|
46
|
-
"Make Masks": (lambda frame: initiate_root(self, 'make_masks'), "Adjust pre-existing Cellpose models to your specific dataset for improved performance"),
|
47
|
+
#"Annotate": (lambda frame: initiate_root(self, 'annotate'), "Annotation single object images on a grid. Annotations are saved to database."),
|
48
|
+
#"Make Masks": (lambda frame: initiate_root(self, 'make_masks'), "Adjust pre-existing Cellpose models to your specific dataset for improved performance"),
|
47
49
|
"Classify": (lambda frame: initiate_root(self, 'classify'), "Train Torch Convolutional Neural Networks (CNNs) or Transformers to classify single object images."),
|
48
50
|
}
|
49
51
|
|
50
52
|
self.additional_gui_apps = {
|
51
|
-
"Umap": (lambda frame: initiate_root(self, 'umap'), "Generate UMAP embeddings with datapoints represented as images."),
|
52
|
-
"Train Cellpose": (lambda frame: initiate_root(self, 'train_cellpose'), "Train custom Cellpose models."),
|
53
|
-
"ML Analyze": (lambda frame: initiate_root(self, 'ml_analyze'), "Machine learning analysis of data."),
|
54
|
-
"Cellpose Masks": (lambda frame: initiate_root(self, 'cellpose_masks'), "Generate Cellpose masks."),
|
55
|
-
"Cellpose All": (lambda frame: initiate_root(self, 'cellpose_all'), "Run Cellpose on all images."),
|
53
|
+
#"Umap": (lambda frame: initiate_root(self, 'umap'), "Generate UMAP embeddings with datapoints represented as images."),
|
54
|
+
#"Train Cellpose": (lambda frame: initiate_root(self, 'train_cellpose'), "Train custom Cellpose models."),
|
55
|
+
#"ML Analyze": (lambda frame: initiate_root(self, 'ml_analyze'), "Machine learning analysis of data."),
|
56
|
+
#"Cellpose Masks": (lambda frame: initiate_root(self, 'cellpose_masks'), "Generate Cellpose masks."),
|
57
|
+
#"Cellpose All": (lambda frame: initiate_root(self, 'cellpose_all'), "Run Cellpose on all images."),
|
56
58
|
"Map Barcodes": (lambda frame: initiate_root(self, 'map_barcodes'), "Map barcodes to data."),
|
57
59
|
"Regression": (lambda frame: initiate_root(self, 'regression'), "Perform regression analysis."),
|
58
|
-
"Recruitment": (lambda frame: initiate_root(self, 'recruitment'), "Analyze recruitment data."),
|
60
|
+
#"Recruitment": (lambda frame: initiate_root(self, 'recruitment'), "Analyze recruitment data."),
|
59
61
|
"Activation": (lambda frame: initiate_root(self, 'activation'), "Generate activation maps of computer vision models and measure channel-activation correlation."),
|
60
|
-
"Plaque": (lambda frame: initiate_root(self, 'analyze_plaques'), "Analyze plaque data.")
|
62
|
+
#"Plaque": (lambda frame: initiate_root(self, 'analyze_plaques'), "Analyze plaque data.")
|
61
63
|
}
|
62
64
|
|
63
65
|
self.selected_app = tk.StringVar()
|
@@ -88,6 +90,13 @@ class MainApp(tk.Tk):
|
|
88
90
|
set_dark_style(ttk.Style(), containers=[self.content_frame, self.inner_frame])
|
89
91
|
|
90
92
|
self.create_startup_screen()
|
93
|
+
|
94
|
+
def _update_wraplength(self, event):
|
95
|
+
if self.description_label.winfo_exists():
|
96
|
+
# Use the actual width of the inner_frame as a proxy for full width
|
97
|
+
available_width = self.inner_frame.winfo_width()
|
98
|
+
if available_width > 0:
|
99
|
+
self.description_label.config(wraplength=int(available_width * 0.9)) # or 0.9
|
91
100
|
|
92
101
|
def create_startup_screen(self):
|
93
102
|
self.clear_frame(self.inner_frame)
|
@@ -99,18 +108,30 @@ class MainApp(tk.Tk):
|
|
99
108
|
additional_buttons_frame = tk.Frame(self.inner_frame)
|
100
109
|
additional_buttons_frame.pack(pady=10)
|
101
110
|
set_dark_style(ttk.Style(), containers=[additional_buttons_frame])
|
102
|
-
|
103
|
-
description_frame = tk.Frame(self.inner_frame
|
111
|
+
|
112
|
+
description_frame = tk.Frame(self.inner_frame)
|
104
113
|
description_frame.pack(fill=tk.X, pady=10)
|
105
|
-
description_frame.
|
114
|
+
description_frame.columnconfigure(0, weight=1)
|
115
|
+
|
106
116
|
set_dark_style(ttk.Style(), containers=[description_frame])
|
117
|
+
style_out = set_dark_style(ttk.Style())
|
118
|
+
font_loader = style_out['font_loader']
|
119
|
+
font_size = style_out['font_size']
|
120
|
+
|
121
|
+
self.description_label = tk.Label( description_frame, text="", wraplength=int(self.width * 0.9), justify="center", font=font_loader.get_font(size=font_size), fg=self.color_settings['fg_color'], bg=self.color_settings['bg_color'])
|
122
|
+
|
123
|
+
# Pack it without expanding
|
124
|
+
self.description_label.pack(pady=10)
|
107
125
|
|
108
|
-
|
109
|
-
self.description_label.
|
110
|
-
|
111
|
-
|
126
|
+
# Force character width and center it
|
127
|
+
self.description_label.configure(width=int(self.width * 0.5 // 7))
|
128
|
+
self.description_label.pack_configure(anchor='center')
|
129
|
+
|
130
|
+
#logo_button = spacrButton(main_buttons_frame, text="SpaCR", command=lambda: self.load_app("logo_spacr", initiate_root), icon_name="logo_spacr", size=100, show_text=False)
|
131
|
+
logo_button = spacrButton(main_buttons_frame,text="SpaCR",command=lambda: webbrowser.open_new("https://einarolafsson.github.io/spacr/tutorial/"),icon_name="logo_spacr",size=100,show_text=False)
|
132
|
+
|
112
133
|
logo_button.grid(row=0, column=0, padx=5, pady=5)
|
113
|
-
self.main_buttons[logo_button] = "
|
134
|
+
self.main_buttons[logo_button] = "spaCR provides a flexible toolset to extract single-cell images and measurements from high-content cell painting experiments, train deep-learning models to classify cellular/subcellular phenotypes, simulate, and analyze pooled CRISPR-Cas9 imaging screens. Click to open the spaCR tutorial in your browser."
|
114
135
|
|
115
136
|
for i, (app_name, app_data) in enumerate(self.main_gui_apps.items()):
|
116
137
|
app_func, app_desc = app_data
|
@@ -125,6 +146,7 @@ class MainApp(tk.Tk):
|
|
125
146
|
self.additional_buttons[button] = app_desc
|
126
147
|
|
127
148
|
self.update_description()
|
149
|
+
self.inner_frame.bind("<Configure>", self._update_wraplength)
|
128
150
|
|
129
151
|
def update_description(self):
|
130
152
|
for button, desc in {**self.main_buttons, **self.additional_buttons}.items():
|
spacr/gui_elements.py
CHANGED
@@ -50,18 +50,18 @@ def create_menu_bar(root):
|
|
50
50
|
gui_apps = {
|
51
51
|
"Mask": lambda: initiate_root(root, settings_type='mask'),
|
52
52
|
"Measure": lambda: initiate_root(root, settings_type='measure'),
|
53
|
-
"Annotate": lambda: initiate_root(root, settings_type='annotate'),
|
54
|
-
"Make Masks": lambda: initiate_root(root, settings_type='make_masks'),
|
53
|
+
"Annotate (Beta)": lambda: initiate_root(root, settings_type='annotate'),
|
54
|
+
#"Make Masks": lambda: initiate_root(root, settings_type='make_masks'),
|
55
55
|
"Classify": lambda: initiate_root(root, settings_type='classify'),
|
56
|
-
"Umap": lambda: initiate_root(root, settings_type='umap'),
|
57
|
-
"Train Cellpose": lambda: initiate_root(root, settings_type='train_cellpose'),
|
56
|
+
#"Umap": lambda: initiate_root(root, settings_type='umap'),
|
57
|
+
#"Train Cellpose": lambda: initiate_root(root, settings_type='train_cellpose'),
|
58
58
|
"ML Analyze": lambda: initiate_root(root, settings_type='ml_analyze'),
|
59
|
-
"Cellpose Masks": lambda: initiate_root(root, settings_type='cellpose_masks'),
|
60
|
-
"Cellpose All": lambda: initiate_root(root, settings_type='cellpose_all'),
|
59
|
+
#"Cellpose Masks": lambda: initiate_root(root, settings_type='cellpose_masks'),
|
60
|
+
#"Cellpose All": lambda: initiate_root(root, settings_type='cellpose_all'),
|
61
61
|
"Map Barcodes": lambda: initiate_root(root, settings_type='map_barcodes'),
|
62
62
|
"Regression": lambda: initiate_root(root, settings_type='regression'),
|
63
63
|
"Activation": lambda: initiate_root(root, settings_type='activation'),
|
64
|
-
"Recruitment": lambda: initiate_root(root, settings_type='recruitment')
|
64
|
+
"Recruitment (Beta)": lambda: initiate_root(root, settings_type='recruitment')
|
65
65
|
}
|
66
66
|
|
67
67
|
# Create the menu bar
|
spacr/io.py
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
import os, re, sqlite3, gc, torch, time, random, shutil, cv2, tarfile, cellpose, glob, queue, tifffile, czifile, atexit, datetime
|
1
|
+
import os, re, sqlite3, gc, torch, time, random, shutil, cv2, tarfile, cellpose, glob, queue, tifffile, czifile, atexit, datetime, readlif
|
2
2
|
import numpy as np
|
3
3
|
import pandas as pd
|
4
4
|
from PIL import Image, ImageOps
|
@@ -22,7 +22,6 @@ import seaborn as sns
|
|
22
22
|
from nd2reader import ND2Reader
|
23
23
|
from torchvision import transforms
|
24
24
|
from sklearn.model_selection import train_test_split
|
25
|
-
import readlif
|
26
25
|
from pylibCZIrw import czi as pyczi
|
27
26
|
|
28
27
|
def process_non_tif_non_2D_images(folder):
|
@@ -1133,6 +1132,7 @@ def _normalize_img_batch(stack, channels, save_dtype, settings):
|
|
1133
1132
|
|
1134
1133
|
def concatenate_and_normalize(src, channels, save_dtype=np.float32, settings={}):
|
1135
1134
|
from .utils import print_progress
|
1135
|
+
from .plot import plot_arrays
|
1136
1136
|
|
1137
1137
|
"""
|
1138
1138
|
Concatenates and normalizes channel data from multiple files and saves the normalized data.
|
@@ -1155,6 +1155,8 @@ def concatenate_and_normalize(src, channels, save_dtype=np.float32, settings={})
|
|
1155
1155
|
"""
|
1156
1156
|
|
1157
1157
|
channels = [item for item in channels if item is not None]
|
1158
|
+
|
1159
|
+
print(f"Generating concatenated and normalized channel data for channels: {channels}")
|
1158
1160
|
|
1159
1161
|
paths = []
|
1160
1162
|
time_ls = []
|
@@ -1175,7 +1177,7 @@ def concatenate_and_normalize(src, channels, save_dtype=np.float32, settings={})
|
|
1175
1177
|
name = parts[0] + '_' + parts[1] + '_' + parts[2]
|
1176
1178
|
array = np.load(path)
|
1177
1179
|
stack_region.append(array)
|
1178
|
-
filenames_region.append(os.path.basename(path))
|
1180
|
+
filenames_region.append(os.path.basename(path))
|
1179
1181
|
stop = time.time()
|
1180
1182
|
duration = stop - start
|
1181
1183
|
time_ls.append(duration)
|
@@ -1193,6 +1195,10 @@ def concatenate_and_normalize(src, channels, save_dtype=np.float32, settings={})
|
|
1193
1195
|
|
1194
1196
|
save_loc = os.path.join(output_fldr, f'{name}_norm_timelapse.npz')
|
1195
1197
|
np.savez(save_loc, data=normalized_stack, filenames=filenames_region)
|
1198
|
+
|
1199
|
+
if i == 0:
|
1200
|
+
plot_arrays(save_loc, settings['figuresize'], settings['cmap'], nr=settings['nr'], normalize=False)
|
1201
|
+
|
1196
1202
|
print(save_loc)
|
1197
1203
|
del stack, normalized_stack
|
1198
1204
|
except Exception as e:
|
@@ -1250,7 +1256,11 @@ def concatenate_and_normalize(src, channels, save_dtype=np.float32, settings={})
|
|
1250
1256
|
normalized_stack = normalized_stack[..., channels]
|
1251
1257
|
|
1252
1258
|
save_loc = os.path.join(output_fldr, f'stack_{batch_index}_norm.npz')
|
1253
|
-
np.savez(save_loc, data=normalized_stack, filenames=filenames_batch)
|
1259
|
+
np.savez(save_loc, data=normalized_stack, filenames=filenames_batch)
|
1260
|
+
if batch_index == 0:
|
1261
|
+
print(f"plotting: {save_loc}")
|
1262
|
+
plot_arrays(save_loc, settings['figuresize'], settings['cmap'], nr=settings['nr'], normalize=False)
|
1263
|
+
|
1254
1264
|
batch_index += 1
|
1255
1265
|
del stack, normalized_stack
|
1256
1266
|
stack_ls = []
|
spacr/plot.py
CHANGED
@@ -760,7 +760,63 @@ def _filter_objects_in_plot(stack, cell_mask_dim, nucleus_mask_dim, pathogen_mas
|
|
760
760
|
|
761
761
|
return stack
|
762
762
|
|
763
|
+
|
763
764
|
def plot_arrays(src, figuresize=10, cmap='inferno', nr=1, normalize=True, q1=1, q2=99):
|
765
|
+
"""
|
766
|
+
Plot randomly selected arrays from a given directory or a single .npz/.npy file.
|
767
|
+
|
768
|
+
Parameters:
|
769
|
+
- src (str): The directory path or file path containing the arrays.
|
770
|
+
- figuresize (int): The size of the figure (default: 10).
|
771
|
+
- cmap (str): The colormap to use for displaying the arrays (default: 'inferno').
|
772
|
+
- nr (int): The number of arrays to plot (default: 1).
|
773
|
+
- normalize (bool): Whether to normalize the arrays (default: True).
|
774
|
+
- q1 (int): The lower percentile for normalization (default: 1).
|
775
|
+
- q2 (int): The upper percentile for normalization (default: 99).
|
776
|
+
"""
|
777
|
+
from .utils import normalize_to_dtype
|
778
|
+
|
779
|
+
mask_cmap = random_cmap()
|
780
|
+
paths = []
|
781
|
+
|
782
|
+
if src.endswith('.npz') or src.endswith('.npy'):
|
783
|
+
paths = [src]
|
784
|
+
else:
|
785
|
+
paths = [os.path.join(src, f) for f in os.listdir(src) if f.endswith(('.npy', '.npz'))]
|
786
|
+
paths = random.sample(paths, min(nr, len(paths)))
|
787
|
+
|
788
|
+
for path in paths:
|
789
|
+
print(f'Image path: {path}')
|
790
|
+
if path.endswith('.npz'):
|
791
|
+
with np.load(path) as data:
|
792
|
+
key = list(data.keys())[0] # assume first key
|
793
|
+
img = data[key][0] # get first image in batch
|
794
|
+
else:
|
795
|
+
img = np.load(path)
|
796
|
+
|
797
|
+
if normalize:
|
798
|
+
img = normalize_to_dtype(array=img, p1=q1, p2=q2)
|
799
|
+
|
800
|
+
if img.ndim == 3:
|
801
|
+
array_nr = img.shape[2]
|
802
|
+
fig, axs = plt.subplots(1, array_nr, figsize=(figuresize, figuresize))
|
803
|
+
if array_nr == 1:
|
804
|
+
axs = [axs] # ensure iterable
|
805
|
+
for channel in range(array_nr):
|
806
|
+
i = img[:, :, channel]
|
807
|
+
axs[channel].imshow(i, cmap=plt.get_cmap(cmap))
|
808
|
+
axs[channel].set_title(f'Channel {channel}', size=24)
|
809
|
+
axs[channel].axis('off')
|
810
|
+
else:
|
811
|
+
fig, ax = plt.subplots(1, 1, figsize=(figuresize, figuresize))
|
812
|
+
ax.imshow(img, cmap=plt.get_cmap(cmap))
|
813
|
+
ax.set_title('Channel 0', size=24)
|
814
|
+
ax.axis('off')
|
815
|
+
|
816
|
+
fig.tight_layout()
|
817
|
+
plt.show()
|
818
|
+
|
819
|
+
def plot_arrays_v1(src, figuresize=10, cmap='inferno', nr=1, normalize=True, q1=1, q2=99):
|
764
820
|
"""
|
765
821
|
Plot randomly selected arrays from a given directory.
|
766
822
|
|
@@ -780,6 +836,7 @@ def plot_arrays(src, figuresize=10, cmap='inferno', nr=1, normalize=True, q1=1,
|
|
780
836
|
|
781
837
|
mask_cmap = random_cmap()
|
782
838
|
paths = []
|
839
|
+
|
783
840
|
for file in os.listdir(src):
|
784
841
|
if file.endswith('.npy'):
|
785
842
|
path = os.path.join(src, file)
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|