spacr 1.0.5__py3-none-any.whl → 1.0.7__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 +0 -9
- spacr/app_annotate.py +1 -1
- spacr/core.py +1 -357
- spacr/deep_spacr.py +1 -3
- spacr/gui.py +30 -15
- spacr/gui_core.py +21 -28
- spacr/gui_elements.py +3 -1677
- spacr/gui_utils.py +3 -7
- spacr/io.py +14 -4
- spacr/plot.py +57 -0
- spacr/resources/data/lopit.csv +3833 -3833
- spacr/resources/icons/activation.png +0 -0
- spacr/resources/icons/activation_heatmap.png +0 -0
- spacr/resources/icons/flow_chart_v3.png +0 -0
- spacr/resources/icons/logo_spacr.png +0 -0
- spacr/resources/icons/logo_spacr_v1.png +0 -0
- spacr/resources/icons/logo_v1.pdf +2786 -6
- spacr/resources/icons/logo_v2.pdf +274 -0
- spacr/resources/icons/logo_v2_dark.pdf +327 -0
- spacr/resources/icons/logo_v2_dark.png +0 -0
- spacr/resources/icons/logo_v2_white.pdf +386 -0
- spacr/resources/icons/logo_v2_white.png +0 -0
- spacr/settings.py +2 -2
- spacr/spacr_cellpose.py +0 -1
- spacr/utils.py +51 -1
- {spacr-1.0.5.dist-info → spacr-1.0.7.dist-info}/METADATA +1 -1
- {spacr-1.0.5.dist-info → spacr-1.0.7.dist-info}/RECORD +31 -22
- {spacr-1.0.5.dist-info → spacr-1.0.7.dist-info}/entry_points.txt +0 -3
- {spacr-1.0.5.dist-info → spacr-1.0.7.dist-info}/LICENSE +0 -0
- {spacr-1.0.5.dist-info → spacr-1.0.7.dist-info}/WHEEL +0 -0
- {spacr-1.0.5.dist-info → spacr-1.0.7.dist-info}/top_level.txt +0 -0
spacr/__init__.py
CHANGED
@@ -15,21 +15,17 @@ from . import utils
|
|
15
15
|
from . import settings
|
16
16
|
from . import plot
|
17
17
|
from . import measure
|
18
|
-
from . import sim
|
19
18
|
from . import sequencing
|
20
19
|
from . import timelapse
|
21
20
|
from . import deep_spacr
|
22
|
-
from . import app_annotate
|
23
21
|
from . import gui_utils
|
24
22
|
from . import gui_elements
|
25
23
|
from . import gui_core
|
26
24
|
from . import gui
|
27
|
-
from . import app_make_masks
|
28
25
|
from . import app_mask
|
29
26
|
from . import app_measure
|
30
27
|
from . import app_classify
|
31
28
|
from . import app_sequencing
|
32
|
-
from . import app_umap
|
33
29
|
from . import submodules
|
34
30
|
from . import ml
|
35
31
|
from . import toxo
|
@@ -44,23 +40,18 @@ __all__ = [
|
|
44
40
|
"settings",
|
45
41
|
"plot",
|
46
42
|
"measure",
|
47
|
-
"sim",
|
48
43
|
"sequencing",
|
49
44
|
"timelapse",
|
50
45
|
"deep_spacr",
|
51
|
-
"app_annotate",
|
52
46
|
"gui_utils",
|
53
47
|
"gui_elements",
|
54
48
|
"gui_core",
|
55
49
|
"gui",
|
56
|
-
"app_make_masks",
|
57
50
|
"app_mask",
|
58
51
|
"app_measure",
|
59
52
|
"app_classify",
|
60
53
|
"app_sequencing",
|
61
|
-
"app_umap",
|
62
54
|
"submodules",
|
63
|
-
"openai",
|
64
55
|
"ml",
|
65
56
|
"toxo",
|
66
57
|
"spacr_cellpose",
|
spacr/app_annotate.py
CHANGED
@@ -11,7 +11,7 @@ def convert_to_number(value):
|
|
11
11
|
return float(value)
|
12
12
|
except ValueError:
|
13
13
|
raise ValueError(f"Unable to convert '{value}' to an integer or float.")
|
14
|
-
|
14
|
+
|
15
15
|
def initiate_annotation_app(parent_frame):
|
16
16
|
from .gui_utils import generate_annotate_fields, annotate_app, convert_to_number
|
17
17
|
# Set up the settings window
|
spacr/core.py
CHANGED
@@ -1,7 +1,6 @@
|
|
1
1
|
import os, gc, torch, time, random
|
2
2
|
import numpy as np
|
3
3
|
import pandas as pd
|
4
|
-
import matplotlib.pyplot as plt
|
5
4
|
from IPython.display import display
|
6
5
|
|
7
6
|
import warnings
|
@@ -238,6 +237,7 @@ def generate_cellpose_masks(src, settings, object_type):
|
|
238
237
|
model_name = object_settings['model_name']
|
239
238
|
|
240
239
|
cellpose_channels = _get_cellpose_channels(src, settings['nucleus_channel'], settings['pathogen_channel'], settings['cell_channel'])
|
240
|
+
|
241
241
|
if settings['verbose']:
|
242
242
|
print(cellpose_channels)
|
243
243
|
|
@@ -448,362 +448,6 @@ def generate_cellpose_masks(src, settings, object_type):
|
|
448
448
|
torch.cuda.empty_cache()
|
449
449
|
return
|
450
450
|
|
451
|
-
def generate_image_umap(settings={}):
|
452
|
-
"""
|
453
|
-
Generate UMAP or tSNE embedding and visualize the data with clustering.
|
454
|
-
|
455
|
-
Parameters:
|
456
|
-
settings (dict): Dictionary containing the following keys:
|
457
|
-
src (str): Source directory containing the data.
|
458
|
-
row_limit (int): Limit the number of rows to process.
|
459
|
-
tables (list): List of table names to read from the database.
|
460
|
-
visualize (str): Visualization type.
|
461
|
-
image_nr (int): Number of images to display.
|
462
|
-
dot_size (int): Size of dots in the scatter plot.
|
463
|
-
n_neighbors (int): Number of neighbors for UMAP.
|
464
|
-
figuresize (int): Size of the figure.
|
465
|
-
black_background (bool): Whether to use a black background.
|
466
|
-
remove_image_canvas (bool): Whether to remove the image canvas.
|
467
|
-
plot_outlines (bool): Whether to plot outlines.
|
468
|
-
plot_points (bool): Whether to plot points.
|
469
|
-
smooth_lines (bool): Whether to smooth lines.
|
470
|
-
verbose (bool): Whether to print verbose output.
|
471
|
-
embedding_by_controls (bool): Whether to use embedding from controls.
|
472
|
-
col_to_compare (str): Column to compare for control-based embedding.
|
473
|
-
pos (str): Positive control value.
|
474
|
-
neg (str): Negative control value.
|
475
|
-
clustering (str): Clustering method ('DBSCAN' or 'KMeans').
|
476
|
-
exclude (list): List of columns to exclude from the analysis.
|
477
|
-
plot_images (bool): Whether to plot images.
|
478
|
-
reduction_method (str): Dimensionality reduction method ('UMAP' or 'tSNE').
|
479
|
-
save_figure (bool): Whether to save the figure as a PDF.
|
480
|
-
|
481
|
-
Returns:
|
482
|
-
pd.DataFrame: DataFrame with the original data and an additional column 'cluster' containing the cluster identity.
|
483
|
-
"""
|
484
|
-
|
485
|
-
from .io import _read_and_join_tables
|
486
|
-
from .utils import get_db_paths, preprocess_data, reduction_and_clustering, remove_noise, generate_colors, correct_paths, plot_embedding, plot_clusters_grid, cluster_feature_analysis, map_condition
|
487
|
-
from .settings import set_default_umap_image_settings
|
488
|
-
settings = set_default_umap_image_settings(settings)
|
489
|
-
|
490
|
-
if isinstance(settings['src'], str):
|
491
|
-
settings['src'] = [settings['src']]
|
492
|
-
|
493
|
-
if settings['plot_images'] is False:
|
494
|
-
settings['black_background'] = False
|
495
|
-
|
496
|
-
if settings['color_by']:
|
497
|
-
settings['remove_cluster_noise'] = False
|
498
|
-
settings['plot_outlines'] = False
|
499
|
-
settings['smooth_lines'] = False
|
500
|
-
|
501
|
-
print(f'Generating Image UMAP ...')
|
502
|
-
settings_df = pd.DataFrame(list(settings.items()), columns=['Key', 'Value'])
|
503
|
-
settings_dir = os.path.join(settings['src'][0],'settings')
|
504
|
-
settings_csv = os.path.join(settings_dir,'embedding_settings.csv')
|
505
|
-
os.makedirs(settings_dir, exist_ok=True)
|
506
|
-
settings_df.to_csv(settings_csv, index=False)
|
507
|
-
display(settings_df)
|
508
|
-
|
509
|
-
db_paths = get_db_paths(settings['src'])
|
510
|
-
tables = settings['tables'] + ['png_list']
|
511
|
-
all_df = pd.DataFrame()
|
512
|
-
|
513
|
-
for i,db_path in enumerate(db_paths):
|
514
|
-
df = _read_and_join_tables(db_path, table_names=tables)
|
515
|
-
df, image_paths_tmp = correct_paths(df, settings['src'][i])
|
516
|
-
all_df = pd.concat([all_df, df], axis=0)
|
517
|
-
#image_paths.extend(image_paths_tmp)
|
518
|
-
|
519
|
-
all_df['cond'] = all_df['columnID'].apply(map_condition, neg=settings['neg'], pos=settings['pos'], mix=settings['mix'])
|
520
|
-
|
521
|
-
if settings['exclude_conditions']:
|
522
|
-
if isinstance(settings['exclude_conditions'], str):
|
523
|
-
settings['exclude_conditions'] = [settings['exclude_conditions']]
|
524
|
-
row_count_before = len(all_df)
|
525
|
-
all_df = all_df[~all_df['cond'].isin(settings['exclude_conditions'])]
|
526
|
-
if settings['verbose']:
|
527
|
-
print(f'Excluded {row_count_before - len(all_df)} rows after excluding: {settings["exclude_conditions"]}, rows left: {len(all_df)}')
|
528
|
-
|
529
|
-
if settings['row_limit'] is not None:
|
530
|
-
all_df = all_df.sample(n=settings['row_limit'], random_state=42)
|
531
|
-
|
532
|
-
image_paths = all_df['png_path'].to_list()
|
533
|
-
|
534
|
-
if settings['embedding_by_controls']:
|
535
|
-
|
536
|
-
# Extract and reset the index for the column to compare
|
537
|
-
col_to_compare = all_df[settings['col_to_compare']].reset_index(drop=True)
|
538
|
-
print(col_to_compare)
|
539
|
-
#if settings['only_top_features']:
|
540
|
-
# column_list = None
|
541
|
-
|
542
|
-
# Preprocess the data to obtain numeric data
|
543
|
-
numeric_data = preprocess_data(all_df, settings['filter_by'], settings['remove_highly_correlated'], settings['log_data'], settings['exclude'])
|
544
|
-
|
545
|
-
# Convert numeric_data back to a DataFrame to align with col_to_compare
|
546
|
-
numeric_data_df = pd.DataFrame(numeric_data)
|
547
|
-
|
548
|
-
# Ensure numeric_data_df and col_to_compare are properly aligned
|
549
|
-
numeric_data_df = numeric_data_df.reset_index(drop=True)
|
550
|
-
|
551
|
-
# Assign the column back to numeric_data_df
|
552
|
-
numeric_data_df[settings['col_to_compare']] = col_to_compare
|
553
|
-
|
554
|
-
# Subset the dataframe based on specified column values for controls
|
555
|
-
positive_control_df = numeric_data_df[numeric_data_df[settings['col_to_compare']] == settings['pos']].copy()
|
556
|
-
negative_control_df = numeric_data_df[numeric_data_df[settings['col_to_compare']] == settings['neg']].copy()
|
557
|
-
control_numeric_data_df = pd.concat([positive_control_df, negative_control_df])
|
558
|
-
|
559
|
-
# Drop the comparison column from numeric_data_df and control_numeric_data_df
|
560
|
-
numeric_data_df = numeric_data_df.drop(columns=[settings['col_to_compare']])
|
561
|
-
control_numeric_data_df = control_numeric_data_df.drop(columns=[settings['col_to_compare']])
|
562
|
-
|
563
|
-
# Convert numeric_data_df and control_numeric_data_df back to numpy arrays
|
564
|
-
numeric_data = numeric_data_df.values
|
565
|
-
control_numeric_data = control_numeric_data_df.values
|
566
|
-
|
567
|
-
# Train the reducer on control data
|
568
|
-
_, _, reducer = reduction_and_clustering(control_numeric_data, settings['n_neighbors'], settings['min_dist'], settings['metric'], settings['eps'], settings['min_samples'], settings['clustering'], settings['reduction_method'], settings['verbose'], n_jobs=settings['n_jobs'], mode='fit', model=False)
|
569
|
-
|
570
|
-
# Apply the trained reducer to the entire dataset
|
571
|
-
numeric_data = preprocess_data(all_df, settings['filter_by'], settings['remove_highly_correlated'], settings['log_data'], settings['exclude'])
|
572
|
-
embedding, labels, _ = reduction_and_clustering(numeric_data, settings['n_neighbors'], settings['min_dist'], settings['metric'], settings['eps'], settings['min_samples'], settings['clustering'], settings['reduction_method'], settings['verbose'], n_jobs=settings['n_jobs'], mode=None, model=reducer)
|
573
|
-
|
574
|
-
else:
|
575
|
-
if settings['resnet_features']:
|
576
|
-
# placeholder for resnet features, not implemented yet
|
577
|
-
pass
|
578
|
-
#numeric_data, embedding, labels = generate_umap_from_images(image_paths, settings['n_neighbors'], settings['min_dist'], settings['metric'], settings['clustering'], settings['eps'], settings['min_samples'], settings['n_jobs'], settings['verbose'])
|
579
|
-
else:
|
580
|
-
# Apply the trained reducer to the entire dataset
|
581
|
-
numeric_data = preprocess_data(all_df, settings['filter_by'], settings['remove_highly_correlated'], settings['log_data'], settings['exclude'])
|
582
|
-
embedding, labels, _ = reduction_and_clustering(numeric_data, settings['n_neighbors'], settings['min_dist'], settings['metric'], settings['eps'], settings['min_samples'], settings['clustering'], settings['reduction_method'], settings['verbose'], n_jobs=settings['n_jobs'])
|
583
|
-
|
584
|
-
if settings['remove_cluster_noise']:
|
585
|
-
# Remove noise from the clusters (removes -1 labels from DBSCAN)
|
586
|
-
embedding, labels = remove_noise(embedding, labels)
|
587
|
-
|
588
|
-
# Plot the results
|
589
|
-
if settings['color_by']:
|
590
|
-
if settings['embedding_by_controls']:
|
591
|
-
labels = all_df[settings['color_by']]
|
592
|
-
else:
|
593
|
-
labels = all_df[settings['color_by']]
|
594
|
-
|
595
|
-
# Generate colors for the clusters
|
596
|
-
colors = generate_colors(len(np.unique(labels)), settings['black_background'])
|
597
|
-
|
598
|
-
# Plot the embedding
|
599
|
-
umap_plt = plot_embedding(embedding, image_paths, labels, settings['image_nr'], settings['img_zoom'], colors, settings['plot_by_cluster'], settings['plot_outlines'], settings['plot_points'], settings['plot_images'], settings['smooth_lines'], settings['black_background'], settings['figuresize'], settings['dot_size'], settings['remove_image_canvas'], settings['verbose'])
|
600
|
-
if settings['plot_cluster_grids'] and settings['plot_images']:
|
601
|
-
grid_plt = plot_clusters_grid(embedding, labels, settings['image_nr'], image_paths, colors, settings['figuresize'], settings['black_background'], settings['verbose'])
|
602
|
-
|
603
|
-
# Save figure as PDF if required
|
604
|
-
if settings['save_figure']:
|
605
|
-
results_dir = os.path.join(settings['src'][0], 'results')
|
606
|
-
os.makedirs(results_dir, exist_ok=True)
|
607
|
-
reduction_method = settings['reduction_method'].upper()
|
608
|
-
embedding_path = os.path.join(results_dir, f'{reduction_method}_embedding.pdf')
|
609
|
-
umap_plt.savefig(embedding_path, format='pdf')
|
610
|
-
print(f'Saved {reduction_method} embedding to {embedding_path} and grid to {embedding_path}')
|
611
|
-
if settings['plot_cluster_grids'] and settings['plot_images']:
|
612
|
-
grid_path = os.path.join(results_dir, f'{reduction_method}_grid.pdf')
|
613
|
-
grid_plt.savefig(grid_path, format='pdf')
|
614
|
-
print(f'Saved {reduction_method} embedding to {embedding_path} and grid to {grid_path}')
|
615
|
-
|
616
|
-
# Add cluster labels to the dataframe
|
617
|
-
if len(labels) > 0:
|
618
|
-
all_df['cluster'] = labels
|
619
|
-
else:
|
620
|
-
all_df['cluster'] = 1 # Assign a default cluster label
|
621
|
-
print("No clusters found. Consider reducing 'min_samples' or increasing 'eps' for DBSCAN.")
|
622
|
-
|
623
|
-
# Save the results to a CSV file
|
624
|
-
results_dir = os.path.join(settings['src'][0], 'results')
|
625
|
-
results_csv = os.path.join(results_dir,'embedding_results.csv')
|
626
|
-
os.makedirs(results_dir, exist_ok=True)
|
627
|
-
all_df.to_csv(results_csv, index=False)
|
628
|
-
print(f'Results saved to {results_csv}')
|
629
|
-
|
630
|
-
if settings['analyze_clusters']:
|
631
|
-
combined_results = cluster_feature_analysis(all_df)
|
632
|
-
results_dir = os.path.join(settings['src'][0], 'results')
|
633
|
-
cluster_results_csv = os.path.join(results_dir,'cluster_results.csv')
|
634
|
-
os.makedirs(results_dir, exist_ok=True)
|
635
|
-
combined_results.to_csv(cluster_results_csv, index=False)
|
636
|
-
print(f'Cluster results saved to {cluster_results_csv}')
|
637
|
-
|
638
|
-
return all_df
|
639
|
-
|
640
|
-
def reducer_hyperparameter_search(settings={}, reduction_params=None, dbscan_params=None, kmeans_params=None, save=False):
|
641
|
-
"""
|
642
|
-
Perform a hyperparameter search for UMAP or tSNE on the given data.
|
643
|
-
|
644
|
-
Parameters:
|
645
|
-
settings (dict): Dictionary containing the following keys:
|
646
|
-
src (str): Source directory containing the data.
|
647
|
-
row_limit (int): Limit the number of rows to process.
|
648
|
-
tables (list): List of table names to read from the database.
|
649
|
-
filter_by (str): Column to filter the data.
|
650
|
-
sample_size (int): Number of samples to use for the hyperparameter search.
|
651
|
-
remove_highly_correlated (bool): Whether to remove highly correlated columns.
|
652
|
-
log_data (bool): Whether to log transform the data.
|
653
|
-
verbose (bool): Whether to print verbose output.
|
654
|
-
reduction_method (str): Dimensionality reduction method ('UMAP' or 'tSNE').
|
655
|
-
reduction_params (list): List of dictionaries containing hyperparameters to test for the reduction method.
|
656
|
-
dbscan_params (list): List of dictionaries containing DBSCAN hyperparameters to test.
|
657
|
-
kmeans_params (list): List of dictionaries containing KMeans hyperparameters to test.
|
658
|
-
pointsize (int): Size of the points in the scatter plot.
|
659
|
-
save (bool): Whether to save the resulting plot as a file.
|
660
|
-
|
661
|
-
Returns:
|
662
|
-
None
|
663
|
-
"""
|
664
|
-
|
665
|
-
from .io import _read_and_join_tables
|
666
|
-
from .utils import get_db_paths, preprocess_data, search_reduction_and_clustering, generate_colors, map_condition
|
667
|
-
from .settings import set_default_umap_image_settings
|
668
|
-
|
669
|
-
settings = set_default_umap_image_settings(settings)
|
670
|
-
pointsize = settings['dot_size']
|
671
|
-
if isinstance(dbscan_params, dict):
|
672
|
-
dbscan_params = [dbscan_params]
|
673
|
-
|
674
|
-
if isinstance(kmeans_params, dict):
|
675
|
-
kmeans_params = [kmeans_params]
|
676
|
-
|
677
|
-
if isinstance(reduction_params, dict):
|
678
|
-
reduction_params = [reduction_params]
|
679
|
-
|
680
|
-
# Determine reduction method based on the keys in reduction_param
|
681
|
-
if any('n_neighbors' in param for param in reduction_params):
|
682
|
-
reduction_method = 'umap'
|
683
|
-
elif any('perplexity' in param for param in reduction_params):
|
684
|
-
reduction_method = 'tsne'
|
685
|
-
elif any('perplexity' in param for param in reduction_params) and any('n_neighbors' in param for param in reduction_params):
|
686
|
-
raise ValueError("Reduction parameters must include 'n_neighbors' for UMAP or 'perplexity' for tSNE, not both.")
|
687
|
-
|
688
|
-
if settings['reduction_method'].lower() != reduction_method:
|
689
|
-
settings['reduction_method'] = reduction_method
|
690
|
-
print(f'Changed reduction method to {reduction_method} based on the provided parameters.')
|
691
|
-
|
692
|
-
if settings['verbose']:
|
693
|
-
display(pd.DataFrame(list(settings.items()), columns=['Key', 'Value']))
|
694
|
-
|
695
|
-
db_paths = get_db_paths(settings['src'])
|
696
|
-
|
697
|
-
tables = settings['tables']
|
698
|
-
all_df = pd.DataFrame()
|
699
|
-
for db_path in db_paths:
|
700
|
-
df = _read_and_join_tables(db_path, table_names=tables)
|
701
|
-
all_df = pd.concat([all_df, df], axis=0)
|
702
|
-
|
703
|
-
all_df['cond'] = all_df['columnID'].apply(map_condition, neg=settings['neg'], pos=settings['pos'], mix=settings['mix'])
|
704
|
-
|
705
|
-
if settings['exclude_conditions']:
|
706
|
-
if isinstance(settings['exclude_conditions'], str):
|
707
|
-
settings['exclude_conditions'] = [settings['exclude_conditions']]
|
708
|
-
row_count_before = len(all_df)
|
709
|
-
all_df = all_df[~all_df['cond'].isin(settings['exclude_conditions'])]
|
710
|
-
if settings['verbose']:
|
711
|
-
print(f'Excluded {row_count_before - len(all_df)} rows after excluding: {settings["exclude_conditions"]}, rows left: {len(all_df)}')
|
712
|
-
|
713
|
-
if settings['row_limit'] is not None:
|
714
|
-
all_df = all_df.sample(n=settings['row_limit'], random_state=42)
|
715
|
-
|
716
|
-
numeric_data = preprocess_data(all_df, settings['filter_by'], settings['remove_highly_correlated'], settings['log_data'], settings['exclude'])
|
717
|
-
|
718
|
-
# Combine DBSCAN and KMeans parameters
|
719
|
-
clustering_params = []
|
720
|
-
if dbscan_params:
|
721
|
-
for param in dbscan_params:
|
722
|
-
param['method'] = 'dbscan'
|
723
|
-
clustering_params.append(param)
|
724
|
-
if kmeans_params:
|
725
|
-
for param in kmeans_params:
|
726
|
-
param['method'] = 'kmeans'
|
727
|
-
clustering_params.append(param)
|
728
|
-
|
729
|
-
print('Testing paramiters:', reduction_params)
|
730
|
-
print('Testing clustering paramiters:', clustering_params)
|
731
|
-
|
732
|
-
# Calculate the grid size
|
733
|
-
grid_rows = len(reduction_params)
|
734
|
-
grid_cols = len(clustering_params)
|
735
|
-
|
736
|
-
fig_width = grid_cols*10
|
737
|
-
fig_height = grid_rows*10
|
738
|
-
|
739
|
-
fig, axs = plt.subplots(grid_rows, grid_cols, figsize=(fig_width, fig_height))
|
740
|
-
|
741
|
-
# Make sure axs is always an array of axes
|
742
|
-
axs = np.atleast_1d(axs)
|
743
|
-
|
744
|
-
# Iterate through the Cartesian product of reduction and clustering hyperparameters
|
745
|
-
for i, reduction_param in enumerate(reduction_params):
|
746
|
-
for j, clustering_param in enumerate(clustering_params):
|
747
|
-
if len(clustering_params) <= 1:
|
748
|
-
axs[i].axis('off')
|
749
|
-
ax = axs[i]
|
750
|
-
elif len(reduction_params) <= 1:
|
751
|
-
axs[j].axis('off')
|
752
|
-
ax = axs[j]
|
753
|
-
else:
|
754
|
-
ax = axs[i, j]
|
755
|
-
|
756
|
-
# Perform dimensionality reduction and clustering
|
757
|
-
if settings['reduction_method'].lower() == 'umap':
|
758
|
-
n_neighbors = reduction_param.get('n_neighbors', 15)
|
759
|
-
|
760
|
-
if isinstance(n_neighbors, float):
|
761
|
-
n_neighbors = int(n_neighbors * len(numeric_data))
|
762
|
-
|
763
|
-
min_dist = reduction_param.get('min_dist', 0.1)
|
764
|
-
embedding, labels = search_reduction_and_clustering(numeric_data, n_neighbors, min_dist, settings['metric'],
|
765
|
-
clustering_param.get('eps', 0.5), clustering_param.get('min_samples', 5),
|
766
|
-
clustering_param['method'], settings['reduction_method'], settings['verbose'], reduction_param, n_jobs=settings['n_jobs'])
|
767
|
-
|
768
|
-
elif settings['reduction_method'].lower() == 'tsne':
|
769
|
-
perplexity = reduction_param.get('perplexity', 30)
|
770
|
-
|
771
|
-
if isinstance(perplexity, float):
|
772
|
-
perplexity = int(perplexity * len(numeric_data))
|
773
|
-
|
774
|
-
embedding, labels = search_reduction_and_clustering(numeric_data, perplexity, 0.1, settings['metric'],
|
775
|
-
clustering_param.get('eps', 0.5), clustering_param.get('min_samples', 5),
|
776
|
-
clustering_param['method'], settings['reduction_method'], settings['verbose'], reduction_param, n_jobs=settings['n_jobs'])
|
777
|
-
|
778
|
-
else:
|
779
|
-
raise ValueError(f"Unsupported reduction method: {settings['reduction_method']}. Supported methods are 'UMAP' and 'tSNE'")
|
780
|
-
|
781
|
-
# Plot the results
|
782
|
-
if settings['color_by']:
|
783
|
-
unique_groups = all_df[settings['color_by']].unique()
|
784
|
-
colors = generate_colors(len(unique_groups), False)
|
785
|
-
for group, color in zip(unique_groups, colors):
|
786
|
-
indices = all_df[settings['color_by']] == group
|
787
|
-
ax.scatter(embedding[indices, 0], embedding[indices, 1], s=pointsize, label=f"{group}", color=color)
|
788
|
-
else:
|
789
|
-
unique_labels = np.unique(labels)
|
790
|
-
colors = generate_colors(len(unique_labels), False)
|
791
|
-
for label, color in zip(unique_labels, colors):
|
792
|
-
ax.scatter(embedding[labels == label, 0], embedding[labels == label, 1], s=pointsize, label=f"Cluster {label}", color=color)
|
793
|
-
|
794
|
-
ax.set_title(f"{settings['reduction_method']} {reduction_param}\n{clustering_param['method']} {clustering_param}")
|
795
|
-
ax.legend()
|
796
|
-
|
797
|
-
plt.tight_layout()
|
798
|
-
if save:
|
799
|
-
results_dir = os.path.join(settings['src'], 'results')
|
800
|
-
os.makedirs(results_dir, exist_ok=True)
|
801
|
-
plt.savefig(os.path.join(results_dir, 'hyperparameter_search.pdf'))
|
802
|
-
else:
|
803
|
-
plt.show()
|
804
|
-
|
805
|
-
return
|
806
|
-
|
807
451
|
def generate_screen_graphs(settings):
|
808
452
|
"""
|
809
453
|
Generate screen graphs for different measurements in a given source directory.
|
spacr/deep_spacr.py
CHANGED
@@ -1,10 +1,8 @@
|
|
1
|
-
import os, torch, time, gc, datetime
|
1
|
+
import os, torch, time, gc, datetime
|
2
2
|
torch.backends.cudnn.benchmark = True
|
3
|
-
|
4
3
|
import numpy as np
|
5
4
|
import pandas as pd
|
6
5
|
from torch.optim import Adagrad, AdamW
|
7
|
-
from torch.autograd import grad
|
8
6
|
from torch.optim.lr_scheduler import StepLR
|
9
7
|
import torch.nn.functional as F
|
10
8
|
import matplotlib.pyplot as plt
|
spacr/gui.py
CHANGED
@@ -4,6 +4,7 @@ from multiprocessing import set_start_method
|
|
4
4
|
from .gui_elements import spacrButton, create_menu_bar, set_dark_style
|
5
5
|
from .gui_core import initiate_root
|
6
6
|
from screeninfo import get_monitors
|
7
|
+
import webbrowser
|
7
8
|
|
8
9
|
class MainApp(tk.Tk):
|
9
10
|
def __init__(self, default_app=None):
|
@@ -22,6 +23,7 @@ class MainApp(tk.Tk):
|
|
22
23
|
for monitor in get_monitors():
|
23
24
|
if monitor.x <= x < monitor.x + monitor.width and monitor.y <= y < monitor.y + monitor.height:
|
24
25
|
width = monitor.width
|
26
|
+
self.width = width
|
25
27
|
height = monitor.height
|
26
28
|
break
|
27
29
|
else:
|
@@ -42,22 +44,15 @@ class MainApp(tk.Tk):
|
|
42
44
|
self.main_gui_apps = {
|
43
45
|
"Mask": (lambda frame: initiate_root(self, 'mask'), "Generate cellpose masks for cells, nuclei and pathogen images."),
|
44
46
|
"Measure": (lambda frame: initiate_root(self, 'measure'), "Measure single object intensity and morphological feature. Crop and save single object image"),
|
45
|
-
"Annotate": (lambda frame: initiate_root(self, 'annotate'), "Annotation single object images on a grid. Annotations are saved to database."),
|
46
|
-
"Make Masks": (lambda frame: initiate_root(self, 'make_masks'), "Adjust pre-existing Cellpose models to your specific dataset for improved performance"),
|
47
47
|
"Classify": (lambda frame: initiate_root(self, 'classify'), "Train Torch Convolutional Neural Networks (CNNs) or Transformers to classify single object images."),
|
48
48
|
}
|
49
49
|
|
50
50
|
self.additional_gui_apps = {
|
51
|
-
"Umap": (lambda frame: initiate_root(self, 'umap'), "Generate UMAP embeddings with datapoints represented as images."),
|
52
|
-
"Train Cellpose": (lambda frame: initiate_root(self, 'train_cellpose'), "Train custom Cellpose models."),
|
53
51
|
"ML Analyze": (lambda frame: initiate_root(self, 'ml_analyze'), "Machine learning analysis of data."),
|
54
|
-
"Cellpose Masks": (lambda frame: initiate_root(self, 'cellpose_masks'), "Generate Cellpose masks."),
|
55
|
-
"Cellpose All": (lambda frame: initiate_root(self, 'cellpose_all'), "Run Cellpose on all images."),
|
56
52
|
"Map Barcodes": (lambda frame: initiate_root(self, 'map_barcodes'), "Map barcodes to data."),
|
57
53
|
"Regression": (lambda frame: initiate_root(self, 'regression'), "Perform regression analysis."),
|
58
54
|
"Recruitment": (lambda frame: initiate_root(self, 'recruitment'), "Analyze recruitment data."),
|
59
55
|
"Activation": (lambda frame: initiate_root(self, 'activation'), "Generate activation maps of computer vision models and measure channel-activation correlation."),
|
60
|
-
"Plaque": (lambda frame: initiate_root(self, 'analyze_plaques'), "Analyze plaque data.")
|
61
56
|
}
|
62
57
|
|
63
58
|
self.selected_app = tk.StringVar()
|
@@ -88,6 +83,13 @@ class MainApp(tk.Tk):
|
|
88
83
|
set_dark_style(ttk.Style(), containers=[self.content_frame, self.inner_frame])
|
89
84
|
|
90
85
|
self.create_startup_screen()
|
86
|
+
|
87
|
+
def _update_wraplength(self, event):
|
88
|
+
if self.description_label.winfo_exists():
|
89
|
+
# Use the actual width of the inner_frame as a proxy for full width
|
90
|
+
available_width = self.inner_frame.winfo_width()
|
91
|
+
if available_width > 0:
|
92
|
+
self.description_label.config(wraplength=int(available_width * 0.9)) # or 0.9
|
91
93
|
|
92
94
|
def create_startup_screen(self):
|
93
95
|
self.clear_frame(self.inner_frame)
|
@@ -99,18 +101,30 @@ class MainApp(tk.Tk):
|
|
99
101
|
additional_buttons_frame = tk.Frame(self.inner_frame)
|
100
102
|
additional_buttons_frame.pack(pady=10)
|
101
103
|
set_dark_style(ttk.Style(), containers=[additional_buttons_frame])
|
102
|
-
|
103
|
-
description_frame = tk.Frame(self.inner_frame
|
104
|
+
|
105
|
+
description_frame = tk.Frame(self.inner_frame)
|
104
106
|
description_frame.pack(fill=tk.X, pady=10)
|
105
|
-
description_frame.
|
107
|
+
description_frame.columnconfigure(0, weight=1)
|
108
|
+
|
106
109
|
set_dark_style(ttk.Style(), containers=[description_frame])
|
110
|
+
style_out = set_dark_style(ttk.Style())
|
111
|
+
font_loader = style_out['font_loader']
|
112
|
+
font_size = style_out['font_size']
|
113
|
+
|
114
|
+
self.description_label = tk.Label( description_frame, text="", wraplength=int(self.width * 0.9), justify="center", font=font_loader.get_font(size=font_size), fg=self.color_settings['fg_color'], bg=self.color_settings['bg_color'])
|
115
|
+
|
116
|
+
# Pack it without expanding
|
117
|
+
self.description_label.pack(pady=10)
|
107
118
|
|
108
|
-
|
109
|
-
self.description_label.
|
110
|
-
|
111
|
-
|
119
|
+
# Force character width and center it
|
120
|
+
self.description_label.configure(width=int(self.width * 0.5 // 7))
|
121
|
+
self.description_label.pack_configure(anchor='center')
|
122
|
+
|
123
|
+
#logo_button = spacrButton(main_buttons_frame, text="SpaCR", command=lambda: self.load_app("logo_spacr", initiate_root), icon_name="logo_spacr", size=100, show_text=False)
|
124
|
+
logo_button = spacrButton(main_buttons_frame,text="SpaCR",command=lambda: webbrowser.open_new("https://einarolafsson.github.io/spacr/tutorial/"),icon_name="logo_spacr",size=100,show_text=False)
|
125
|
+
|
112
126
|
logo_button.grid(row=0, column=0, padx=5, pady=5)
|
113
|
-
self.main_buttons[logo_button] = "
|
127
|
+
self.main_buttons[logo_button] = "spaCR provides a flexible toolset to extract single-cell images and measurements from high-content cell painting experiments, train deep-learning models to classify cellular/subcellular phenotypes, simulate, and analyze pooled CRISPR-Cas9 imaging screens. Click to open the spaCR tutorial in your browser."
|
114
128
|
|
115
129
|
for i, (app_name, app_data) in enumerate(self.main_gui_apps.items()):
|
116
130
|
app_func, app_desc = app_data
|
@@ -125,6 +139,7 @@ class MainApp(tk.Tk):
|
|
125
139
|
self.additional_buttons[button] = app_desc
|
126
140
|
|
127
141
|
self.update_description()
|
142
|
+
self.inner_frame.bind("<Configure>", self._update_wraplength)
|
128
143
|
|
129
144
|
def update_description(self):
|
130
145
|
for button, desc in {**self.main_buttons, **self.additional_buttons}.items():
|
spacr/gui_core.py
CHANGED
@@ -1266,36 +1266,29 @@ def initiate_root(parent, settings_type='mask'):
|
|
1266
1266
|
fig_queue = Queue()
|
1267
1267
|
parent_frame, vertical_container, horizontal_container, settings_container = setup_frame(parent_frame)
|
1268
1268
|
|
1269
|
-
|
1270
|
-
|
1271
|
-
|
1272
|
-
|
1273
|
-
|
1274
|
-
|
1269
|
+
scrollable_frame, vars_dict = setup_settings_panel(settings_container, settings_type)
|
1270
|
+
print('setup_settings_panel')
|
1271
|
+
canvas, canvas_widget = setup_plot_section(vertical_container, settings_type)
|
1272
|
+
console_output, _ = setup_console(vertical_container) #, chatbot)
|
1273
|
+
button_scrollable_frame, btn_col = setup_button_section(horizontal_container, settings_type)
|
1274
|
+
|
1275
|
+
if num_cores > 12:
|
1276
|
+
_, usage_bars, btn_col = setup_usage_panel(horizontal_container, btn_col, uppdate_frequency)
|
1275
1277
|
else:
|
1276
|
-
|
1277
|
-
print('setup_settings_panel')
|
1278
|
-
canvas, canvas_widget = setup_plot_section(vertical_container, settings_type)
|
1279
|
-
console_output, _ = setup_console(vertical_container) #, chatbot)
|
1280
|
-
button_scrollable_frame, btn_col = setup_button_section(horizontal_container, settings_type)
|
1281
|
-
|
1282
|
-
if num_cores > 12:
|
1283
|
-
_, usage_bars, btn_col = setup_usage_panel(horizontal_container, btn_col, uppdate_frequency)
|
1284
|
-
else:
|
1285
|
-
usage_bars = []
|
1278
|
+
usage_bars = []
|
1286
1279
|
|
1287
|
-
|
1288
|
-
|
1289
|
-
|
1290
|
-
|
1291
|
-
|
1292
|
-
|
1293
|
-
|
1294
|
-
|
1295
|
-
|
1296
|
-
|
1297
|
-
|
1298
|
-
|
1280
|
+
set_globals(thread_control, q, console_output, parent_frame, vars_dict, canvas, canvas_widget, scrollable_frame, fig_queue, progress_bar, usage_bars)
|
1281
|
+
description_text = descriptions.get(settings_type, "No description available for this module.")
|
1282
|
+
|
1283
|
+
q.put(f"Console")
|
1284
|
+
q.put(f" ")
|
1285
|
+
q.put(description_text)
|
1286
|
+
|
1287
|
+
process_console_queue()
|
1288
|
+
process_fig_queue()
|
1289
|
+
create_menu_bar(parent)
|
1290
|
+
after_id = parent_window.after(uppdate_frequency, lambda: main_thread_update_function(parent_window, q, fig_queue, canvas_widget))
|
1291
|
+
parent_window.after_tasks.append(after_id)
|
1299
1292
|
|
1300
1293
|
print("Root initialization complete")
|
1301
1294
|
return parent_frame, vars_dict
|