spacr 0.0.1__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 +6 -2
- spacr/__main__.py +0 -2
- spacr/alpha.py +807 -0
- spacr/annotate_app.py +118 -120
- spacr/chris.py +50 -0
- spacr/cli.py +25 -187
- spacr/core.py +1611 -389
- spacr/deep_spacr.py +696 -0
- spacr/foldseek.py +779 -0
- spacr/get_alfafold_structures.py +72 -0
- spacr/graph_learning.py +320 -0
- spacr/graph_learning_lap.py +84 -0
- spacr/gui.py +145 -0
- spacr/gui_2.py +90 -0
- spacr/gui_classify_app.py +187 -0
- spacr/gui_mask_app.py +149 -174
- spacr/gui_measure_app.py +116 -109
- spacr/gui_sim_app.py +0 -0
- spacr/gui_utils.py +679 -139
- spacr/io.py +620 -469
- spacr/mask_app.py +116 -9
- spacr/measure.py +178 -84
- spacr/models/cp/toxo_pv_lumen.CP_model +0 -0
- spacr/old_code.py +255 -1
- spacr/plot.py +263 -100
- spacr/sequencing.py +1130 -0
- spacr/sim.py +634 -122
- spacr/timelapse.py +343 -53
- spacr/train.py +195 -22
- spacr/umap.py +0 -689
- spacr/utils.py +1530 -188
- spacr-0.0.6.dist-info/METADATA +118 -0
- spacr-0.0.6.dist-info/RECORD +39 -0
- {spacr-0.0.1.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.1.dist-info/METADATA +0 -64
- spacr-0.0.1.dist-info/RECORD +0 -26
- spacr-0.0.1.dist-info/entry_points.txt +0 -5
- {spacr-0.0.1.dist-info → spacr-0.0.6.dist-info}/LICENSE +0 -0
- {spacr-0.0.1.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,6 +93,70 @@ def _calculate_zernike(mask, df, degree=8):
|
|
92
93
|
else:
|
93
94
|
return df
|
94
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
|
95
160
|
def _morphological_measurements(cell_mask, nucleus_mask, pathogen_mask, cytoplasm_mask, settings, zernike=True, degree=8):
|
96
161
|
"""
|
97
162
|
Calculate morphological measurements for cells, nucleus, pathogens, and cytoplasms based on the given masks.
|
@@ -435,6 +500,7 @@ def _estimate_blur(image):
|
|
435
500
|
# Compute and return the variance of the Laplacian
|
436
501
|
return lap.var()
|
437
502
|
|
503
|
+
#@log_function_call
|
438
504
|
def _intensity_measurements(cell_mask, nucleus_mask, pathogen_mask, cytoplasm_mask, channel_arrays, settings, sizes=[3, 6, 12, 24], periphery=True, outside=True):
|
439
505
|
"""
|
440
506
|
Calculate various intensity measurements for different regions in the image.
|
@@ -522,8 +588,9 @@ def _intensity_measurements(cell_mask, nucleus_mask, pathogen_mask, cytoplasm_ma
|
|
522
588
|
|
523
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)
|
524
590
|
|
525
|
-
|
591
|
+
#@log_function_call
|
526
592
|
def _measure_crop_core(index, time_ls, file, settings):
|
593
|
+
|
527
594
|
"""
|
528
595
|
Measure and crop the images based on specified settings.
|
529
596
|
|
@@ -622,9 +689,8 @@ def _measure_crop_core(index, time_ls, file, settings):
|
|
622
689
|
if settings['cytoplasm_min_size'] is not None and settings['cytoplasm_min_size'] != 0:
|
623
690
|
cytoplasm_mask = _filter_object(cytoplasm_mask, settings['cytoplasm_min_size'])
|
624
691
|
|
625
|
-
if settings['cell_mask_dim'] is not None
|
626
|
-
|
627
|
-
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'])
|
628
694
|
|
629
695
|
# Update data with the new masks
|
630
696
|
if settings['cell_mask_dim'] is not None:
|
@@ -643,6 +709,10 @@ def _measure_crop_core(index, time_ls, file, settings):
|
|
643
709
|
|
644
710
|
cell_df, nucleus_df, pathogen_df, cytoplasm_df = _morphological_measurements(cell_mask, nucleus_mask, pathogen_mask, cytoplasm_mask, settings)
|
645
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
|
+
|
646
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)
|
647
717
|
if settings['cell_mask_dim'] is not None:
|
648
718
|
cell_merged_df = _merge_and_save_to_database(cell_df, cell_intensity_df, 'cell', source_folder, file_name, settings['experiment'], settings['timelapse'])
|
@@ -656,7 +726,6 @@ def _measure_crop_core(index, time_ls, file, settings):
|
|
656
726
|
if settings['cytoplasm']:
|
657
727
|
cytoplasm_merged_df = _merge_and_save_to_database(cytoplasm_df, cytoplasm_intensity_df, 'cytoplasm', source_folder, file_name, settings['experiment'], settings['timelapse'])
|
658
728
|
|
659
|
-
|
660
729
|
if settings['save_png'] or settings['save_arrays'] or settings['plot']:
|
661
730
|
|
662
731
|
if isinstance(settings['dialate_pngs'], bool):
|
@@ -676,7 +745,6 @@ def _measure_crop_core(index, time_ls, file, settings):
|
|
676
745
|
crop_ls = settings['crop_mode']
|
677
746
|
size_ls = settings['png_size']
|
678
747
|
for crop_idx, crop_mode in enumerate(crop_ls):
|
679
|
-
print(crop_idx, crop_mode)
|
680
748
|
width, height = size_ls[crop_idx]
|
681
749
|
if crop_mode == 'cell':
|
682
750
|
crop_mask = cell_mask.copy()
|
@@ -730,7 +798,7 @@ def _measure_crop_core(index, time_ls, file, settings):
|
|
730
798
|
png_channels = data[:, :, settings['png_dims']].astype(data_type)
|
731
799
|
|
732
800
|
if settings['normalize_by'] == 'fov':
|
733
|
-
percentiles_list = _get_percentiles(png_channels, settings['
|
801
|
+
percentiles_list = _get_percentiles(png_channels, settings['normalize'][0],q2=settings['normalize'][1])
|
734
802
|
|
735
803
|
png_channels = _crop_center(png_channels, region, new_width=width, new_height=height)
|
736
804
|
|
@@ -787,6 +855,7 @@ def _measure_crop_core(index, time_ls, file, settings):
|
|
787
855
|
conn.commit()
|
788
856
|
except sqlite3.OperationalError as e:
|
789
857
|
print(f"SQLite error: {e}", flush=True)
|
858
|
+
traceback.print_exc()
|
790
859
|
|
791
860
|
if settings['plot']:
|
792
861
|
_plot_cropped_arrays(png_channels)
|
@@ -817,38 +886,32 @@ def _measure_crop_core(index, time_ls, file, settings):
|
|
817
886
|
average_time = np.mean(time_ls) if len(time_ls) > 0 else 0
|
818
887
|
return average_time, cells
|
819
888
|
|
820
|
-
|
821
|
-
def measure_crop(settings
|
889
|
+
#@log_function_call
|
890
|
+
def measure_crop(settings):
|
891
|
+
|
822
892
|
"""
|
823
893
|
Measure the crop of an image based on the provided settings.
|
824
894
|
|
825
895
|
Args:
|
826
896
|
settings (dict): The settings for measuring the crop.
|
827
|
-
annotation_settings (dict): The annotation settings.
|
828
|
-
advanced_settings (dict): The advanced settings.
|
829
897
|
|
830
898
|
Returns:
|
831
899
|
None
|
832
900
|
"""
|
901
|
+
|
902
|
+
if settings.get('test_mode', False):
|
903
|
+
if not os.basename(settings['src']) == 'test':
|
904
|
+
src = os.path.join(src, 'test')
|
905
|
+
settings['src'] = src
|
906
|
+
print(f'Changed source folder to {src} for test mode')
|
907
|
+
else:
|
908
|
+
print(f'Test mode enabled, using source folder {settings["src"]}')
|
833
909
|
|
834
910
|
from .io import _save_settings_to_db
|
835
911
|
from .timelapse import _timelapse_masks_to_gif, _scmovie
|
836
912
|
from .plot import _save_scimg_plot
|
837
913
|
from .utils import _list_endpoint_subdirectories, _generate_representative_images
|
838
914
|
|
839
|
-
settings = {**settings, **annotation_settings, **advanced_settings}
|
840
|
-
|
841
|
-
dirname = os.path.dirname(settings['input_folder'])
|
842
|
-
settings_df = pd.DataFrame(list(settings.items()), columns=['Key', 'Value'])
|
843
|
-
settings_csv = os.path.join(dirname,'settings','measure_crop_settings.csv')
|
844
|
-
os.makedirs(os.path.join(dirname,'settings'), exist_ok=True)
|
845
|
-
settings_df.to_csv(settings_csv, index=False)
|
846
|
-
|
847
|
-
if settings['timelapse_objects'] == 'nucleus':
|
848
|
-
if not settings['cell_mask_dim'] is None:
|
849
|
-
tlo = settings['timelapse_objects']
|
850
|
-
print(f'timelapse object:{tlo}, cells will be relabeled to nucleus labels to track cells.')
|
851
|
-
|
852
915
|
#general settings
|
853
916
|
settings['merge_edge_pathogen_cells'] = True
|
854
917
|
settings['radial_dist'] = True
|
@@ -857,6 +920,26 @@ def measure_crop(settings, annotation_settings, advanced_settings):
|
|
857
920
|
settings['homogeneity'] = True
|
858
921
|
settings['homogeneity_distances'] = [8,16,32]
|
859
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
|
860
943
|
|
861
944
|
if settings['cell_mask_dim'] is None:
|
862
945
|
settings['include_uninfected'] = True
|
@@ -869,7 +952,18 @@ def measure_crop(settings, annotation_settings, advanced_settings):
|
|
869
952
|
else:
|
870
953
|
settings['cytoplasm'] = False
|
871
954
|
|
872
|
-
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.')
|
873
967
|
|
874
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']
|
875
969
|
|
@@ -913,64 +1007,49 @@ def measure_crop(settings, annotation_settings, advanced_settings):
|
|
913
1007
|
time_left = (((files_to_process-files_processed)*average_time)/max_workers)/60
|
914
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)
|
915
1009
|
result.get()
|
916
|
-
|
917
|
-
|
918
|
-
|
919
|
-
|
920
|
-
|
921
|
-
|
922
|
-
|
923
|
-
|
924
|
-
|
925
|
-
|
926
|
-
|
927
|
-
|
928
|
-
|
929
|
-
|
930
|
-
|
931
|
-
|
932
|
-
|
933
|
-
|
934
|
-
|
935
|
-
|
936
|
-
for i, well_src in enumerate(sc_img_fldrs):
|
937
|
-
if len(os.listdir(well_src)) < 16:
|
938
|
-
nr_imgs = len(os.listdir(well_src))
|
939
|
-
standardize = False
|
940
|
-
else:
|
941
|
-
nr_imgs = 16
|
942
|
-
standardize = True
|
943
|
-
try:
|
944
|
-
all_folders = len(sc_img_fldrs)
|
945
|
-
_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)
|
946
|
-
|
947
|
-
except Exception as e:
|
948
|
-
print(f"Unable to generate figure for folder {well_src}: {e}", end='\r', flush=True)
|
949
|
-
#traceback.print_exc()
|
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()
|
950
1030
|
|
951
1031
|
if settings['save_measurements']:
|
952
|
-
|
953
|
-
|
954
|
-
|
955
|
-
|
956
|
-
|
957
|
-
|
958
|
-
|
959
|
-
|
960
|
-
|
961
|
-
|
962
|
-
|
963
|
-
|
964
|
-
|
965
|
-
|
966
|
-
|
967
|
-
|
968
|
-
|
969
|
-
|
970
|
-
|
971
|
-
|
972
|
-
|
973
|
-
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)
|
974
1053
|
|
975
1054
|
if settings['timelapse']:
|
976
1055
|
if settings['timelapse_objects'] == 'nucleus':
|
@@ -979,11 +1058,11 @@ def measure_crop(settings, annotation_settings, advanced_settings):
|
|
979
1058
|
object_types = ['nucleus','pathogen','cell']
|
980
1059
|
_timelapse_masks_to_gif(folder_path, mask_channels, object_types)
|
981
1060
|
|
982
|
-
if settings['save_png']:
|
1061
|
+
#if settings['save_png']:
|
983
1062
|
img_fldr = os.path.join(os.path.dirname(settings['input_folder']), 'data')
|
984
1063
|
sc_img_fldrs = _list_endpoint_subdirectories(img_fldr)
|
985
1064
|
_scmovie(sc_img_fldrs)
|
986
|
-
|
1065
|
+
print("Successfully completed run")
|
987
1066
|
|
988
1067
|
def generate_cellpose_train_set(folders, dst, min_objects=5):
|
989
1068
|
os.makedirs(dst, exist_ok=True)
|
@@ -1012,3 +1091,18 @@ def generate_cellpose_train_set(folders, dst, min_objects=5):
|
|
1012
1091
|
shutil.copy(img_path, new_img)
|
1013
1092
|
except Exception as e:
|
1014
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
|