spacr 0.9.0__py3-none-any.whl → 0.9.2__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/gui_core.py +0 -75
- spacr/measure.py +219 -7
- spacr/plot.py +0 -122
- spacr/resources/icons/flow_chart_v2.png +0 -0
- spacr/settings.py +6 -30
- spacr/timelapse.py +0 -90
- spacr/utils.py +10 -49
- {spacr-0.9.0.dist-info → spacr-0.9.2.dist-info}/METADATA +68 -19
- {spacr-0.9.0.dist-info → spacr-0.9.2.dist-info}/RECORD +13 -12
- {spacr-0.9.0.dist-info → spacr-0.9.2.dist-info}/LICENSE +0 -0
- {spacr-0.9.0.dist-info → spacr-0.9.2.dist-info}/WHEEL +0 -0
- {spacr-0.9.0.dist-info → spacr-0.9.2.dist-info}/entry_points.txt +0 -0
- {spacr-0.9.0.dist-info → spacr-0.9.2.dist-info}/top_level.txt +0 -0
spacr/gui_core.py
CHANGED
@@ -1083,81 +1083,6 @@ def process_console_queue():
|
|
1083
1083
|
# **Continue processing if no error was detected**
|
1084
1084
|
after_id = console_output.after(uppdate_frequency, process_console_queue)
|
1085
1085
|
parent_frame.after_tasks.append(after_id)
|
1086
|
-
|
1087
|
-
def process_console_queue_v2():
|
1088
|
-
global q, console_output, parent_frame, progress_bar, process_console_queue
|
1089
|
-
|
1090
|
-
# Initialize function attribute if it doesn't exist
|
1091
|
-
if not hasattr(process_console_queue, "completed_tasks"):
|
1092
|
-
process_console_queue.completed_tasks = []
|
1093
|
-
if not hasattr(process_console_queue, "current_maximum"):
|
1094
|
-
process_console_queue.current_maximum = None
|
1095
|
-
|
1096
|
-
ansi_escape_pattern = re.compile(r'\x1B\[[0-?]*[ -/]*[@-~]')
|
1097
|
-
|
1098
|
-
while not q.empty():
|
1099
|
-
message = q.get_nowait()
|
1100
|
-
clean_message = ansi_escape_pattern.sub('', message)
|
1101
|
-
|
1102
|
-
# **Abort Execution if an Error Message is Detected**
|
1103
|
-
if clean_message.startswith("Error:"):
|
1104
|
-
console_output.insert(tk.END, clean_message + "\n", "error")
|
1105
|
-
console_output.see(tk.END)
|
1106
|
-
print("Run aborted due to error:", clean_message) # Debug message
|
1107
|
-
return # **Exit immediately to stop further execution**
|
1108
|
-
|
1109
|
-
# Check if the message contains progress information
|
1110
|
-
if clean_message.startswith("Progress:"):
|
1111
|
-
try:
|
1112
|
-
# Extract the progress information
|
1113
|
-
match = re.search(r'Progress: (\d+)/(\d+), operation_type: ([\w\s]*),(.*)', clean_message)
|
1114
|
-
|
1115
|
-
if match:
|
1116
|
-
current_progress = int(match.group(1))
|
1117
|
-
total_progress = int(match.group(2))
|
1118
|
-
operation_type = match.group(3).strip()
|
1119
|
-
additional_info = match.group(4).strip() # Capture everything after operation_type
|
1120
|
-
|
1121
|
-
# Check if the maximum value has changed
|
1122
|
-
if process_console_queue.current_maximum != total_progress:
|
1123
|
-
process_console_queue.current_maximum = total_progress
|
1124
|
-
process_console_queue.completed_tasks = []
|
1125
|
-
|
1126
|
-
# Add the task to the completed set
|
1127
|
-
process_console_queue.completed_tasks.append(current_progress)
|
1128
|
-
|
1129
|
-
# Calculate the unique progress count
|
1130
|
-
unique_progress_count = len(np.unique(process_console_queue.completed_tasks))
|
1131
|
-
|
1132
|
-
# Update the progress bar
|
1133
|
-
if progress_bar:
|
1134
|
-
progress_bar['maximum'] = total_progress
|
1135
|
-
progress_bar['value'] = unique_progress_count
|
1136
|
-
|
1137
|
-
# Store operation type and additional info
|
1138
|
-
if operation_type:
|
1139
|
-
progress_bar.operation_type = operation_type
|
1140
|
-
progress_bar.additional_info = additional_info
|
1141
|
-
|
1142
|
-
# Update the progress label
|
1143
|
-
if progress_bar.progress_label:
|
1144
|
-
progress_bar.update_label()
|
1145
|
-
|
1146
|
-
# Clear completed tasks when progress is complete
|
1147
|
-
if unique_progress_count >= total_progress:
|
1148
|
-
process_console_queue.completed_tasks.clear()
|
1149
|
-
|
1150
|
-
except Exception as e:
|
1151
|
-
print(f"Error parsing progress message: {e}")
|
1152
|
-
|
1153
|
-
else:
|
1154
|
-
# Insert non-progress messages into the console
|
1155
|
-
console_output.insert(tk.END, clean_message + "\n")
|
1156
|
-
console_output.see(tk.END)
|
1157
|
-
|
1158
|
-
# **Continue processing if no error was detected**
|
1159
|
-
after_id = console_output.after(uppdate_frequency, process_console_queue)
|
1160
|
-
parent_frame.after_tasks.append(after_id)
|
1161
1086
|
|
1162
1087
|
def main_thread_update_function(root, q, fig_queue, canvas_widget):
|
1163
1088
|
global uppdate_frequency
|
spacr/measure.py
CHANGED
@@ -2,12 +2,11 @@ import os, cv2, time, sqlite3, traceback, shutil
|
|
2
2
|
import numpy as np
|
3
3
|
import pandas as pd
|
4
4
|
from collections import defaultdict
|
5
|
-
from scipy.stats import pearsonr
|
5
|
+
from scipy.stats import pearsonr, skew, kurtosis, mode
|
6
6
|
import multiprocessing as mp
|
7
|
-
from scipy.ndimage import distance_transform_edt, generate_binary_structure
|
7
|
+
from scipy.ndimage import distance_transform_edt, generate_binary_structure, binary_dilation, gaussian_filter, center_of_mass
|
8
8
|
from skimage.measure import regionprops, regionprops_table, shannon_entropy
|
9
9
|
from skimage.exposure import rescale_intensity
|
10
|
-
from scipy.ndimage import binary_dilation
|
11
10
|
from skimage.segmentation import find_boundaries
|
12
11
|
from skimage.feature import graycomatrix, graycoprops
|
13
12
|
from mahotas.features import zernike_moments
|
@@ -16,7 +15,6 @@ from skimage.util import img_as_bool
|
|
16
15
|
import matplotlib.pyplot as plt
|
17
16
|
from math import ceil, sqrt
|
18
17
|
|
19
|
-
|
20
18
|
def get_components(cell_mask, nucleus_mask, pathogen_mask):
|
21
19
|
"""
|
22
20
|
Get the components (nucleus and pathogens) for each cell in the given masks.
|
@@ -250,6 +248,148 @@ def _create_dataframe(radial_distributions, object_type):
|
|
250
248
|
return df
|
251
249
|
|
252
250
|
def _extended_regionprops_table(labels, image, intensity_props):
|
251
|
+
"""
|
252
|
+
Calculate extended region properties table, adding a suite of advanced quantitative features.
|
253
|
+
"""
|
254
|
+
|
255
|
+
def _gini(array):
|
256
|
+
# Compute Gini coefficient (nan safe)
|
257
|
+
array = np.abs(array[~np.isnan(array)])
|
258
|
+
n = array.size
|
259
|
+
if n == 0:
|
260
|
+
return np.nan
|
261
|
+
array = np.sort(array)
|
262
|
+
index = np.arange(1, n + 1)
|
263
|
+
return (np.sum((2 * index - n - 1) * array)) / (n * np.sum(array)) if np.sum(array) else np.nan
|
264
|
+
|
265
|
+
props = regionprops_table(labels, image, properties=intensity_props)
|
266
|
+
df = pd.DataFrame(props)
|
267
|
+
|
268
|
+
regions = regionprops(labels, intensity_image=image)
|
269
|
+
integrated_intensity = []
|
270
|
+
std_intensity = []
|
271
|
+
median_intensity = []
|
272
|
+
skew_intensity = []
|
273
|
+
kurtosis_intensity = []
|
274
|
+
mode_intensity = []
|
275
|
+
range_intensity = []
|
276
|
+
iqr_intensity = []
|
277
|
+
cv_intensity = []
|
278
|
+
gini_intensity = []
|
279
|
+
frac_high90 = []
|
280
|
+
frac_low10 = []
|
281
|
+
entropy_intensity = []
|
282
|
+
|
283
|
+
for region in regions:
|
284
|
+
intens = region.intensity_image[region.image]
|
285
|
+
intens = intens[~np.isnan(intens)]
|
286
|
+
if intens.size == 0:
|
287
|
+
integrated_intensity.append(np.nan)
|
288
|
+
std_intensity.append(np.nan)
|
289
|
+
median_intensity.append(np.nan)
|
290
|
+
skew_intensity.append(np.nan)
|
291
|
+
kurtosis_intensity.append(np.nan)
|
292
|
+
mode_intensity.append(np.nan)
|
293
|
+
range_intensity.append(np.nan)
|
294
|
+
iqr_intensity.append(np.nan)
|
295
|
+
cv_intensity.append(np.nan)
|
296
|
+
gini_intensity.append(np.nan)
|
297
|
+
frac_high90.append(np.nan)
|
298
|
+
frac_low10.append(np.nan)
|
299
|
+
entropy_intensity.append(np.nan)
|
300
|
+
else:
|
301
|
+
integrated_intensity.append(np.sum(intens))
|
302
|
+
std_intensity.append(np.std(intens))
|
303
|
+
median_intensity.append(np.median(intens))
|
304
|
+
skew_intensity.append(skew(intens) if intens.size > 2 else np.nan)
|
305
|
+
kurtosis_intensity.append(kurtosis(intens) if intens.size > 3 else np.nan)
|
306
|
+
# Mode (use first mode value if multimodal)
|
307
|
+
try:
|
308
|
+
mode_val = mode(intens, nan_policy='omit').mode
|
309
|
+
mode_intensity.append(mode_val[0] if len(mode_val) > 0 else np.nan)
|
310
|
+
except Exception:
|
311
|
+
mode_intensity.append(np.nan)
|
312
|
+
range_intensity.append(np.ptp(intens))
|
313
|
+
iqr_intensity.append(np.percentile(intens, 75) - np.percentile(intens, 25))
|
314
|
+
cv_intensity.append(np.std(intens) / np.mean(intens) if np.mean(intens) != 0 else np.nan)
|
315
|
+
gini_intensity.append(_gini(intens))
|
316
|
+
frac_high90.append(np.mean(intens > np.percentile(intens, 90)))
|
317
|
+
frac_low10.append(np.mean(intens < np.percentile(intens, 10)))
|
318
|
+
entropy_intensity.append(shannon_entropy(intens) if intens.size > 1 else 0.0)
|
319
|
+
|
320
|
+
df['integrated_intensity'] = integrated_intensity
|
321
|
+
df['std_intensity'] = std_intensity
|
322
|
+
df['median_intensity'] = median_intensity
|
323
|
+
df['skew_intensity'] = skew_intensity
|
324
|
+
df['kurtosis_intensity'] = kurtosis_intensity
|
325
|
+
df['mode_intensity'] = mode_intensity
|
326
|
+
df['range_intensity'] = range_intensity
|
327
|
+
df['iqr_intensity'] = iqr_intensity
|
328
|
+
df['cv_intensity'] = cv_intensity
|
329
|
+
df['gini_intensity'] = gini_intensity
|
330
|
+
df['frac_high90'] = frac_high90
|
331
|
+
df['frac_low10'] = frac_low10
|
332
|
+
df['entropy_intensity'] = entropy_intensity
|
333
|
+
|
334
|
+
percentiles = [5, 10, 25, 50, 75, 85, 95]
|
335
|
+
for p in percentiles:
|
336
|
+
df[f'percentile_{p}'] = [
|
337
|
+
np.percentile(region.intensity_image[region.image], p)
|
338
|
+
for region in regions
|
339
|
+
]
|
340
|
+
return df
|
341
|
+
|
342
|
+
def _extended_regionprops_table_v2(labels, image, intensity_props):
|
343
|
+
"""
|
344
|
+
Calculate extended region properties table, adding integrated intensity,
|
345
|
+
skewness, kurtosis, std, and median intensity per region.
|
346
|
+
"""
|
347
|
+
# regionprops_table gives you vectorized props, but not everything you want
|
348
|
+
props = regionprops_table(labels, image, properties=intensity_props)
|
349
|
+
df = pd.DataFrame(props)
|
350
|
+
|
351
|
+
# Compute extra features region-by-region
|
352
|
+
regions = regionprops(labels, intensity_image=image)
|
353
|
+
integrated_intensity = []
|
354
|
+
std_intensity = []
|
355
|
+
median_intensity = []
|
356
|
+
skew_intensity = []
|
357
|
+
kurtosis_intensity = []
|
358
|
+
for region in regions:
|
359
|
+
intens = region.intensity_image[region.image]
|
360
|
+
# Handle empty region edge-case (shouldn't happen)
|
361
|
+
if intens.size == 0:
|
362
|
+
integrated_intensity.append(np.nan)
|
363
|
+
std_intensity.append(np.nan)
|
364
|
+
median_intensity.append(np.nan)
|
365
|
+
skew_intensity.append(np.nan)
|
366
|
+
kurtosis_intensity.append(np.nan)
|
367
|
+
else:
|
368
|
+
integrated_intensity.append(np.sum(intens))
|
369
|
+
std_intensity.append(np.std(intens))
|
370
|
+
median_intensity.append(np.median(intens))
|
371
|
+
# Only valid for >2 pixels
|
372
|
+
skew_intensity.append(skew(intens) if intens.size > 2 else np.nan)
|
373
|
+
kurtosis_intensity.append(kurtosis(intens) if intens.size > 3 else np.nan)
|
374
|
+
|
375
|
+
df['integrated_intensity'] = integrated_intensity
|
376
|
+
df['std_intensity'] = std_intensity
|
377
|
+
df['median_intensity'] = median_intensity
|
378
|
+
df['skew_intensity'] = skew_intensity
|
379
|
+
df['kurtosis_intensity'] = kurtosis_intensity
|
380
|
+
|
381
|
+
# You can add other features here if desired
|
382
|
+
|
383
|
+
# Percentiles (your existing code—optional if you want to keep)
|
384
|
+
percentiles = [5, 10, 25, 50, 75, 85, 95]
|
385
|
+
for p in percentiles:
|
386
|
+
df[f'percentile_{p}'] = [
|
387
|
+
np.percentile(region.intensity_image[region.image], p)
|
388
|
+
for region in regions
|
389
|
+
]
|
390
|
+
return df
|
391
|
+
|
392
|
+
def _extended_regionprops_table_v1(labels, image, intensity_props):
|
253
393
|
"""
|
254
394
|
Calculate extended region properties table.
|
255
395
|
|
@@ -495,8 +635,75 @@ def _estimate_blur(image):
|
|
495
635
|
# Compute and return the variance of the Laplacian
|
496
636
|
return lap.var()
|
497
637
|
|
638
|
+
def _measure_intensity_distance(cell_mask, nucleus_mask, pathogen_mask, channel_arrays, settings):
|
639
|
+
"""
|
640
|
+
Compute Gaussian-smoothed intensity-weighted centroid distances for each cell object.
|
641
|
+
"""
|
642
|
+
|
643
|
+
sigma = settings.get('distance_gaussian_sigma', 1.0)
|
644
|
+
cell_labels = np.unique(cell_mask)
|
645
|
+
cell_labels = cell_labels[cell_labels > 0]
|
646
|
+
|
647
|
+
dfs = []
|
648
|
+
nucleus_dt = distance_transform_edt(nucleus_mask == 0)
|
649
|
+
pathogen_dt = distance_transform_edt(pathogen_mask == 0)
|
650
|
+
|
651
|
+
for ch in range(channel_arrays.shape[-1]):
|
652
|
+
channel_img = channel_arrays[:, :, ch]
|
653
|
+
blurred_img = gaussian_filter(channel_img, sigma=sigma)
|
654
|
+
|
655
|
+
data = []
|
656
|
+
for label in cell_labels:
|
657
|
+
cell_coords = np.argwhere(cell_mask == label)
|
658
|
+
if cell_coords.size == 0:
|
659
|
+
data.append([label, np.nan, np.nan])
|
660
|
+
continue
|
661
|
+
|
662
|
+
minr, minc = np.min(cell_coords, axis=0)
|
663
|
+
maxr, maxc = np.max(cell_coords, axis=0) + 1
|
664
|
+
|
665
|
+
cell_submask = (cell_mask[minr:maxr, minc:maxc] == label)
|
666
|
+
blurred_subimg = blurred_img[minr:maxr, minc:maxc]
|
667
|
+
|
668
|
+
if np.sum(cell_submask) == 0:
|
669
|
+
data.append([label, np.nan, np.nan])
|
670
|
+
continue
|
671
|
+
|
672
|
+
masked_intensity = blurred_subimg * cell_submask
|
673
|
+
com_local = center_of_mass(masked_intensity)
|
674
|
+
if np.isnan(com_local[0]):
|
675
|
+
data.append([label, np.nan, np.nan])
|
676
|
+
continue
|
677
|
+
|
678
|
+
com_global = (com_local[0] + minr, com_local[1] + minc)
|
679
|
+
com_global_int = tuple(np.round(com_global).astype(int))
|
680
|
+
|
681
|
+
x, y = com_global_int
|
682
|
+
if not (0 <= x < cell_mask.shape[0] and 0 <= y < cell_mask.shape[1]):
|
683
|
+
data.append([label, np.nan, np.nan])
|
684
|
+
continue
|
685
|
+
|
686
|
+
nucleus_dist = nucleus_dt[x, y]
|
687
|
+
pathogen_dist = pathogen_dt[x, y]
|
688
|
+
|
689
|
+
data.append([label, nucleus_dist, pathogen_dist])
|
690
|
+
|
691
|
+
df = pd.DataFrame(data, columns=['label',
|
692
|
+
f'cell_channel_{ch}_distance_to_nucleus',
|
693
|
+
f'cell_channel_{ch}_distance_to_pathogen'])
|
694
|
+
dfs.append(df)
|
695
|
+
|
696
|
+
# Merge all channel dataframes on label
|
697
|
+
merged_df = dfs[0]
|
698
|
+
for df in dfs[1:]:
|
699
|
+
merged_df = merged_df.merge(df, on='label', how='outer')
|
700
|
+
|
701
|
+
return merged_df
|
702
|
+
|
703
|
+
|
498
704
|
#@log_function_call
|
499
705
|
def _intensity_measurements(cell_mask, nucleus_mask, pathogen_mask, cytoplasm_mask, channel_arrays, settings, sizes=[3, 6, 12, 24], periphery=True, outside=True):
|
706
|
+
|
500
707
|
"""
|
501
708
|
Calculate various intensity measurements for different regions in the image.
|
502
709
|
|
@@ -536,8 +743,8 @@ def _intensity_measurements(cell_mask, nucleus_mask, pathogen_mask, cytoplasm_ma
|
|
536
743
|
df.append(empty_df)
|
537
744
|
continue
|
538
745
|
|
539
|
-
mask_intensity_df = _extended_regionprops_table(label, channel, intensity_props)
|
540
|
-
mask_intensity_df['shannon_entropy'] = shannon_entropy(channel, base=2)
|
746
|
+
mask_intensity_df = _extended_regionprops_table(label, channel, intensity_props)
|
747
|
+
#mask_intensity_df['shannon_entropy'] = shannon_entropy(channel, base=2)
|
541
748
|
|
542
749
|
if homogeneity:
|
543
750
|
homogeneity_df = _calculate_homogeneity(label, channel, distances)
|
@@ -558,6 +765,10 @@ def _intensity_measurements(cell_mask, nucleus_mask, pathogen_mask, cytoplasm_ma
|
|
558
765
|
|
559
766
|
mask_intensity_df.columns = [f'{ls[j]}_channel_{i}_{col}' if col != 'label' else col for col in mask_intensity_df.columns]
|
560
767
|
df.append(mask_intensity_df)
|
768
|
+
|
769
|
+
if isinstance(settings['distance_gaussian_sigma'], int):
|
770
|
+
intensity_distance_df = _measure_intensity_distance(cell_mask, nucleus_mask, pathogen_mask, channel_arrays, settings)
|
771
|
+
cell_dfs.append(intensity_distance_df)
|
561
772
|
|
562
773
|
if radial_dist:
|
563
774
|
if np.max(nucleus_mask) != 0:
|
@@ -565,7 +776,7 @@ def _intensity_measurements(cell_mask, nucleus_mask, pathogen_mask, cytoplasm_ma
|
|
565
776
|
nucleus_df = _create_dataframe(nucleus_radial_distributions, 'nucleus')
|
566
777
|
dfs[1].append(nucleus_df)
|
567
778
|
|
568
|
-
if np.max(
|
779
|
+
if np.max(pathogen_mask) != 0:
|
569
780
|
pathogen_radial_distributions = _calculate_radial_distribution(cell_mask, pathogen_mask, channel_arrays, num_bins=6)
|
570
781
|
pathogen_df = _create_dataframe(pathogen_radial_distributions, 'pathogen')
|
571
782
|
dfs[2].append(pathogen_df)
|
@@ -785,6 +996,7 @@ def _measure_crop_core(index, time_ls, file, settings):
|
|
785
996
|
#merge skeleton_df with cell_df here
|
786
997
|
|
787
998
|
cell_intensity_df, nucleus_intensity_df, pathogen_intensity_df, cytoplasm_intensity_df = _intensity_measurements(cell_mask, nucleus_mask, pathogen_mask, cytoplasm_mask, channel_arrays, settings, sizes=[1, 2, 3, 4, 5], periphery=True, outside=True)
|
999
|
+
|
788
1000
|
if settings['cell_mask_dim'] is not None:
|
789
1001
|
cell_merged_df = _merge_and_save_to_database(cell_df, cell_intensity_df, 'cell', source_folder, file_name, settings['experiment'], settings['timelapse'])
|
790
1002
|
if settings['nucleus_mask_dim'] is not None:
|
spacr/plot.py
CHANGED
@@ -1241,103 +1241,6 @@ def _display_gif(path):
|
|
1241
1241
|
"""
|
1242
1242
|
with open(path, 'rb') as file:
|
1243
1243
|
display(ipyimage(file.read()))
|
1244
|
-
|
1245
|
-
def _plot_recruitment_v2(df, df_type, channel_of_interest, columns=[], figuresize=10):
|
1246
|
-
"""
|
1247
|
-
Plot recruitment data for different conditions and pathogens.
|
1248
|
-
|
1249
|
-
Args:
|
1250
|
-
df (DataFrame): The input DataFrame containing the recruitment data.
|
1251
|
-
df_type (str): The type of DataFrame (e.g., 'train', 'test').
|
1252
|
-
channel_of_interest (str): The channel of interest for plotting.
|
1253
|
-
target (str): The target variable for plotting.
|
1254
|
-
columns (list, optional): Additional columns to plot. Defaults to an empty list.
|
1255
|
-
figuresize (int, optional): The size of the figure. Defaults to 50.
|
1256
|
-
|
1257
|
-
Returns:
|
1258
|
-
None
|
1259
|
-
"""
|
1260
|
-
|
1261
|
-
color_list = [(55/255, 155/255, 155/255),
|
1262
|
-
(155/255, 55/255, 155/255),
|
1263
|
-
(55/255, 155/255, 255/255),
|
1264
|
-
(255/255, 55/255, 155/255)]
|
1265
|
-
|
1266
|
-
sns.set_palette(sns.color_palette(color_list))
|
1267
|
-
font = figuresize/2
|
1268
|
-
width=figuresize
|
1269
|
-
height=figuresize/4
|
1270
|
-
|
1271
|
-
# Create the subplots
|
1272
|
-
fig, axes = plt.subplots(nrows=1, ncols=4, figsize=(width, height))
|
1273
|
-
|
1274
|
-
# Plot for 'cell_channel' on axes[0]
|
1275
|
-
plotter_cell = spacrGraph(df,grouping_column='condition', data_column=f'cell_channel_{channel_of_interest}_mean_intensity')
|
1276
|
-
plotter_cell.create_plot(ax=axes[0])
|
1277
|
-
axes[0].set_xlabel(f'pathogen {df_type}', fontsize=font)
|
1278
|
-
axes[0].set_ylabel(f'cell_channel_{channel_of_interest}_mean_intensity', fontsize=font)
|
1279
|
-
|
1280
|
-
# Plot for 'nucleus_channel' on axes[1]
|
1281
|
-
plotter_nucleus = spacrGraph(df,grouping_column='condition', data_column=f'nucleus_channel_{channel_of_interest}_mean_intensity')
|
1282
|
-
plotter_nucleus.create_plot(ax=axes[1])
|
1283
|
-
axes[1].set_xlabel(f'pathogen {df_type}', fontsize=font)
|
1284
|
-
axes[1].set_ylabel(f'nucleus_channel_{channel_of_interest}_mean_intensity', fontsize=font)
|
1285
|
-
|
1286
|
-
# Plot for 'cytoplasm_channel' on axes[2]
|
1287
|
-
plotter_cytoplasm = spacrGraph(df, grouping_column='condition', data_column=f'cytoplasm_channel_{channel_of_interest}_mean_intensity')
|
1288
|
-
plotter_cytoplasm.create_plot(ax=axes[2])
|
1289
|
-
axes[2].set_xlabel(f'pathogen {df_type}', fontsize=font)
|
1290
|
-
axes[2].set_ylabel(f'cytoplasm_channel_{channel_of_interest}_mean_intensity', fontsize=font)
|
1291
|
-
|
1292
|
-
# Plot for 'pathogen_channel' on axes[3]
|
1293
|
-
plotter_pathogen = spacrGraph(df, grouping_column='condition', data_column=f'pathogen_channel_{channel_of_interest}_mean_intensity')
|
1294
|
-
plotter_pathogen.create_plot(ax=axes[3])
|
1295
|
-
axes[3].set_xlabel(f'pathogen {df_type}', fontsize=font)
|
1296
|
-
axes[3].set_ylabel(f'pathogen_channel_{channel_of_interest}_mean_intensity', fontsize=font)
|
1297
|
-
|
1298
|
-
#axes[0].legend_.remove()
|
1299
|
-
#axes[1].legend_.remove()
|
1300
|
-
#axes[2].legend_.remove()
|
1301
|
-
#axes[3].legend_.remove()
|
1302
|
-
|
1303
|
-
handles, labels = axes[3].get_legend_handles_labels()
|
1304
|
-
axes[3].legend(handles, labels, bbox_to_anchor=(1.05, 0.5), loc='center left')
|
1305
|
-
for i in [0,1,2,3]:
|
1306
|
-
axes[i].tick_params(axis='both', which='major', labelsize=font)
|
1307
|
-
axes[i].set_xticklabels(axes[i].get_xticklabels(), rotation=45)
|
1308
|
-
|
1309
|
-
plt.tight_layout()
|
1310
|
-
plt.show()
|
1311
|
-
|
1312
|
-
columns = columns + ['pathogen_cytoplasm_mean_mean', 'pathogen_cytoplasm_q75_mean', 'pathogen_periphery_cytoplasm_mean_mean', 'pathogen_outside_cytoplasm_mean_mean', 'pathogen_outside_cytoplasm_q75_mean']
|
1313
|
-
#columns = columns + [f'pathogen_slope_channel_{channel_of_interest}', f'pathogen_cell_distance_channel_{channel_of_interest}', f'nucleus_cell_distance_channel_{channel_of_interest}']
|
1314
|
-
|
1315
|
-
width = figuresize*2
|
1316
|
-
columns_per_row = math.ceil(len(columns) / 2)
|
1317
|
-
height = (figuresize*2)/columns_per_row
|
1318
|
-
|
1319
|
-
fig, axes = plt.subplots(nrows=2, ncols=columns_per_row, figsize=(width, height * 2))
|
1320
|
-
axes = axes.flatten()
|
1321
|
-
|
1322
|
-
print(f'{columns}')
|
1323
|
-
for i, col in enumerate(columns):
|
1324
|
-
ax = axes[i]
|
1325
|
-
plotter_col = spacrGraph(df, grouping_column='condition', data_column=col)
|
1326
|
-
plotter_col.create_plot(ax=ax)
|
1327
|
-
ax.set_xlabel(f'pathogen {df_type}', fontsize=font)
|
1328
|
-
ax.set_ylabel(f'{col}', fontsize=int(font * 2))
|
1329
|
-
#ax.legend_.remove()
|
1330
|
-
ax.tick_params(axis='both', which='major', labelsize=font)
|
1331
|
-
ax.set_xticklabels(ax.get_xticklabels(), rotation=45)
|
1332
|
-
if i <= 5:
|
1333
|
-
ax.set_ylim(1, None)
|
1334
|
-
|
1335
|
-
# Turn off any unused axes
|
1336
|
-
for i in range(len(columns), len(axes)):
|
1337
|
-
axes[i].axis('off')
|
1338
|
-
|
1339
|
-
plt.tight_layout()
|
1340
|
-
plt.show()
|
1341
1244
|
|
1342
1245
|
def _plot_recruitment(df, df_type, channel_of_interest, columns=[], figuresize=10):
|
1343
1246
|
"""
|
@@ -1843,31 +1746,6 @@ def print_mask_and_flows(stack, mask, flows, overlay=True, max_size=1000, thickn
|
|
1843
1746
|
fig.tight_layout()
|
1844
1747
|
plt.show()
|
1845
1748
|
|
1846
|
-
def plot_resize_v1(images, resized_images, labels, resized_labels):
|
1847
|
-
# Display an example image and label before and after resizing
|
1848
|
-
fig, ax = plt.subplots(2, 2, figsize=(20, 20))
|
1849
|
-
|
1850
|
-
# Check if the image is grayscale; if so, add a colormap and keep dimensions correct
|
1851
|
-
if images[0].ndim == 2: # Grayscale image
|
1852
|
-
ax[0, 0].imshow(images[0], cmap='gray')
|
1853
|
-
else: # RGB or RGBA image
|
1854
|
-
ax[0, 0].imshow(images[0])
|
1855
|
-
ax[0, 0].set_title('Original Image')
|
1856
|
-
|
1857
|
-
if resized_images[0].ndim == 2: # Grayscale image
|
1858
|
-
ax[0, 1].imshow(resized_images[0], cmap='gray')
|
1859
|
-
else: # RGB or RGBA image
|
1860
|
-
ax[0, 1].imshow(resized_images[0])
|
1861
|
-
ax[0, 1].set_title('Resized Image')
|
1862
|
-
|
1863
|
-
# Assuming labels are always grayscale (most common scenario)
|
1864
|
-
ax[1, 0].imshow(labels[0], cmap='gray')
|
1865
|
-
ax[1, 0].set_title('Original Label')
|
1866
|
-
ax[1, 1].imshow(resized_labels[0], cmap='gray')
|
1867
|
-
ax[1, 1].set_title('Resized Label')
|
1868
|
-
plt.show()
|
1869
|
-
|
1870
|
-
|
1871
1749
|
def plot_resize(images, resized_images, labels, resized_labels):
|
1872
1750
|
def prepare_image(img):
|
1873
1751
|
if img.ndim == 2:
|
Binary file
|
spacr/settings.py
CHANGED
@@ -124,33 +124,6 @@ def set_default_plot_data_from_db(settings):
|
|
124
124
|
settings.setdefault('uninfected', False)
|
125
125
|
return settings
|
126
126
|
|
127
|
-
|
128
|
-
|
129
|
-
def set_default_settings_preprocess_img_data_v1(settings):
|
130
|
-
|
131
|
-
metadata_type = settings.setdefault('metadata_type', 'cellvoyager')
|
132
|
-
custom_regex = settings.setdefault('custom_regex', None)
|
133
|
-
nr = settings.setdefault('nr', 1)
|
134
|
-
plot = settings.setdefault('plot', True)
|
135
|
-
batch_size = settings.setdefault('batch_size', 50)
|
136
|
-
timelapse = settings.setdefault('timelapse', False)
|
137
|
-
lower_percentile = settings.setdefault('lower_percentile', 2)
|
138
|
-
randomize = settings.setdefault('randomize', True)
|
139
|
-
all_to_mip = settings.setdefault('all_to_mip', False)
|
140
|
-
pick_slice = settings.setdefault('pick_slice', False)
|
141
|
-
skip_mode = settings.setdefault('skip_mode', False)
|
142
|
-
|
143
|
-
cmap = settings.setdefault('cmap', 'inferno')
|
144
|
-
figuresize = settings.setdefault('figuresize', 10)
|
145
|
-
normalize = settings.setdefault('normalize', True)
|
146
|
-
save_dtype = settings.setdefault('save_dtype', 'uint16')
|
147
|
-
|
148
|
-
test_mode = settings.setdefault('test_mode', False)
|
149
|
-
test_images = settings.setdefault('test_images', 10)
|
150
|
-
random_test = settings.setdefault('random_test', True)
|
151
|
-
|
152
|
-
return settings, metadata_type, custom_regex, nr, plot, batch_size, timelapse, lower_percentile, randomize, all_to_mip, pick_slice, skip_mode, cmap, figuresize, normalize, save_dtype, test_mode, test_images, random_test
|
153
|
-
|
154
127
|
def set_default_settings_preprocess_img_data(settings):
|
155
128
|
|
156
129
|
settings.setdefault('metadata_type', 'cellvoyager')
|
@@ -340,7 +313,9 @@ def get_measure_crop_settings(settings={}):
|
|
340
313
|
settings.setdefault('pathogen_min_size',0)
|
341
314
|
settings.setdefault('cytoplasm_min_size',0)
|
342
315
|
settings.setdefault('merge_edge_pathogen_cells', True)
|
343
|
-
|
316
|
+
|
317
|
+
settings.setdefault('distance_gaussian_sigma', 1)
|
318
|
+
|
344
319
|
if settings['test_mode']:
|
345
320
|
settings['verbose'] = True
|
346
321
|
settings['plot'] = True
|
@@ -1027,7 +1002,8 @@ expected_types = {
|
|
1027
1002
|
"cell_diamiter":int,
|
1028
1003
|
"nucleus_diamiter":int,
|
1029
1004
|
"pathogen_diamiter":int,
|
1030
|
-
"consolidate":bool
|
1005
|
+
"consolidate":bool,
|
1006
|
+
"distance_gaussian_sigma":int
|
1031
1007
|
}
|
1032
1008
|
|
1033
1009
|
categories = {"Paths":[ "src", "grna", "barcodes", "custom_model_path", "dataset","model_path","grna_csv","row_csv","column_csv", "metadata_files", "score_data","count_data"],
|
@@ -1049,7 +1025,7 @@ categories = {"Paths":[ "src", "grna", "barcodes", "custom_model_path", "dataset
|
|
1049
1025
|
"Plot": ["split_axis_lims", "x_lim","log_x","log_y", "plot_control", "plot_nr", "examples_to_plot", "normalize_plots", "cmap", "figuresize", "plot_cluster_grids", "img_zoom", "row_limit", "color_by", "plot_images", "smooth_lines", "plot_points", "plot_outlines", "black_background", "plot_by_cluster", "heatmap_feature","grouping","min_max","cmap","save_figure"],
|
1050
1026
|
"Timelapse": ["timelapse", "fps", "timelapse_displacement", "timelapse_memory", "timelapse_frame_limits", "timelapse_remove_transient", "timelapse_mode", "timelapse_objects", "compartments"],
|
1051
1027
|
"Advanced": ["merge_edge_pathogen_cells", "test_images", "random_test", "test_nr", "test", "test_split", "normalize", "target_unique_count","threshold_multiplier", "threshold_method", "min_n","shuffle", "target_intensity_min", "cells_per_well", "nuclei_limit", "pathogen_limit", "background", "backgrounds", "schedule", "test_size","exclude","n_repeats","top_features", "model_type_ml", "model_type","minimum_cell_count","n_estimators","preprocess", "remove_background", "normalize", "lower_percentile", "merge_pathogens", "batch_size", "filter", "save", "masks", "verbose", "randomize", "n_jobs"],
|
1052
|
-
"Beta": ["all_to_mip", "upscale", "upscale_factor", "consolidate"]
|
1028
|
+
"Beta": ["all_to_mip", "upscale", "upscale_factor", "consolidate", "distance_gaussian_sigma"]
|
1053
1029
|
}
|
1054
1030
|
|
1055
1031
|
|
spacr/timelapse.py
CHANGED
@@ -255,56 +255,6 @@ def _relabel_masks_based_on_tracks(masks, tracks, mode='btrack'):
|
|
255
255
|
|
256
256
|
return relabeled_masks
|
257
257
|
|
258
|
-
def _prepare_for_tracking_v1(mask_array):
|
259
|
-
"""
|
260
|
-
Prepare the mask array for object tracking.
|
261
|
-
|
262
|
-
Args:
|
263
|
-
mask_array (ndarray): Array of binary masks representing objects.
|
264
|
-
|
265
|
-
Returns:
|
266
|
-
DataFrame: DataFrame containing information about each object in the mask array.
|
267
|
-
The DataFrame has the following columns:
|
268
|
-
- frame: The frame number.
|
269
|
-
- y: The y-coordinate of the object's centroid.
|
270
|
-
- x: The x-coordinate of the object's centroid.
|
271
|
-
- mass: The area of the object.
|
272
|
-
- original_label: The original label of the object.
|
273
|
-
|
274
|
-
"""
|
275
|
-
frames = []
|
276
|
-
for t, frame in enumerate(mask_array):
|
277
|
-
props = regionprops(frame)
|
278
|
-
for obj in props:
|
279
|
-
# Include 'label' in the dictionary to capture the original label of the object
|
280
|
-
frames.append({
|
281
|
-
'frame': t,
|
282
|
-
'y': obj.centroid[0],
|
283
|
-
'x': obj.centroid[1],
|
284
|
-
'mass': obj.area,
|
285
|
-
'original_label': obj.label # Capture the original label
|
286
|
-
})
|
287
|
-
return pd.DataFrame(frames)
|
288
|
-
|
289
|
-
def _prepare_for_tracking_v1(mask_array):
|
290
|
-
frames = []
|
291
|
-
for t, frame in enumerate(mask_array):
|
292
|
-
props = regionprops_table(
|
293
|
-
frame,
|
294
|
-
properties=('label', 'centroid-0', 'centroid-1', 'area',
|
295
|
-
'bbox-0', 'bbox-1', 'bbox-2', 'bbox-3',
|
296
|
-
'eccentricity')
|
297
|
-
)
|
298
|
-
df = pd.DataFrame(props)
|
299
|
-
df = df.rename(columns={
|
300
|
-
'centroid-0': 'y', 'centroid-1': 'x', 'area': 'mass',
|
301
|
-
'label': 'original_label'
|
302
|
-
})
|
303
|
-
df['frame'] = t
|
304
|
-
frames.append(df[['frame','y','x','mass','original_label',
|
305
|
-
'bbox-0','bbox-1','bbox-2','bbox-3','eccentricity']])
|
306
|
-
return pd.concat(frames, ignore_index=True)
|
307
|
-
|
308
258
|
def _prepare_for_tracking(mask_array):
|
309
259
|
frames = []
|
310
260
|
for t, frame in enumerate(mask_array):
|
@@ -522,46 +472,6 @@ def _facilitate_trackin_with_adaptive_removal(masks, search_range=None, max_atte
|
|
522
472
|
f"Failed to track after {max_attempts} attempts; last search_range={search_range}"
|
523
473
|
)
|
524
474
|
|
525
|
-
def _facilitate_trackin_with_adaptive_removal_v1(masks, search_range=500, max_attempts=100, memory=3):
|
526
|
-
"""
|
527
|
-
Facilitates object tracking with adaptive removal.
|
528
|
-
|
529
|
-
Args:
|
530
|
-
masks (numpy.ndarray): Array of binary masks representing objects in each frame.
|
531
|
-
search_range (int, optional): Maximum distance objects can move between frames. Defaults to 500.
|
532
|
-
max_attempts (int, optional): Maximum number of attempts to track objects. Defaults to 100.
|
533
|
-
memory (int, optional): Number of frames to remember when linking tracks. Defaults to 3.
|
534
|
-
|
535
|
-
Returns:
|
536
|
-
tuple: A tuple containing the updated masks, features, and tracks_df.
|
537
|
-
masks (numpy.ndarray): Updated array of binary masks.
|
538
|
-
features (pandas.DataFrame): DataFrame containing features for object tracking.
|
539
|
-
tracks_df (pandas.DataFrame): DataFrame containing the tracked object trajectories.
|
540
|
-
|
541
|
-
Raises:
|
542
|
-
Exception: If tracking fails after the maximum number of attempts.
|
543
|
-
|
544
|
-
"""
|
545
|
-
attempts = 0
|
546
|
-
first_frame = masks[0]
|
547
|
-
starting_objects = np.unique(first_frame[first_frame != 0])
|
548
|
-
while attempts < max_attempts:
|
549
|
-
try:
|
550
|
-
masks = _remove_objects_from_first_frame(masks, 10)
|
551
|
-
first_frame = masks[0]
|
552
|
-
objects = np.unique(first_frame[first_frame != 0])
|
553
|
-
print(len(objects))
|
554
|
-
features = _prepare_for_tracking(masks)
|
555
|
-
tracks_df = tp.link(features, search_range=search_range, memory=memory)
|
556
|
-
print(f"Success with {len(objects)} objects, started with {len(starting_objects)} objects")
|
557
|
-
return masks, features, tracks_df
|
558
|
-
except Exception as e: # Consider catching a more specific exception if possible
|
559
|
-
print(f"Retrying with fewer objects. Exception: {e}", flush=True)
|
560
|
-
finally:
|
561
|
-
attempts += 1
|
562
|
-
print(f"Failed to track objects after {max_attempts} attempts. Consider adjusting parameters.")
|
563
|
-
return None, None, None
|
564
|
-
|
565
475
|
def _trackpy_track_cells(src, name, batch_filenames, object_type, masks, timelapse_displacement, timelapse_memory, timelapse_remove_transient, plot, save, mode, track_by_iou):
|
566
476
|
"""
|
567
477
|
Track cells using the Trackpy library.
|
spacr/utils.py
CHANGED
@@ -4602,7 +4602,10 @@ def adjust_cell_masks(parasite_folder, cell_folder, nuclei_folder, overlap_thres
|
|
4602
4602
|
raise ValueError("The number of files in the folders do not match.")
|
4603
4603
|
|
4604
4604
|
# Match files by name
|
4605
|
-
|
4605
|
+
time_ls = []
|
4606
|
+
files_to_process = len(parasite_files)
|
4607
|
+
for files_processed, file_name in enumerate(parasite_files):
|
4608
|
+
start = time.time()
|
4606
4609
|
parasite_path = os.path.join(parasite_folder, file_name)
|
4607
4610
|
cell_path = os.path.join(cell_folder, file_name)
|
4608
4611
|
nuclei_path = os.path.join(nuclei_folder, file_name)
|
@@ -4621,6 +4624,12 @@ def adjust_cell_masks(parasite_folder, cell_folder, nuclei_folder, overlap_thres
|
|
4621
4624
|
|
4622
4625
|
# Overwrite the original cell mask file with the merged result
|
4623
4626
|
np.save(cell_path, merged_cell_mask)
|
4627
|
+
|
4628
|
+
stop = time.time()
|
4629
|
+
duration = (stop - start)
|
4630
|
+
time_ls.append(duration)
|
4631
|
+
files_processed += 1
|
4632
|
+
print_progress(files_processed, files_to_process, n_jobs=1, time_ls=time_ls, batch_size=None, operation_type=f'adjust_cell_masks')
|
4624
4633
|
|
4625
4634
|
def process_masks(mask_folder, image_folder, channel, batch_size=50, n_clusters=2, plot=False):
|
4626
4635
|
|
@@ -5210,54 +5219,6 @@ def rename_columns_in_db(db_path):
|
|
5210
5219
|
|
5211
5220
|
con.commit()
|
5212
5221
|
con.close()
|
5213
|
-
|
5214
|
-
def rename_columns_in_db_v1(db_path):
|
5215
|
-
with sqlite3.connect(db_path) as conn:
|
5216
|
-
cursor = conn.cursor()
|
5217
|
-
|
5218
|
-
# Retrieve all table names in the database
|
5219
|
-
cursor.execute("SELECT name FROM sqlite_master WHERE type='table';")
|
5220
|
-
tables = [table[0] for table in cursor.fetchall()]
|
5221
|
-
|
5222
|
-
for table in tables:
|
5223
|
-
# Retrieve column names for each table
|
5224
|
-
cursor.execute(f"PRAGMA table_info({table});")
|
5225
|
-
columns_info = cursor.fetchall()
|
5226
|
-
column_names = [col[1] for col in columns_info]
|
5227
|
-
|
5228
|
-
# Check if columns 'rowID' or 'columnID' exist
|
5229
|
-
columns_to_rename = {}
|
5230
|
-
if 'row' in column_names:
|
5231
|
-
columns_to_rename['row'] = 'rowID'
|
5232
|
-
if 'col' in column_names:
|
5233
|
-
columns_to_rename['col'] = 'columnID'
|
5234
|
-
|
5235
|
-
# Rename columns if necessary
|
5236
|
-
if columns_to_rename:
|
5237
|
-
# Rename existing table to a temporary name
|
5238
|
-
temp_table = f"{table}_old"
|
5239
|
-
cursor.execute(f"ALTER TABLE `{table}` RENAME TO `{temp_table}`")
|
5240
|
-
|
5241
|
-
# Define new columns with updated names
|
5242
|
-
column_definitions = ", ".join(
|
5243
|
-
[f"`{columns_to_rename.get(col[1], col[1])}` {col[2]}" for col in columns_info]
|
5244
|
-
)
|
5245
|
-
cursor.execute(f"CREATE TABLE `{table}` ({column_definitions})")
|
5246
|
-
|
5247
|
-
# Copy data to the new table
|
5248
|
-
old_columns = ", ".join([f"`{col}`" for col in column_names])
|
5249
|
-
new_columns = ", ".join(
|
5250
|
-
[f"`{columns_to_rename.get(col, col)}`" for col in column_names]
|
5251
|
-
)
|
5252
|
-
cursor.execute(f"INSERT INTO `{table}` ({new_columns}) SELECT {old_columns} FROM `{temp_table}`")
|
5253
|
-
try:
|
5254
|
-
cursor.execute(f"DROP TABLE `{temp_table}`")
|
5255
|
-
except sqlite3.Error as e:
|
5256
|
-
print(f"Error while dropping temporary table '{temp_table}': {e}")
|
5257
|
-
|
5258
|
-
# After closing the 'with' block, run VACUUM outside of any transaction
|
5259
|
-
with sqlite3.connect(db_path) as conn:
|
5260
|
-
conn.execute("VACUUM;")
|
5261
5222
|
|
5262
5223
|
def group_feature_class(df, feature_groups=['cell', 'cytoplasm', 'nucleus', 'pathogen'], name='compartment'):
|
5263
5224
|
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: spacr
|
3
|
-
Version: 0.9.
|
3
|
+
Version: 0.9.2
|
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
|
@@ -80,8 +80,8 @@ Requires-Dist: opencv-python ; extra == 'full'
|
|
80
80
|
Provides-Extra: headless
|
81
81
|
Requires-Dist: opencv-python-headless ; extra == 'headless'
|
82
82
|
|
83
|
-
.. |
|
84
|
-
:target: https://einarolafsson.github.io/spacr
|
83
|
+
.. |Docs| image:: https://github.com/EinarOlafsson/spacr/actions/workflows/pages/pages-build-deployment/badge.svg
|
84
|
+
:target: https://einarolafsson.github.io/spacr/index.html
|
85
85
|
.. |PyPI version| image:: https://badge.fury.io/py/spacr.svg
|
86
86
|
:target: https://badge.fury.io/py/spacr
|
87
87
|
.. |Python version| image:: https://img.shields.io/pypi/pyversions/spacr
|
@@ -91,54 +91,103 @@ Requires-Dist: opencv-python-headless ; extra == 'headless'
|
|
91
91
|
.. |repo size| image:: https://img.shields.io/github/repo-size/EinarOlafsson/spacr
|
92
92
|
:target: https://github.com/EinarOlafsson/spacr/
|
93
93
|
|
94
|
-
|
94
|
+
.. _docs: https://einarolafsson.github.io/spacr/index.html
|
95
|
+
|
96
|
+
Badges
|
97
|
+
------
|
98
|
+
|Docs| |PyPI version| |Python version| |Licence: GPL v3| |repo size|
|
95
99
|
|
96
100
|
SpaCr
|
97
101
|
=====
|
98
102
|
|
99
|
-
Spatial phenotype analysis of CRISPR-Cas9 screens (SpaCr)
|
103
|
+
**Spatial phenotype analysis of CRISPR-Cas9 screens (SpaCr).**
|
104
|
+
|
105
|
+
The spatial organization of organelles and proteins within cells constitutes a key level of functional regulation. In the context of infectious disease, the spatial relationships between host cell structures and intracellular pathogens are critical to understanding host clearance mechanisms and how pathogens evade them. SpaCr is a Python-based software package for generating single-cell image data for deep-learning sub-cellular/cellular phenotypic classification from pooled genetic CRISPR-Cas9 screens. 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.
|
100
106
|
|
101
107
|
Features
|
102
108
|
--------
|
103
109
|
|
104
110
|
- **Generate Masks:** Generate cellpose masks of cell, nuclei, and pathogen objects.
|
105
|
-
|
106
|
-
- **Object Measurements:** Measurements for each object including scikit-image-regionprops, intensity percentiles, shannon-entropy, pearsons and manders correlations, homogeneity, and radial distribution. Measurements are saved to a SQL database in object-level tables.
|
107
|
-
|
111
|
+
- **Object Measurements:** Measurements for each object including scikit-image regionprops, intensity percentiles, shannon-entropy, Pearson’s and Manders’ correlations, homogeneity, and radial distribution. Measurements are saved to a SQL database in object-level tables.
|
108
112
|
- **Crop Images:** Save objects (cells, nuclei, pathogen, cytoplasm) as images. Object image paths are saved in a SQL database.
|
109
|
-
|
110
113
|
- **Train CNNs or Transformers:** Train Torch models to classify single object images.
|
111
|
-
|
112
114
|
- **Manual Annotation:** Supports manual annotation of single-cell images and segmentation to refine training datasets for training CNNs/Transformers or cellpose, respectively.
|
113
|
-
|
114
115
|
- **Finetune Cellpose Models:** Adjust pre-existing Cellpose models to your specific dataset for improved performance.
|
115
|
-
|
116
116
|
- **Timelapse Data Support:** Track objects in timelapse image data.
|
117
|
-
|
118
117
|
- **Simulations:** Simulate spatial phenotype screens.
|
119
|
-
|
120
118
|
- **Sequencing:** Map FASTQ reads to barcode and gRNA barcode metadata.
|
121
|
-
|
122
119
|
- **Misc:** Analyze Ca oscillation, recruitment, infection rate, plaque size/count.
|
123
120
|
|
121
|
+
.. image:: https://github.com/EinarOlafsson/spacr/raw/main/spacr/resources/icons/flow_chart_v2.png
|
122
|
+
:alt: SpaCr workflow
|
123
|
+
:align: center
|
124
|
+
|
125
|
+
|
126
|
+
**Overview and data organization of spaCR.**
|
127
|
+
|
128
|
+
**a.** Schematic workflow of the spaCR pipeline for pooled image-based CRISPR screens. Microscopy images (TIFF, LIF, CZI, NDI) and sequencing reads (FASTQ) are used as inputs (black). The main modules (teal) are: (1) Mask: generates object masks for cells, nuclei, pathogens, and cytoplasm; (2) Measure: extracts object-level features and crops object images, storing quantitative data in an SQL database; (3) Classify—applies machine learning (ML, e.g., XGBoost) or deep learning (DL, e.g., PyTorch) models to classify objects, summarizing results as well-level classification scores; (4) Map Barcodes: extracts and maps row, column, and gRNA barcodes from sequencing data to corresponding wells; (5) Regression: estimates gRNA effect sizes and gene scores via multiple linear regression using well-level summary statistics.
|
129
|
+
**b.** Downstream submodules available for extended analyses at each stage.
|
130
|
+
**c.** Output folder structure for each module, including locations for raw and processed images, masks, object-level measurements, datasets, and results.
|
131
|
+
**d.** List of all spaCR package modules.
|
132
|
+
|
124
133
|
Installation
|
125
134
|
------------
|
126
135
|
|
136
|
+
**Linux recommended.**
|
127
137
|
If using Windows, switch to Linux—it's free, open-source, and better.
|
128
138
|
|
129
|
-
|
139
|
+
**macOS prerequisites (before install):**
|
140
|
+
|
141
|
+
::
|
130
142
|
|
131
143
|
brew install libomp
|
132
144
|
brew install hdf5
|
133
145
|
|
134
|
-
|
146
|
+
**Linux GUI requirement:**
|
147
|
+
SpaCr GUI requires Tkinter.
|
148
|
+
|
149
|
+
::
|
135
150
|
|
136
151
|
sudo apt-get install python3-tk
|
137
152
|
|
138
|
-
|
153
|
+
**Installation:**
|
154
|
+
|
155
|
+
::
|
139
156
|
|
140
157
|
pip install spacr
|
141
158
|
|
142
|
-
Run SpaCr GUI
|
159
|
+
**Run SpaCr GUI:**
|
160
|
+
|
161
|
+
::
|
143
162
|
|
144
163
|
spacr
|
164
|
+
|
165
|
+
Data Availability
|
166
|
+
-----------------
|
167
|
+
|
168
|
+
- **Raw sequencing data** are available from NCBI BioProject `PRJNA1261935 <https://www.ncbi.nlm.nih.gov/bioproject/PRJNA1261935>`_ and SRA accessions: `SRR33531217 <https://www.ncbi.nlm.nih.gov/sra/SRR33531217>`_, `SRR33531218 <https://www.ncbi.nlm.nih.gov/sra/SRR33531218>`_, `SRR33531219 <https://www.ncbi.nlm.nih.gov/sra/SRR33531219>`_, `SRR33531220 <https://www.ncbi.nlm.nih.gov/sra/SRR33531220>`_
|
169
|
+
|
170
|
+
- **Image data** is deposited at EBI BioStudies under accession: `S-BIAD2076 <https://www.ebi.ac.uk/biostudies/studies/S-BIAD2076>`_
|
171
|
+
|
172
|
+
Example Notebooks
|
173
|
+
-----------------
|
174
|
+
|
175
|
+
The following example Jupyter notebooks illustrate common workflows using spaCR.
|
176
|
+
|
177
|
+
- `1_spacr_generate_masks.ipynb <Notebooks/1_spacr_generate_masks.ipynb>`_
|
178
|
+
*Generate cell, nuclei, and pathogen segmentation masks from microscopy images using Cellpose.*
|
179
|
+
|
180
|
+
- `2_spacr_generate_mesurments_crop_images.ipynb <Notebooks/2_spacr_generate_mesurments_crop_images.ipynb>`_
|
181
|
+
*Extract object-level measurements and crop single-cell images for downstream analysis.*
|
182
|
+
|
183
|
+
- `3a_spacr_machine_learning.ipynb <Notebooks/3a_spacr_machine_learning.ipynb>`_
|
184
|
+
*Train traditional machine learning models (e.g., XGBoost) to classify cell phenotypes based on extracted features.*
|
185
|
+
|
186
|
+
- `3b_spacr_computer_vision.ipynb <Notebooks/3b_spacr_computer_vision.ipynb>`_
|
187
|
+
*Train and evaluate deep learning models (PyTorch CNNs/Transformers) on cropped object images.*
|
188
|
+
|
189
|
+
- `4_spacr_map_barecodes.ipynb <Notebooks/4_spacr_map_barecodes.ipynb>`_
|
190
|
+
*Map sequencing reads to row, column, and gRNA barcodes for CRISPR screen genotype-phenotype mapping.*
|
191
|
+
|
192
|
+
- `5_spacr_train_cellpose.ipynb <Notebooks/5_spacr_train_cellpose.ipynb>`_
|
193
|
+
*Finetune Cellpose models using your own annotated training data for improved segmentation accuracy.*
|
@@ -11,25 +11,25 @@ spacr/chat_bot.py,sha256=n3Fhqg3qofVXHmh3H9sUcmfYy9MmgRnr48663MVdY9E,1244
|
|
11
11
|
spacr/core.py,sha256=w4E3Pg-ZnA8BOK0iUMTjiNO0GeR5YCEs8fUTbESzqjY,47392
|
12
12
|
spacr/deep_spacr.py,sha256=055tIo3WP3elGFiIuSZaLURgu2XyUDxAdbw5ezASEqM,54526
|
13
13
|
spacr/gui.py,sha256=NhMh96KoArrSAaJBV6PhDQpIC1cQpxgb6SclhRbYG8s,8122
|
14
|
-
spacr/gui_core.py,sha256=
|
14
|
+
spacr/gui_core.py,sha256=m-AYUmaSXqsGFj9EzPf4fp1irZGN-X-21S4FBO8PoXc,52727
|
15
15
|
spacr/gui_elements.py,sha256=5a3BOpctBPklsT1NungqS72h1Bg1FArUndE0OfvWD8Y,152646
|
16
16
|
spacr/gui_utils.py,sha256=vv_uBOA0n-04KCCicYHhNt3sRbm0IPLM5r8QX5EkJ1Q,40867
|
17
17
|
spacr/io.py,sha256=SYLhupKnOJJscNSGE4N67E32-ywhwrjRccIfZrL38Uk,157966
|
18
18
|
spacr/logger.py,sha256=lJhTqt-_wfAunCPl93xE65Wr9Y1oIHJWaZMjunHUeIw,1538
|
19
|
-
spacr/measure.py,sha256=
|
19
|
+
spacr/measure.py,sha256=XmOCKriS-kuRy-EPhQ_z7CRNg6DukyTpwQCiuSPNd_c,63414
|
20
20
|
spacr/mediar.py,sha256=p0F515eFbm6_rePSnChsgqrgH-H5Sr_3zWrghtOnAUg,14863
|
21
21
|
spacr/ml.py,sha256=XCRZeX7UkbMctQICIoskeWVx8CCmmCoHNauUOAkfFq0,91692
|
22
22
|
spacr/openai.py,sha256=5vBZ3Jl2llYcW3oaTEXgdyCB2aJujMUIO5K038z7w_A,1246
|
23
|
-
spacr/plot.py,sha256=
|
23
|
+
spacr/plot.py,sha256=M2w9ytR8iMFtsVPhmQ5tzIWTQDmbtCzs1-7hALUIQtg,167339
|
24
24
|
spacr/sequencing.py,sha256=EY12RdW5QRKpHDRQCw1QoAlxCq8FK2v6WoVa5uuDBXQ,26745
|
25
|
-
spacr/settings.py,sha256=
|
25
|
+
spacr/settings.py,sha256=tEFKYqMYQoObrNuKL3XdinRebQxJcuA3Gn9wK44vqhs,87505
|
26
26
|
spacr/sim.py,sha256=1xKhXimNU3ukzIw-3l9cF3Znc_brW8h20yv8fSTzvss,71173
|
27
27
|
spacr/sp_stats.py,sha256=mbhwsyIqt5upsSD346qGjdCw7CFBa0tIS7zHU9e0jNI,9536
|
28
28
|
spacr/spacr_cellpose.py,sha256=RBHMs2vwXcfkj0xqAULpALyzJYXddSRycgZSzmwI7v0,14755
|
29
29
|
spacr/submodules.py,sha256=Z2i4kv_rWdxqoXsOKCF7BaSXtvaCZB69Ow8_FQBnZsY,83093
|
30
|
-
spacr/timelapse.py,sha256
|
30
|
+
spacr/timelapse.py,sha256=-5ZupTsCCpbenIQ2zsUmnwXh45B82fO-gPrSXOxu2s8,42980
|
31
31
|
spacr/toxo.py,sha256=GoNfgyH-NJx3WOzNQPgzODir7Jp65fs7UM46XpzcrUo,26056
|
32
|
-
spacr/utils.py,sha256=
|
32
|
+
spacr/utils.py,sha256=cw5zM6zpFWWUZQKwtYvXc_rNfBMW2ldbnlw8s6f6bFQ,234397
|
33
33
|
spacr/version.py,sha256=axH5tnGwtgSnJHb5IDhiu4Zjk5GhLyAEDRe-rnaoFOA,409
|
34
34
|
spacr/resources/data/lopit.csv,sha256=ERI5f9W8RdJGiSx_khoaylD374f8kmvLia1xjhD_mII,4421709
|
35
35
|
spacr/resources/data/toxoplasma_metadata.csv,sha256=9TXx0VlClDHAxQmaLhoklE8NuETduXaGHZjhR_6lZfs,2969409
|
@@ -82,6 +82,7 @@ spacr/resources/icons/convert.png,sha256=vLyTkQeUZ9q-pirhtZeXDq3-DzfjoPMjLlgKl5W
|
|
82
82
|
spacr/resources/icons/default.png,sha256=KoNhaSHukO4wDyivyYEgSbb5mGj-sAxmhKikLLtNpWs,20341
|
83
83
|
spacr/resources/icons/dna_matrix.mp4,sha256=NegOQkn4q4kHhFgqcIX2dd58wVytBtnkmbgg0ZegL8U,23462876
|
84
84
|
spacr/resources/icons/download.png,sha256=1nUoWRaTc4vIsK6gompdeqk0cIv2GdH-gCNHaEBX6Mc,20467
|
85
|
+
spacr/resources/icons/flow_chart_v2.png,sha256=4U4uzJlyQ8L-exWIXIhyqtkoO-KIiubO23kA7eLZYYE,640609
|
85
86
|
spacr/resources/icons/logo.pdf,sha256=VB4cS41V3VV_QxD7l6CwdQKQiYLErugLBxWoCoxjQU0,377925
|
86
87
|
spacr/resources/icons/logo_spacr.png,sha256=qG3e3bdrAefhl1281rfo0R2XP0qA-c-oaBCXjxMGXkw,42587
|
87
88
|
spacr/resources/icons/logo_spacr_1.png,sha256=g9y2ZmnV3hab8r1idDfytm8AaHbBiQdu_93Jd7YKzwA,610892
|
@@ -101,9 +102,9 @@ spacr/resources/icons/umap.png,sha256=dOLF3DeLYy9k0nkUybiZMe1wzHQwLJFRmgccppw-8b
|
|
101
102
|
spacr/resources/images/plate1_E01_T0001F001L01A01Z01C02.tif,sha256=Tl0ZUfZ_AYAbu0up_nO0tPRtF1BxXhWQ3T3pURBCCRo,7958528
|
102
103
|
spacr/resources/images/plate1_E01_T0001F001L01A02Z01C01.tif,sha256=m8N-V71rA1TT4dFlENNg8s0Q0YEXXs8slIn7yObmZJQ,7958528
|
103
104
|
spacr/resources/images/plate1_E01_T0001F001L01A03Z01C03.tif,sha256=Pbhk7xn-KUP6RSIhJsxQcrHFImBm3GEpLkzx7WOc-5M,7958528
|
104
|
-
spacr-0.9.
|
105
|
-
spacr-0.9.
|
106
|
-
spacr-0.9.
|
107
|
-
spacr-0.9.
|
108
|
-
spacr-0.9.
|
109
|
-
spacr-0.9.
|
105
|
+
spacr-0.9.2.dist-info/LICENSE,sha256=SR-2MeGc6SCM1UORJYyarSWY_A-JaOMFDj7ReSs9tRM,1083
|
106
|
+
spacr-0.9.2.dist-info/METADATA,sha256=vfXoB5rq-qP1Yq4Hj4noXrPKrI-Fs1Q09RN_WTRkQLw,9336
|
107
|
+
spacr-0.9.2.dist-info/WHEEL,sha256=R0nc6qTxuoLk7ShA2_Y-UWkN8ZdfDBG2B6Eqpz2WXbs,91
|
108
|
+
spacr-0.9.2.dist-info/entry_points.txt,sha256=BMC0ql9aNNpv8lUZ8sgDLQMsqaVnX5L535gEhKUP5ho,296
|
109
|
+
spacr-0.9.2.dist-info/top_level.txt,sha256=GJPU8FgwRXGzKeut6JopsSRY2R8T3i9lDgya42tLInY,6
|
110
|
+
spacr-0.9.2.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|