spacr 0.3.43__py3-none-any.whl → 0.3.46__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/submodules.py CHANGED
@@ -141,14 +141,13 @@ def analyze_plaques(settings):
141
141
 
142
142
  settings = get_analyze_plaque_settings(settings)
143
143
  save_settings(settings, name='analyze_plaques', show=True)
144
+ settings['dst'] = os.path.join(settings['src'], 'masks')
144
145
 
145
146
  if settings['masks']:
146
- settings['dst'] = os.path.join(settings['src'], 'masks')
147
- display(settings)
148
147
  identify_masks_finetune(settings)
149
148
  folder = settings['dst']
150
149
  else:
151
- folder = settings['src']
150
+ folder = settings['dst']
152
151
 
153
152
  summary_data = []
154
153
  details_data = []
@@ -156,9 +155,9 @@ def analyze_plaques(settings):
156
155
 
157
156
  for filename in os.listdir(folder):
158
157
  filepath = os.path.join(folder, filename)
159
- if os.path.isfile(filepath):
160
- # Assuming each file is a NumPy array file (.npy) containing a 16-bit labeled image
161
- #image = np.load(filepath)
158
+
159
+ if filepath.endswith('.tif') and os.path.isfile(filepath):
160
+ print(f"Analyzing: {filepath}")
162
161
  image = cellpose.io.imread(filepath)
163
162
  labeled_image = label(image)
164
163
  regions = regionprops(labeled_image)
@@ -241,7 +240,6 @@ def train_cellpose(settings):
241
240
  label_files,
242
241
  settings['channels'],
243
242
  settings['percentiles'],
244
- settings['circular'],
245
243
  settings['invert'],
246
244
  settings['verbose'],
247
245
  settings['remove_background'],
@@ -258,7 +256,6 @@ def train_cellpose(settings):
258
256
  test_label_files,
259
257
  settings['channels'],
260
258
  settings['percentiles'],
261
- settings['circular'],
262
259
  settings['invert'],
263
260
  settings['verbose'],
264
261
  settings['remove_background'],
@@ -269,13 +266,12 @@ def train_cellpose(settings):
269
266
  test_images = [np.squeeze(img) if img.shape[-1] == 1 else img for img in test_images]
270
267
 
271
268
  else:
272
- images, masks, image_names, mask_names = _load_images_and_labels(img_src, mask_src, settings['circular'], settings['invert'])
269
+ images, masks, image_names, mask_names = _load_images_and_labels(img_src, mask_src, settings['invert'])
273
270
  images = [np.squeeze(img) if img.shape[-1] == 1 else img for img in images]
274
271
 
275
272
  if settings['test']:
276
273
  test_images, test_masks, test_image_names, test_mask_names = _load_images_and_labels(test_img_src,
277
274
  test_mask_src,
278
- settings['circular'],
279
275
  settings['invert'])
280
276
 
281
277
  test_images = [np.squeeze(img) if img.shape[-1] == 1 else img for img in test_images]
@@ -320,7 +316,6 @@ def train_cellpose(settings):
320
316
  SGD=False,
321
317
  channels=cp_channels,
322
318
  channel_axis=None,
323
- #rgb=False,
324
319
  normalize=False,
325
320
  compute_flows=False,
326
321
  save_path=model_save_path,
@@ -375,7 +370,10 @@ def count_phenotypes(settings):
375
370
 
376
371
  return
377
372
 
378
- def compare_reads_to_scores(reads_csv, scores_csv, empirical_dict={}, column='column', value='c3', plate='plate1', fraction_threshold=0.05):
373
+ def compare_reads_to_scores(reads_csv, scores_csv, empirical_dict={'r1':(90,10),'r2':(90,10),'r3':(80,20),'r4':(80,20),'r5':(70,30),'r6':(70,30),'r7':(60,40),'r8':(60,40),'r9':(50,50),'r10':(50,50),'r11':(40,60),'r12':(40,60),'r13':(30,70),'r14':(30,70),'r15':(20,80),'r16':(20,80)},
374
+ pc_grna='TGGT1_220950_1', nc_grna='TGGT1_233460_4',
375
+ y_columns=['class_1_fraction', 'TGGT1_220950_1_fraction', 'nc_fraction'],
376
+ column='column', value='c3', plate=None, save_paths=None):
379
377
 
380
378
  def calculate_well_score_fractions(df, class_columns='cv_predictions'):
381
379
  if all(col in df.columns for col in ['plate', 'row', 'column']):
@@ -392,34 +390,74 @@ def compare_reads_to_scores(reads_csv, scores_csv, empirical_dict={}, column='co
392
390
  summary_df['class_0_fraction'] = summary_df['class_0'] / summary_df['total_rows']
393
391
  summary_df['class_1_fraction'] = summary_df['class_1'] / summary_df['total_rows']
394
392
  return summary_df
395
-
396
- def plot_line(df, x_column, y_columns, group_column=None,
397
- xlabel=None, ylabel=None, title=None, figsize=(10, 6),
398
- save_path=None):
393
+
394
+ def plot_line(df, x_column, y_columns, group_column=None, xlabel=None, ylabel=None,
395
+ title=None, figsize=(10, 6), save_path=None, theme='deep'):
399
396
  """
400
397
  Create a line plot that can handle multiple y-columns, each becoming a separate line.
401
398
  """
402
- df = df.loc[natsorted(df.index, key=lambda x: df.loc[x, x_column])]
403
399
 
404
- plt.figure(figsize=figsize)
400
+ def _set_theme(theme):
401
+ """Set the Seaborn theme and reorder colors if necessary."""
402
+
403
+ def __set_reordered_theme(theme='deep', order=None, n_colors=100, show_theme=False):
404
+ """Set and reorder the Seaborn color palette."""
405
+ palette = sns.color_palette(theme, n_colors)
406
+ if order:
407
+ reordered_palette = [palette[i] for i in order]
408
+ else:
409
+ reordered_palette = palette
410
+ if show_theme:
411
+ sns.palplot(reordered_palette)
412
+ plt.show()
413
+ return reordered_palette
414
+
415
+ integer_list = list(range(1, 81))
416
+ color_order = [7, 9, 4, 0, 3, 6, 2] + integer_list
417
+ sns_palette = __set_reordered_theme(theme, color_order, 100)
418
+ return sns_palette
419
+
420
+ sns_palette = _set_theme(theme)
421
+
422
+ # Sort the DataFrame based on the x_column
423
+ df = df.loc[natsorted(df.index, key=lambda x: df.loc[x, x_column])]
424
+
425
+ fig, ax = plt.subplots(figsize=figsize)
405
426
 
427
+ # Handle multiple y-columns, each as a separate line
406
428
  if isinstance(y_columns, list):
407
- for y_col in y_columns:
408
- sns.lineplot(data=df, x=x_column, y=y_col, label=y_col, marker='o')
429
+ for idx, y_col in enumerate(y_columns):
430
+ sns.lineplot(
431
+ data=df, x=x_column, y=y_col, ax=ax, label=y_col,
432
+ color=sns_palette[idx % len(sns_palette)], linewidth=1
433
+ )
409
434
  else:
410
- sns.lineplot(data=df, x=x_column, y=y_columns, hue=group_column, marker='o')
411
- plt.xlabel(xlabel if xlabel else x_column)
412
- plt.ylabel(ylabel if ylabel else 'Value')
413
- plt.title(title if title else f'Line Plot')
435
+ sns.lineplot(
436
+ data=df, x=x_column, y=y_columns, hue=group_column, ax=ax,
437
+ palette=sns_palette, linewidth=2
438
+ )
439
+
440
+ # Set axis labels and title
441
+ ax.set_xlabel(xlabel if xlabel else x_column)
442
+ ax.set_ylabel(ylabel if ylabel else 'Value')
443
+ ax.set_title(title if title else 'Line Plot')
444
+
445
+ # Remove top and right spines
446
+ sns.despine(ax=ax)
447
+
448
+ # Ensure legend only appears when needed and place it to the right
414
449
  if group_column or isinstance(y_columns, list):
415
- plt.legend(title='Legend')
450
+ ax.legend(title='Legend', loc='center left', bbox_to_anchor=(1, 0.5))
416
451
 
417
452
  plt.tight_layout()
418
453
 
454
+ # Save the plot if a save path is provided
419
455
  if save_path:
420
- plt.savefig(save_path, format='png', dpi=300, bbox_inches='tight')
456
+ plt.savefig(save_path, format='pdf', dpi=600, bbox_inches='tight')
421
457
  print(f"Plot saved to {save_path}")
458
+
422
459
  plt.show()
460
+ return fig
423
461
 
424
462
  def calculate_grna_fraction_ratio(df, grna1='TGGT1_220950_1', grna2='TGGT1_233460_4'):
425
463
  # Filter relevant grna_names within each prc and group them
@@ -437,12 +475,9 @@ def compare_reads_to_scores(reads_csv, scores_csv, empirical_dict={}, column='co
437
475
  f'count_{grna2}': f'{grna2}_count'
438
476
  })
439
477
  result = grouped.reset_index()[['prc', f'{grna1}_count', f'{grna2}_count', 'fraction_ratio']]
440
-
441
478
  result['total_reads'] = result[f'{grna1}_count'] + result[f'{grna2}_count']
442
-
443
479
  result[f'{grna1}_fraction'] = result[f'{grna1}_count'] / result['total_reads']
444
480
  result[f'{grna2}_fraction'] = result[f'{grna2}_count'] / result['total_reads']
445
-
446
481
  return result
447
482
 
448
483
  def calculate_well_read_fraction(df, count_column='count'):
@@ -456,53 +491,63 @@ def compare_reads_to_scores(reads_csv, scores_csv, empirical_dict={}, column='co
456
491
  df['fraction'] = df['count'] / df['total_counts']
457
492
  return df
458
493
 
459
- reads_df = pd.read_csv(reads_csv)
460
- scores_df = pd.read_csv(scores_csv)
461
-
462
- if plate != None:
463
- reads_df['plate'] = plate
464
- scores_df['plate'] = plate
465
-
466
- if 'col' in reads_df.columns:
467
- reads_df = reads_df.rename(columns={'col': 'column'})
468
- if 'column_name' in reads_df.columns:
469
- reads_df = reads_df.rename(columns={'column_name': 'column'})
470
- if 'col' in scores_df.columns:
471
- scores_df = scores_df.rename(columns={'col': 'column'})
472
- if 'column_name' in scores_df.columns:
473
- scores_df = scores_df.rename(columns={'column_name': 'column'})
474
- if 'row_name' in reads_df.columns:
475
- reads_df = reads_df.rename(columns={'row_name': 'row'})
476
- if 'row_name' in scores_df.columns:
477
- scores_df = scores_df.rename(columns={'row_name': 'row'})
478
-
494
+ if isinstance(reads_csv, list):
495
+ if len(reads_csv) == len(scores_csv):
496
+ reads_ls = []
497
+ scores_ls = []
498
+ for i, reads_csv_temp in enumerate(reads_csv):
499
+ reads_df_temp = pd.read_csv(reads_csv_temp)
500
+ scores_df_temp = pd.read_csv(scores_csv[i])
501
+ reads_df_temp['plate'] = f"plate{i+1}"
502
+ scores_df_temp['plate'] = f"plate{i+1}"
503
+
504
+ if 'col' in reads_df_temp.columns:
505
+ reads_df_temp = reads_df_temp.rename(columns={'col': 'column'})
506
+ if 'column_name' in reads_df_temp.columns:
507
+ reads_df_temp = reads_df_temp.rename(columns={'column_name': 'column'})
508
+ if 'col' in scores_df_temp.columns:
509
+ scores_df_temp = scores_df_temp.rename(columns={'col': 'column'})
510
+ if 'column_name' in scores_df_temp.columns:
511
+ scores_df_temp = scores_df_temp.rename(columns={'column_name': 'column'})
512
+ if 'row_name' in reads_df_temp.columns:
513
+ reads_df_temp = reads_df_temp.rename(columns={'row_name': 'row'})
514
+ if 'row_name' in scores_df_temp.columns:
515
+ scores_df_temp = scores_df_temp.rename(columns={'row_name': 'row'})
516
+
517
+ reads_ls.append(reads_df_temp)
518
+ scores_ls.append(scores_df_temp)
519
+
520
+ reads_df = pd.concat(reads_ls, axis=0)
521
+ scores_df = pd.concat(scores_ls, axis=0)
522
+ print(f"Reads: {len(reads_df)} Scores: {len(scores_df)}")
523
+ else:
524
+ print(f"reads_csv and scores_csv must contain the same number of elements if reads_csv is a list")
525
+ else:
526
+ reads_df = pd.read_csv(reads_csv)
527
+ scores_df = pd.read_csv(scores_csv)
528
+ if plate != None:
529
+ reads_df['plate'] = plate
530
+ scores_df['plate'] = plate
531
+
479
532
  reads_df = calculate_well_read_fraction(reads_df)
480
533
  scores_df = calculate_well_score_fractions(scores_df)
481
534
  reads_col_df = reads_df[reads_df[column]==value]
482
535
  scores_col_df = scores_df[scores_df[column]==value]
483
536
 
484
- #reads_col_df = reads_col_df[reads_col_df['fraction'] >= fraction_threshold]
485
- reads_col_df = calculate_grna_fraction_ratio(reads_col_df, grna1='TGGT1_220950_1', grna2='TGGT1_233460_4')
537
+ reads_col_df = calculate_grna_fraction_ratio(reads_col_df, grna1=pc_grna, grna2=nc_grna)
486
538
  df = pd.merge(reads_col_df, scores_col_df, on='prc')
487
539
 
540
+ df_emp = pd.DataFrame([(key, val[0], val[1], val[0] / (val[0] + val[1]), val[1] / (val[0] + val[1])) for key, val in empirical_dict.items()],columns=['key', 'value1', 'value2', 'pc_fraction', 'nc_fraction'])
488
541
 
489
- # Convert the dictionary to a DataFrame and calculate fractions
490
- df_emp = pd.DataFrame(
491
- [(key, val[0], val[1], val[0] / (val[0] + val[1]), val[1] / (val[0] + val[1]))
492
- for key, val in empirical_dict.items()],
493
- columns=['key', 'value1', 'value2', 'fraction1', 'fraction2']
494
- )
495
-
496
542
  df = pd.merge(df, df_emp, left_on='row', right_on='key')
497
- display(df)
498
- y_columns = ['class_1_fraction', 'TGGT1_220950_1_fraction', 'fraction2']
499
543
 
500
- plot_line(df, x_column='row', y_columns=y_columns, group_column=None,
501
- xlabel=None, ylabel=None, title=None, figsize=(10, 6),
502
- save_path=None)
544
+ if any in y_columns not in df.columns:
545
+ print(f"columns in dataframe:")
546
+ for col in df.columns:
547
+ print(col)
548
+ return
549
+ display(df)
550
+ fig_1 = plot_line(df, x_column = 'pc_fraction', y_columns=y_columns, group_column=None, xlabel=None, ylabel='Fraction', title=None, figsize=(10, 6), save_path=save_paths[0])
551
+ fig_2 = plot_line(df, x_column = 'nc_fraction', y_columns=y_columns, group_column=None, xlabel=None, ylabel='Fraction', title=None, figsize=(10, 6), save_path=save_paths[1])
503
552
 
504
- y_columns = ['class_0_fraction', 'TGGT1_233460_4_fraction', 'fraction1']
505
-
506
- plot_line(df, x_column='row', y_columns=y_columns, group_column=None,
507
- xlabel=None, ylabel=None, title=None, figsize=(10, 6),
508
- save_path=None)
553
+ return [fig_1, fig_2]
spacr/toxo.py CHANGED
@@ -1,3 +1,4 @@
1
+ import os
1
2
  import matplotlib.pyplot as plt
2
3
  import seaborn as sns
3
4
  import numpy as np
@@ -6,125 +7,13 @@ import pandas as pd
6
7
  from scipy.stats import fisher_exact
7
8
  from IPython.display import display
8
9
 
9
- def custom_volcano_plot_v1(data_path, metadata_path, metadata_column='tagm_location', point_size=50, figsize=20, threshold=0):
10
- """
11
- Create a volcano plot with the ability to control the shape of points based on a categorical column,
12
- color points based on a string list, annotate specific points based on p-value and coefficient thresholds,
13
- and control the size of points.
14
-
15
- Parameters:
16
- - data_path: Path to the data CSV file.
17
- - metadata_path: Path to the metadata CSV file.
18
- - metadata_column: Column name in the metadata to control point shapes.
19
- - string_list: List of strings to color points differently if present in 'coefficient' names.
20
- - point_size: Fixed value to control the size of points.
21
- - figsize: Width of the plot (height is half the width).
22
- """
23
-
24
-
25
- filename = 'volcano_plot.pdf'
26
-
27
- # Load the data
28
-
29
- if isinstance(data_path, pd.DataFrame):
30
- data = data_path
31
- else:
32
- data = pd.read_csv(data_path)
33
- data['variable'] = data['feature'].str.extract(r'\[(.*?)\]')
34
- data['variable'].fillna(data['feature'], inplace=True)
35
- split_columns = data['variable'].str.split('_', expand=True)
36
- data['gene_nr'] = split_columns[0]
37
-
38
- # Load metadata
39
- if isinstance(metadata_path, pd.DataFrame):
40
- metadata = metadata_path
41
- else:
42
- metadata = pd.read_csv(metadata_path)
43
-
44
- metadata['gene_nr'] = metadata['gene_nr'].astype(str)
45
- data['gene_nr'] = data['gene_nr'].astype(str)
46
-
47
-
48
- # Merge data and metadata on 'gene_nr'
49
- merged_data = pd.merge(data, metadata[['gene_nr', 'tagm_location']], on='gene_nr', how='left')
50
-
51
- merged_data.loc[merged_data['gene_nr'].str.startswith('4'), metadata_column] = 'GT1_gene'
52
- merged_data.loc[merged_data['gene_nr'] == 'Intercept', metadata_column] = 'Intercept'
53
-
54
- # Create the volcano plot
55
- figsize_2 = figsize / 2
56
- plt.figure(figsize=(figsize_2, figsize))
57
-
58
- palette = {
59
- 'pc': 'red',
60
- 'nc': 'green',
61
- 'control': 'black',
62
- 'other': 'gray'
63
- }
64
-
65
- merged_data['condition'] = pd.Categorical(
66
- merged_data['condition'],
67
- categories=['pc', 'nc', 'control', 'other'],
68
- ordered=True
69
- )
70
-
71
- display(merged_data)
72
-
73
- # Create the scatter plot with fixed point size
74
- sns.scatterplot(
75
- data=merged_data,
76
- x='coefficient',
77
- y='-log10(p_value)',
78
- hue='condition', # Controls color
79
- style=metadata_column if metadata_column else None, # Controls point shape
80
- s=point_size, # Fixed size for all points
81
- palette=palette, # Color palette
82
- alpha=1.0 # Transparency
83
- )
84
-
85
- # Set the plot title and labels
86
- plt.title('Custom Volcano Plot of Coefficients')
87
- plt.xlabel('Coefficient')
88
- plt.ylabel('-log10(p-value)')
89
-
90
- if threshold > 0:
91
- plt.gca().axvline(x=-abs(threshold), linestyle='--', color='black')
92
- plt.gca().axvline(x=abs(threshold), linestyle='--', color='black')
93
-
94
- # Horizontal line at p-value threshold (0.05)
95
- plt.axhline(y=-np.log10(0.05), color='black', linestyle='--')
96
-
97
- texts = []
98
- for i, row in merged_data.iterrows():
99
- if row['p_value'] <= 0.05 and abs(row['coefficient']) >= abs(threshold):
100
- texts.append(plt.text(
101
- row['coefficient'],
102
- -np.log10(row['p_value']),
103
- row['variable'],
104
- fontsize=8
105
- ))
106
-
107
- # Adjust text positions to avoid overlap
108
- adjust_text(texts, arrowprops=dict(arrowstyle='-', color='black'))
109
-
110
- # Move the legend outside the plot
111
- plt.legend(bbox_to_anchor=(1.05, 1), loc='upper left', borderaxespad=0.)
112
-
113
- # Save the plot
114
- plt.savefig(filename, format='pdf', bbox_inches='tight') # bbox_inches ensures the legend doesn't get cut off
115
- print(f'Saved Volcano plot: {filename}')
116
-
117
- # Show the plot
118
- plt.show()
119
-
120
- def custom_volcano_plot(data_path, metadata_path, metadata_column='tagm_location', point_size=50, figsize=20, threshold=0, split_axis_lims = [10, None, None, 10]):
10
+ def custom_volcano_plot(data_path, metadata_path, metadata_column='tagm_location', point_size=50, figsize=20, threshold=0, split_axis_lims = [10, None, None, 10], save_path=None):
121
11
  """
122
12
  Create a volcano plot with the ability to control the shape of points based on a categorical column,
123
13
  color points based on a condition, annotate specific points based on p-value and coefficient thresholds,
124
14
  and control the size of points.
125
15
  """
126
-
127
- filename = 'volcano_plot.pdf'
16
+ volcano_path = save_path
128
17
 
129
18
  # Load the data
130
19
  if isinstance(data_path, pd.DataFrame):
@@ -297,8 +186,8 @@ def custom_volcano_plot(data_path, metadata_path, metadata_column='tagm_location
297
186
  fig.suptitle('Custom Volcano Plot of Coefficients', y=1.02, fontsize=16) # Title above the top plot
298
187
 
299
188
  # Save the plot as PDF
300
- plt.savefig(filename, format='pdf', bbox_inches='tight')
301
- print(f'Saved Volcano plot: {filename}')
189
+ plt.savefig(volcano_path, format='pdf', bbox_inches='tight')
190
+ print(f'Saved Volcano plot: {volcano_path}')
302
191
 
303
192
  # Show the plot
304
193
  plt.show()
spacr/utils.py CHANGED
@@ -51,7 +51,8 @@ from scipy.stats import fisher_exact, f_oneway, kruskal
51
51
  from scipy.ndimage.filters import gaussian_filter
52
52
  from scipy.spatial import ConvexHull
53
53
  from scipy.interpolate import splprep, splev
54
- from scipy.ndimage import binary_dilation
54
+ from scipy import ndimage
55
+ from scipy.ndimage import binary_dilation, binary_fill_holes
55
56
 
56
57
  from skimage.exposure import rescale_intensity
57
58
  from sklearn.metrics import auc, precision_recall_curve
@@ -706,6 +707,7 @@ def _update_database_with_merged_info(db_path, df, table='png_list', columns=['p
706
707
  conn.close()
707
708
 
708
709
  def _generate_representative_images(db_path, cells=['HeLa'], cell_loc=None, pathogens=['rh'], pathogen_loc=None, treatments=['cm'], treatment_loc=None, channel_of_interest=1, compartments = ['pathogen','cytoplasm'], measurement = 'mean_intensity', nr_imgs=16, channel_indices=[0,1,2], um_per_pixel=0.1, scale_bar_length_um=10, plot=False, fontsize=12, show_filename=True, channel_names=None, update_db=True):
710
+
709
711
  """
710
712
  Generates representative images based on the provided parameters.
711
713
 
@@ -4480,6 +4482,7 @@ def cluster_feature_analysis(all_df, cluster_col='cluster'):
4480
4482
  return combined_df
4481
4483
 
4482
4484
  def _merge_cells_based_on_parasite_overlap(parasite_mask, cell_mask, nuclei_mask, overlap_threshold=5, perimeter_threshold=30):
4485
+
4483
4486
  """
4484
4487
  Merge cells in cell_mask if a parasite in parasite_mask overlaps with more than one cell,
4485
4488
  and if cells share more than a specified perimeter percentage.
@@ -4607,9 +4610,9 @@ def adjust_cell_masks(parasite_folder, cell_folder, nuclei_folder, overlap_thres
4607
4610
  if not (os.path.exists(cell_path) and os.path.exists(nuclei_path)):
4608
4611
  raise ValueError(f"Corresponding cell or nuclei mask file for {file_name} not found.")
4609
4612
  # Load the masks
4610
- parasite_mask = np.load(parasite_path)
4611
- cell_mask = np.load(cell_path)
4612
- nuclei_mask = np.load(nuclei_path)
4613
+ parasite_mask = np.load(parasite_path, allow_pickle=True)
4614
+ cell_mask = np.load(cell_path, allow_pickle=True)
4615
+ nuclei_mask = np.load(nuclei_path, allow_pickle=True)
4613
4616
  # Merge and relabel cells
4614
4617
  merged_cell_mask = _merge_cells_based_on_parasite_overlap(parasite_mask, cell_mask, nuclei_mask, overlap_threshold, perimeter_threshold)
4615
4618
 
@@ -5177,4 +5180,33 @@ def add_column_to_database(settings):
5177
5180
  conn.commit()
5178
5181
  conn.close()
5179
5182
 
5180
- print(f"Updated '{new_column_name}' in '{settings['table_name']}' using '{settings['match_column']}'.")
5183
+ print(f"Updated '{new_column_name}' in '{settings['table_name']}' using '{settings['match_column']}'.")
5184
+
5185
+ def fill_holes_in_mask(mask):
5186
+ """
5187
+ Fill holes in each object in the mask while keeping objects separated.
5188
+
5189
+ Args:
5190
+ mask (np.ndarray): A labeled mask where each object has a unique integer value.
5191
+
5192
+ Returns:
5193
+ np.ndarray: A mask with holes filled and original labels preserved.
5194
+ """
5195
+ # Ensure the mask is integer-labeled
5196
+ labeled_mask, num_features = ndimage.label(mask)
5197
+
5198
+ # Create an empty mask to store the result
5199
+ filled_mask = np.zeros_like(labeled_mask)
5200
+
5201
+ # Fill holes for each labeled object independently
5202
+ for i in range(1, num_features + 1):
5203
+ # Create a binary mask for the current object
5204
+ object_mask = (labeled_mask == i)
5205
+
5206
+ # Fill holes within this object
5207
+ filled_object = binary_fill_holes(object_mask)
5208
+
5209
+ # Assign the original label back to the filled object
5210
+ filled_mask[filled_object] = i
5211
+
5212
+ return filled_mask
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: spacr
3
- Version: 0.3.43
3
+ Version: 0.3.46
4
4
  Summary: Spatial phenotype analysis of crisp screens (SpaCr)
5
5
  Home-page: https://github.com/EinarOlafsson/spacr
6
6
  Author: Einar Birnir Olafsson
@@ -1,4 +1,4 @@
1
- spacr/__init__.py,sha256=3TNo4PgxHZTHOhyPc8AORvG3tzdPFEc30KAtsOou174,1618
1
+ spacr/__init__.py,sha256=CZtAdU5etLcb9dVmz-4Y7Hjhw3ubjMzfjG0L5ybyFVA,1592
2
2
  spacr/__main__.py,sha256=bkAJJD2kjIqOP-u1kLvct9jQQCeUXzlEjdgitwi1Lm8,75
3
3
  spacr/app_annotate.py,sha256=zGmAJplDOckhaUZijkHgbFH9LJNbd6TolU2hamplOBc,2769
4
4
  spacr/app_classify.py,sha256=urTP_wlZ58hSyM5a19slYlBxN0PdC-9-ga0hvq8CGWc,165
@@ -7,27 +7,27 @@ spacr/app_mask.py,sha256=l-dBY8ftzCMdDe6-pXc2Nh_u-idNL9G7UOARiLJBtds,153
7
7
  spacr/app_measure.py,sha256=_K7APYIeOKpV6e_LcqabBjvEi7mfq9Fch8175x1x0k8,162
8
8
  spacr/app_sequencing.py,sha256=DjG26jy4cpddnV8WOOAIiExtOe9MleVMY4MFa5uTo5w,157
9
9
  spacr/app_umap.py,sha256=ZWAmf_OsIKbYvolYuWPMYhdlVe-n2CADoJulAizMiEo,153
10
- spacr/cellpose.py,sha256=KxgPAHEs4iLYZA-h_HBYnpSB_rSZKhEBZ6Fs0I9x5E0,13849
10
+ spacr/cellpose.py,sha256=RBHMs2vwXcfkj0xqAULpALyzJYXddSRycgZSzmwI7v0,14755
11
11
  spacr/core.py,sha256=dW9RrAKFLfVsFhX0-kaVMc2T7b47Ky0pTXK-CEVOeWQ,48235
12
12
  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=N7R7yvfK_dJhOReM_kW3Ci8Bokhi1OzsxeKqvSGdvV4,41460
15
15
  spacr/gui_elements.py,sha256=w-S1MZdyxt5O3DsNAHNNXy_WGfwBPg0NhwQtCsJeiao,137071
16
16
  spacr/gui_utils.py,sha256=KDWDWsi7UdZVhXk1ZWGx3ZqJMIxCUm3lGfjrVhbk52s,45463
17
- spacr/io.py,sha256=ahsUaDwvkCHxGu_uvhgNCGWiJL_-ze291rHHQvdrFXQ,144622
17
+ spacr/io.py,sha256=1rIdJ_8dyn7W4D2zXjaOqlgyo_Y5Z7X86aRp4hNYWCU,144194
18
18
  spacr/logger.py,sha256=lJhTqt-_wfAunCPl93xE65Wr9Y1oIHJWaZMjunHUeIw,1538
19
19
  spacr/measure.py,sha256=KdboGXoi85BO5-_6er7932FgjFI7G7tuaQDnWSiEuew,54817
20
20
  spacr/mediar.py,sha256=FwLvbLQW5LQzPgvJZG8Lw7GniA2vbZx6Jv6vIKu7I5c,14743
21
- spacr/ml.py,sha256=vzuEnbQd96mn7T8h3GRsEDnpWSSpxd3ApGMXTiG6b2o,50507
21
+ spacr/ml.py,sha256=bPcKVk1camnOhv8jQglj6EYyipAxxmiB1QJ2Fdo3dEM,50654
22
22
  spacr/openai.py,sha256=5vBZ3Jl2llYcW3oaTEXgdyCB2aJujMUIO5K038z7w_A,1246
23
- spacr/plot.py,sha256=mqD0XyExAZ_qhnz71bLJo7nTVGod2eN8bJ_9sAV2eN8,135868
23
+ spacr/plot.py,sha256=r4kbrMA8iQ317f0lvIDj4wJDIDwDXXYHEgGtFJrO3-k,145387
24
24
  spacr/sequencing.py,sha256=t18mgpK6rhWuB1LtFOsPxqgpFXxuUmrD06ecsaVQ0Gw,19655
25
- spacr/settings.py,sha256=VkCgZ8r30Q3VmTmYCf2_KRX3htqXR80osOjq37vLbwM,77770
25
+ spacr/settings.py,sha256=3ygnAY6uLtkzFQdK8TMBbWV6zXEX-G_wV19YLyjCBeM,77668
26
26
  spacr/sim.py,sha256=1xKhXimNU3ukzIw-3l9cF3Znc_brW8h20yv8fSTzvss,71173
27
- spacr/submodules.py,sha256=QRzojeHMZ2iRskmU5D7Q9iu6U1wPTODRm55r30KLZyY,25653
27
+ spacr/submodules.py,sha256=3C5M4UbI9Ral1MX4PTpucaAaqhL3RADuCOCqaHhMyUg,28048
28
28
  spacr/timelapse.py,sha256=FSYpUtAVy6xc3lwprRYgyDTT9ysUhfRQ4zrP9_h2mvg,39465
29
- spacr/toxo.py,sha256=MVDfkfTl6fhbzg3izLWdtr2arARYIhI1TdScnHtPVqI,16770
30
- spacr/utils.py,sha256=yDxP8TslqLoKFpKyaCPDOulAitkDBR6MOwPG8FH8mYw,219417
29
+ spacr/toxo.py,sha256=X62hKFcSzFhIxFYlhL2AZb0qNpvtjLs3y1HldReAQEY,12880
30
+ spacr/utils.py,sha256=K36BxYr4GN956V4S7IkNty2sP4Y265WS7yMzAw8Tqeg,220451
31
31
  spacr/version.py,sha256=axH5tnGwtgSnJHb5IDhiu4Zjk5GhLyAEDRe-rnaoFOA,409
32
32
  spacr/resources/MEDIAR/.gitignore,sha256=Ff1q9Nme14JUd-4Q3jZ65aeQ5X4uttptssVDgBVHYo8,152
33
33
  spacr/resources/MEDIAR/LICENSE,sha256=yEj_TRDLUfDpHDNM0StALXIt6mLqSgaV2hcCwa6_TcY,1065
@@ -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.43.dist-info/LICENSE,sha256=SR-2MeGc6SCM1UORJYyarSWY_A-JaOMFDj7ReSs9tRM,1083
154
- spacr-0.3.43.dist-info/METADATA,sha256=NBeaa28RVdVWa4lgSnWFKTuqVS_hJzUtg3sdMjgmf40,5949
155
- spacr-0.3.43.dist-info/WHEEL,sha256=HiCZjzuy6Dw0hdX5R3LCFPDmFS4BWl8H-8W39XfmgX4,91
156
- spacr-0.3.43.dist-info/entry_points.txt,sha256=BMC0ql9aNNpv8lUZ8sgDLQMsqaVnX5L535gEhKUP5ho,296
157
- spacr-0.3.43.dist-info/top_level.txt,sha256=GJPU8FgwRXGzKeut6JopsSRY2R8T3i9lDgya42tLInY,6
158
- spacr-0.3.43.dist-info/RECORD,,
153
+ spacr-0.3.46.dist-info/LICENSE,sha256=SR-2MeGc6SCM1UORJYyarSWY_A-JaOMFDj7ReSs9tRM,1083
154
+ spacr-0.3.46.dist-info/METADATA,sha256=rDVd_7S8qknwKjW3gzWpaC4FvKLLArfmA3xqGlby088,5949
155
+ spacr-0.3.46.dist-info/WHEEL,sha256=HiCZjzuy6Dw0hdX5R3LCFPDmFS4BWl8H-8W39XfmgX4,91
156
+ spacr-0.3.46.dist-info/entry_points.txt,sha256=BMC0ql9aNNpv8lUZ8sgDLQMsqaVnX5L535gEhKUP5ho,296
157
+ spacr-0.3.46.dist-info/top_level.txt,sha256=GJPU8FgwRXGzKeut6JopsSRY2R8T3i9lDgya42tLInY,6
158
+ spacr-0.3.46.dist-info/RECORD,,
File without changes