spacr 0.2.3__py3-none-any.whl → 0.2.5__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- spacr/app_annotate.py +3 -4
- spacr/core.py +100 -282
- spacr/gui.py +20 -38
- spacr/gui_core.py +406 -499
- spacr/gui_elements.py +395 -60
- spacr/gui_utils.py +393 -73
- spacr/io.py +130 -50
- spacr/measure.py +199 -154
- spacr/plot.py +108 -42
- spacr/resources/font/open_sans/OFL.txt +93 -0
- spacr/resources/font/open_sans/OpenSans-Italic-VariableFont_wdth,wght.ttf +0 -0
- spacr/resources/font/open_sans/OpenSans-VariableFont_wdth,wght.ttf +0 -0
- spacr/resources/font/open_sans/README.txt +100 -0
- spacr/resources/font/open_sans/static/OpenSans-Bold.ttf +0 -0
- spacr/resources/font/open_sans/static/OpenSans-BoldItalic.ttf +0 -0
- spacr/resources/font/open_sans/static/OpenSans-ExtraBold.ttf +0 -0
- spacr/resources/font/open_sans/static/OpenSans-ExtraBoldItalic.ttf +0 -0
- spacr/resources/font/open_sans/static/OpenSans-Italic.ttf +0 -0
- spacr/resources/font/open_sans/static/OpenSans-Light.ttf +0 -0
- spacr/resources/font/open_sans/static/OpenSans-LightItalic.ttf +0 -0
- spacr/resources/font/open_sans/static/OpenSans-Medium.ttf +0 -0
- spacr/resources/font/open_sans/static/OpenSans-MediumItalic.ttf +0 -0
- spacr/resources/font/open_sans/static/OpenSans-Regular.ttf +0 -0
- spacr/resources/font/open_sans/static/OpenSans-SemiBold.ttf +0 -0
- spacr/resources/font/open_sans/static/OpenSans-SemiBoldItalic.ttf +0 -0
- spacr/resources/font/open_sans/static/OpenSans_Condensed-Bold.ttf +0 -0
- spacr/resources/font/open_sans/static/OpenSans_Condensed-BoldItalic.ttf +0 -0
- spacr/resources/font/open_sans/static/OpenSans_Condensed-ExtraBold.ttf +0 -0
- spacr/resources/font/open_sans/static/OpenSans_Condensed-ExtraBoldItalic.ttf +0 -0
- spacr/resources/font/open_sans/static/OpenSans_Condensed-Italic.ttf +0 -0
- spacr/resources/font/open_sans/static/OpenSans_Condensed-Light.ttf +0 -0
- spacr/resources/font/open_sans/static/OpenSans_Condensed-LightItalic.ttf +0 -0
- spacr/resources/font/open_sans/static/OpenSans_Condensed-Medium.ttf +0 -0
- spacr/resources/font/open_sans/static/OpenSans_Condensed-MediumItalic.ttf +0 -0
- spacr/resources/font/open_sans/static/OpenSans_Condensed-Regular.ttf +0 -0
- spacr/resources/font/open_sans/static/OpenSans_Condensed-SemiBold.ttf +0 -0
- spacr/resources/font/open_sans/static/OpenSans_Condensed-SemiBoldItalic.ttf +0 -0
- spacr/resources/font/open_sans/static/OpenSans_SemiCondensed-Bold.ttf +0 -0
- spacr/resources/font/open_sans/static/OpenSans_SemiCondensed-BoldItalic.ttf +0 -0
- spacr/resources/font/open_sans/static/OpenSans_SemiCondensed-ExtraBold.ttf +0 -0
- spacr/resources/font/open_sans/static/OpenSans_SemiCondensed-ExtraBoldItalic.ttf +0 -0
- spacr/resources/font/open_sans/static/OpenSans_SemiCondensed-Italic.ttf +0 -0
- spacr/resources/font/open_sans/static/OpenSans_SemiCondensed-Light.ttf +0 -0
- spacr/resources/font/open_sans/static/OpenSans_SemiCondensed-LightItalic.ttf +0 -0
- spacr/resources/font/open_sans/static/OpenSans_SemiCondensed-Medium.ttf +0 -0
- spacr/resources/font/open_sans/static/OpenSans_SemiCondensed-MediumItalic.ttf +0 -0
- spacr/resources/font/open_sans/static/OpenSans_SemiCondensed-Regular.ttf +0 -0
- spacr/resources/font/open_sans/static/OpenSans_SemiCondensed-SemiBold.ttf +0 -0
- spacr/resources/font/open_sans/static/OpenSans_SemiCondensed-SemiBoldItalic.ttf +0 -0
- spacr/resources/icons/logo.pdf +2786 -6
- spacr/resources/icons/logo_spacr.png +0 -0
- spacr/resources/icons/logo_spacr_1.png +0 -0
- spacr/settings.py +12 -87
- spacr/utils.py +45 -10
- {spacr-0.2.3.dist-info → spacr-0.2.5.dist-info}/METADATA +5 -1
- spacr-0.2.5.dist-info/RECORD +100 -0
- spacr-0.2.3.dist-info/RECORD +0 -58
- {spacr-0.2.3.dist-info → spacr-0.2.5.dist-info}/LICENSE +0 -0
- {spacr-0.2.3.dist-info → spacr-0.2.5.dist-info}/WHEEL +0 -0
- {spacr-0.2.3.dist-info → spacr-0.2.5.dist-info}/entry_points.txt +0 -0
- {spacr-0.2.3.dist-info → spacr-0.2.5.dist-info}/top_level.txt +0 -0
spacr/measure.py
CHANGED
@@ -13,6 +13,8 @@ from skimage.feature import graycomatrix, graycoprops
|
|
13
13
|
from mahotas.features import zernike_moments
|
14
14
|
from skimage import morphology, measure, filters
|
15
15
|
from skimage.util import img_as_bool
|
16
|
+
import matplotlib.pyplot as plt
|
17
|
+
from math import ceil, sqrt
|
16
18
|
|
17
19
|
from .logger import log_function_call
|
18
20
|
|
@@ -582,6 +584,113 @@ def _intensity_measurements(cell_mask, nucleus_mask, pathogen_mask, cytoplasm_ma
|
|
582
584
|
|
583
585
|
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)
|
584
586
|
|
587
|
+
def save_and_add_image_to_grid(png_channels, img_path, grid, plot=False):
|
588
|
+
"""
|
589
|
+
Add an image to a grid and save it as PNG.
|
590
|
+
|
591
|
+
Args:
|
592
|
+
png_channels (ndarray): The array representing the image channels.
|
593
|
+
img_path (str): The path to save the image as PNG.
|
594
|
+
grid (list): The grid of images to be plotted later.
|
595
|
+
|
596
|
+
Returns:
|
597
|
+
grid (list): Updated grid with the new image added.
|
598
|
+
"""
|
599
|
+
|
600
|
+
# Save the image as a PNG
|
601
|
+
cv2.imwrite(img_path, png_channels)
|
602
|
+
|
603
|
+
if plot:
|
604
|
+
|
605
|
+
# Ensure the image is in uint8 format for cv2 functions
|
606
|
+
if png_channels.dtype == np.uint16:
|
607
|
+
png_channels = (png_channels / 256).astype(np.uint8)
|
608
|
+
|
609
|
+
# Get the filename without the extension
|
610
|
+
filename = os.path.splitext(os.path.basename(img_path))[0]
|
611
|
+
|
612
|
+
# Add the label to the image
|
613
|
+
#labeled_image = cv2.putText(png_channels.copy(), filename, (10, 30),
|
614
|
+
# cv2.FONT_HERSHEY_SIMPLEX, 1, (255, 255, 255), 2, cv2.LINE_AA)
|
615
|
+
|
616
|
+
# Add the labeled image to the grid
|
617
|
+
grid.append(png_channels)
|
618
|
+
|
619
|
+
return grid
|
620
|
+
|
621
|
+
def img_list_to_grid(grid, titles=None):
|
622
|
+
"""
|
623
|
+
Plot a grid of images with optional titles.
|
624
|
+
|
625
|
+
Args:
|
626
|
+
grid (list): List of images to be plotted.
|
627
|
+
titles (list): List of titles for the images.
|
628
|
+
|
629
|
+
Returns:
|
630
|
+
fig (Figure): The matplotlib figure object containing the image grid.
|
631
|
+
"""
|
632
|
+
n_images = len(grid)
|
633
|
+
grid_size = ceil(sqrt(n_images))
|
634
|
+
|
635
|
+
fig, axs = plt.subplots(grid_size, grid_size, figsize=(15, 15), facecolor='black')
|
636
|
+
|
637
|
+
for i, ax in enumerate(axs.flat):
|
638
|
+
if i < n_images:
|
639
|
+
image = grid[i]
|
640
|
+
ax.imshow(cv2.cvtColor(image, cv2.COLOR_BGR2RGB))
|
641
|
+
ax.axis('off')
|
642
|
+
ax.set_facecolor('black')
|
643
|
+
|
644
|
+
if titles:
|
645
|
+
# Determine text size
|
646
|
+
img_height, img_width = image.shape[:2]
|
647
|
+
text_size = max(min(img_width / (len(titles[i]) * 1.5), img_height / 10), 4)
|
648
|
+
ax.text(5, 5, titles[i], color='white', fontsize=text_size, ha='left', va='top', fontweight='bold')
|
649
|
+
else:
|
650
|
+
fig.delaxes(ax)
|
651
|
+
|
652
|
+
# Adjust spacing
|
653
|
+
plt.subplots_adjust(wspace=0.05, hspace=0.05)
|
654
|
+
plt.tight_layout(pad=0.1)
|
655
|
+
return fig
|
656
|
+
|
657
|
+
def filepaths_to_database(img_paths, settings, source_folder, crop_mode):
|
658
|
+
from. utils import _map_wells_png
|
659
|
+
png_df = pd.DataFrame(img_paths, columns=['png_path'])
|
660
|
+
|
661
|
+
png_df['file_name'] = png_df['png_path'].apply(lambda x: os.path.basename(x))
|
662
|
+
|
663
|
+
parts = png_df['file_name'].apply(lambda x: pd.Series(_map_wells_png(x, timelapse=settings['timelapse'])))
|
664
|
+
|
665
|
+
columns = ['plate', 'row', 'col', 'field']
|
666
|
+
|
667
|
+
if settings['timelapse']:
|
668
|
+
columns = columns + ['time_id']
|
669
|
+
|
670
|
+
columns = columns + ['prcfo']
|
671
|
+
|
672
|
+
if crop_mode == 'cell':
|
673
|
+
columns = columns + ['cell_id']
|
674
|
+
|
675
|
+
if crop_mode == 'nucleus':
|
676
|
+
columns = columns + ['nucleus_id']
|
677
|
+
|
678
|
+
if crop_mode == 'pathogen':
|
679
|
+
columns = columns + ['pathogen_id']
|
680
|
+
|
681
|
+
if crop_mode == 'cytoplasm':
|
682
|
+
columns = columns + ['cytoplasm_id']
|
683
|
+
|
684
|
+
png_df[columns] = parts
|
685
|
+
|
686
|
+
try:
|
687
|
+
conn = sqlite3.connect(f'{source_folder}/measurements/measurements.db', timeout=5)
|
688
|
+
png_df.to_sql('png_list', conn, if_exists='append', index=False)
|
689
|
+
conn.commit()
|
690
|
+
except sqlite3.OperationalError as e:
|
691
|
+
print(f"SQLite error: {e}", flush=True)
|
692
|
+
traceback.print_exc()
|
693
|
+
|
585
694
|
#@log_function_call
|
586
695
|
def _measure_crop_core(index, time_ls, file, settings):
|
587
696
|
|
@@ -603,20 +712,15 @@ def _measure_crop_core(index, time_ls, file, settings):
|
|
603
712
|
A list of cropped images.
|
604
713
|
"""
|
605
714
|
|
606
|
-
from .io import _create_database
|
607
715
|
from .plot import _plot_cropped_arrays
|
608
716
|
from .utils import _merge_overlapping_objects, _filter_object, _relabel_parent_with_child_labels, _exclude_objects, normalize_to_dtype
|
609
|
-
from .utils import _merge_and_save_to_database, _crop_center, _find_bounding_box, _generate_names, _get_percentiles
|
610
|
-
|
717
|
+
from .utils import _merge_and_save_to_database, _crop_center, _find_bounding_box, _generate_names, _get_percentiles
|
718
|
+
|
719
|
+
figs = {}
|
720
|
+
grid = []
|
611
721
|
start = time.time()
|
612
722
|
try:
|
613
723
|
source_folder = os.path.dirname(settings['src'])
|
614
|
-
#if not os.path.basename(source_folder).endswith('merged'):
|
615
|
-
# source_folder = os.path.join(source_folder, 'merged')
|
616
|
-
# print(f'changed source_folder to {source_folder}')
|
617
|
-
|
618
|
-
#if not os.path.exists(source_folder):
|
619
|
-
# return
|
620
724
|
|
621
725
|
file_name = os.path.splitext(file)[0]
|
622
726
|
data = np.load(os.path.join(settings['src'], file))
|
@@ -628,18 +732,13 @@ def _measure_crop_core(index, time_ls, file, settings):
|
|
628
732
|
if settings['verbose']:
|
629
733
|
print(f'Converted data from {data_type_before} to {data_type}')
|
630
734
|
|
631
|
-
if settings['
|
632
|
-
os.makedirs(source_folder+'/measurements', exist_ok=True)
|
633
|
-
_create_database(source_folder+'/measurements/measurements.db')
|
634
|
-
|
635
|
-
if settings['plot_filtration']:
|
636
|
-
|
735
|
+
if settings['plot']:
|
637
736
|
if len(data.shape) == 3:
|
638
737
|
figuresize = data.shape[2]*10
|
639
738
|
else:
|
640
739
|
figuresize = 10
|
641
|
-
|
642
|
-
|
740
|
+
fig = _plot_cropped_arrays(data, file, figuresize)
|
741
|
+
figs[f'{file_name}__before_filtration'] = fig
|
643
742
|
|
644
743
|
channel_arrays = data[:, :, settings['channels']].astype(data_type)
|
645
744
|
if settings['cell_mask_dim'] is not None:
|
@@ -714,9 +813,9 @@ def _measure_crop_core(index, time_ls, file, settings):
|
|
714
813
|
if settings['cytoplasm']:
|
715
814
|
data = np.concatenate((data, cytoplasm_mask[:, :, np.newaxis]), axis=2)
|
716
815
|
|
717
|
-
if settings['
|
718
|
-
_plot_cropped_arrays(data, file, figuresize)
|
719
|
-
|
816
|
+
if settings['plot']:
|
817
|
+
fig = _plot_cropped_arrays(data, file, figuresize)
|
818
|
+
figs[f'{file_name}__after_filtration'] = fig
|
720
819
|
|
721
820
|
if settings['save_measurements']:
|
722
821
|
|
@@ -837,53 +936,12 @@ def _measure_crop_core(index, time_ls, file, settings):
|
|
837
936
|
if png_channels.shape[2] == 2:
|
838
937
|
dummy_channel = np.zeros_like(png_channels[:,:,0]) # Create a 2D zero array with same shape as one channel
|
839
938
|
png_channels = np.dstack((png_channels, dummy_channel))
|
840
|
-
|
939
|
+
grid = save_and_add_image_to_grid(png_channels, img_path, grid, settings['plot'])
|
841
940
|
else:
|
842
|
-
|
941
|
+
grid = save_and_add_image_to_grid(png_channels, img_path, grid, settings['plot'])
|
843
942
|
|
844
943
|
if len(img_paths) == len(objects_in_image):
|
845
|
-
|
846
|
-
png_df = pd.DataFrame(img_paths, columns=['png_path'])
|
847
|
-
|
848
|
-
png_df['file_name'] = png_df['png_path'].apply(lambda x: os.path.basename(x))
|
849
|
-
|
850
|
-
parts = png_df['file_name'].apply(lambda x: pd.Series(_map_wells_png(x, timelapse=settings['timelapse'])))
|
851
|
-
|
852
|
-
columns = ['plate', 'row', 'col', 'field']
|
853
|
-
|
854
|
-
if settings['timelapse']:
|
855
|
-
columns = columns + ['time_id']
|
856
|
-
|
857
|
-
columns = columns + ['prcfo']
|
858
|
-
|
859
|
-
if crop_mode == 'cell':
|
860
|
-
columns = columns + ['cell_id']
|
861
|
-
|
862
|
-
if crop_mode == 'nucleus':
|
863
|
-
columns = columns + ['nucleus_id']
|
864
|
-
|
865
|
-
if crop_mode == 'pathogen':
|
866
|
-
columns = columns + ['pathogen_id']
|
867
|
-
|
868
|
-
if crop_mode == 'cytoplasm':
|
869
|
-
columns = columns + ['cytoplasm_id']
|
870
|
-
|
871
|
-
png_df[columns] = parts
|
872
|
-
|
873
|
-
try:
|
874
|
-
conn = sqlite3.connect(f'{source_folder}/measurements/measurements.db', timeout=5)
|
875
|
-
png_df.to_sql('png_list', conn, if_exists='append', index=False)
|
876
|
-
conn.commit()
|
877
|
-
except sqlite3.OperationalError as e:
|
878
|
-
print(f"SQLite error: {e}", flush=True)
|
879
|
-
traceback.print_exc()
|
880
|
-
|
881
|
-
if settings['plot']:
|
882
|
-
if len(png_channels.shape) == 3:
|
883
|
-
figuresize = png_channels.shape[2]*10
|
884
|
-
else:
|
885
|
-
figuresize = 10
|
886
|
-
_plot_cropped_arrays(png_channels, img_name, figuresize, threshold=1)
|
944
|
+
filepaths_to_database(img_paths, settings, source_folder, crop_mode)
|
887
945
|
|
888
946
|
if settings['save_arrays']:
|
889
947
|
row_idx, col_idx = np.where(region)
|
@@ -891,21 +949,12 @@ def _measure_crop_core(index, time_ls, file, settings):
|
|
891
949
|
array_folder = f"{fldr}/region_array/"
|
892
950
|
os.makedirs(array_folder, exist_ok=True)
|
893
951
|
np.save(os.path.join(array_folder, img_name), region_array)
|
894
|
-
|
895
|
-
|
896
|
-
|
897
|
-
|
898
|
-
|
899
|
-
|
900
|
-
|
901
|
-
if not settings['save_arrays'] and not settings['save_png'] and settings['plot']:
|
902
|
-
row_idx, col_idx = np.where(region)
|
903
|
-
region_array = data[row_idx.min():row_idx.max()+1, col_idx.min():col_idx.max()+1, :]
|
904
|
-
if len(png_channels.shape) == 3:
|
905
|
-
figuresize = png_channels.shape[2]*10
|
906
|
-
else:
|
907
|
-
figuresize = 10
|
908
|
-
_plot_cropped_arrays(png_channels, file, figuresize, threshold=1)
|
952
|
+
|
953
|
+
grid = save_and_add_image_to_grid(png_channels, img_path, grid, settings['plot'])
|
954
|
+
|
955
|
+
img_paths.append(img_path)
|
956
|
+
if len(img_paths) == len(objects_in_image):
|
957
|
+
filepaths_to_database(img_paths, settings, source_folder, crop_mode)
|
909
958
|
|
910
959
|
cells = np.unique(cell_mask)
|
911
960
|
except Exception as e:
|
@@ -917,7 +966,10 @@ def _measure_crop_core(index, time_ls, file, settings):
|
|
917
966
|
duration = end-start
|
918
967
|
time_ls.append(duration)
|
919
968
|
average_time = np.mean(time_ls) if len(time_ls) > 0 else 0
|
920
|
-
|
969
|
+
if settings['plot']:
|
970
|
+
fig = img_list_to_grid(grid)
|
971
|
+
figs[f'{file_name}__pngs'] = fig
|
972
|
+
return index, average_time, cells, figs
|
921
973
|
|
922
974
|
#@log_function_call
|
923
975
|
def measure_crop(settings):
|
@@ -932,23 +984,24 @@ def measure_crop(settings):
|
|
932
984
|
None
|
933
985
|
"""
|
934
986
|
|
935
|
-
from .io import _save_settings_to_db
|
936
|
-
from .timelapse import _timelapse_masks_to_gif
|
937
|
-
from .
|
938
|
-
from .utils import _list_endpoint_subdirectories, _generate_representative_images, measure_test_mode
|
987
|
+
from .io import _save_settings_to_db, _create_database
|
988
|
+
from .timelapse import _timelapse_masks_to_gif
|
989
|
+
from .utils import measure_test_mode, print_progress
|
939
990
|
from .settings import get_measure_crop_settings
|
940
991
|
|
941
992
|
settings = get_measure_crop_settings(settings)
|
942
993
|
settings = measure_test_mode(settings)
|
943
994
|
|
944
|
-
|
945
|
-
|
946
|
-
|
947
|
-
|
995
|
+
src_fldr = settings['src']
|
996
|
+
if not os.path.basename(src_fldr).endswith('merged'):
|
997
|
+
print(f"WARNING: Source folder, settings: src: {src_fldr} should end with '/merged'")
|
998
|
+
src_fldr = os.path.join(src_fldr, 'merged')
|
999
|
+
print(f"Changed source folder to: {src_fldr}")
|
948
1000
|
|
949
|
-
|
950
|
-
|
951
|
-
|
1001
|
+
if settings['save_measurements']:
|
1002
|
+
source_folder = os.path.dirname(settings['src'])
|
1003
|
+
os.makedirs(source_folder+'/measurements', exist_ok=True)
|
1004
|
+
_create_database(source_folder+'/measurements/measurements.db')
|
952
1005
|
|
953
1006
|
if settings['cell_mask_dim'] is None:
|
954
1007
|
settings['include_uninfected'] = True
|
@@ -960,7 +1013,15 @@ def measure_crop(settings):
|
|
960
1013
|
settings['cytoplasm'] = True
|
961
1014
|
else:
|
962
1015
|
settings['cytoplasm'] = False
|
963
|
-
|
1016
|
+
|
1017
|
+
spacr_cores = int(mp.cpu_count() - 6)
|
1018
|
+
if spacr_cores <= 2:
|
1019
|
+
spacr_cores = 1
|
1020
|
+
|
1021
|
+
if settings['n_jobs'] > spacr_cores:
|
1022
|
+
print(f'Warning reserving 6 CPU cores for other processes, setting n_jobs to {spacr_cores}')
|
1023
|
+
settings['n_jobs'] = spacr_cores
|
1024
|
+
|
964
1025
|
dirname = os.path.dirname(settings['src'])
|
965
1026
|
settings_df = pd.DataFrame(list(settings.items()), columns=['Key', 'Value'])
|
966
1027
|
settings_csv = os.path.join(dirname,'settings','measure_crop_settings.csv')
|
@@ -996,81 +1057,63 @@ def measure_crop(settings):
|
|
996
1057
|
return
|
997
1058
|
|
998
1059
|
_save_settings_to_db(settings)
|
999
|
-
|
1000
1060
|
files = [f for f in os.listdir(settings['src']) if f.endswith('.npy')]
|
1001
|
-
n_jobs = settings['n_jobs']
|
1061
|
+
n_jobs = settings['n_jobs']
|
1002
1062
|
print(f'using {n_jobs} cpu cores')
|
1003
1063
|
|
1064
|
+
def job_callback(result):
|
1065
|
+
completed_jobs.add(result[0])
|
1066
|
+
process_meassure_crop_results([result], settings)
|
1067
|
+
files_processed = len(completed_jobs)
|
1068
|
+
files_to_process = len(files)
|
1069
|
+
print_progress(files_processed, files_to_process, n_jobs, time_ls=time_ls, operation_type='Measure and Crop')
|
1070
|
+
if files_processed >= files_to_process:
|
1071
|
+
pool.terminate()
|
1072
|
+
|
1004
1073
|
with mp.Manager() as manager:
|
1005
1074
|
time_ls = manager.list()
|
1075
|
+
completed_jobs = set() # Set to keep track of completed jobs
|
1076
|
+
|
1006
1077
|
with mp.Pool(n_jobs) as pool:
|
1007
|
-
|
1008
|
-
|
1009
|
-
|
1010
|
-
|
1011
|
-
|
1012
|
-
files_processed = len(time_ls)
|
1013
|
-
files_to_process = len(files)
|
1014
|
-
average_time = np.mean(time_ls) if len(time_ls) > 0 else 0
|
1015
|
-
time_left = (((files_to_process-files_processed)*average_time)/n_jobs)/60
|
1016
|
-
print(f'Progress: {files_processed}/{files_to_process} Time/img {average_time:.3f}sec, Time Remaining {time_left:.3f} min.', end='\r', flush=True)
|
1017
|
-
result.get()
|
1018
|
-
|
1019
|
-
if settings['representative_images']:
|
1020
|
-
if settings['save_png']:
|
1021
|
-
img_fldr = os.path.join(os.path.dirname(settings['src']), 'data')
|
1022
|
-
sc_img_fldrs = _list_endpoint_subdirectories(img_fldr)
|
1023
|
-
|
1024
|
-
for i, well_src in enumerate(sc_img_fldrs):
|
1025
|
-
if len(os.listdir(well_src)) < 16:
|
1026
|
-
nr_imgs = len(os.listdir(well_src))
|
1027
|
-
standardize = False
|
1028
|
-
else:
|
1029
|
-
nr_imgs = 16
|
1030
|
-
standardize = True
|
1031
|
-
try:
|
1032
|
-
all_folders = len(sc_img_fldrs)
|
1033
|
-
_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)
|
1034
|
-
|
1035
|
-
except Exception as e:
|
1036
|
-
print(f"Unable to generate figure for folder {well_src}: {e}", end='\r', flush=True)
|
1037
|
-
#traceback.print_exc()
|
1038
|
-
|
1039
|
-
if settings['save_measurements']:
|
1040
|
-
db_path = os.path.join(os.path.dirname(settings['src']), 'measurements', 'measurements.db')
|
1041
|
-
channel_indices = settings['png_dims']
|
1042
|
-
channel_indices = [min(value, 2) for value in channel_indices]
|
1043
|
-
_generate_representative_images(db_path,
|
1044
|
-
cells=settings['cells'],
|
1045
|
-
cell_loc=settings['cell_loc'],
|
1046
|
-
pathogens=settings['pathogens'],
|
1047
|
-
pathogen_loc=settings['pathogen_loc'],
|
1048
|
-
treatments=settings['treatments'],
|
1049
|
-
treatment_loc=settings['treatment_loc'],
|
1050
|
-
channel_of_interest=settings['channel_of_interest'],
|
1051
|
-
compartments = settings['compartments'],
|
1052
|
-
measurement = settings['measurement'],
|
1053
|
-
nr_imgs=settings['nr_imgs'],
|
1054
|
-
channel_indices=channel_indices,
|
1055
|
-
um_per_pixel=settings['um_per_pixel'],
|
1056
|
-
scale_bar_length_um=10,
|
1057
|
-
plot=False,
|
1058
|
-
fontsize=12,
|
1059
|
-
show_filename=True,
|
1060
|
-
channel_names=None)
|
1078
|
+
for index, file in enumerate(files):
|
1079
|
+
pool.apply_async(_measure_crop_core, args=(index, time_ls, file, settings), callback=job_callback)
|
1080
|
+
|
1081
|
+
pool.close()
|
1082
|
+
pool.join()
|
1061
1083
|
|
1062
1084
|
if settings['timelapse']:
|
1063
1085
|
if settings['timelapse_objects'] == 'nucleus':
|
1064
1086
|
folder_path = settings['src']
|
1065
|
-
mask_channels = [settings['nucleus_mask_dim'], settings['pathogen_mask_dim'],settings['cell_mask_dim']]
|
1066
|
-
object_types = ['nucleus','pathogen','cell']
|
1087
|
+
mask_channels = [settings['nucleus_mask_dim'], settings['pathogen_mask_dim'], settings['cell_mask_dim']]
|
1088
|
+
object_types = ['nucleus', 'pathogen', 'cell']
|
1067
1089
|
_timelapse_masks_to_gif(folder_path, mask_channels, object_types)
|
1068
1090
|
|
1069
|
-
#if settings['save_png']:
|
1070
|
-
img_fldr = os.path.join(os.path.dirname(settings['src']), 'data')
|
1071
|
-
sc_img_fldrs = _list_endpoint_subdirectories(img_fldr)
|
1072
|
-
_scmovie(sc_img_fldrs)
|
1073
1091
|
print("Successfully completed run")
|
1092
|
+
|
1093
|
+
def process_meassure_crop_results(partial_results, settings):
|
1094
|
+
"""
|
1095
|
+
Process the results, display, and optionally save the figures.
|
1096
|
+
|
1097
|
+
Args:
|
1098
|
+
partial_results (list): List of partial results.
|
1099
|
+
settings (dict): Settings dictionary.
|
1100
|
+
save_figures (bool): Flag to save figures or not.
|
1101
|
+
"""
|
1102
|
+
for result in partial_results:
|
1103
|
+
if result is None:
|
1104
|
+
continue
|
1105
|
+
index, avg_time, cells, figs = result
|
1106
|
+
if figs is not None:
|
1107
|
+
for key, fig in figs.items():
|
1108
|
+
part_1, part_2 = key.split('__')
|
1109
|
+
save_dir = os.path.join(os.path.dirname(settings['src']), 'results', f"{part_1}")
|
1110
|
+
os.makedirs(save_dir, exist_ok=True)
|
1111
|
+
fig_path = os.path.join(save_dir, f"{part_2}.pdf")
|
1112
|
+
fig.savefig(fig_path)
|
1113
|
+
plt.figure(fig.number)
|
1114
|
+
plt.show()
|
1115
|
+
plt.close(fig)
|
1116
|
+
result = (index, None, None, None)
|
1074
1117
|
|
1075
1118
|
def generate_cellpose_train_set(folders, dst, min_objects=5):
|
1076
1119
|
os.makedirs(dst, exist_ok=True)
|
@@ -1114,3 +1157,5 @@ def get_object_counts(src):
|
|
1114
1157
|
# Close the database connection
|
1115
1158
|
conn.close()
|
1116
1159
|
return grouped_df
|
1160
|
+
|
1161
|
+
|
spacr/plot.py
CHANGED
@@ -19,6 +19,112 @@ from IPython.display import Image as ipyimage
|
|
19
19
|
|
20
20
|
from .logger import log_function_call
|
21
21
|
|
22
|
+
def plot_image_mask_overlay(file, channels, cell_channel, nucleus_channel, pathogen_channel, figuresize=10, normalize=True, thickness=3, save_pdf=True):
|
23
|
+
"""Plot image and mask overlays."""
|
24
|
+
|
25
|
+
def _plot_merged_plot(image, outlines, outline_colors, figuresize, thickness):
|
26
|
+
"""Plot the merged plot with overlay, image channels, and masks."""
|
27
|
+
|
28
|
+
def _normalize_image(image, percentiles=(2, 98)):
|
29
|
+
"""Normalize the image to the given percentiles."""
|
30
|
+
v_min, v_max = np.percentile(image, percentiles)
|
31
|
+
image_normalized = np.clip((image - v_min) / (v_max - v_min), 0, 1)
|
32
|
+
return image_normalized
|
33
|
+
|
34
|
+
def _generate_contours(mask):
|
35
|
+
"""Generate contours for the given mask using OpenCV."""
|
36
|
+
contours, _ = cv2.findContours(mask.astype(np.uint8), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
|
37
|
+
return contours
|
38
|
+
|
39
|
+
def _apply_contours(image, mask, color, thickness):
|
40
|
+
"""Apply the contours to the RGB image for each unique label."""
|
41
|
+
unique_labels = np.unique(mask)
|
42
|
+
for label in unique_labels:
|
43
|
+
if label == 0:
|
44
|
+
continue # Skip background
|
45
|
+
label_mask = np.where(mask == label, 1, 0).astype(np.uint8)
|
46
|
+
contours = _generate_contours(label_mask)
|
47
|
+
for contour in contours:
|
48
|
+
cv2.drawContours(image, [contour], -1, mpl.colors.to_rgb(color), thickness)
|
49
|
+
return image
|
50
|
+
|
51
|
+
num_channels = image.shape[-1]
|
52
|
+
fig, ax = plt.subplots(1, num_channels + 1, figsize=(4 * figuresize, figuresize))
|
53
|
+
|
54
|
+
# Plot each channel with its corresponding outlines
|
55
|
+
for v in range(num_channels):
|
56
|
+
channel_image = image[..., v]
|
57
|
+
channel_image_normalized = _normalize_image(channel_image)
|
58
|
+
channel_image_rgb = np.dstack((channel_image_normalized, channel_image_normalized, channel_image_normalized))
|
59
|
+
|
60
|
+
for outline, color in zip(outlines, outline_colors):
|
61
|
+
channel_image_rgb = _apply_contours(channel_image_rgb, outline, color, thickness)
|
62
|
+
|
63
|
+
ax[v].imshow(channel_image_rgb)
|
64
|
+
ax[v].set_title(f'Image - Channel {v}')
|
65
|
+
|
66
|
+
# Plot the combined RGB image with all outlines
|
67
|
+
rgb_image = np.zeros((*image.shape[:2], 3), dtype=float)
|
68
|
+
rgb_channels = min(3, num_channels)
|
69
|
+
for i in range(rgb_channels):
|
70
|
+
channel_image = image[..., i]
|
71
|
+
channel_image_normalized = _normalize_image(channel_image)
|
72
|
+
rgb_image[..., i] = channel_image_normalized
|
73
|
+
|
74
|
+
for outline, color in zip(outlines, outline_colors):
|
75
|
+
rgb_image = _apply_contours(rgb_image, outline, color, thickness)
|
76
|
+
|
77
|
+
ax[-1].imshow(rgb_image)
|
78
|
+
ax[-1].set_title('Combined RGB Image')
|
79
|
+
|
80
|
+
plt.tight_layout()
|
81
|
+
|
82
|
+
# Save the figure as a PDF
|
83
|
+
if save_pdf:
|
84
|
+
pdf_dir = os.path.join(os.path.dirname(os.path.dirname(file)), 'results', 'overlay')
|
85
|
+
os.makedirs(pdf_dir, exist_ok=True)
|
86
|
+
pdf_path = os.path.join(pdf_dir, os.path.basename(file).replace('.npy', '.pdf'))
|
87
|
+
fig.savefig(pdf_path, format='pdf')
|
88
|
+
|
89
|
+
plt.show()
|
90
|
+
return fig
|
91
|
+
|
92
|
+
stack = np.load(file)
|
93
|
+
|
94
|
+
# Convert to float for normalization and ensure correct handling of both 8-bit and 16-bit arrays
|
95
|
+
if stack.dtype == np.uint16:
|
96
|
+
stack = stack.astype(np.float32)
|
97
|
+
elif stack.dtype == np.uint8:
|
98
|
+
stack = stack.astype(np.float32)
|
99
|
+
|
100
|
+
image = stack[..., channels]
|
101
|
+
outlines = []
|
102
|
+
outline_colors = []
|
103
|
+
|
104
|
+
if pathogen_channel is not None:
|
105
|
+
pathogen_mask_dim = -1 # last dimension
|
106
|
+
outlines.append(np.take(stack, pathogen_mask_dim, axis=2))
|
107
|
+
outline_colors.append('blue')
|
108
|
+
|
109
|
+
if nucleus_channel is not None:
|
110
|
+
nucleus_mask_dim = -2 if pathogen_channel is not None else -1
|
111
|
+
outlines.append(np.take(stack, nucleus_mask_dim, axis=2))
|
112
|
+
outline_colors.append('green')
|
113
|
+
|
114
|
+
if cell_channel is not None:
|
115
|
+
if nucleus_channel is not None and pathogen_channel is not None:
|
116
|
+
cell_mask_dim = -3
|
117
|
+
elif nucleus_channel is not None or pathogen_channel is not None:
|
118
|
+
cell_mask_dim = -2
|
119
|
+
else:
|
120
|
+
cell_mask_dim = -1
|
121
|
+
outlines.append(np.take(stack, cell_mask_dim, axis=2))
|
122
|
+
outline_colors.append('red')
|
123
|
+
|
124
|
+
fig = _plot_merged_plot(image=image, outlines=outlines, outline_colors=outline_colors, figuresize=figuresize, thickness=thickness)
|
125
|
+
|
126
|
+
return
|
127
|
+
|
22
128
|
def plot_masks(batch, masks, flows, cmap='inferno', figuresize=20, nr=1, file_type='.npz', print_object_number=True):
|
23
129
|
"""
|
24
130
|
Plot the masks and flows for a given batch of images.
|
@@ -802,49 +908,9 @@ def _plot_cropped_arrays(stack, filename, figuresize=20, cmap='inferno', thresho
|
|
802
908
|
fig, axs = plt.subplots(1, num_channels, figsize=(figuresize, figuresize))
|
803
909
|
for channel in range(num_channels):
|
804
910
|
plot_single_array(stack[:, :, channel], axs[channel], f'C. {channel}', plt.get_cmap(cmap))
|
805
|
-
fig.tight_layout()
|
806
|
-
plt.show()
|
807
|
-
|
808
|
-
#stop = time.time()
|
809
|
-
#duration = stop - start
|
810
|
-
#print('plot_cropped_arrays', duration)
|
911
|
+
fig.tight_layout()
|
811
912
|
print(f'{filename}')
|
812
|
-
|
813
|
-
def _plot_cropped_arrays_v1(stack, figuresize=20, cmap='inferno'):
|
814
|
-
"""
|
815
|
-
Plot cropped arrays.
|
816
|
-
|
817
|
-
Args:
|
818
|
-
stack (ndarray): The array to be plotted.
|
819
|
-
figuresize (int, optional): The size of the figure. Defaults to 20.
|
820
|
-
cmap (str, optional): The colormap to be used. Defaults to 'inferno'.
|
821
|
-
|
822
|
-
Returns:
|
823
|
-
None
|
824
|
-
"""
|
825
|
-
start = time.time()
|
826
|
-
dim = stack.shape
|
827
|
-
channel=min(dim)
|
828
|
-
if len(stack.shape) == 2:
|
829
|
-
f, a = plt.subplots(1, 1,figsize=(figuresize,figuresize))
|
830
|
-
a.imshow(stack, cmap=plt.get_cmap(cmap))
|
831
|
-
a.set_title('Channel one',size=18)
|
832
|
-
a.axis('off')
|
833
|
-
f.tight_layout()
|
834
|
-
plt.show()
|
835
|
-
if len(stack.shape) > 2:
|
836
|
-
anr = stack.shape[2]
|
837
|
-
f, a = plt.subplots(1, anr,figsize=(figuresize,figuresize))
|
838
|
-
for channel in range(anr):
|
839
|
-
a[channel].imshow(stack[:,:,channel], cmap=plt.get_cmap(cmap))
|
840
|
-
a[channel].set_title('Channel '+str(channel),size=18)
|
841
|
-
a[channel].axis('off')
|
842
|
-
f.tight_layout()
|
843
|
-
plt.show()
|
844
|
-
stop = time.time()
|
845
|
-
duration = stop - start
|
846
|
-
print('plot_cropped_arrays', duration)
|
847
|
-
return
|
913
|
+
return fig
|
848
914
|
|
849
915
|
def _visualize_and_save_timelapse_stack_with_tracks(masks, tracks_df, save, src, name, plot, filenames, object_type, mode='btrack', interactive=False):
|
850
916
|
"""
|