spacr 0.3.32__py3-none-any.whl → 0.3.34__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- spacr/gui_utils.py +1 -1
- spacr/ml.py +34 -1
- spacr/plot.py +397 -168
- spacr/settings.py +1 -0
- {spacr-0.3.32.dist-info → spacr-0.3.34.dist-info}/METADATA +1 -1
- {spacr-0.3.32.dist-info → spacr-0.3.34.dist-info}/RECORD +10 -10
- {spacr-0.3.32.dist-info → spacr-0.3.34.dist-info}/LICENSE +0 -0
- {spacr-0.3.32.dist-info → spacr-0.3.34.dist-info}/WHEEL +0 -0
- {spacr-0.3.32.dist-info → spacr-0.3.34.dist-info}/entry_points.txt +0 -0
- {spacr-0.3.32.dist-info → spacr-0.3.34.dist-info}/top_level.txt +0 -0
spacr/gui_utils.py
CHANGED
@@ -508,7 +508,7 @@ def run_function_gui(settings_type, settings, q, fig_queue, stop_requested):
|
|
508
508
|
imports = 1
|
509
509
|
elif settings_type == 'ml_analyze':
|
510
510
|
function = generate_ml_scores
|
511
|
-
imports =
|
511
|
+
imports = 1
|
512
512
|
elif settings_type == 'cellpose_masks':
|
513
513
|
function = identify_masks_finetune
|
514
514
|
imports = 1
|
spacr/ml.py
CHANGED
@@ -730,7 +730,7 @@ def process_scores(df, dependent_variable, plate, min_cell_count=25, agg_type='m
|
|
730
730
|
|
731
731
|
def generate_ml_scores(settings):
|
732
732
|
|
733
|
-
from .io import _read_and_merge_data
|
733
|
+
from .io import _read_and_merge_data, _read_db
|
734
734
|
from .plot import plot_plates
|
735
735
|
from .utils import get_ml_results_paths
|
736
736
|
from .settings import set_default_analyze_screen
|
@@ -753,6 +753,38 @@ def generate_ml_scores(settings):
|
|
753
753
|
nuclei_limit,
|
754
754
|
pathogen_limit,
|
755
755
|
uninfected)
|
756
|
+
|
757
|
+
if settings['annotation_column'] is not None:
|
758
|
+
|
759
|
+
settings['location_column'] = settings['annotation_column']
|
760
|
+
|
761
|
+
png_list_df = _read_db(db_loc[0], tables=['png_list'])[0]
|
762
|
+
if not {'prcfo', settings['annotation_column']}.issubset(png_list_df.columns):
|
763
|
+
raise ValueError("The 'png_list_df' DataFrame must contain 'prcfo' and 'test' columns.")
|
764
|
+
annotated_df = png_list_df[['prcfo', settings['annotation_column']]].set_index('prcfo')
|
765
|
+
df = annotated_df.merge(df, left_index=True, right_index=True)
|
766
|
+
display(df)
|
767
|
+
unique_values = df[settings['annotation_column']].dropna().unique()
|
768
|
+
if len(unique_values) == 1:
|
769
|
+
unannotated_rows = df[df[settings['annotation_column']].isna()].index
|
770
|
+
existing_value = unique_values[0]
|
771
|
+
next_value = existing_value + 1
|
772
|
+
|
773
|
+
settings['positive_control'] = str(existing_value)
|
774
|
+
settings['negative_control'] = str(next_value)
|
775
|
+
|
776
|
+
existing_count = df[df[settings['annotation_column']] == existing_value].shape[0]
|
777
|
+
num_to_select = min(existing_count, len(unannotated_rows))
|
778
|
+
selected_rows = np.random.choice(unannotated_rows, size=num_to_select, replace=False)
|
779
|
+
df.loc[selected_rows, settings['annotation_column']] = next_value
|
780
|
+
|
781
|
+
# Print the counts for existing_value and next_value
|
782
|
+
existing_count_final = df[df[settings['annotation_column']] == existing_value].shape[0]
|
783
|
+
next_count_final = df[df[settings['annotation_column']] == next_value].shape[0]
|
784
|
+
|
785
|
+
print(f"Number of rows with value {existing_value}: {existing_count_final}")
|
786
|
+
print(f"Number of rows with value {next_value}: {next_count_final}")
|
787
|
+
df[settings['annotation_column']] = df[settings['annotation_column']].apply(str)
|
756
788
|
|
757
789
|
if settings['channel_of_interest'] in [0,1,2,3]:
|
758
790
|
|
@@ -847,6 +879,7 @@ def ml_analysis(df, channel_of_interest=3, location_column='col', positive_contr
|
|
847
879
|
if verbose:
|
848
880
|
print(f'Found {len(features)} numerical features in the dataframe')
|
849
881
|
print(f'Features used in training: {features}')
|
882
|
+
|
850
883
|
df = pd.concat([df, df_metadata[location_column]], axis=1)
|
851
884
|
|
852
885
|
# Subset the dataframe based on specified column values
|
spacr/plot.py
CHANGED
@@ -23,6 +23,9 @@ import pingouin as pg
|
|
23
23
|
from ipywidgets import IntSlider, interact
|
24
24
|
from IPython.display import Image as ipyimage
|
25
25
|
|
26
|
+
import matplotlib.patches as patches
|
27
|
+
from collections import defaultdict
|
28
|
+
|
26
29
|
def plot_image_mask_overlay(file, channels, cell_channel, nucleus_channel, pathogen_channel, figuresize=10, normalize=True, thickness=3, save_pdf=True):
|
27
30
|
"""Plot image and mask overlays."""
|
28
31
|
|
@@ -1930,7 +1933,7 @@ def jitterplot_by_annotation(src, x_column, y_column, plot_title='Jitter Plot',
|
|
1930
1933
|
|
1931
1934
|
return balanced_df
|
1932
1935
|
|
1933
|
-
def create_grouped_plot(df, grouping_column, data_column, graph_type='bar', summary_func='mean', order=None, colors=None, output_dir='./output', save=False,
|
1936
|
+
def create_grouped_plot(df, grouping_column, data_column, graph_type='bar', summary_func='mean', order=None, colors=None, output_dir='./output', save=False, y_lim=None, error_bar_type='std'):
|
1934
1937
|
"""
|
1935
1938
|
Create a grouped plot, perform statistical tests, and optionally export the results along with the plot.
|
1936
1939
|
|
@@ -1944,7 +1947,7 @@ def create_grouped_plot(df, grouping_column, data_column, graph_type='bar', summ
|
|
1944
1947
|
- colors: List of colors for each group.
|
1945
1948
|
- output_dir: Directory where the figure and test results will be saved if `save=True`.
|
1946
1949
|
- save: Boolean flag indicating whether to save the plot and results to files.
|
1947
|
-
-
|
1950
|
+
- y_lim: Optional y-axis min and max.
|
1948
1951
|
- error_bar_type: Type of error bars to plot, either 'std' for standard deviation or 'sem' for standard error of the mean.
|
1949
1952
|
|
1950
1953
|
Outputs:
|
@@ -2068,10 +2071,8 @@ def create_grouped_plot(df, grouping_column, data_column, graph_type='bar', summ
|
|
2068
2071
|
results_df = pd.DataFrame(test_results)
|
2069
2072
|
|
2070
2073
|
# Set y-axis start if provided
|
2071
|
-
if
|
2072
|
-
plt.ylim(
|
2073
|
-
else:
|
2074
|
-
plt.ylim(0, None) # Default to starting at 0 if no custom start value is provided
|
2074
|
+
if isinstance(y_lim, list) and len(y_lim) == 2:
|
2075
|
+
plt.ylim(y_lim)
|
2075
2076
|
|
2076
2077
|
# If save is True, save the plot and results as PNG and CSV
|
2077
2078
|
if save:
|
@@ -2095,22 +2096,23 @@ def create_grouped_plot(df, grouping_column, data_column, graph_type='bar', summ
|
|
2095
2096
|
|
2096
2097
|
class spacrGraph:
|
2097
2098
|
def __init__(self, df, grouping_column, data_column, graph_type='bar', summary_func='mean',
|
2098
|
-
order=None, colors=None, output_dir='./output', save=False,
|
2099
|
+
order=None, colors=None, output_dir='./output', save=False, y_lim=None,
|
2099
2100
|
error_bar_type='std', remove_outliers=False, theme='pastel', representation='object',
|
2100
2101
|
paired=False, all_to_all=True, compare_group=None):
|
2102
|
+
|
2101
2103
|
"""
|
2102
2104
|
Class for creating grouped plots with optional statistical tests and data preprocessing.
|
2103
2105
|
"""
|
2106
|
+
|
2104
2107
|
self.df = df
|
2105
2108
|
self.grouping_column = grouping_column
|
2106
|
-
self.data_column = data_column
|
2109
|
+
self.data_column = data_column if isinstance(data_column, list) else [data_column]
|
2107
2110
|
self.graph_type = graph_type
|
2108
2111
|
self.summary_func = summary_func
|
2109
2112
|
self.order = order
|
2110
2113
|
self.colors = colors
|
2111
2114
|
self.output_dir = output_dir
|
2112
2115
|
self.save = save
|
2113
|
-
self.y_axis_start = y_axis_start
|
2114
2116
|
self.error_bar_type = error_bar_type
|
2115
2117
|
self.remove_outliers = remove_outliers
|
2116
2118
|
self.theme = theme
|
@@ -2118,14 +2120,15 @@ class spacrGraph:
|
|
2118
2120
|
self.paired = paired
|
2119
2121
|
self.all_to_all = all_to_all
|
2120
2122
|
self.compare_group = compare_group
|
2121
|
-
|
2123
|
+
self.y_lim = y_lim
|
2122
2124
|
self.results_df = pd.DataFrame()
|
2123
2125
|
self.sns_palette = None
|
2124
|
-
self.fig = None
|
2126
|
+
self.fig = None
|
2127
|
+
|
2128
|
+
self.results_name = str(self.data_column[0])+'_'+str(self.grouping_column)+'_'+str(self.graph_type)
|
2125
2129
|
|
2126
|
-
# Preprocess and set palette
|
2127
2130
|
self._set_theme()
|
2128
|
-
self.raw_df = self.df.copy()
|
2131
|
+
self.raw_df = self.df.copy()
|
2129
2132
|
self.df = self.preprocess_data()
|
2130
2133
|
|
2131
2134
|
def _set_theme(self):
|
@@ -2148,17 +2151,15 @@ class spacrGraph:
|
|
2148
2151
|
|
2149
2152
|
def preprocess_data(self):
|
2150
2153
|
"""Preprocess the data: remove NaNs, sort/order the grouping column, and optionally group by 'prc'."""
|
2151
|
-
|
2152
|
-
|
2154
|
+
# Remove NaNs in both the grouping column and each data column
|
2155
|
+
df = self.df.dropna(subset=[self.grouping_column] + self.data_column) # Handle multiple data columns
|
2153
2156
|
# Group by 'prc' column if representation is 'well'
|
2154
2157
|
if self.representation == 'well':
|
2155
2158
|
df = df.groupby(['prc', self.grouping_column])[self.data_column].agg(self.summary_func).reset_index()
|
2156
|
-
|
2157
2159
|
if self.order:
|
2158
2160
|
df[self.grouping_column] = pd.Categorical(df[self.grouping_column], categories=self.order, ordered=True)
|
2159
2161
|
else:
|
2160
2162
|
df[self.grouping_column] = pd.Categorical(df[self.grouping_column], categories=sorted(df[self.grouping_column].unique()), ordered=True)
|
2161
|
-
|
2162
2163
|
return df
|
2163
2164
|
|
2164
2165
|
def remove_outliers_from_plot(self):
|
@@ -2176,26 +2177,24 @@ class spacrGraph:
|
|
2176
2177
|
return filtered_df
|
2177
2178
|
|
2178
2179
|
def perform_normality_tests(self):
|
2179
|
-
"""Perform normality tests for each group."""
|
2180
|
+
"""Perform normality tests for each group and each data column."""
|
2180
2181
|
unique_groups = self.df[self.grouping_column].unique()
|
2181
|
-
|
2182
|
-
|
2183
|
-
|
2184
|
-
|
2185
|
-
|
2186
|
-
|
2187
|
-
|
2188
|
-
|
2189
|
-
|
2190
|
-
|
2191
|
-
|
2192
|
-
|
2193
|
-
|
2194
|
-
|
2195
|
-
|
2196
|
-
|
2197
|
-
})
|
2198
|
-
return is_normal, test_results
|
2182
|
+
normality_results = []
|
2183
|
+
for column in self.data_column:
|
2184
|
+
grouped_data = [self.df.loc[self.df[self.grouping_column] == group, column] for group in unique_groups]
|
2185
|
+
normal_p_values = [normaltest(data).pvalue for data in grouped_data]
|
2186
|
+
normal_stats = [normaltest(data).statistic for data in grouped_data]
|
2187
|
+
is_normal = all(p > 0.05 for p in normal_p_values) # Test if all groups are normal
|
2188
|
+
for group, stat, p_value in zip(unique_groups, normal_stats, normal_p_values):
|
2189
|
+
normality_results.append({
|
2190
|
+
'Comparison': f'Normality test for {group} on {column}',
|
2191
|
+
'Test Statistic': stat,
|
2192
|
+
'p-value': p_value,
|
2193
|
+
'Test Name': 'Normality test',
|
2194
|
+
'Column': column,
|
2195
|
+
'n': len(self.df[self.df[self.grouping_column] == group]) # Sample size
|
2196
|
+
})
|
2197
|
+
return is_normal, normality_results
|
2199
2198
|
|
2200
2199
|
def perform_levene_test(self, unique_groups):
|
2201
2200
|
"""Perform Levene's test for equal variance."""
|
@@ -2204,57 +2203,48 @@ class spacrGraph:
|
|
2204
2203
|
return stat, p_value
|
2205
2204
|
|
2206
2205
|
def perform_statistical_tests(self, unique_groups, is_normal):
|
2207
|
-
"""Perform statistical tests
|
2208
|
-
|
2209
|
-
|
2210
|
-
|
2211
|
-
|
2212
|
-
|
2206
|
+
"""Perform statistical tests separately for each data column."""
|
2207
|
+
test_results = []
|
2208
|
+
for column in self.data_column: # Iterate over each data column
|
2209
|
+
grouped_data = [self.df.loc[self.df[self.grouping_column] == group, column] for group in unique_groups]
|
2210
|
+
if len(unique_groups) == 2: # For two groups: class_0 vs class_1
|
2211
|
+
if is_normal:
|
2212
|
+
if self.paired:
|
2213
|
+
stat, p = pg.ttest(grouped_data[0], grouped_data[1], paired=True).iloc[0][['T', 'p-val']]
|
2214
|
+
test_name = 'Paired T-test'
|
2215
|
+
else:
|
2216
|
+
stat, p = ttest_ind(grouped_data[0], grouped_data[1])
|
2217
|
+
test_name = 'T-test'
|
2213
2218
|
else:
|
2214
|
-
|
2215
|
-
|
2219
|
+
if self.paired:
|
2220
|
+
stat, p = pg.wilcoxon(grouped_data[0], grouped_data[1]).iloc[0][['T', 'p-val']]
|
2221
|
+
test_name = 'Paired Wilcoxon test'
|
2222
|
+
else:
|
2223
|
+
stat, p = mannwhitneyu(grouped_data[0], grouped_data[1])
|
2224
|
+
test_name = 'Mann-Whitney U test'
|
2216
2225
|
else:
|
2217
|
-
if
|
2218
|
-
|
2219
|
-
test_name = '
|
2226
|
+
if is_normal:
|
2227
|
+
stat, p = f_oneway(*grouped_data)
|
2228
|
+
test_name = 'One-way ANOVA'
|
2220
2229
|
else:
|
2221
|
-
|
2222
|
-
test_name = '
|
2223
|
-
else:
|
2224
|
-
if is_normal:
|
2225
|
-
stat_test = f_oneway
|
2226
|
-
test_name = 'One-way ANOVA'
|
2227
|
-
else:
|
2228
|
-
stat_test = kruskal
|
2229
|
-
test_name = 'Kruskal-Wallis test'
|
2230
|
-
|
2231
|
-
comparisons = list(itertools.combinations(unique_groups, 2))
|
2232
|
-
test_results = []
|
2233
|
-
for (group1, group2) in comparisons:
|
2234
|
-
data1 = self.df[self.df[self.grouping_column] == group1][self.data_column]
|
2235
|
-
data2 = self.df[self.df[self.grouping_column] == group2][self.data_column]
|
2236
|
-
raw_data1 = self.raw_df[self.raw_df[self.grouping_column] == group1][self.data_column]
|
2237
|
-
raw_data2 = self.raw_df[self.raw_df[self.grouping_column] == group2][self.data_column]
|
2238
|
-
|
2239
|
-
if self.paired:
|
2240
|
-
stat, p = stat_test(data1, data2, paired=True)
|
2241
|
-
else:
|
2242
|
-
stat, p = stat_test(data1, data2)
|
2230
|
+
stat, p = kruskal(*grouped_data)
|
2231
|
+
test_name = 'Kruskal-Wallis test'
|
2243
2232
|
|
2244
2233
|
test_results.append({
|
2245
|
-
'Comparison': f'{
|
2234
|
+
'Comparison': f'{unique_groups[0]} vs {unique_groups[1]} ({column})',
|
2246
2235
|
'Test Statistic': stat,
|
2247
2236
|
'p-value': p,
|
2248
2237
|
'Test Name': test_name,
|
2249
|
-
'
|
2250
|
-
'
|
2251
|
-
|
2238
|
+
'Column': column,
|
2239
|
+
'n_object': len(grouped_data[0]) + len(grouped_data[1]),
|
2240
|
+
'n_well': len(self.df[self.df[self.grouping_column] == unique_groups[0]]) +
|
2241
|
+
len(self.df[self.df[self.grouping_column] == unique_groups[1]])})
|
2242
|
+
|
2252
2243
|
return test_results
|
2253
2244
|
|
2254
2245
|
def perform_posthoc_tests(self, is_normal, unique_groups):
|
2255
2246
|
"""Perform post-hoc tests for multiple groups based on all_to_all flag."""
|
2256
2247
|
if is_normal and len(unique_groups) > 2 and self.all_to_all:
|
2257
|
-
# Tukey HSD Post-hoc when comparing all to all
|
2258
2248
|
tukey_result = pairwise_tukeyhsd(self.df[self.data_column], self.df[self.grouping_column], alpha=0.05)
|
2259
2249
|
posthoc_results = []
|
2260
2250
|
for comparison, p_value in zip(tukey_result._results_table.data[1:], tukey_result.pvalues):
|
@@ -2267,12 +2257,10 @@ class spacrGraph:
|
|
2267
2257
|
'p-value': p_value,
|
2268
2258
|
'Test Name': 'Tukey HSD Post-hoc',
|
2269
2259
|
'n_object': len(raw_data1) + len(raw_data2),
|
2270
|
-
'n_well': len(self.df[self.df[self.grouping_column] == comparison[0]]) + len(self.df[self.df[self.grouping_column] == comparison[1]])
|
2271
|
-
})
|
2260
|
+
'n_well': len(self.df[self.df[self.grouping_column] == comparison[0]]) + len(self.df[self.df[self.grouping_column] == comparison[1]])})
|
2272
2261
|
return posthoc_results
|
2273
2262
|
|
2274
2263
|
elif len(unique_groups) > 2 and not self.all_to_all and self.compare_group:
|
2275
|
-
# Dunn's post-hoc test using Pingouin
|
2276
2264
|
dunn_result = pg.pairwise_tests(data=self.df, dv=self.data_column, between=self.grouping_column, padjust='bonf', test='dunn')
|
2277
2265
|
posthoc_results = []
|
2278
2266
|
for idx, row in dunn_result.iterrows():
|
@@ -2283,138 +2271,379 @@ class spacrGraph:
|
|
2283
2271
|
'p-value': row['p-val'],
|
2284
2272
|
'Test Name': 'Dunn’s Post-hoc',
|
2285
2273
|
'n_object': None,
|
2286
|
-
'n_well': None
|
2287
|
-
|
2274
|
+
'n_well': None})
|
2275
|
+
|
2288
2276
|
return posthoc_results
|
2289
2277
|
return []
|
2290
|
-
|
2278
|
+
|
2291
2279
|
def create_plot(self, ax=None):
|
2292
2280
|
"""Create and display the plot based on the chosen graph type."""
|
2293
|
-
# Optional: Remove outliers for plotting
|
2294
|
-
if self.remove_outliers:
|
2295
|
-
self.df = self.remove_outliers_from_plot()
|
2296
2281
|
|
2297
|
-
|
2298
|
-
|
2282
|
+
def _generate_tabels(unique_groups):
|
2283
|
+
"""Generate row labels and a symbol table for multi-level grouping."""
|
2284
|
+
# Create row labels: Include the grouping column and data columns
|
2285
|
+
row_labels = [self.grouping_column] + self.data_column
|
2286
|
+
|
2287
|
+
# Initialize table data
|
2288
|
+
table_data = []
|
2289
|
+
|
2290
|
+
# Create the grouping row: Alternate each group for every data column
|
2291
|
+
grouping_row = []
|
2292
|
+
for _ in self.data_column:
|
2293
|
+
for group in unique_groups:
|
2294
|
+
grouping_row.append(group)
|
2295
|
+
table_data.append(grouping_row) # Add the grouping row to the table
|
2296
|
+
|
2297
|
+
# Create symbol rows for each data column
|
2298
|
+
for column in self.data_column:
|
2299
|
+
column_row = [] # Initialize a row for this column
|
2300
|
+
for data_col in self.data_column: # Iterate over data columns to align with the structure
|
2301
|
+
for group in unique_groups:
|
2302
|
+
# Assign '+' if the column matches, otherwise assign '-'
|
2303
|
+
if column == data_col:
|
2304
|
+
column_row.append('+')
|
2305
|
+
else:
|
2306
|
+
column_row.append('-')
|
2307
|
+
table_data.append(column_row) # Add this row to the table
|
2308
|
+
|
2309
|
+
# Transpose the table to align with the plot layout
|
2310
|
+
transposed_table = list(map(list, zip(*table_data)))
|
2311
|
+
return row_labels, transposed_table
|
2312
|
+
|
2313
|
+
def _place_symbols(row_labels, transposed_table, x_positions, ax):
|
2314
|
+
|
2315
|
+
# Get the bottom of the y-axis (y=0) in data coordinates and convert to display coordinates
|
2316
|
+
y_axis_min = ax.get_ylim()[0] # Minimum y-axis value (usually 0)
|
2317
|
+
symbol_start_y = ax.transData.transform((0, y_axis_min))[1] - 30 # Slightly below the x-axis line
|
2318
|
+
|
2319
|
+
# Convert to figure coordinates
|
2320
|
+
symbol_start_y_fig = ax.transAxes.inverted().transform((0, symbol_start_y))[1]
|
2321
|
+
|
2322
|
+
# Calculate y-spacing for the table rows (adjust as needed)
|
2323
|
+
y_spacing = 0.02 # Control vertical spacing between elements
|
2324
|
+
|
2325
|
+
# X-coordinate for the row labels at the y-axis and x-axis intersection
|
2326
|
+
label_x_pos = ax.get_xlim()[0] - 0.5 # Slightly offset from the y-axis
|
2327
|
+
|
2328
|
+
# Place the row titles at the y-axis intersection
|
2329
|
+
for row_idx, title in enumerate(row_labels):
|
2330
|
+
y_pos = symbol_start_y_fig - (row_idx * y_spacing) # Align with row index
|
2331
|
+
ax.text(label_x_pos, y_pos, title, ha='right', va='center', fontsize=12, fontweight='regular')
|
2332
|
+
|
2333
|
+
# Place the symbols under each bar
|
2334
|
+
for idx, (x_pos, column_data) in enumerate(zip(x_positions, transposed_table)):
|
2335
|
+
for row_idx, text in enumerate(column_data):
|
2336
|
+
y_pos = symbol_start_y_fig - (row_idx * y_spacing)
|
2337
|
+
ax.text(x_pos, y_pos, text, ha='center', va='center', fontsize=12)
|
2338
|
+
|
2339
|
+
def _get_positions(self, ax):
|
2340
|
+
if self.graph_type == 'bar':
|
2341
|
+
x_positions = [np.mean(bar.get_paths()[0].vertices[:, 0]) for bar in ax.collections if hasattr(bar, 'get_paths')]
|
2342
|
+
|
2343
|
+
elif self.graph_type == 'violin':
|
2344
|
+
x_positions = [np.mean(violin.get_paths()[0].vertices[:, 0]) for violin in ax.collections if hasattr(violin, 'get_paths')]
|
2345
|
+
|
2346
|
+
elif self.graph_type == 'box':
|
2347
|
+
x_positions = list(set(line.get_xdata().mean() for line in ax.lines if line.get_linestyle() == '-'))
|
2348
|
+
|
2349
|
+
elif self.graph_type == 'jitter':
|
2350
|
+
x_positions = [np.mean(collection.get_offsets()[:, 0]) for collection in ax.collections if collection.get_offsets().size > 0]
|
2351
|
+
return x_positions
|
2352
|
+
|
2353
|
+
def _draw_comparison_lines(ax, x_positions):
|
2354
|
+
"""Draw comparison lines and annotate significance based on results_df."""
|
2355
|
+
if self.results_df.empty:
|
2356
|
+
print("No comparisons available to annotate.")
|
2357
|
+
return
|
2358
|
+
|
2359
|
+
y_max = max([bar.get_height() for bar in ax.patches])
|
2360
|
+
ax.set_ylim(0, y_max * 1.3)
|
2361
|
+
|
2362
|
+
for idx, row in self.results_df.iterrows():
|
2363
|
+
group1, group2 = row['Comparison'].split(' vs ')
|
2364
|
+
p_value = row['p-value']
|
2365
|
+
|
2366
|
+
# Determine significance marker
|
2367
|
+
if p_value <= 0.001:
|
2368
|
+
significance = '***'
|
2369
|
+
elif p_value <= 0.01:
|
2370
|
+
significance = '**'
|
2371
|
+
elif p_value <= 0.05:
|
2372
|
+
significance = '*'
|
2373
|
+
else:
|
2374
|
+
significance = 'ns'
|
2299
2375
|
|
2300
|
-
|
2301
|
-
|
2302
|
-
|
2303
|
-
levene_result = {
|
2304
|
-
'Comparison': 'Levene’s test for equal variance',
|
2305
|
-
'Test Statistic': levene_stat,
|
2306
|
-
'p-value': levene_p,
|
2307
|
-
'Test Name': 'Levene’s Test'
|
2308
|
-
}
|
2376
|
+
# Find the x positions of the compared groups
|
2377
|
+
x1 = x_positions[unique_groups.tolist().index(group1)]
|
2378
|
+
x2 = x_positions[unique_groups.tolist().index(group2)]
|
2309
2379
|
|
2310
|
-
|
2311
|
-
|
2380
|
+
# Stagger lines to avoid overlap
|
2381
|
+
line_y = y_max + (0.1 * y_max) * (idx + 1)
|
2312
2382
|
|
2313
|
-
|
2314
|
-
|
2383
|
+
# Draw the comparison line
|
2384
|
+
ax.plot([x1, x1, x2, x2], [line_y - 0.02, line_y, line_y, line_y - 0.02], lw=1.5, c='black')
|
2315
2385
|
|
2316
|
-
|
2317
|
-
|
2386
|
+
# Add the significance marker
|
2387
|
+
ax.text((x1 + x2) / 2, line_y, significance, ha='center', va='bottom', fontsize=12)
|
2318
2388
|
|
2319
|
-
#
|
2320
|
-
|
2321
|
-
|
2322
|
-
lambda x: next((sample_sizes[sample_sizes[self.grouping_column] == g]['n'].values[0] for g in sample_sizes[self.grouping_column] if g in x), np.nan)
|
2323
|
-
)
|
2389
|
+
# Optional: Remove outliers for plotting
|
2390
|
+
if self.remove_outliers:
|
2391
|
+
self.df = self.remove_outliers_from_plot()
|
2324
2392
|
|
2325
|
-
|
2326
|
-
|
2327
|
-
|
2328
|
-
|
2393
|
+
self.df_melted = pd.melt(self.df, id_vars=[self.grouping_column], value_vars=self.data_column,var_name='Data Column', value_name='Value')
|
2394
|
+
unique_groups = self.df[self.grouping_column].unique()
|
2395
|
+
is_normal, normality_results = self.perform_normality_tests()
|
2396
|
+
levene_stat, levene_p = self.perform_levene_test(unique_groups)
|
2397
|
+
test_results = self.perform_statistical_tests(unique_groups, is_normal)
|
2398
|
+
posthoc_results = self.perform_posthoc_tests(is_normal, unique_groups)
|
2399
|
+
self.results_df = pd.DataFrame(normality_results + test_results + posthoc_results)
|
2329
2400
|
|
2330
|
-
|
2331
|
-
|
2401
|
+
num_groups = len(self.data_column)*len(self.grouping_column)
|
2402
|
+
num_data_columns = len(self.data_column)
|
2403
|
+
self.bar_width = 0.4
|
2404
|
+
spacing_between_groups = self.bar_width/0.5
|
2332
2405
|
|
2406
|
+
self.fig_width = (num_groups * self.bar_width) + (spacing_between_groups * num_groups)
|
2407
|
+
self.fig_height = self.fig_width/2
|
2408
|
+
|
2333
2409
|
if ax is None:
|
2334
|
-
self.fig, ax = plt.subplots(figsize=(
|
2410
|
+
self.fig, ax = plt.subplots(figsize=(self.fig_height, self.fig_width))
|
2335
2411
|
else:
|
2336
|
-
self.fig = ax.figure
|
2337
|
-
|
2338
|
-
sns.set(style="ticks")
|
2339
|
-
color_palette = self.sns_palette if not self.colors else self.colors
|
2412
|
+
self.fig = ax.figure
|
2340
2413
|
|
2341
|
-
|
2342
|
-
|
2343
|
-
|
2344
|
-
|
2345
|
-
|
2346
|
-
|
2347
|
-
|
2348
|
-
|
2349
|
-
|
2414
|
+
if len(self.data_column) == 1:
|
2415
|
+
self.hue=self.grouping_column
|
2416
|
+
self.jitter_bar_dodge = False
|
2417
|
+
else:
|
2418
|
+
self.hue='Data Column'
|
2419
|
+
self.jitter_bar_dodge = True
|
2420
|
+
|
2421
|
+
# Handle the different plot types based on `graph_type`
|
2422
|
+
if self.graph_type == 'bar':
|
2423
|
+
self._create_bar_plot(ax)
|
2424
|
+
elif self.graph_type == 'jitter':
|
2425
|
+
self._create_jitter_plot(ax)
|
2350
2426
|
elif self.graph_type == 'box':
|
2351
2427
|
self._create_box_plot(ax)
|
2352
2428
|
elif self.graph_type == 'violin':
|
2353
2429
|
self._create_violin_plot(ax)
|
2354
|
-
elif self.graph_type == 'jitter':
|
2355
|
-
self._create_jitter_plot(ax)
|
2356
2430
|
else:
|
2357
|
-
raise ValueError(f"
|
2358
|
-
|
2431
|
+
raise ValueError(f"Unknown graph type: {self.graph_type}")
|
2432
|
+
|
2359
2433
|
# Set y-axis start
|
2360
|
-
if self.
|
2361
|
-
|
2362
|
-
|
2363
|
-
|
2364
|
-
|
2365
|
-
ax.tick_params(axis='x', which='minor', bottom=False) # Disable minor ticks on x-axis
|
2366
|
-
ax.tick_params(axis='x', which='major', length=6, width=2, direction='out')
|
2367
|
-
ax.tick_params(axis='y', which='major', length=6, width=2, direction='out')
|
2368
|
-
ax.tick_params(axis='y', which='minor', length=4, width=1, direction='out')
|
2369
|
-
sns.despine(ax=ax, top=True, right=True)
|
2434
|
+
if isinstance(self.y_lim, list):
|
2435
|
+
if len(self.y_lim) == 2:
|
2436
|
+
ax.set_ylim(self.y_lim[0], self.y_lim[1])
|
2437
|
+
elif len(self.y_lim) == 1:
|
2438
|
+
ax.set_ylim(self.y_lim[0], None)
|
2370
2439
|
|
2440
|
+
sns.despine(ax=ax, top=True, right=True)
|
2441
|
+
ax.legend(loc='center left', bbox_to_anchor=(1, 0.5), title='Data Column') # Move the legend outside the plot
|
2442
|
+
ax.set_xlabel('')
|
2443
|
+
x_positions = _get_positions(self, ax)
|
2444
|
+
|
2445
|
+
if len(self.data_column) == 1:
|
2446
|
+
ax.legend().remove()
|
2447
|
+
ax.set_xticklabels(ax.get_xticklabels(), rotation=45, ha='right')
|
2448
|
+
|
2449
|
+
elif len(self.data_column) > 1:
|
2450
|
+
ax.set_xticks([])
|
2451
|
+
ax.tick_params(bottom=False)
|
2452
|
+
ax.set_xticklabels([])
|
2453
|
+
legend_ax = self.fig.add_axes([0.1, -0.2, 0.62, 0.2]) # Position the table closer to the graph
|
2454
|
+
legend_ax.set_axis_off()
|
2455
|
+
|
2456
|
+
row_labels, table_data = _generate_tabels(unique_groups)
|
2457
|
+
_place_symbols(row_labels, table_data, x_positions, ax)
|
2458
|
+
|
2459
|
+
#_draw_comparison_lines(ax, x_positions)
|
2460
|
+
|
2371
2461
|
if self.save:
|
2372
2462
|
self._save_results()
|
2373
2463
|
|
2374
|
-
|
2375
|
-
|
2376
|
-
def get_figure(self):
|
2377
|
-
"""Return the generated figure."""
|
2378
|
-
return self.fig
|
2464
|
+
ax.margins(x=0.12)
|
2379
2465
|
|
2380
|
-
def _create_bar_plot(self,
|
2466
|
+
def _create_bar_plot(self, ax):
|
2381
2467
|
"""Helper method to create a bar plot with consistent bar thickness and centered error bars."""
|
2382
|
-
|
2383
|
-
|
2384
|
-
|
2385
|
-
|
2386
|
-
|
2387
|
-
|
2468
|
+
# Flatten DataFrame: Combine grouping column and data column into one group if needed
|
2469
|
+
if len(self.data_column) > 1:
|
2470
|
+
self.df_melted['Combined Group'] = (self.df_melted[self.grouping_column].astype(str) + " - " + self.df_melted['Data Column'].astype(str))
|
2471
|
+
x_axis_column = 'Combined Group'
|
2472
|
+
hue = None
|
2473
|
+
ax.set_ylabel('Value')
|
2388
2474
|
else:
|
2389
|
-
|
2390
|
-
|
2391
|
-
|
2392
|
-
|
2393
|
-
|
2394
|
-
|
2475
|
+
x_axis_column = self.grouping_column
|
2476
|
+
ax.set_ylabel(self.data_column[0])
|
2477
|
+
hue = None
|
2478
|
+
|
2479
|
+
summary_df = self.df_melted.groupby([x_axis_column]).agg(mean=('Value', 'mean'),std=('Value', 'std'),sem=('Value', 'sem')).reset_index()
|
2480
|
+
error_bars = summary_df[self.error_bar_type] if self.error_bar_type in ['std', 'sem'] else None
|
2481
|
+
sns.barplot(data=self.df_melted, x=x_axis_column, y='Value', hue=self.hue, palette=self.sns_palette, ax=ax, dodge=self.jitter_bar_dodge, ci=None)
|
2482
|
+
|
2483
|
+
# Adjust the bar width manually
|
2484
|
+
if len(self.data_column) > 1:
|
2485
|
+
bars = [bar for bar in ax.patches if isinstance(bar, plt.Rectangle)]
|
2486
|
+
target_width = self.bar_width * 2
|
2487
|
+
for bar in bars:
|
2488
|
+
bar.set_width(target_width) # Set new width
|
2489
|
+
# Center the bar on its x-coordinate
|
2490
|
+
bar.set_x(bar.get_x() - target_width / 2)
|
2491
|
+
|
2492
|
+
# Adjust error bars alignment with bars
|
2493
|
+
bars = [bar for bar in ax.patches if isinstance(bar, plt.Rectangle)]
|
2494
|
+
for bar, (_, row) in zip(bars, summary_df.iterrows()):
|
2495
|
+
x_bar = bar.get_x() + bar.get_width() / 2
|
2496
|
+
err = row[self.error_bar_type]
|
2497
|
+
ax.errorbar(x=x_bar, y=bar.get_height(), yerr=err, fmt='none', c='black', capsize=5, lw=2)
|
2498
|
+
|
2499
|
+
# Set legend and labels
|
2500
|
+
ax.set_xlabel(self.grouping_column)
|
2395
2501
|
|
2396
2502
|
def _create_jitter_plot(self, ax):
|
2397
|
-
"""Helper method to create a jitter plot (strip plot)."""
|
2398
|
-
|
2399
|
-
|
2503
|
+
"""Helper method to create a jitter plot (strip plot) with consistent spacing."""
|
2504
|
+
# Combine grouping column and data column if needed
|
2505
|
+
if len(self.data_column) > 1:
|
2506
|
+
self.df_melted['Combined Group'] = (self.df_melted[self.grouping_column].astype(str) + " - " + self.df_melted['Data Column'].astype(str))
|
2507
|
+
x_axis_column = 'Combined Group'
|
2508
|
+
hue = None # Disable hue to avoid two-level grouping
|
2509
|
+
ax.set_ylabel('Value')
|
2510
|
+
else:
|
2511
|
+
x_axis_column = self.grouping_column
|
2512
|
+
ax.set_ylabel(self.data_column[0])
|
2513
|
+
hue = None
|
2514
|
+
|
2515
|
+
# Create the jitter plot
|
2516
|
+
sns.stripplot(data=self.df_melted,x=x_axis_column,y='Value',hue=self.hue, palette=self.sns_palette, dodge=self.jitter_bar_dodge, jitter=self.bar_width, ax=ax,alpha=0.6)
|
2517
|
+
|
2518
|
+
# Adjust legend and labels
|
2519
|
+
ax.set_xlabel(self.grouping_column)
|
2520
|
+
|
2521
|
+
# Manage the legend
|
2522
|
+
handles, labels = ax.get_legend_handles_labels()
|
2523
|
+
unique_labels = dict(zip(labels, handles))
|
2524
|
+
ax.legend(unique_labels.values(), unique_labels.keys(), loc='best')
|
2525
|
+
|
2400
2526
|
def _create_box_plot(self, ax):
|
2401
|
-
"""Helper method to create a box plot."""
|
2402
|
-
|
2527
|
+
"""Helper method to create a box plot with consistent spacing."""
|
2528
|
+
# Combine grouping column and data column if needed
|
2529
|
+
if len(self.data_column) > 1:
|
2530
|
+
self.df_melted['Combined Group'] = (self.df_melted[self.grouping_column].astype(str) + " - " + self.df_melted['Data Column'].astype(str))
|
2531
|
+
x_axis_column = 'Combined Group'
|
2532
|
+
hue = None
|
2533
|
+
ax.set_ylabel('Value')
|
2534
|
+
else:
|
2535
|
+
x_axis_column = self.grouping_column
|
2536
|
+
ax.set_ylabel(self.data_column[0])
|
2537
|
+
hue = None
|
2538
|
+
|
2539
|
+
# Create the box plot
|
2540
|
+
sns.boxplot(data=self.df_melted,x=x_axis_column,y='Value',hue=self.hue,palette=self.sns_palette,ax=ax)
|
2541
|
+
|
2542
|
+
# Adjust legend and labels
|
2543
|
+
ax.set_xlabel(self.grouping_column)
|
2403
2544
|
|
2545
|
+
# Manage the legend
|
2546
|
+
handles, labels = ax.get_legend_handles_labels()
|
2547
|
+
unique_labels = dict(zip(labels, handles))
|
2548
|
+
ax.legend(unique_labels.values(), unique_labels.keys(), loc='best')
|
2549
|
+
|
2404
2550
|
def _create_violin_plot(self, ax):
|
2405
|
-
"""Helper method to create a violin plot."""
|
2406
|
-
|
2551
|
+
"""Helper method to create a violin plot with consistent spacing."""
|
2552
|
+
# Combine grouping column and data column if needed
|
2553
|
+
if len(self.data_column) > 1:
|
2554
|
+
self.df_melted['Combined Group'] = (self.df_melted[self.grouping_column].astype(str) + " - " + self.df_melted['Data Column'].astype(str))
|
2555
|
+
x_axis_column = 'Combined Group'
|
2556
|
+
hue = None
|
2557
|
+
ax.set_ylabel('Value')
|
2558
|
+
else:
|
2559
|
+
x_axis_column = self.grouping_column
|
2560
|
+
ax.set_ylabel(self.data_column[0])
|
2561
|
+
hue = None
|
2562
|
+
|
2563
|
+
# Create the violin plot
|
2564
|
+
sns.violinplot(data=self.df_melted,x=x_axis_column,y='Value', hue=self.hue,palette=self.sns_palette,ax=ax)
|
2565
|
+
|
2566
|
+
# Adjust legend and labels
|
2567
|
+
ax.set_xlabel(self.grouping_column)
|
2568
|
+
ax.set_ylabel('Value')
|
2569
|
+
|
2570
|
+
# Manage the legend
|
2571
|
+
handles, labels = ax.get_legend_handles_labels()
|
2572
|
+
unique_labels = dict(zip(labels, handles))
|
2573
|
+
ax.legend(unique_labels.values(), unique_labels.keys(), loc='best')
|
2407
2574
|
|
2408
2575
|
def _save_results(self):
|
2409
2576
|
"""Helper method to save the plot and results."""
|
2410
2577
|
os.makedirs(self.output_dir, exist_ok=True)
|
2411
|
-
plot_path = os.path.join(self.output_dir,
|
2412
|
-
self.fig.savefig(plot_path)
|
2413
|
-
results_path = os.path.join(self.output_dir,
|
2578
|
+
plot_path = os.path.join(self.output_dir, f"{self.results_name}.pdf")
|
2579
|
+
self.fig.savefig(plot_path, bbox_inches='tight', dpi=600, transparent=True, format='pdf')
|
2580
|
+
results_path = os.path.join(self.output_dir, f"{self.results_name}.csv")
|
2414
2581
|
self.results_df.to_csv(results_path, index=False)
|
2415
2582
|
print(f"Plot saved to {plot_path}")
|
2416
2583
|
print(f"Test results saved to {results_path}")
|
2417
2584
|
|
2418
2585
|
def get_results(self):
|
2419
2586
|
"""Return the results dataframe."""
|
2420
|
-
return self.results_df
|
2587
|
+
return self.results_df
|
2588
|
+
|
2589
|
+
def get_figure(self):
|
2590
|
+
"""Return the generated figure."""
|
2591
|
+
return self.fig
|
2592
|
+
|
2593
|
+
def plot_data_from_db(settings):
|
2594
|
+
from .io import _read_db
|
2595
|
+
from .utils import annotate_conditions
|
2596
|
+
"""
|
2597
|
+
Extracts the specified table from the SQLite database and plots a specified column.
|
2598
|
+
|
2599
|
+
Args:
|
2600
|
+
db_path (str): The path to the SQLite database.
|
2601
|
+
table_name (str): The name of the table to extract.
|
2602
|
+
column_name (str): The column to plot from the table.
|
2603
|
+
|
2604
|
+
Returns:
|
2605
|
+
df (pd.DataFrame): The extracted table as a DataFrame.
|
2606
|
+
"""
|
2607
|
+
|
2608
|
+
db_loc = os.path.join(settings['src'], 'measurements',settings['database'])
|
2609
|
+
|
2610
|
+
[df] = _read_db(db_loc, tables=[settings['table_name']])
|
2611
|
+
|
2612
|
+
df = annotate_conditions(df,
|
2613
|
+
cells=settings['cell_types'],
|
2614
|
+
cell_loc=settings['cell_plate_metadata'],
|
2615
|
+
pathogens=settings['pathogen_types'],
|
2616
|
+
pathogen_loc=settings['pathogen_plate_metadata'],
|
2617
|
+
treatments=settings['treatments'],
|
2618
|
+
treatment_loc=settings['treatment_plate_metadata'])
|
2619
|
+
|
2620
|
+
df['prc'] = df['plate'].astype(str) + '_' + df['row'].astype(str) + '_' + df['col'].astype(str)
|
2621
|
+
df = df.dropna(subset=settings['column_name'])
|
2622
|
+
df['class'] = df['png_path'].apply(lambda x: 'class_1' if 'class_1' in x else ('class_0' if 'class_0' in x else None))
|
2623
|
+
|
2624
|
+
spacr_graph = spacrGraph(
|
2625
|
+
df=df, # Your DataFrame
|
2626
|
+
grouping_column=settings['grouping_column'], # Column for grouping the data (x-axis)
|
2627
|
+
data_column=settings['column_name'], # Column for the data (y-axis)
|
2628
|
+
graph_type=settings['graph_type'], # Type of plot ('bar', 'box', 'violin', 'jitter')
|
2629
|
+
summary_func='mean', # Function to summarize data (e.g., 'mean', 'median')
|
2630
|
+
colors=None, # Custom colors for the plot (optional)
|
2631
|
+
output_dir=settings['dst'], # Directory to save the plot and results
|
2632
|
+
save=settings['save'], # Whether to save the plot and results
|
2633
|
+
y_lim=settings['y_lim'], # Starting point for y-axis (optional)
|
2634
|
+
error_bar_type='std', # Type of error bar ('std' or 'sem')
|
2635
|
+
representation='well',
|
2636
|
+
theme=settings['theme'], # Seaborn color palette theme (e.g., 'pastel', 'muted')
|
2637
|
+
)
|
2638
|
+
|
2639
|
+
# Create the plot
|
2640
|
+
spacr_graph.create_plot()
|
2641
|
+
|
2642
|
+
# Get the figure object if needed
|
2643
|
+
fig = spacr_graph.get_figure()
|
2644
|
+
plt.show()
|
2645
|
+
|
2646
|
+
# Optional: Get the results DataFrame containing statistical test results
|
2647
|
+
results_df = spacr_graph.get_results()
|
2648
|
+
|
2649
|
+
return fig, results_df
|
spacr/settings.py
CHANGED
@@ -278,6 +278,7 @@ def get_measure_crop_settings(settings={}):
|
|
278
278
|
|
279
279
|
def set_default_analyze_screen(settings):
|
280
280
|
settings.setdefault('src', 'path')
|
281
|
+
settings.setdefault('annotation_column', None)
|
281
282
|
settings.setdefault('model_type_ml','xgboost')
|
282
283
|
settings.setdefault('heatmap_feature','predictions')
|
283
284
|
settings.setdefault('grouping','mean')
|
@@ -13,16 +13,16 @@ spacr/deep_spacr.py,sha256=HdOcNU8cHcE_19nP7_5uTz-ih3E169ffr2Hm--NvMvA,43255
|
|
13
13
|
spacr/gui.py,sha256=ARyn9Q_g8HoP-cXh1nzMLVFCKqthY4v2u9yORyaQqQE,8230
|
14
14
|
spacr/gui_core.py,sha256=LV_HX5zreu3Bye6sQFDbOuk8Dfj4StMoohy6hsrDEXA,41363
|
15
15
|
spacr/gui_elements.py,sha256=puDqf7PJJ_UMA01fjqODk-zsfSmvzVXpvaZ1BYV988w,136554
|
16
|
-
spacr/gui_utils.py,sha256=
|
16
|
+
spacr/gui_utils.py,sha256=7e9DsZIuV7-jh97kEf7v1In_cFzlFueV4SGcGYGpTxw,45454
|
17
17
|
spacr/io.py,sha256=AARmqn1fMmTgVDwWy8bEYK6SjH-6DZIulgCSPdBTyf0,143370
|
18
18
|
spacr/logger.py,sha256=lJhTqt-_wfAunCPl93xE65Wr9Y1oIHJWaZMjunHUeIw,1538
|
19
19
|
spacr/measure.py,sha256=BThn_sALgKrwGKnLOGpT4FyoJeRVoTZoP9SXbCtCMRw,54857
|
20
20
|
spacr/mediar.py,sha256=FwLvbLQW5LQzPgvJZG8Lw7GniA2vbZx6Jv6vIKu7I5c,14743
|
21
|
-
spacr/ml.py,sha256=
|
21
|
+
spacr/ml.py,sha256=8i2D9YEC9rSYdbgkuuMLl6adwivWn2Z5BEUjPRsW4t4,48983
|
22
22
|
spacr/openai.py,sha256=5vBZ3Jl2llYcW3oaTEXgdyCB2aJujMUIO5K038z7w_A,1246
|
23
|
-
spacr/plot.py,sha256=
|
23
|
+
spacr/plot.py,sha256=PtCSoBmLFlGC7ebmsk-vMlyd7q2ahXgRVaTtAq3w_po,116513
|
24
24
|
spacr/sequencing.py,sha256=t18mgpK6rhWuB1LtFOsPxqgpFXxuUmrD06ecsaVQ0Gw,19655
|
25
|
-
spacr/settings.py,sha256=
|
25
|
+
spacr/settings.py,sha256=7rAvzPkfyfbpY6JQqcTe6PcCghEvLebUgsfKMVBtNyU,75879
|
26
26
|
spacr/sim.py,sha256=1xKhXimNU3ukzIw-3l9cF3Znc_brW8h20yv8fSTzvss,71173
|
27
27
|
spacr/submodules.py,sha256=AB7s6-cULsaqz-haAaCtXfGEIi8uPZGT4xoCslUJC3Y,18391
|
28
28
|
spacr/timelapse.py,sha256=FSYpUtAVy6xc3lwprRYgyDTT9ysUhfRQ4zrP9_h2mvg,39465
|
@@ -150,9 +150,9 @@ spacr/resources/icons/umap.png,sha256=dOLF3DeLYy9k0nkUybiZMe1wzHQwLJFRmgccppw-8b
|
|
150
150
|
spacr/resources/images/plate1_E01_T0001F001L01A01Z01C02.tif,sha256=Tl0ZUfZ_AYAbu0up_nO0tPRtF1BxXhWQ3T3pURBCCRo,7958528
|
151
151
|
spacr/resources/images/plate1_E01_T0001F001L01A02Z01C01.tif,sha256=m8N-V71rA1TT4dFlENNg8s0Q0YEXXs8slIn7yObmZJQ,7958528
|
152
152
|
spacr/resources/images/plate1_E01_T0001F001L01A03Z01C03.tif,sha256=Pbhk7xn-KUP6RSIhJsxQcrHFImBm3GEpLkzx7WOc-5M,7958528
|
153
|
-
spacr-0.3.
|
154
|
-
spacr-0.3.
|
155
|
-
spacr-0.3.
|
156
|
-
spacr-0.3.
|
157
|
-
spacr-0.3.
|
158
|
-
spacr-0.3.
|
153
|
+
spacr-0.3.34.dist-info/LICENSE,sha256=SR-2MeGc6SCM1UORJYyarSWY_A-JaOMFDj7ReSs9tRM,1083
|
154
|
+
spacr-0.3.34.dist-info/METADATA,sha256=6C9_x3YSb9ycM9cXRufcPTJG809Cf-hkNYDOdEVXMT0,5949
|
155
|
+
spacr-0.3.34.dist-info/WHEEL,sha256=HiCZjzuy6Dw0hdX5R3LCFPDmFS4BWl8H-8W39XfmgX4,91
|
156
|
+
spacr-0.3.34.dist-info/entry_points.txt,sha256=BMC0ql9aNNpv8lUZ8sgDLQMsqaVnX5L535gEhKUP5ho,296
|
157
|
+
spacr-0.3.34.dist-info/top_level.txt,sha256=GJPU8FgwRXGzKeut6JopsSRY2R8T3i9lDgya42tLInY,6
|
158
|
+
spacr-0.3.34.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|