spacr 0.0.2__py3-none-any.whl → 0.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/__init__.py +2 -2
- spacr/__main__.py +0 -2
- spacr/alpha.py +803 -14
- spacr/annotate_app.py +118 -120
- spacr/chris.py +50 -0
- spacr/core.py +1544 -533
- spacr/deep_spacr.py +696 -0
- spacr/foldseek.py +779 -0
- spacr/get_alfafold_structures.py +72 -0
- spacr/graph_learning.py +297 -253
- spacr/gui.py +145 -0
- spacr/gui_2.py +90 -0
- spacr/gui_classify_app.py +70 -80
- spacr/gui_mask_app.py +114 -91
- spacr/gui_measure_app.py +109 -88
- spacr/gui_utils.py +376 -32
- spacr/io.py +441 -438
- spacr/mask_app.py +116 -9
- spacr/measure.py +169 -69
- spacr/models/cp/toxo_pv_lumen.CP_model +0 -0
- spacr/old_code.py +70 -2
- spacr/plot.py +173 -17
- spacr/sequencing.py +1130 -0
- spacr/sim.py +630 -125
- spacr/timelapse.py +139 -10
- spacr/train.py +188 -21
- spacr/umap.py +0 -689
- spacr/utils.py +1360 -119
- {spacr-0.0.2.dist-info → spacr-0.0.6.dist-info}/METADATA +17 -29
- spacr-0.0.6.dist-info/RECORD +39 -0
- {spacr-0.0.2.dist-info → spacr-0.0.6.dist-info}/WHEEL +1 -1
- spacr-0.0.6.dist-info/entry_points.txt +9 -0
- spacr-0.0.2.dist-info/RECORD +0 -31
- spacr-0.0.2.dist-info/entry_points.txt +0 -7
- {spacr-0.0.2.dist-info → spacr-0.0.6.dist-info}/LICENSE +0 -0
- {spacr-0.0.2.dist-info → spacr-0.0.6.dist-info}/top_level.txt +0 -0
spacr/mask_app.py
CHANGED
@@ -13,7 +13,7 @@ from ttkthemes import ThemedTk
|
|
13
13
|
|
14
14
|
from .logger import log_function_call
|
15
15
|
|
16
|
-
from .gui_utils import ScrollableFrame, set_dark_style, set_default_font, create_dark_mode
|
16
|
+
from .gui_utils import ScrollableFrame, set_dark_style, set_default_font, create_dark_mode, style_text_boxes, create_menu_bar
|
17
17
|
|
18
18
|
class modify_masks:
|
19
19
|
|
@@ -128,10 +128,13 @@ class modify_masks:
|
|
128
128
|
self.zoom_active = False
|
129
129
|
self.magic_wand_active = False
|
130
130
|
self.brush_active = False
|
131
|
+
self.dividing_line_active = False
|
132
|
+
self.dividing_line_coords = []
|
133
|
+
self.current_dividing_line = None
|
131
134
|
self.lower_quantile = tk.StringVar(value="1.0")
|
132
135
|
self.upper_quantile = tk.StringVar(value="99.9")
|
133
136
|
self.magic_wand_tolerance = tk.StringVar(value="1000")
|
134
|
-
|
137
|
+
|
135
138
|
def update_mouse_info(self, event):
|
136
139
|
x, y = event.x, event.y
|
137
140
|
intensity = "N/A"
|
@@ -187,13 +190,14 @@ class modify_masks:
|
|
187
190
|
self.max_pixels_entry.pack(side='left')
|
188
191
|
self.erase_btn = tk.Button(self.mode_toolbar, text="Erase", command=self.toggle_erase_mode)
|
189
192
|
self.erase_btn.pack(side='left')
|
190
|
-
|
191
193
|
self.brush_btn = tk.Button(self.mode_toolbar, text="Brush", command=self.toggle_brush_mode)
|
192
194
|
self.brush_btn.pack(side='left')
|
193
195
|
self.brush_size_entry = tk.Entry(self.mode_toolbar)
|
194
|
-
self.brush_size_entry.insert(0, "10")
|
196
|
+
self.brush_size_entry.insert(0, "10")
|
195
197
|
self.brush_size_entry.pack(side='left')
|
196
198
|
tk.Label(self.mode_toolbar, text="Brush Size:").pack(side='left')
|
199
|
+
self.dividing_line_btn = tk.Button(self.mode_toolbar, text="Dividing Line", command=self.toggle_dividing_line_mode)
|
200
|
+
self.dividing_line_btn.pack(side='left')
|
197
201
|
|
198
202
|
def setup_function_toolbar(self):
|
199
203
|
self.function_toolbar = tk.Frame(self.root)
|
@@ -393,10 +397,12 @@ class modify_masks:
|
|
393
397
|
self.magic_wand_active = False
|
394
398
|
self.erase_active = False
|
395
399
|
self.brush_active = False
|
400
|
+
self.dividing_line_active = False
|
396
401
|
self.draw_btn.config(text="Draw")
|
397
402
|
self.erase_btn.config(text="Erase")
|
398
403
|
self.magic_wand_btn.config(text="Magic Wand")
|
399
404
|
self.zoom_btn.config(text="Zoom ON")
|
405
|
+
self.dividing_line_btn.config(text="Dividing Line")
|
400
406
|
self.canvas.unbind("<Button-1>")
|
401
407
|
self.canvas.unbind("<Button-3>")
|
402
408
|
self.canvas.unbind("<Motion>")
|
@@ -423,7 +429,7 @@ class modify_masks:
|
|
423
429
|
self.zoom_mask = None
|
424
430
|
self.zoom_image = None
|
425
431
|
self.zoom_image_orig = None
|
426
|
-
|
432
|
+
|
427
433
|
def toggle_brush_mode(self):
|
428
434
|
self.brush_active = not self.brush_active
|
429
435
|
if self.brush_active:
|
@@ -448,7 +454,105 @@ class modify_masks:
|
|
448
454
|
self.canvas.unbind("<B3-Motion>")
|
449
455
|
self.canvas.unbind("<ButtonRelease-1>")
|
450
456
|
self.canvas.unbind("<ButtonRelease-3>")
|
451
|
-
|
457
|
+
|
458
|
+
def image_to_canvas(self, x_image, y_image):
|
459
|
+
x_scale, y_scale = self.get_scaling_factors(
|
460
|
+
self.image.shape[1], self.image.shape[0],
|
461
|
+
self.canvas_width, self.canvas_height
|
462
|
+
)
|
463
|
+
x_canvas = int(x_image / x_scale)
|
464
|
+
y_canvas = int(y_image / y_scale)
|
465
|
+
return x_canvas, y_canvas
|
466
|
+
|
467
|
+
def toggle_dividing_line_mode(self):
|
468
|
+
self.dividing_line_active = not self.dividing_line_active
|
469
|
+
if self.dividing_line_active:
|
470
|
+
self.drawing = False
|
471
|
+
self.magic_wand_active = False
|
472
|
+
self.erase_active = False
|
473
|
+
self.brush_active = False
|
474
|
+
self.draw_btn.config(text="Draw")
|
475
|
+
self.erase_btn.config(text="Erase")
|
476
|
+
self.magic_wand_btn.config(text="Magic Wand")
|
477
|
+
self.brush_btn.config(text="Brush")
|
478
|
+
self.dividing_line_btn.config(text="Dividing Line ON")
|
479
|
+
self.canvas.unbind("<Button-1>")
|
480
|
+
self.canvas.unbind("<ButtonRelease-1>")
|
481
|
+
self.canvas.unbind("<Motion>")
|
482
|
+
self.canvas.bind("<Button-1>", self.start_dividing_line)
|
483
|
+
self.canvas.bind("<ButtonRelease-1>", self.finish_dividing_line)
|
484
|
+
self.canvas.bind("<Motion>", self.update_dividing_line_preview)
|
485
|
+
else:
|
486
|
+
print("Dividing Line Mode: OFF")
|
487
|
+
self.dividing_line_active = False
|
488
|
+
self.dividing_line_btn.config(text="Dividing Line")
|
489
|
+
self.canvas.unbind("<Button-1>")
|
490
|
+
self.canvas.unbind("<ButtonRelease-1>")
|
491
|
+
self.canvas.unbind("<Motion>")
|
492
|
+
self.display_image()
|
493
|
+
|
494
|
+
def start_dividing_line(self, event):
|
495
|
+
if self.dividing_line_active:
|
496
|
+
self.dividing_line_coords = [(event.x, event.y)]
|
497
|
+
self.current_dividing_line = self.canvas.create_line(event.x, event.y, event.x, event.y, fill="red", width=2)
|
498
|
+
|
499
|
+
def finish_dividing_line(self, event):
|
500
|
+
if self.dividing_line_active:
|
501
|
+
self.dividing_line_coords.append((event.x, event.y))
|
502
|
+
if self.zoom_active:
|
503
|
+
self.dividing_line_coords = [self.canvas_to_image(x, y) for x, y in self.dividing_line_coords]
|
504
|
+
self.apply_dividing_line()
|
505
|
+
self.canvas.delete(self.current_dividing_line)
|
506
|
+
self.current_dividing_line = None
|
507
|
+
|
508
|
+
def update_dividing_line_preview(self, event):
|
509
|
+
if self.dividing_line_active and self.dividing_line_coords:
|
510
|
+
x, y = event.x, event.y
|
511
|
+
if self.zoom_active:
|
512
|
+
x, y = self.canvas_to_image(x, y)
|
513
|
+
self.dividing_line_coords.append((x, y))
|
514
|
+
canvas_coords = [(self.image_to_canvas(*pt) if self.zoom_active else pt) for pt in self.dividing_line_coords]
|
515
|
+
flat_canvas_coords = [coord for pt in canvas_coords for coord in pt]
|
516
|
+
self.canvas.coords(self.current_dividing_line, *flat_canvas_coords)
|
517
|
+
|
518
|
+
def apply_dividing_line(self):
|
519
|
+
if self.dividing_line_coords:
|
520
|
+
coords = self.dividing_line_coords
|
521
|
+
if self.zoom_active:
|
522
|
+
coords = [self.canvas_to_image(x, y) for x, y in coords]
|
523
|
+
|
524
|
+
rr, cc = [], []
|
525
|
+
for (x0, y0), (x1, y1) in zip(coords[:-1], coords[1:]):
|
526
|
+
line_rr, line_cc = line(y0, x0, y1, x1)
|
527
|
+
rr.extend(line_rr)
|
528
|
+
cc.extend(line_cc)
|
529
|
+
rr, cc = np.array(rr), np.array(cc)
|
530
|
+
|
531
|
+
mask_copy = self.mask.copy()
|
532
|
+
|
533
|
+
if self.zoom_active:
|
534
|
+
# Update the zoomed mask
|
535
|
+
self.zoom_mask[rr, cc] = 0
|
536
|
+
# Reflect changes to the original mask
|
537
|
+
y0, y1, x0, x1 = self.zoom_y0, self.zoom_y1, self.zoom_x0, self.zoom_x1
|
538
|
+
zoomed_mask_resized_back = resize(self.zoom_mask, (y1 - y0, x1 - x0), order=0, preserve_range=True).astype(np.uint8)
|
539
|
+
self.mask[y0:y1, x0:x1] = zoomed_mask_resized_back
|
540
|
+
else:
|
541
|
+
# Directly update the original mask
|
542
|
+
mask_copy[rr, cc] = 0
|
543
|
+
self.mask = mask_copy
|
544
|
+
|
545
|
+
labeled_mask, num_labels = label(self.mask > 0)
|
546
|
+
self.mask = labeled_mask
|
547
|
+
self.update_display()
|
548
|
+
|
549
|
+
self.dividing_line_coords = []
|
550
|
+
self.canvas.unbind("<Button-1>")
|
551
|
+
self.canvas.unbind("<ButtonRelease-1>")
|
552
|
+
self.canvas.unbind("<Motion>")
|
553
|
+
self.dividing_line_active = False
|
554
|
+
self.dividing_line_btn.config(text="Dividing Line")
|
555
|
+
|
452
556
|
def toggle_draw_mode(self):
|
453
557
|
self.drawing = not self.drawing
|
454
558
|
if self.drawing:
|
@@ -753,15 +857,18 @@ class modify_masks:
|
|
753
857
|
self.mask[labeled_mask == i] = 0 # Remove small objects
|
754
858
|
self.update_display()
|
755
859
|
|
756
|
-
|
860
|
+
##@log_function_call
|
757
861
|
def initiate_mask_app_root(width, height):
|
758
862
|
theme = 'breeze'
|
759
863
|
root = ThemedTk(theme=theme)
|
760
864
|
style = ttk.Style(root)
|
761
865
|
set_dark_style(style)
|
762
|
-
|
866
|
+
|
867
|
+
style_text_boxes(style)
|
868
|
+
set_default_font(root, font_name="Arial", size=8)
|
763
869
|
root.geometry(f"{width}x{height}")
|
764
870
|
root.title("Mask App")
|
871
|
+
create_menu_bar(root)
|
765
872
|
|
766
873
|
container = tk.PanedWindow(root, orient=tk.HORIZONTAL)
|
767
874
|
container.pack(fill=tk.BOTH, expand=True)
|
@@ -806,7 +913,7 @@ def initiate_mask_app_root(width, height):
|
|
806
913
|
create_dark_mode(root, style, console_output=None)
|
807
914
|
|
808
915
|
run_button = ttk.Button(scrollable_frame.scrollable_frame, text="Run", command=run_app)
|
809
|
-
run_button.grid(row=row, column=0, columnspan=2, pady=10)
|
916
|
+
run_button.grid(row=row, column=0, columnspan=2, pady=10, padx=10)
|
810
917
|
|
811
918
|
return root
|
812
919
|
|
spacr/measure.py
CHANGED
@@ -3,7 +3,6 @@ import numpy as np
|
|
3
3
|
import pandas as pd
|
4
4
|
from collections import defaultdict
|
5
5
|
from scipy.stats import pearsonr
|
6
|
-
import matplotlib as mpl
|
7
6
|
import multiprocessing as mp
|
8
7
|
from scipy.ndimage import distance_transform_edt, generate_binary_structure
|
9
8
|
from skimage.measure import regionprops, regionprops_table, shannon_entropy
|
@@ -12,6 +11,8 @@ from scipy.ndimage import binary_dilation
|
|
12
11
|
from skimage.segmentation import find_boundaries
|
13
12
|
from skimage.feature import graycomatrix, graycoprops
|
14
13
|
from mahotas.features import zernike_moments
|
14
|
+
from skimage import morphology, measure, filters
|
15
|
+
from skimage.util import img_as_bool
|
15
16
|
|
16
17
|
from .logger import log_function_call
|
17
18
|
|
@@ -92,7 +93,70 @@ def _calculate_zernike(mask, df, degree=8):
|
|
92
93
|
else:
|
93
94
|
return df
|
94
95
|
|
95
|
-
|
96
|
+
def _analyze_cytoskeleton(array, mask, channel):
|
97
|
+
"""
|
98
|
+
Analyzes and extracts skeleton properties from labeled objects in a masked image based on microtubule staining intensities.
|
99
|
+
|
100
|
+
Parameters:
|
101
|
+
image : numpy array
|
102
|
+
Intensity image where the microtubules are stained.
|
103
|
+
mask : numpy array
|
104
|
+
Mask where objects are labeled for analysis. Each label corresponds to a unique object.
|
105
|
+
|
106
|
+
Returns:
|
107
|
+
DataFrame
|
108
|
+
A pandas DataFrame containing the measured properties of each object's skeleton.
|
109
|
+
"""
|
110
|
+
|
111
|
+
image = array[:, :, channel]
|
112
|
+
|
113
|
+
properties_list = []
|
114
|
+
|
115
|
+
# Process each object in the mask based on its label
|
116
|
+
for label in np.unique(mask):
|
117
|
+
if label == 0:
|
118
|
+
continue # Skip background
|
119
|
+
|
120
|
+
# Isolate the object using the label
|
121
|
+
object_region = mask == label
|
122
|
+
region_intensity = np.where(object_region, image, 0) # Use np.where for more efficient masking
|
123
|
+
|
124
|
+
# Ensure there are non-zero values to process
|
125
|
+
if np.any(region_intensity):
|
126
|
+
# Calculate adaptive offset based on intensity percentiles within the object
|
127
|
+
valid_pixels = region_intensity[region_intensity > 0]
|
128
|
+
if len(valid_pixels) > 1: # Ensure there are enough pixels to compute percentiles
|
129
|
+
offset = np.percentile(valid_pixels, 90) - np.percentile(valid_pixels, 50)
|
130
|
+
block_size = 35 # Adjust this based on your object sizes and detail needs
|
131
|
+
local_thresh = filters.threshold_local(region_intensity, block_size=block_size, offset=offset)
|
132
|
+
cytoskeleton = region_intensity > local_thresh
|
133
|
+
|
134
|
+
# Skeletonize the thresholded cytoskeleton
|
135
|
+
skeleton = morphology.skeletonize(img_as_bool(cytoskeleton))
|
136
|
+
|
137
|
+
# Measure properties of the skeleton
|
138
|
+
skeleton_props = measure.regionprops(measure.label(skeleton), intensity_image=image)
|
139
|
+
skeleton_length = sum(prop.area for prop in skeleton_props) # Sum of lengths of all skeleton segments
|
140
|
+
branch_data = morphology.skeleton_branch_analysis(skeleton)
|
141
|
+
|
142
|
+
# Store properties
|
143
|
+
properties = {
|
144
|
+
"object_label": label,
|
145
|
+
"skeleton_length": skeleton_length,
|
146
|
+
"skeleton_branch_points": len(branch_data['branch_points'])
|
147
|
+
}
|
148
|
+
properties_list.append(properties)
|
149
|
+
else:
|
150
|
+
# Handle cases with insufficient pixels
|
151
|
+
properties_list.append({
|
152
|
+
"object_label": label,
|
153
|
+
"skeleton_length": 0,
|
154
|
+
"skeleton_branch_points": 0
|
155
|
+
})
|
156
|
+
|
157
|
+
return pd.DataFrame(properties_list)
|
158
|
+
|
159
|
+
#@log_function_call
|
96
160
|
def _morphological_measurements(cell_mask, nucleus_mask, pathogen_mask, cytoplasm_mask, settings, zernike=True, degree=8):
|
97
161
|
"""
|
98
162
|
Calculate morphological measurements for cells, nucleus, pathogens, and cytoplasms based on the given masks.
|
@@ -436,7 +500,7 @@ def _estimate_blur(image):
|
|
436
500
|
# Compute and return the variance of the Laplacian
|
437
501
|
return lap.var()
|
438
502
|
|
439
|
-
|
503
|
+
#@log_function_call
|
440
504
|
def _intensity_measurements(cell_mask, nucleus_mask, pathogen_mask, cytoplasm_mask, channel_arrays, settings, sizes=[3, 6, 12, 24], periphery=True, outside=True):
|
441
505
|
"""
|
442
506
|
Calculate various intensity measurements for different regions in the image.
|
@@ -524,8 +588,9 @@ def _intensity_measurements(cell_mask, nucleus_mask, pathogen_mask, cytoplasm_ma
|
|
524
588
|
|
525
589
|
return pd.concat(cell_dfs, axis=1), pd.concat(nucleus_dfs, axis=1), pd.concat(pathogen_dfs, axis=1), pd.concat(cytoplasm_dfs, axis=1)
|
526
590
|
|
527
|
-
|
591
|
+
#@log_function_call
|
528
592
|
def _measure_crop_core(index, time_ls, file, settings):
|
593
|
+
|
529
594
|
"""
|
530
595
|
Measure and crop the images based on specified settings.
|
531
596
|
|
@@ -624,9 +689,8 @@ def _measure_crop_core(index, time_ls, file, settings):
|
|
624
689
|
if settings['cytoplasm_min_size'] is not None and settings['cytoplasm_min_size'] != 0:
|
625
690
|
cytoplasm_mask = _filter_object(cytoplasm_mask, settings['cytoplasm_min_size'])
|
626
691
|
|
627
|
-
if settings['cell_mask_dim'] is not None
|
628
|
-
|
629
|
-
cell_mask, nucleus_mask, pathogen_mask, cytoplasm_mask = _exclude_objects(cell_mask, nucleus_mask, pathogen_mask, cytoplasm_mask, include_uninfected=False)
|
692
|
+
if settings['cell_mask_dim'] is not None:
|
693
|
+
cell_mask, nucleus_mask, pathogen_mask, cytoplasm_mask = _exclude_objects(cell_mask, nucleus_mask, pathogen_mask, cytoplasm_mask, include_uninfected=settings['include_uninfected'])
|
630
694
|
|
631
695
|
# Update data with the new masks
|
632
696
|
if settings['cell_mask_dim'] is not None:
|
@@ -645,6 +709,10 @@ def _measure_crop_core(index, time_ls, file, settings):
|
|
645
709
|
|
646
710
|
cell_df, nucleus_df, pathogen_df, cytoplasm_df = _morphological_measurements(cell_mask, nucleus_mask, pathogen_mask, cytoplasm_mask, settings)
|
647
711
|
|
712
|
+
#if settings['skeleton']:
|
713
|
+
#skeleton_df = _analyze_cytoskeleton(image=channel_arrays, mask=cell_mask, channel=1)
|
714
|
+
#merge skeleton_df with cell_df here
|
715
|
+
|
648
716
|
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)
|
649
717
|
if settings['cell_mask_dim'] is not None:
|
650
718
|
cell_merged_df = _merge_and_save_to_database(cell_df, cell_intensity_df, 'cell', source_folder, file_name, settings['experiment'], settings['timelapse'])
|
@@ -658,7 +726,6 @@ def _measure_crop_core(index, time_ls, file, settings):
|
|
658
726
|
if settings['cytoplasm']:
|
659
727
|
cytoplasm_merged_df = _merge_and_save_to_database(cytoplasm_df, cytoplasm_intensity_df, 'cytoplasm', source_folder, file_name, settings['experiment'], settings['timelapse'])
|
660
728
|
|
661
|
-
|
662
729
|
if settings['save_png'] or settings['save_arrays'] or settings['plot']:
|
663
730
|
|
664
731
|
if isinstance(settings['dialate_pngs'], bool):
|
@@ -731,7 +798,7 @@ def _measure_crop_core(index, time_ls, file, settings):
|
|
731
798
|
png_channels = data[:, :, settings['png_dims']].astype(data_type)
|
732
799
|
|
733
800
|
if settings['normalize_by'] == 'fov':
|
734
|
-
percentiles_list = _get_percentiles(png_channels, settings['
|
801
|
+
percentiles_list = _get_percentiles(png_channels, settings['normalize'][0],q2=settings['normalize'][1])
|
735
802
|
|
736
803
|
png_channels = _crop_center(png_channels, region, new_width=width, new_height=height)
|
737
804
|
|
@@ -788,6 +855,7 @@ def _measure_crop_core(index, time_ls, file, settings):
|
|
788
855
|
conn.commit()
|
789
856
|
except sqlite3.OperationalError as e:
|
790
857
|
print(f"SQLite error: {e}", flush=True)
|
858
|
+
traceback.print_exc()
|
791
859
|
|
792
860
|
if settings['plot']:
|
793
861
|
_plot_cropped_arrays(png_channels)
|
@@ -818,15 +886,14 @@ def _measure_crop_core(index, time_ls, file, settings):
|
|
818
886
|
average_time = np.mean(time_ls) if len(time_ls) > 0 else 0
|
819
887
|
return average_time, cells
|
820
888
|
|
821
|
-
|
822
|
-
def measure_crop(settings
|
889
|
+
#@log_function_call
|
890
|
+
def measure_crop(settings):
|
891
|
+
|
823
892
|
"""
|
824
893
|
Measure the crop of an image based on the provided settings.
|
825
894
|
|
826
895
|
Args:
|
827
896
|
settings (dict): The settings for measuring the crop.
|
828
|
-
annotation_settings (dict): The annotation settings.
|
829
|
-
advanced_settings (dict): The advanced settings.
|
830
897
|
|
831
898
|
Returns:
|
832
899
|
None
|
@@ -845,19 +912,6 @@ def measure_crop(settings, annotation_settings, advanced_settings):
|
|
845
912
|
from .plot import _save_scimg_plot
|
846
913
|
from .utils import _list_endpoint_subdirectories, _generate_representative_images
|
847
914
|
|
848
|
-
settings = {**settings, **annotation_settings, **advanced_settings}
|
849
|
-
|
850
|
-
dirname = os.path.dirname(settings['input_folder'])
|
851
|
-
settings_df = pd.DataFrame(list(settings.items()), columns=['Key', 'Value'])
|
852
|
-
settings_csv = os.path.join(dirname,'settings','measure_crop_settings.csv')
|
853
|
-
os.makedirs(os.path.join(dirname,'settings'), exist_ok=True)
|
854
|
-
settings_df.to_csv(settings_csv, index=False)
|
855
|
-
|
856
|
-
if settings['timelapse_objects'] == 'nucleus':
|
857
|
-
if not settings['cell_mask_dim'] is None:
|
858
|
-
tlo = settings['timelapse_objects']
|
859
|
-
print(f'timelapse object:{tlo}, cells will be relabeled to nucleus labels to track cells.')
|
860
|
-
|
861
915
|
#general settings
|
862
916
|
settings['merge_edge_pathogen_cells'] = True
|
863
917
|
settings['radial_dist'] = True
|
@@ -866,6 +920,26 @@ def measure_crop(settings, annotation_settings, advanced_settings):
|
|
866
920
|
settings['homogeneity'] = True
|
867
921
|
settings['homogeneity_distances'] = [8,16,32]
|
868
922
|
settings['save_arrays'] = False
|
923
|
+
|
924
|
+
settings['dialate_pngs'] = False
|
925
|
+
settings['dialate_png_ratios'] = [0.2]
|
926
|
+
settings['timelapse'] = False
|
927
|
+
settings['representative_images'] = False
|
928
|
+
settings['timelapse_objects'] = 'cell'
|
929
|
+
settings['max_workers'] = os.cpu_count()-2
|
930
|
+
settings['experiment'] = 'test'
|
931
|
+
settings['cells'] = 'HeLa'
|
932
|
+
settings['cell_loc'] = None
|
933
|
+
settings['pathogens'] = ['ME49Dku80WT', 'ME49Dku80dgra8:GRA8', 'ME49Dku80dgra8', 'ME49Dku80TKO']
|
934
|
+
settings['pathogen_loc'] = [['c1', 'c2', 'c3', 'c4', 'c5', 'c6'], ['c7', 'c8', 'c9', 'c10', 'c11', 'c12'], ['c13', 'c14', 'c15', 'c16', 'c17', 'c18'], ['c19', 'c20', 'c21', 'c22', 'c23', 'c24']]
|
935
|
+
settings['treatments'] = ['BR1', 'BR2', 'BR3']
|
936
|
+
settings['treatment_loc'] = [['c1', 'c2', 'c7', 'c8', 'c13', 'c14', 'c19', 'c20'], ['c3', 'c4', 'c9', 'c10', 'c15', 'c16', 'c21', 'c22'], ['c5', 'c6', 'c11', 'c12', 'c17', 'c18', 'c23', 'c24']]
|
937
|
+
settings['channel_of_interest'] = 2
|
938
|
+
settings['compartments'] = ['pathogen', 'cytoplasm']
|
939
|
+
settings['measurement'] = 'mean_intensity'
|
940
|
+
settings['nr_imgs'] = 32
|
941
|
+
settings['um_per_pixel'] = 0.1
|
942
|
+
settings['center_crop'] = True
|
869
943
|
|
870
944
|
if settings['cell_mask_dim'] is None:
|
871
945
|
settings['include_uninfected'] = True
|
@@ -878,7 +952,18 @@ def measure_crop(settings, annotation_settings, advanced_settings):
|
|
878
952
|
else:
|
879
953
|
settings['cytoplasm'] = False
|
880
954
|
|
881
|
-
settings
|
955
|
+
#settings = {**settings, **annotation_settings, **advanced_settings}
|
956
|
+
|
957
|
+
dirname = os.path.dirname(settings['input_folder'])
|
958
|
+
settings_df = pd.DataFrame(list(settings.items()), columns=['Key', 'Value'])
|
959
|
+
settings_csv = os.path.join(dirname,'settings','measure_crop_settings.csv')
|
960
|
+
os.makedirs(os.path.join(dirname,'settings'), exist_ok=True)
|
961
|
+
settings_df.to_csv(settings_csv, index=False)
|
962
|
+
|
963
|
+
if settings['timelapse_objects'] == 'nucleus':
|
964
|
+
if not settings['cell_mask_dim'] is None:
|
965
|
+
tlo = settings['timelapse_objects']
|
966
|
+
print(f'timelapse object:{tlo}, cells will be relabeled to nucleus labels to track cells.')
|
882
967
|
|
883
968
|
int_setting_keys = ['cell_mask_dim', 'nucleus_mask_dim', 'pathogen_mask_dim', 'cell_min_size', 'nucleus_min_size', 'pathogen_min_size', 'cytoplasm_min_size']
|
884
969
|
|
@@ -922,49 +1007,49 @@ def measure_crop(settings, annotation_settings, advanced_settings):
|
|
922
1007
|
time_left = (((files_to_process-files_processed)*average_time)/max_workers)/60
|
923
1008
|
print(f'Progress: {files_processed}/{files_to_process} Time/img {average_time:.3f}sec, Time Remaining {time_left:.3f} min.', end='\r', flush=True)
|
924
1009
|
result.get()
|
925
|
-
|
926
|
-
if settings['
|
927
|
-
|
928
|
-
|
929
|
-
|
930
|
-
|
931
|
-
|
932
|
-
|
933
|
-
|
934
|
-
|
935
|
-
|
936
|
-
|
937
|
-
|
938
|
-
|
939
|
-
|
940
|
-
|
941
|
-
|
942
|
-
|
943
|
-
|
1010
|
+
|
1011
|
+
if settings['representative_images']:
|
1012
|
+
if settings['save_png']:
|
1013
|
+
img_fldr = os.path.join(os.path.dirname(settings['input_folder']), 'data')
|
1014
|
+
sc_img_fldrs = _list_endpoint_subdirectories(img_fldr)
|
1015
|
+
|
1016
|
+
for i, well_src in enumerate(sc_img_fldrs):
|
1017
|
+
if len(os.listdir(well_src)) < 16:
|
1018
|
+
nr_imgs = len(os.listdir(well_src))
|
1019
|
+
standardize = False
|
1020
|
+
else:
|
1021
|
+
nr_imgs = 16
|
1022
|
+
standardize = True
|
1023
|
+
try:
|
1024
|
+
all_folders = len(sc_img_fldrs)
|
1025
|
+
_save_scimg_plot(src=well_src, nr_imgs=nr_imgs, channel_indices=settings['png_dims'], um_per_pixel=0.1, scale_bar_length_um=10, standardize=standardize, fontsize=12, show_filename=True, channel_names=['red','green','blue'], dpi=300, plot=False, i=i, all_folders=all_folders)
|
1026
|
+
|
1027
|
+
except Exception as e:
|
1028
|
+
print(f"Unable to generate figure for folder {well_src}: {e}", end='\r', flush=True)
|
1029
|
+
#traceback.print_exc()
|
944
1030
|
|
945
1031
|
if settings['save_measurements']:
|
946
|
-
|
947
|
-
|
948
|
-
|
949
|
-
|
950
|
-
|
951
|
-
|
952
|
-
|
953
|
-
|
954
|
-
|
955
|
-
|
956
|
-
|
957
|
-
|
958
|
-
|
959
|
-
|
960
|
-
|
961
|
-
|
962
|
-
|
963
|
-
|
964
|
-
|
965
|
-
|
966
|
-
|
967
|
-
channel_names=None)
|
1032
|
+
db_path = os.path.join(os.path.dirname(settings['input_folder']), 'measurements', 'measurements.db')
|
1033
|
+
channel_indices = settings['png_dims']
|
1034
|
+
channel_indices = [min(value, 2) for value in channel_indices]
|
1035
|
+
_generate_representative_images(db_path,
|
1036
|
+
cells=settings['cells'],
|
1037
|
+
cell_loc=settings['cell_loc'],
|
1038
|
+
pathogens=settings['pathogens'],
|
1039
|
+
pathogen_loc=settings['pathogen_loc'],
|
1040
|
+
treatments=settings['treatments'],
|
1041
|
+
treatment_loc=settings['treatment_loc'],
|
1042
|
+
channel_of_interest=settings['channel_of_interest'],
|
1043
|
+
compartments = settings['compartments'],
|
1044
|
+
measurement = settings['measurement'],
|
1045
|
+
nr_imgs=settings['nr_imgs'],
|
1046
|
+
channel_indices=channel_indices,
|
1047
|
+
um_per_pixel=settings['um_per_pixel'],
|
1048
|
+
scale_bar_length_um=10,
|
1049
|
+
plot=False,
|
1050
|
+
fontsize=12,
|
1051
|
+
show_filename=True,
|
1052
|
+
channel_names=None)
|
968
1053
|
|
969
1054
|
if settings['timelapse']:
|
970
1055
|
if settings['timelapse_objects'] == 'nucleus':
|
@@ -973,7 +1058,7 @@ def measure_crop(settings, annotation_settings, advanced_settings):
|
|
973
1058
|
object_types = ['nucleus','pathogen','cell']
|
974
1059
|
_timelapse_masks_to_gif(folder_path, mask_channels, object_types)
|
975
1060
|
|
976
|
-
if settings['save_png']:
|
1061
|
+
#if settings['save_png']:
|
977
1062
|
img_fldr = os.path.join(os.path.dirname(settings['input_folder']), 'data')
|
978
1063
|
sc_img_fldrs = _list_endpoint_subdirectories(img_fldr)
|
979
1064
|
_scmovie(sc_img_fldrs)
|
@@ -1006,3 +1091,18 @@ def generate_cellpose_train_set(folders, dst, min_objects=5):
|
|
1006
1091
|
shutil.copy(img_path, new_img)
|
1007
1092
|
except Exception as e:
|
1008
1093
|
print(f"Error copying {path} to {new_mask}: {e}")
|
1094
|
+
|
1095
|
+
def get_object_counts(src):
|
1096
|
+
database_path = os.path.join(src, 'measurements/measurements.db')
|
1097
|
+
# Connect to the SQLite database
|
1098
|
+
conn = sqlite3.connect(database_path)
|
1099
|
+
# Read the table into a pandas DataFrame
|
1100
|
+
df = pd.read_sql_query("SELECT * FROM object_counts", conn)
|
1101
|
+
# Group by 'count_type' and calculate the sum of 'object_count' and the average 'object_count' per 'file_name'
|
1102
|
+
grouped_df = df.groupby('count_type').agg(
|
1103
|
+
total_object_count=('object_count', 'sum'),
|
1104
|
+
avg_object_count_per_file_name=('object_count', 'mean')
|
1105
|
+
).reset_index()
|
1106
|
+
# Close the database connection
|
1107
|
+
conn.close()
|
1108
|
+
return grouped_df
|
Binary file
|
spacr/old_code.py
CHANGED
@@ -103,7 +103,7 @@ def run_mask_gui(q):
|
|
103
103
|
except Exception as e:
|
104
104
|
q.put(f"Error during processing: {e}\n")
|
105
105
|
|
106
|
-
|
106
|
+
#@log_function_call
|
107
107
|
def main_thread_update_function(root, q, fig_queue, canvas_widget, progress_label):
|
108
108
|
try:
|
109
109
|
while not q.empty():
|
@@ -287,4 +287,72 @@ def _extract_filename_metadata(filenames, src, images_by_key, regular_expression
|
|
287
287
|
print(f"Filename {filename} did not match provided regex")
|
288
288
|
continue
|
289
289
|
|
290
|
-
return images_by_key
|
290
|
+
return images_by_key
|
291
|
+
|
292
|
+
def compare_cellpose_masks_v1(src, verbose=False, save=False):
|
293
|
+
|
294
|
+
from .io import _read_mask
|
295
|
+
from .plot import visualize_masks, plot_comparison_results, visualize_cellpose_masks
|
296
|
+
from .utils import extract_boundaries, boundary_f1_score, compute_segmentation_ap, jaccard_index
|
297
|
+
|
298
|
+
import os
|
299
|
+
import numpy as np
|
300
|
+
from skimage.measure import label
|
301
|
+
|
302
|
+
# Collect all subdirectories in src
|
303
|
+
dirs = [os.path.join(src, d) for d in os.listdir(src) if os.path.isdir(os.path.join(src, d))]
|
304
|
+
|
305
|
+
dirs.sort() # Optional: sort directories if needed
|
306
|
+
|
307
|
+
# Get common files in all directories
|
308
|
+
common_files = set(os.listdir(dirs[0]))
|
309
|
+
for d in dirs[1:]:
|
310
|
+
common_files.intersection_update(os.listdir(d))
|
311
|
+
common_files = list(common_files)
|
312
|
+
|
313
|
+
results = []
|
314
|
+
conditions = [os.path.basename(d) for d in dirs]
|
315
|
+
|
316
|
+
for index, filename in enumerate(common_files):
|
317
|
+
print(f'Processing image {index+1}/{len(common_files)}', end='\r', flush=True)
|
318
|
+
paths = [os.path.join(d, filename) for d in dirs]
|
319
|
+
|
320
|
+
# Check if file exists in all directories
|
321
|
+
if not all(os.path.exists(path) for path in paths):
|
322
|
+
print(f'Skipping {filename} as it is not present in all directories.')
|
323
|
+
continue
|
324
|
+
|
325
|
+
masks = [_read_mask(path) for path in paths]
|
326
|
+
boundaries = [extract_boundaries(mask) for mask in masks]
|
327
|
+
|
328
|
+
if verbose:
|
329
|
+
visualize_cellpose_masks(masks, titles=conditions, comparison_title=f"Masks Comparison for {filename}", save=save, src=src)
|
330
|
+
|
331
|
+
# Initialize data structure for results
|
332
|
+
file_results = {'filename': filename}
|
333
|
+
|
334
|
+
# Compare each mask with each other
|
335
|
+
for i in range(len(masks)):
|
336
|
+
for j in range(i + 1, len(masks)):
|
337
|
+
condition_i = conditions[i]
|
338
|
+
condition_j = conditions[j]
|
339
|
+
mask_i = masks[i]
|
340
|
+
mask_j = masks[j]
|
341
|
+
|
342
|
+
# Compute metrics
|
343
|
+
boundary_f1 = boundary_f1_score(mask_i, mask_j)
|
344
|
+
jaccard = jaccard_index(mask_i, mask_j)
|
345
|
+
average_precision = compute_segmentation_ap(mask_i, mask_j)
|
346
|
+
|
347
|
+
# Store results
|
348
|
+
file_results[f'jaccard_{condition_i}_{condition_j}'] = jaccard
|
349
|
+
file_results[f'boundary_f1_{condition_i}_{condition_j}'] = boundary_f1
|
350
|
+
file_results[f'average_precision_{condition_i}_{condition_j}'] = average_precision
|
351
|
+
|
352
|
+
results.append(file_results)
|
353
|
+
|
354
|
+
fig = plot_comparison_results(results)
|
355
|
+
|
356
|
+
save_results_and_figure(src, fig, results)
|
357
|
+
|
358
|
+
return results, fig
|