spacr 0.9.0__py3-none-any.whl → 0.9.2__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_core.py CHANGED
@@ -1083,81 +1083,6 @@ def process_console_queue():
1083
1083
  # **Continue processing if no error was detected**
1084
1084
  after_id = console_output.after(uppdate_frequency, process_console_queue)
1085
1085
  parent_frame.after_tasks.append(after_id)
1086
-
1087
- def process_console_queue_v2():
1088
- global q, console_output, parent_frame, progress_bar, process_console_queue
1089
-
1090
- # Initialize function attribute if it doesn't exist
1091
- if not hasattr(process_console_queue, "completed_tasks"):
1092
- process_console_queue.completed_tasks = []
1093
- if not hasattr(process_console_queue, "current_maximum"):
1094
- process_console_queue.current_maximum = None
1095
-
1096
- ansi_escape_pattern = re.compile(r'\x1B\[[0-?]*[ -/]*[@-~]')
1097
-
1098
- while not q.empty():
1099
- message = q.get_nowait()
1100
- clean_message = ansi_escape_pattern.sub('', message)
1101
-
1102
- # **Abort Execution if an Error Message is Detected**
1103
- if clean_message.startswith("Error:"):
1104
- console_output.insert(tk.END, clean_message + "\n", "error")
1105
- console_output.see(tk.END)
1106
- print("Run aborted due to error:", clean_message) # Debug message
1107
- return # **Exit immediately to stop further execution**
1108
-
1109
- # Check if the message contains progress information
1110
- if clean_message.startswith("Progress:"):
1111
- try:
1112
- # Extract the progress information
1113
- match = re.search(r'Progress: (\d+)/(\d+), operation_type: ([\w\s]*),(.*)', clean_message)
1114
-
1115
- if match:
1116
- current_progress = int(match.group(1))
1117
- total_progress = int(match.group(2))
1118
- operation_type = match.group(3).strip()
1119
- additional_info = match.group(4).strip() # Capture everything after operation_type
1120
-
1121
- # Check if the maximum value has changed
1122
- if process_console_queue.current_maximum != total_progress:
1123
- process_console_queue.current_maximum = total_progress
1124
- process_console_queue.completed_tasks = []
1125
-
1126
- # Add the task to the completed set
1127
- process_console_queue.completed_tasks.append(current_progress)
1128
-
1129
- # Calculate the unique progress count
1130
- unique_progress_count = len(np.unique(process_console_queue.completed_tasks))
1131
-
1132
- # Update the progress bar
1133
- if progress_bar:
1134
- progress_bar['maximum'] = total_progress
1135
- progress_bar['value'] = unique_progress_count
1136
-
1137
- # Store operation type and additional info
1138
- if operation_type:
1139
- progress_bar.operation_type = operation_type
1140
- progress_bar.additional_info = additional_info
1141
-
1142
- # Update the progress label
1143
- if progress_bar.progress_label:
1144
- progress_bar.update_label()
1145
-
1146
- # Clear completed tasks when progress is complete
1147
- if unique_progress_count >= total_progress:
1148
- process_console_queue.completed_tasks.clear()
1149
-
1150
- except Exception as e:
1151
- print(f"Error parsing progress message: {e}")
1152
-
1153
- else:
1154
- # Insert non-progress messages into the console
1155
- console_output.insert(tk.END, clean_message + "\n")
1156
- console_output.see(tk.END)
1157
-
1158
- # **Continue processing if no error was detected**
1159
- after_id = console_output.after(uppdate_frequency, process_console_queue)
1160
- parent_frame.after_tasks.append(after_id)
1161
1086
 
1162
1087
  def main_thread_update_function(root, q, fig_queue, canvas_widget):
1163
1088
  global uppdate_frequency
spacr/measure.py CHANGED
@@ -2,12 +2,11 @@ import os, cv2, time, sqlite3, traceback, shutil
2
2
  import numpy as np
3
3
  import pandas as pd
4
4
  from collections import defaultdict
5
- from scipy.stats import pearsonr
5
+ from scipy.stats import pearsonr, skew, kurtosis, mode
6
6
  import multiprocessing as mp
7
- from scipy.ndimage import distance_transform_edt, generate_binary_structure
7
+ from scipy.ndimage import distance_transform_edt, generate_binary_structure, binary_dilation, gaussian_filter, center_of_mass
8
8
  from skimage.measure import regionprops, regionprops_table, shannon_entropy
9
9
  from skimage.exposure import rescale_intensity
10
- from scipy.ndimage import binary_dilation
11
10
  from skimage.segmentation import find_boundaries
12
11
  from skimage.feature import graycomatrix, graycoprops
13
12
  from mahotas.features import zernike_moments
@@ -16,7 +15,6 @@ from skimage.util import img_as_bool
16
15
  import matplotlib.pyplot as plt
17
16
  from math import ceil, sqrt
18
17
 
19
-
20
18
  def get_components(cell_mask, nucleus_mask, pathogen_mask):
21
19
  """
22
20
  Get the components (nucleus and pathogens) for each cell in the given masks.
@@ -250,6 +248,148 @@ def _create_dataframe(radial_distributions, object_type):
250
248
  return df
251
249
 
252
250
  def _extended_regionprops_table(labels, image, intensity_props):
251
+ """
252
+ Calculate extended region properties table, adding a suite of advanced quantitative features.
253
+ """
254
+
255
+ def _gini(array):
256
+ # Compute Gini coefficient (nan safe)
257
+ array = np.abs(array[~np.isnan(array)])
258
+ n = array.size
259
+ if n == 0:
260
+ return np.nan
261
+ array = np.sort(array)
262
+ index = np.arange(1, n + 1)
263
+ return (np.sum((2 * index - n - 1) * array)) / (n * np.sum(array)) if np.sum(array) else np.nan
264
+
265
+ props = regionprops_table(labels, image, properties=intensity_props)
266
+ df = pd.DataFrame(props)
267
+
268
+ regions = regionprops(labels, intensity_image=image)
269
+ integrated_intensity = []
270
+ std_intensity = []
271
+ median_intensity = []
272
+ skew_intensity = []
273
+ kurtosis_intensity = []
274
+ mode_intensity = []
275
+ range_intensity = []
276
+ iqr_intensity = []
277
+ cv_intensity = []
278
+ gini_intensity = []
279
+ frac_high90 = []
280
+ frac_low10 = []
281
+ entropy_intensity = []
282
+
283
+ for region in regions:
284
+ intens = region.intensity_image[region.image]
285
+ intens = intens[~np.isnan(intens)]
286
+ if intens.size == 0:
287
+ integrated_intensity.append(np.nan)
288
+ std_intensity.append(np.nan)
289
+ median_intensity.append(np.nan)
290
+ skew_intensity.append(np.nan)
291
+ kurtosis_intensity.append(np.nan)
292
+ mode_intensity.append(np.nan)
293
+ range_intensity.append(np.nan)
294
+ iqr_intensity.append(np.nan)
295
+ cv_intensity.append(np.nan)
296
+ gini_intensity.append(np.nan)
297
+ frac_high90.append(np.nan)
298
+ frac_low10.append(np.nan)
299
+ entropy_intensity.append(np.nan)
300
+ else:
301
+ integrated_intensity.append(np.sum(intens))
302
+ std_intensity.append(np.std(intens))
303
+ median_intensity.append(np.median(intens))
304
+ skew_intensity.append(skew(intens) if intens.size > 2 else np.nan)
305
+ kurtosis_intensity.append(kurtosis(intens) if intens.size > 3 else np.nan)
306
+ # Mode (use first mode value if multimodal)
307
+ try:
308
+ mode_val = mode(intens, nan_policy='omit').mode
309
+ mode_intensity.append(mode_val[0] if len(mode_val) > 0 else np.nan)
310
+ except Exception:
311
+ mode_intensity.append(np.nan)
312
+ range_intensity.append(np.ptp(intens))
313
+ iqr_intensity.append(np.percentile(intens, 75) - np.percentile(intens, 25))
314
+ cv_intensity.append(np.std(intens) / np.mean(intens) if np.mean(intens) != 0 else np.nan)
315
+ gini_intensity.append(_gini(intens))
316
+ frac_high90.append(np.mean(intens > np.percentile(intens, 90)))
317
+ frac_low10.append(np.mean(intens < np.percentile(intens, 10)))
318
+ entropy_intensity.append(shannon_entropy(intens) if intens.size > 1 else 0.0)
319
+
320
+ df['integrated_intensity'] = integrated_intensity
321
+ df['std_intensity'] = std_intensity
322
+ df['median_intensity'] = median_intensity
323
+ df['skew_intensity'] = skew_intensity
324
+ df['kurtosis_intensity'] = kurtosis_intensity
325
+ df['mode_intensity'] = mode_intensity
326
+ df['range_intensity'] = range_intensity
327
+ df['iqr_intensity'] = iqr_intensity
328
+ df['cv_intensity'] = cv_intensity
329
+ df['gini_intensity'] = gini_intensity
330
+ df['frac_high90'] = frac_high90
331
+ df['frac_low10'] = frac_low10
332
+ df['entropy_intensity'] = entropy_intensity
333
+
334
+ percentiles = [5, 10, 25, 50, 75, 85, 95]
335
+ for p in percentiles:
336
+ df[f'percentile_{p}'] = [
337
+ np.percentile(region.intensity_image[region.image], p)
338
+ for region in regions
339
+ ]
340
+ return df
341
+
342
+ def _extended_regionprops_table_v2(labels, image, intensity_props):
343
+ """
344
+ Calculate extended region properties table, adding integrated intensity,
345
+ skewness, kurtosis, std, and median intensity per region.
346
+ """
347
+ # regionprops_table gives you vectorized props, but not everything you want
348
+ props = regionprops_table(labels, image, properties=intensity_props)
349
+ df = pd.DataFrame(props)
350
+
351
+ # Compute extra features region-by-region
352
+ regions = regionprops(labels, intensity_image=image)
353
+ integrated_intensity = []
354
+ std_intensity = []
355
+ median_intensity = []
356
+ skew_intensity = []
357
+ kurtosis_intensity = []
358
+ for region in regions:
359
+ intens = region.intensity_image[region.image]
360
+ # Handle empty region edge-case (shouldn't happen)
361
+ if intens.size == 0:
362
+ integrated_intensity.append(np.nan)
363
+ std_intensity.append(np.nan)
364
+ median_intensity.append(np.nan)
365
+ skew_intensity.append(np.nan)
366
+ kurtosis_intensity.append(np.nan)
367
+ else:
368
+ integrated_intensity.append(np.sum(intens))
369
+ std_intensity.append(np.std(intens))
370
+ median_intensity.append(np.median(intens))
371
+ # Only valid for >2 pixels
372
+ skew_intensity.append(skew(intens) if intens.size > 2 else np.nan)
373
+ kurtosis_intensity.append(kurtosis(intens) if intens.size > 3 else np.nan)
374
+
375
+ df['integrated_intensity'] = integrated_intensity
376
+ df['std_intensity'] = std_intensity
377
+ df['median_intensity'] = median_intensity
378
+ df['skew_intensity'] = skew_intensity
379
+ df['kurtosis_intensity'] = kurtosis_intensity
380
+
381
+ # You can add other features here if desired
382
+
383
+ # Percentiles (your existing code—optional if you want to keep)
384
+ percentiles = [5, 10, 25, 50, 75, 85, 95]
385
+ for p in percentiles:
386
+ df[f'percentile_{p}'] = [
387
+ np.percentile(region.intensity_image[region.image], p)
388
+ for region in regions
389
+ ]
390
+ return df
391
+
392
+ def _extended_regionprops_table_v1(labels, image, intensity_props):
253
393
  """
254
394
  Calculate extended region properties table.
255
395
 
@@ -495,8 +635,75 @@ def _estimate_blur(image):
495
635
  # Compute and return the variance of the Laplacian
496
636
  return lap.var()
497
637
 
638
+ def _measure_intensity_distance(cell_mask, nucleus_mask, pathogen_mask, channel_arrays, settings):
639
+ """
640
+ Compute Gaussian-smoothed intensity-weighted centroid distances for each cell object.
641
+ """
642
+
643
+ sigma = settings.get('distance_gaussian_sigma', 1.0)
644
+ cell_labels = np.unique(cell_mask)
645
+ cell_labels = cell_labels[cell_labels > 0]
646
+
647
+ dfs = []
648
+ nucleus_dt = distance_transform_edt(nucleus_mask == 0)
649
+ pathogen_dt = distance_transform_edt(pathogen_mask == 0)
650
+
651
+ for ch in range(channel_arrays.shape[-1]):
652
+ channel_img = channel_arrays[:, :, ch]
653
+ blurred_img = gaussian_filter(channel_img, sigma=sigma)
654
+
655
+ data = []
656
+ for label in cell_labels:
657
+ cell_coords = np.argwhere(cell_mask == label)
658
+ if cell_coords.size == 0:
659
+ data.append([label, np.nan, np.nan])
660
+ continue
661
+
662
+ minr, minc = np.min(cell_coords, axis=0)
663
+ maxr, maxc = np.max(cell_coords, axis=0) + 1
664
+
665
+ cell_submask = (cell_mask[minr:maxr, minc:maxc] == label)
666
+ blurred_subimg = blurred_img[minr:maxr, minc:maxc]
667
+
668
+ if np.sum(cell_submask) == 0:
669
+ data.append([label, np.nan, np.nan])
670
+ continue
671
+
672
+ masked_intensity = blurred_subimg * cell_submask
673
+ com_local = center_of_mass(masked_intensity)
674
+ if np.isnan(com_local[0]):
675
+ data.append([label, np.nan, np.nan])
676
+ continue
677
+
678
+ com_global = (com_local[0] + minr, com_local[1] + minc)
679
+ com_global_int = tuple(np.round(com_global).astype(int))
680
+
681
+ x, y = com_global_int
682
+ if not (0 <= x < cell_mask.shape[0] and 0 <= y < cell_mask.shape[1]):
683
+ data.append([label, np.nan, np.nan])
684
+ continue
685
+
686
+ nucleus_dist = nucleus_dt[x, y]
687
+ pathogen_dist = pathogen_dt[x, y]
688
+
689
+ data.append([label, nucleus_dist, pathogen_dist])
690
+
691
+ df = pd.DataFrame(data, columns=['label',
692
+ f'cell_channel_{ch}_distance_to_nucleus',
693
+ f'cell_channel_{ch}_distance_to_pathogen'])
694
+ dfs.append(df)
695
+
696
+ # Merge all channel dataframes on label
697
+ merged_df = dfs[0]
698
+ for df in dfs[1:]:
699
+ merged_df = merged_df.merge(df, on='label', how='outer')
700
+
701
+ return merged_df
702
+
703
+
498
704
  #@log_function_call
499
705
  def _intensity_measurements(cell_mask, nucleus_mask, pathogen_mask, cytoplasm_mask, channel_arrays, settings, sizes=[3, 6, 12, 24], periphery=True, outside=True):
706
+
500
707
  """
501
708
  Calculate various intensity measurements for different regions in the image.
502
709
 
@@ -536,8 +743,8 @@ def _intensity_measurements(cell_mask, nucleus_mask, pathogen_mask, cytoplasm_ma
536
743
  df.append(empty_df)
537
744
  continue
538
745
 
539
- mask_intensity_df = _extended_regionprops_table(label, channel, intensity_props)
540
- mask_intensity_df['shannon_entropy'] = shannon_entropy(channel, base=2)
746
+ mask_intensity_df = _extended_regionprops_table(label, channel, intensity_props)
747
+ #mask_intensity_df['shannon_entropy'] = shannon_entropy(channel, base=2)
541
748
 
542
749
  if homogeneity:
543
750
  homogeneity_df = _calculate_homogeneity(label, channel, distances)
@@ -558,6 +765,10 @@ def _intensity_measurements(cell_mask, nucleus_mask, pathogen_mask, cytoplasm_ma
558
765
 
559
766
  mask_intensity_df.columns = [f'{ls[j]}_channel_{i}_{col}' if col != 'label' else col for col in mask_intensity_df.columns]
560
767
  df.append(mask_intensity_df)
768
+
769
+ if isinstance(settings['distance_gaussian_sigma'], int):
770
+ intensity_distance_df = _measure_intensity_distance(cell_mask, nucleus_mask, pathogen_mask, channel_arrays, settings)
771
+ cell_dfs.append(intensity_distance_df)
561
772
 
562
773
  if radial_dist:
563
774
  if np.max(nucleus_mask) != 0:
@@ -565,7 +776,7 @@ def _intensity_measurements(cell_mask, nucleus_mask, pathogen_mask, cytoplasm_ma
565
776
  nucleus_df = _create_dataframe(nucleus_radial_distributions, 'nucleus')
566
777
  dfs[1].append(nucleus_df)
567
778
 
568
- if np.max(nucleus_mask) != 0:
779
+ if np.max(pathogen_mask) != 0:
569
780
  pathogen_radial_distributions = _calculate_radial_distribution(cell_mask, pathogen_mask, channel_arrays, num_bins=6)
570
781
  pathogen_df = _create_dataframe(pathogen_radial_distributions, 'pathogen')
571
782
  dfs[2].append(pathogen_df)
@@ -785,6 +996,7 @@ def _measure_crop_core(index, time_ls, file, settings):
785
996
  #merge skeleton_df with cell_df here
786
997
 
787
998
  cell_intensity_df, nucleus_intensity_df, pathogen_intensity_df, cytoplasm_intensity_df = _intensity_measurements(cell_mask, nucleus_mask, pathogen_mask, cytoplasm_mask, channel_arrays, settings, sizes=[1, 2, 3, 4, 5], periphery=True, outside=True)
999
+
788
1000
  if settings['cell_mask_dim'] is not None:
789
1001
  cell_merged_df = _merge_and_save_to_database(cell_df, cell_intensity_df, 'cell', source_folder, file_name, settings['experiment'], settings['timelapse'])
790
1002
  if settings['nucleus_mask_dim'] is not None:
spacr/plot.py CHANGED
@@ -1241,103 +1241,6 @@ def _display_gif(path):
1241
1241
  """
1242
1242
  with open(path, 'rb') as file:
1243
1243
  display(ipyimage(file.read()))
1244
-
1245
- def _plot_recruitment_v2(df, df_type, channel_of_interest, columns=[], figuresize=10):
1246
- """
1247
- Plot recruitment data for different conditions and pathogens.
1248
-
1249
- Args:
1250
- df (DataFrame): The input DataFrame containing the recruitment data.
1251
- df_type (str): The type of DataFrame (e.g., 'train', 'test').
1252
- channel_of_interest (str): The channel of interest for plotting.
1253
- target (str): The target variable for plotting.
1254
- columns (list, optional): Additional columns to plot. Defaults to an empty list.
1255
- figuresize (int, optional): The size of the figure. Defaults to 50.
1256
-
1257
- Returns:
1258
- None
1259
- """
1260
-
1261
- color_list = [(55/255, 155/255, 155/255),
1262
- (155/255, 55/255, 155/255),
1263
- (55/255, 155/255, 255/255),
1264
- (255/255, 55/255, 155/255)]
1265
-
1266
- sns.set_palette(sns.color_palette(color_list))
1267
- font = figuresize/2
1268
- width=figuresize
1269
- height=figuresize/4
1270
-
1271
- # Create the subplots
1272
- fig, axes = plt.subplots(nrows=1, ncols=4, figsize=(width, height))
1273
-
1274
- # Plot for 'cell_channel' on axes[0]
1275
- plotter_cell = spacrGraph(df,grouping_column='condition', data_column=f'cell_channel_{channel_of_interest}_mean_intensity')
1276
- plotter_cell.create_plot(ax=axes[0])
1277
- axes[0].set_xlabel(f'pathogen {df_type}', fontsize=font)
1278
- axes[0].set_ylabel(f'cell_channel_{channel_of_interest}_mean_intensity', fontsize=font)
1279
-
1280
- # Plot for 'nucleus_channel' on axes[1]
1281
- plotter_nucleus = spacrGraph(df,grouping_column='condition', data_column=f'nucleus_channel_{channel_of_interest}_mean_intensity')
1282
- plotter_nucleus.create_plot(ax=axes[1])
1283
- axes[1].set_xlabel(f'pathogen {df_type}', fontsize=font)
1284
- axes[1].set_ylabel(f'nucleus_channel_{channel_of_interest}_mean_intensity', fontsize=font)
1285
-
1286
- # Plot for 'cytoplasm_channel' on axes[2]
1287
- plotter_cytoplasm = spacrGraph(df, grouping_column='condition', data_column=f'cytoplasm_channel_{channel_of_interest}_mean_intensity')
1288
- plotter_cytoplasm.create_plot(ax=axes[2])
1289
- axes[2].set_xlabel(f'pathogen {df_type}', fontsize=font)
1290
- axes[2].set_ylabel(f'cytoplasm_channel_{channel_of_interest}_mean_intensity', fontsize=font)
1291
-
1292
- # Plot for 'pathogen_channel' on axes[3]
1293
- plotter_pathogen = spacrGraph(df, grouping_column='condition', data_column=f'pathogen_channel_{channel_of_interest}_mean_intensity')
1294
- plotter_pathogen.create_plot(ax=axes[3])
1295
- axes[3].set_xlabel(f'pathogen {df_type}', fontsize=font)
1296
- axes[3].set_ylabel(f'pathogen_channel_{channel_of_interest}_mean_intensity', fontsize=font)
1297
-
1298
- #axes[0].legend_.remove()
1299
- #axes[1].legend_.remove()
1300
- #axes[2].legend_.remove()
1301
- #axes[3].legend_.remove()
1302
-
1303
- handles, labels = axes[3].get_legend_handles_labels()
1304
- axes[3].legend(handles, labels, bbox_to_anchor=(1.05, 0.5), loc='center left')
1305
- for i in [0,1,2,3]:
1306
- axes[i].tick_params(axis='both', which='major', labelsize=font)
1307
- axes[i].set_xticklabels(axes[i].get_xticklabels(), rotation=45)
1308
-
1309
- plt.tight_layout()
1310
- plt.show()
1311
-
1312
- columns = columns + ['pathogen_cytoplasm_mean_mean', 'pathogen_cytoplasm_q75_mean', 'pathogen_periphery_cytoplasm_mean_mean', 'pathogen_outside_cytoplasm_mean_mean', 'pathogen_outside_cytoplasm_q75_mean']
1313
- #columns = columns + [f'pathogen_slope_channel_{channel_of_interest}', f'pathogen_cell_distance_channel_{channel_of_interest}', f'nucleus_cell_distance_channel_{channel_of_interest}']
1314
-
1315
- width = figuresize*2
1316
- columns_per_row = math.ceil(len(columns) / 2)
1317
- height = (figuresize*2)/columns_per_row
1318
-
1319
- fig, axes = plt.subplots(nrows=2, ncols=columns_per_row, figsize=(width, height * 2))
1320
- axes = axes.flatten()
1321
-
1322
- print(f'{columns}')
1323
- for i, col in enumerate(columns):
1324
- ax = axes[i]
1325
- plotter_col = spacrGraph(df, grouping_column='condition', data_column=col)
1326
- plotter_col.create_plot(ax=ax)
1327
- ax.set_xlabel(f'pathogen {df_type}', fontsize=font)
1328
- ax.set_ylabel(f'{col}', fontsize=int(font * 2))
1329
- #ax.legend_.remove()
1330
- ax.tick_params(axis='both', which='major', labelsize=font)
1331
- ax.set_xticklabels(ax.get_xticklabels(), rotation=45)
1332
- if i <= 5:
1333
- ax.set_ylim(1, None)
1334
-
1335
- # Turn off any unused axes
1336
- for i in range(len(columns), len(axes)):
1337
- axes[i].axis('off')
1338
-
1339
- plt.tight_layout()
1340
- plt.show()
1341
1244
 
1342
1245
  def _plot_recruitment(df, df_type, channel_of_interest, columns=[], figuresize=10):
1343
1246
  """
@@ -1843,31 +1746,6 @@ def print_mask_and_flows(stack, mask, flows, overlay=True, max_size=1000, thickn
1843
1746
  fig.tight_layout()
1844
1747
  plt.show()
1845
1748
 
1846
- def plot_resize_v1(images, resized_images, labels, resized_labels):
1847
- # Display an example image and label before and after resizing
1848
- fig, ax = plt.subplots(2, 2, figsize=(20, 20))
1849
-
1850
- # Check if the image is grayscale; if so, add a colormap and keep dimensions correct
1851
- if images[0].ndim == 2: # Grayscale image
1852
- ax[0, 0].imshow(images[0], cmap='gray')
1853
- else: # RGB or RGBA image
1854
- ax[0, 0].imshow(images[0])
1855
- ax[0, 0].set_title('Original Image')
1856
-
1857
- if resized_images[0].ndim == 2: # Grayscale image
1858
- ax[0, 1].imshow(resized_images[0], cmap='gray')
1859
- else: # RGB or RGBA image
1860
- ax[0, 1].imshow(resized_images[0])
1861
- ax[0, 1].set_title('Resized Image')
1862
-
1863
- # Assuming labels are always grayscale (most common scenario)
1864
- ax[1, 0].imshow(labels[0], cmap='gray')
1865
- ax[1, 0].set_title('Original Label')
1866
- ax[1, 1].imshow(resized_labels[0], cmap='gray')
1867
- ax[1, 1].set_title('Resized Label')
1868
- plt.show()
1869
-
1870
-
1871
1749
  def plot_resize(images, resized_images, labels, resized_labels):
1872
1750
  def prepare_image(img):
1873
1751
  if img.ndim == 2:
Binary file
spacr/settings.py CHANGED
@@ -124,33 +124,6 @@ def set_default_plot_data_from_db(settings):
124
124
  settings.setdefault('uninfected', False)
125
125
  return settings
126
126
 
127
-
128
-
129
- def set_default_settings_preprocess_img_data_v1(settings):
130
-
131
- metadata_type = settings.setdefault('metadata_type', 'cellvoyager')
132
- custom_regex = settings.setdefault('custom_regex', None)
133
- nr = settings.setdefault('nr', 1)
134
- plot = settings.setdefault('plot', True)
135
- batch_size = settings.setdefault('batch_size', 50)
136
- timelapse = settings.setdefault('timelapse', False)
137
- lower_percentile = settings.setdefault('lower_percentile', 2)
138
- randomize = settings.setdefault('randomize', True)
139
- all_to_mip = settings.setdefault('all_to_mip', False)
140
- pick_slice = settings.setdefault('pick_slice', False)
141
- skip_mode = settings.setdefault('skip_mode', False)
142
-
143
- cmap = settings.setdefault('cmap', 'inferno')
144
- figuresize = settings.setdefault('figuresize', 10)
145
- normalize = settings.setdefault('normalize', True)
146
- save_dtype = settings.setdefault('save_dtype', 'uint16')
147
-
148
- test_mode = settings.setdefault('test_mode', False)
149
- test_images = settings.setdefault('test_images', 10)
150
- random_test = settings.setdefault('random_test', True)
151
-
152
- return settings, metadata_type, custom_regex, nr, plot, batch_size, timelapse, lower_percentile, randomize, all_to_mip, pick_slice, skip_mode, cmap, figuresize, normalize, save_dtype, test_mode, test_images, random_test
153
-
154
127
  def set_default_settings_preprocess_img_data(settings):
155
128
 
156
129
  settings.setdefault('metadata_type', 'cellvoyager')
@@ -340,7 +313,9 @@ def get_measure_crop_settings(settings={}):
340
313
  settings.setdefault('pathogen_min_size',0)
341
314
  settings.setdefault('cytoplasm_min_size',0)
342
315
  settings.setdefault('merge_edge_pathogen_cells', True)
343
-
316
+
317
+ settings.setdefault('distance_gaussian_sigma', 1)
318
+
344
319
  if settings['test_mode']:
345
320
  settings['verbose'] = True
346
321
  settings['plot'] = True
@@ -1027,7 +1002,8 @@ expected_types = {
1027
1002
  "cell_diamiter":int,
1028
1003
  "nucleus_diamiter":int,
1029
1004
  "pathogen_diamiter":int,
1030
- "consolidate":bool
1005
+ "consolidate":bool,
1006
+ "distance_gaussian_sigma":int
1031
1007
  }
1032
1008
 
1033
1009
  categories = {"Paths":[ "src", "grna", "barcodes", "custom_model_path", "dataset","model_path","grna_csv","row_csv","column_csv", "metadata_files", "score_data","count_data"],
@@ -1049,7 +1025,7 @@ categories = {"Paths":[ "src", "grna", "barcodes", "custom_model_path", "dataset
1049
1025
  "Plot": ["split_axis_lims", "x_lim","log_x","log_y", "plot_control", "plot_nr", "examples_to_plot", "normalize_plots", "cmap", "figuresize", "plot_cluster_grids", "img_zoom", "row_limit", "color_by", "plot_images", "smooth_lines", "plot_points", "plot_outlines", "black_background", "plot_by_cluster", "heatmap_feature","grouping","min_max","cmap","save_figure"],
1050
1026
  "Timelapse": ["timelapse", "fps", "timelapse_displacement", "timelapse_memory", "timelapse_frame_limits", "timelapse_remove_transient", "timelapse_mode", "timelapse_objects", "compartments"],
1051
1027
  "Advanced": ["merge_edge_pathogen_cells", "test_images", "random_test", "test_nr", "test", "test_split", "normalize", "target_unique_count","threshold_multiplier", "threshold_method", "min_n","shuffle", "target_intensity_min", "cells_per_well", "nuclei_limit", "pathogen_limit", "background", "backgrounds", "schedule", "test_size","exclude","n_repeats","top_features", "model_type_ml", "model_type","minimum_cell_count","n_estimators","preprocess", "remove_background", "normalize", "lower_percentile", "merge_pathogens", "batch_size", "filter", "save", "masks", "verbose", "randomize", "n_jobs"],
1052
- "Beta": ["all_to_mip", "upscale", "upscale_factor", "consolidate"]
1028
+ "Beta": ["all_to_mip", "upscale", "upscale_factor", "consolidate", "distance_gaussian_sigma"]
1053
1029
  }
1054
1030
 
1055
1031
 
spacr/timelapse.py CHANGED
@@ -255,56 +255,6 @@ def _relabel_masks_based_on_tracks(masks, tracks, mode='btrack'):
255
255
 
256
256
  return relabeled_masks
257
257
 
258
- def _prepare_for_tracking_v1(mask_array):
259
- """
260
- Prepare the mask array for object tracking.
261
-
262
- Args:
263
- mask_array (ndarray): Array of binary masks representing objects.
264
-
265
- Returns:
266
- DataFrame: DataFrame containing information about each object in the mask array.
267
- The DataFrame has the following columns:
268
- - frame: The frame number.
269
- - y: The y-coordinate of the object's centroid.
270
- - x: The x-coordinate of the object's centroid.
271
- - mass: The area of the object.
272
- - original_label: The original label of the object.
273
-
274
- """
275
- frames = []
276
- for t, frame in enumerate(mask_array):
277
- props = regionprops(frame)
278
- for obj in props:
279
- # Include 'label' in the dictionary to capture the original label of the object
280
- frames.append({
281
- 'frame': t,
282
- 'y': obj.centroid[0],
283
- 'x': obj.centroid[1],
284
- 'mass': obj.area,
285
- 'original_label': obj.label # Capture the original label
286
- })
287
- return pd.DataFrame(frames)
288
-
289
- def _prepare_for_tracking_v1(mask_array):
290
- frames = []
291
- for t, frame in enumerate(mask_array):
292
- props = regionprops_table(
293
- frame,
294
- properties=('label', 'centroid-0', 'centroid-1', 'area',
295
- 'bbox-0', 'bbox-1', 'bbox-2', 'bbox-3',
296
- 'eccentricity')
297
- )
298
- df = pd.DataFrame(props)
299
- df = df.rename(columns={
300
- 'centroid-0': 'y', 'centroid-1': 'x', 'area': 'mass',
301
- 'label': 'original_label'
302
- })
303
- df['frame'] = t
304
- frames.append(df[['frame','y','x','mass','original_label',
305
- 'bbox-0','bbox-1','bbox-2','bbox-3','eccentricity']])
306
- return pd.concat(frames, ignore_index=True)
307
-
308
258
  def _prepare_for_tracking(mask_array):
309
259
  frames = []
310
260
  for t, frame in enumerate(mask_array):
@@ -522,46 +472,6 @@ def _facilitate_trackin_with_adaptive_removal(masks, search_range=None, max_atte
522
472
  f"Failed to track after {max_attempts} attempts; last search_range={search_range}"
523
473
  )
524
474
 
525
- def _facilitate_trackin_with_adaptive_removal_v1(masks, search_range=500, max_attempts=100, memory=3):
526
- """
527
- Facilitates object tracking with adaptive removal.
528
-
529
- Args:
530
- masks (numpy.ndarray): Array of binary masks representing objects in each frame.
531
- search_range (int, optional): Maximum distance objects can move between frames. Defaults to 500.
532
- max_attempts (int, optional): Maximum number of attempts to track objects. Defaults to 100.
533
- memory (int, optional): Number of frames to remember when linking tracks. Defaults to 3.
534
-
535
- Returns:
536
- tuple: A tuple containing the updated masks, features, and tracks_df.
537
- masks (numpy.ndarray): Updated array of binary masks.
538
- features (pandas.DataFrame): DataFrame containing features for object tracking.
539
- tracks_df (pandas.DataFrame): DataFrame containing the tracked object trajectories.
540
-
541
- Raises:
542
- Exception: If tracking fails after the maximum number of attempts.
543
-
544
- """
545
- attempts = 0
546
- first_frame = masks[0]
547
- starting_objects = np.unique(first_frame[first_frame != 0])
548
- while attempts < max_attempts:
549
- try:
550
- masks = _remove_objects_from_first_frame(masks, 10)
551
- first_frame = masks[0]
552
- objects = np.unique(first_frame[first_frame != 0])
553
- print(len(objects))
554
- features = _prepare_for_tracking(masks)
555
- tracks_df = tp.link(features, search_range=search_range, memory=memory)
556
- print(f"Success with {len(objects)} objects, started with {len(starting_objects)} objects")
557
- return masks, features, tracks_df
558
- except Exception as e: # Consider catching a more specific exception if possible
559
- print(f"Retrying with fewer objects. Exception: {e}", flush=True)
560
- finally:
561
- attempts += 1
562
- print(f"Failed to track objects after {max_attempts} attempts. Consider adjusting parameters.")
563
- return None, None, None
564
-
565
475
  def _trackpy_track_cells(src, name, batch_filenames, object_type, masks, timelapse_displacement, timelapse_memory, timelapse_remove_transient, plot, save, mode, track_by_iou):
566
476
  """
567
477
  Track cells using the Trackpy library.
spacr/utils.py CHANGED
@@ -4602,7 +4602,10 @@ def adjust_cell_masks(parasite_folder, cell_folder, nuclei_folder, overlap_thres
4602
4602
  raise ValueError("The number of files in the folders do not match.")
4603
4603
 
4604
4604
  # Match files by name
4605
- for file_name in parasite_files:
4605
+ time_ls = []
4606
+ files_to_process = len(parasite_files)
4607
+ for files_processed, file_name in enumerate(parasite_files):
4608
+ start = time.time()
4606
4609
  parasite_path = os.path.join(parasite_folder, file_name)
4607
4610
  cell_path = os.path.join(cell_folder, file_name)
4608
4611
  nuclei_path = os.path.join(nuclei_folder, file_name)
@@ -4621,6 +4624,12 @@ def adjust_cell_masks(parasite_folder, cell_folder, nuclei_folder, overlap_thres
4621
4624
 
4622
4625
  # Overwrite the original cell mask file with the merged result
4623
4626
  np.save(cell_path, merged_cell_mask)
4627
+
4628
+ stop = time.time()
4629
+ duration = (stop - start)
4630
+ time_ls.append(duration)
4631
+ files_processed += 1
4632
+ print_progress(files_processed, files_to_process, n_jobs=1, time_ls=time_ls, batch_size=None, operation_type=f'adjust_cell_masks')
4624
4633
 
4625
4634
  def process_masks(mask_folder, image_folder, channel, batch_size=50, n_clusters=2, plot=False):
4626
4635
 
@@ -5210,54 +5219,6 @@ def rename_columns_in_db(db_path):
5210
5219
 
5211
5220
  con.commit()
5212
5221
  con.close()
5213
-
5214
- def rename_columns_in_db_v1(db_path):
5215
- with sqlite3.connect(db_path) as conn:
5216
- cursor = conn.cursor()
5217
-
5218
- # Retrieve all table names in the database
5219
- cursor.execute("SELECT name FROM sqlite_master WHERE type='table';")
5220
- tables = [table[0] for table in cursor.fetchall()]
5221
-
5222
- for table in tables:
5223
- # Retrieve column names for each table
5224
- cursor.execute(f"PRAGMA table_info({table});")
5225
- columns_info = cursor.fetchall()
5226
- column_names = [col[1] for col in columns_info]
5227
-
5228
- # Check if columns 'rowID' or 'columnID' exist
5229
- columns_to_rename = {}
5230
- if 'row' in column_names:
5231
- columns_to_rename['row'] = 'rowID'
5232
- if 'col' in column_names:
5233
- columns_to_rename['col'] = 'columnID'
5234
-
5235
- # Rename columns if necessary
5236
- if columns_to_rename:
5237
- # Rename existing table to a temporary name
5238
- temp_table = f"{table}_old"
5239
- cursor.execute(f"ALTER TABLE `{table}` RENAME TO `{temp_table}`")
5240
-
5241
- # Define new columns with updated names
5242
- column_definitions = ", ".join(
5243
- [f"`{columns_to_rename.get(col[1], col[1])}` {col[2]}" for col in columns_info]
5244
- )
5245
- cursor.execute(f"CREATE TABLE `{table}` ({column_definitions})")
5246
-
5247
- # Copy data to the new table
5248
- old_columns = ", ".join([f"`{col}`" for col in column_names])
5249
- new_columns = ", ".join(
5250
- [f"`{columns_to_rename.get(col, col)}`" for col in column_names]
5251
- )
5252
- cursor.execute(f"INSERT INTO `{table}` ({new_columns}) SELECT {old_columns} FROM `{temp_table}`")
5253
- try:
5254
- cursor.execute(f"DROP TABLE `{temp_table}`")
5255
- except sqlite3.Error as e:
5256
- print(f"Error while dropping temporary table '{temp_table}': {e}")
5257
-
5258
- # After closing the 'with' block, run VACUUM outside of any transaction
5259
- with sqlite3.connect(db_path) as conn:
5260
- conn.execute("VACUUM;")
5261
5222
 
5262
5223
  def group_feature_class(df, feature_groups=['cell', 'cytoplasm', 'nucleus', 'pathogen'], name='compartment'):
5263
5224
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: spacr
3
- Version: 0.9.0
3
+ Version: 0.9.2
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
@@ -80,8 +80,8 @@ Requires-Dist: opencv-python ; extra == 'full'
80
80
  Provides-Extra: headless
81
81
  Requires-Dist: opencv-python-headless ; extra == 'headless'
82
82
 
83
- .. |Documentation Status| image:: https://readthedocs.org/projects/spacr/badge/?version=latest
84
- :target: https://einarolafsson.github.io/spacr
83
+ .. |Docs| image:: https://github.com/EinarOlafsson/spacr/actions/workflows/pages/pages-build-deployment/badge.svg
84
+ :target: https://einarolafsson.github.io/spacr/index.html
85
85
  .. |PyPI version| image:: https://badge.fury.io/py/spacr.svg
86
86
  :target: https://badge.fury.io/py/spacr
87
87
  .. |Python version| image:: https://img.shields.io/pypi/pyversions/spacr
@@ -91,54 +91,103 @@ Requires-Dist: opencv-python-headless ; extra == 'headless'
91
91
  .. |repo size| image:: https://img.shields.io/github/repo-size/EinarOlafsson/spacr
92
92
  :target: https://github.com/EinarOlafsson/spacr/
93
93
 
94
- |Documentation Status| |PyPI version| |Python version| |Licence: GPL v3| |repo size|
94
+ .. _docs: https://einarolafsson.github.io/spacr/index.html
95
+
96
+ Badges
97
+ ------
98
+ |Docs| |PyPI version| |Python version| |Licence: GPL v3| |repo size|
95
99
 
96
100
  SpaCr
97
101
  =====
98
102
 
99
- Spatial phenotype analysis of CRISPR-Cas9 screens (SpaCr). The spatial organization of organelles and proteins within cells constitutes a key level of functional regulation. In the context of infectious disease, the spatial relationships between host cell structures and intracellular pathogens are critical to understanding host clearance mechanisms and how pathogens evade them. SpaCr is a Python-based software package for generating single-cell image data for deep-learning sub-cellular/cellular phenotypic classification from pooled genetic CRISPR-Cas9 screens. 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.
103
+ **Spatial phenotype analysis of CRISPR-Cas9 screens (SpaCr).**
104
+
105
+ The spatial organization of organelles and proteins within cells constitutes a key level of functional regulation. In the context of infectious disease, the spatial relationships between host cell structures and intracellular pathogens are critical to understanding host clearance mechanisms and how pathogens evade them. SpaCr is a Python-based software package for generating single-cell image data for deep-learning sub-cellular/cellular phenotypic classification from pooled genetic CRISPR-Cas9 screens. 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.
100
106
 
101
107
  Features
102
108
  --------
103
109
 
104
110
  - **Generate Masks:** Generate cellpose masks of cell, nuclei, and pathogen objects.
105
-
106
- - **Object Measurements:** Measurements for each object including scikit-image-regionprops, intensity percentiles, shannon-entropy, pearsons and manders correlations, homogeneity, and radial distribution. Measurements are saved to a SQL database in object-level tables.
107
-
111
+ - **Object Measurements:** Measurements for each object including scikit-image regionprops, intensity percentiles, shannon-entropy, Pearson’s and Manders’ correlations, homogeneity, and radial distribution. Measurements are saved to a SQL database in object-level tables.
108
112
  - **Crop Images:** Save objects (cells, nuclei, pathogen, cytoplasm) as images. Object image paths are saved in a SQL database.
109
-
110
113
  - **Train CNNs or Transformers:** Train Torch models to classify single object images.
111
-
112
114
  - **Manual Annotation:** Supports manual annotation of single-cell images and segmentation to refine training datasets for training CNNs/Transformers or cellpose, respectively.
113
-
114
115
  - **Finetune Cellpose Models:** Adjust pre-existing Cellpose models to your specific dataset for improved performance.
115
-
116
116
  - **Timelapse Data Support:** Track objects in timelapse image data.
117
-
118
117
  - **Simulations:** Simulate spatial phenotype screens.
119
-
120
118
  - **Sequencing:** Map FASTQ reads to barcode and gRNA barcode metadata.
121
-
122
119
  - **Misc:** Analyze Ca oscillation, recruitment, infection rate, plaque size/count.
123
120
 
121
+ .. image:: https://github.com/EinarOlafsson/spacr/raw/main/spacr/resources/icons/flow_chart_v2.png
122
+ :alt: SpaCr workflow
123
+ :align: center
124
+
125
+
126
+ **Overview and data organization of spaCR.**
127
+
128
+ **a.** Schematic workflow of the spaCR pipeline for pooled image-based CRISPR screens. Microscopy images (TIFF, LIF, CZI, NDI) and sequencing reads (FASTQ) are used as inputs (black). The main modules (teal) are: (1) Mask: generates object masks for cells, nuclei, pathogens, and cytoplasm; (2) Measure: extracts object-level features and crops object images, storing quantitative data in an SQL database; (3) Classify—applies machine learning (ML, e.g., XGBoost) or deep learning (DL, e.g., PyTorch) models to classify objects, summarizing results as well-level classification scores; (4) Map Barcodes: extracts and maps row, column, and gRNA barcodes from sequencing data to corresponding wells; (5) Regression: estimates gRNA effect sizes and gene scores via multiple linear regression using well-level summary statistics.
129
+ **b.** Downstream submodules available for extended analyses at each stage.
130
+ **c.** Output folder structure for each module, including locations for raw and processed images, masks, object-level measurements, datasets, and results.
131
+ **d.** List of all spaCR package modules.
132
+
124
133
  Installation
125
134
  ------------
126
135
 
136
+ **Linux recommended.**
127
137
  If using Windows, switch to Linux—it's free, open-source, and better.
128
138
 
129
- Before installing SpaCr on OSX ensure OpenMP is installed::
139
+ **macOS prerequisites (before install):**
140
+
141
+ ::
130
142
 
131
143
  brew install libomp
132
144
  brew install hdf5
133
145
 
134
- SpaCr GUI requires Tkinter. On Linux, ensure Tkinter is installed. (Tkinter is included with the standard Python installation on macOS and Windows)::
146
+ **Linux GUI requirement:**
147
+ SpaCr GUI requires Tkinter.
148
+
149
+ ::
135
150
 
136
151
  sudo apt-get install python3-tk
137
152
 
138
- Install SpaCr with pip::
153
+ **Installation:**
154
+
155
+ ::
139
156
 
140
157
  pip install spacr
141
158
 
142
- Run SpaCr GUI::
159
+ **Run SpaCr GUI:**
160
+
161
+ ::
143
162
 
144
163
  spacr
164
+
165
+ Data Availability
166
+ -----------------
167
+
168
+ - **Raw sequencing data** are available from NCBI BioProject `PRJNA1261935 <https://www.ncbi.nlm.nih.gov/bioproject/PRJNA1261935>`_ and SRA accessions: `SRR33531217 <https://www.ncbi.nlm.nih.gov/sra/SRR33531217>`_, `SRR33531218 <https://www.ncbi.nlm.nih.gov/sra/SRR33531218>`_, `SRR33531219 <https://www.ncbi.nlm.nih.gov/sra/SRR33531219>`_, `SRR33531220 <https://www.ncbi.nlm.nih.gov/sra/SRR33531220>`_
169
+
170
+ - **Image data** is deposited at EBI BioStudies under accession: `S-BIAD2076 <https://www.ebi.ac.uk/biostudies/studies/S-BIAD2076>`_
171
+
172
+ Example Notebooks
173
+ -----------------
174
+
175
+ The following example Jupyter notebooks illustrate common workflows using spaCR.
176
+
177
+ - `1_spacr_generate_masks.ipynb <Notebooks/1_spacr_generate_masks.ipynb>`_
178
+ *Generate cell, nuclei, and pathogen segmentation masks from microscopy images using Cellpose.*
179
+
180
+ - `2_spacr_generate_mesurments_crop_images.ipynb <Notebooks/2_spacr_generate_mesurments_crop_images.ipynb>`_
181
+ *Extract object-level measurements and crop single-cell images for downstream analysis.*
182
+
183
+ - `3a_spacr_machine_learning.ipynb <Notebooks/3a_spacr_machine_learning.ipynb>`_
184
+ *Train traditional machine learning models (e.g., XGBoost) to classify cell phenotypes based on extracted features.*
185
+
186
+ - `3b_spacr_computer_vision.ipynb <Notebooks/3b_spacr_computer_vision.ipynb>`_
187
+ *Train and evaluate deep learning models (PyTorch CNNs/Transformers) on cropped object images.*
188
+
189
+ - `4_spacr_map_barecodes.ipynb <Notebooks/4_spacr_map_barecodes.ipynb>`_
190
+ *Map sequencing reads to row, column, and gRNA barcodes for CRISPR screen genotype-phenotype mapping.*
191
+
192
+ - `5_spacr_train_cellpose.ipynb <Notebooks/5_spacr_train_cellpose.ipynb>`_
193
+ *Finetune Cellpose models using your own annotated training data for improved segmentation accuracy.*
@@ -11,25 +11,25 @@ spacr/chat_bot.py,sha256=n3Fhqg3qofVXHmh3H9sUcmfYy9MmgRnr48663MVdY9E,1244
11
11
  spacr/core.py,sha256=w4E3Pg-ZnA8BOK0iUMTjiNO0GeR5YCEs8fUTbESzqjY,47392
12
12
  spacr/deep_spacr.py,sha256=055tIo3WP3elGFiIuSZaLURgu2XyUDxAdbw5ezASEqM,54526
13
13
  spacr/gui.py,sha256=NhMh96KoArrSAaJBV6PhDQpIC1cQpxgb6SclhRbYG8s,8122
14
- spacr/gui_core.py,sha256=jQPDQVP43YL_AW7Sm8cIQgdZVaB9Yig8U6ov9clCYOc,56180
14
+ spacr/gui_core.py,sha256=m-AYUmaSXqsGFj9EzPf4fp1irZGN-X-21S4FBO8PoXc,52727
15
15
  spacr/gui_elements.py,sha256=5a3BOpctBPklsT1NungqS72h1Bg1FArUndE0OfvWD8Y,152646
16
16
  spacr/gui_utils.py,sha256=vv_uBOA0n-04KCCicYHhNt3sRbm0IPLM5r8QX5EkJ1Q,40867
17
17
  spacr/io.py,sha256=SYLhupKnOJJscNSGE4N67E32-ywhwrjRccIfZrL38Uk,157966
18
18
  spacr/logger.py,sha256=lJhTqt-_wfAunCPl93xE65Wr9Y1oIHJWaZMjunHUeIw,1538
19
- spacr/measure.py,sha256=Z3u4BU5RzcY82IZuboQ0OsxuXaPVwOlH65Rw6FrL5z4,55045
19
+ spacr/measure.py,sha256=XmOCKriS-kuRy-EPhQ_z7CRNg6DukyTpwQCiuSPNd_c,63414
20
20
  spacr/mediar.py,sha256=p0F515eFbm6_rePSnChsgqrgH-H5Sr_3zWrghtOnAUg,14863
21
21
  spacr/ml.py,sha256=XCRZeX7UkbMctQICIoskeWVx8CCmmCoHNauUOAkfFq0,91692
22
22
  spacr/openai.py,sha256=5vBZ3Jl2llYcW3oaTEXgdyCB2aJujMUIO5K038z7w_A,1246
23
- spacr/plot.py,sha256=DGQulM2425DFVRMT9ZVvZW8EMxnc3hzjXhcL7fTMrGA,172668
23
+ spacr/plot.py,sha256=M2w9ytR8iMFtsVPhmQ5tzIWTQDmbtCzs1-7hALUIQtg,167339
24
24
  spacr/sequencing.py,sha256=EY12RdW5QRKpHDRQCw1QoAlxCq8FK2v6WoVa5uuDBXQ,26745
25
- spacr/settings.py,sha256=LzJbebCRkDVYJxbojZKQE88MJ8Fg8iR6HZNHjN_np28,88687
25
+ spacr/settings.py,sha256=tEFKYqMYQoObrNuKL3XdinRebQxJcuA3Gn9wK44vqhs,87505
26
26
  spacr/sim.py,sha256=1xKhXimNU3ukzIw-3l9cF3Znc_brW8h20yv8fSTzvss,71173
27
27
  spacr/sp_stats.py,sha256=mbhwsyIqt5upsSD346qGjdCw7CFBa0tIS7zHU9e0jNI,9536
28
28
  spacr/spacr_cellpose.py,sha256=RBHMs2vwXcfkj0xqAULpALyzJYXddSRycgZSzmwI7v0,14755
29
29
  spacr/submodules.py,sha256=Z2i4kv_rWdxqoXsOKCF7BaSXtvaCZB69Ow8_FQBnZsY,83093
30
- spacr/timelapse.py,sha256=YJciEQS15eVC9R9adcen62xx7uttGAr_Gaz1vGX2Ke8,46824
30
+ spacr/timelapse.py,sha256=-5ZupTsCCpbenIQ2zsUmnwXh45B82fO-gPrSXOxu2s8,42980
31
31
  spacr/toxo.py,sha256=GoNfgyH-NJx3WOzNQPgzODir7Jp65fs7UM46XpzcrUo,26056
32
- spacr/utils.py,sha256=ddeLh_KWA1bQsB9R3OtljFmsoWrriWjIJDq7x61J_XI,236185
32
+ spacr/utils.py,sha256=cw5zM6zpFWWUZQKwtYvXc_rNfBMW2ldbnlw8s6f6bFQ,234397
33
33
  spacr/version.py,sha256=axH5tnGwtgSnJHb5IDhiu4Zjk5GhLyAEDRe-rnaoFOA,409
34
34
  spacr/resources/data/lopit.csv,sha256=ERI5f9W8RdJGiSx_khoaylD374f8kmvLia1xjhD_mII,4421709
35
35
  spacr/resources/data/toxoplasma_metadata.csv,sha256=9TXx0VlClDHAxQmaLhoklE8NuETduXaGHZjhR_6lZfs,2969409
@@ -82,6 +82,7 @@ spacr/resources/icons/convert.png,sha256=vLyTkQeUZ9q-pirhtZeXDq3-DzfjoPMjLlgKl5W
82
82
  spacr/resources/icons/default.png,sha256=KoNhaSHukO4wDyivyYEgSbb5mGj-sAxmhKikLLtNpWs,20341
83
83
  spacr/resources/icons/dna_matrix.mp4,sha256=NegOQkn4q4kHhFgqcIX2dd58wVytBtnkmbgg0ZegL8U,23462876
84
84
  spacr/resources/icons/download.png,sha256=1nUoWRaTc4vIsK6gompdeqk0cIv2GdH-gCNHaEBX6Mc,20467
85
+ spacr/resources/icons/flow_chart_v2.png,sha256=4U4uzJlyQ8L-exWIXIhyqtkoO-KIiubO23kA7eLZYYE,640609
85
86
  spacr/resources/icons/logo.pdf,sha256=VB4cS41V3VV_QxD7l6CwdQKQiYLErugLBxWoCoxjQU0,377925
86
87
  spacr/resources/icons/logo_spacr.png,sha256=qG3e3bdrAefhl1281rfo0R2XP0qA-c-oaBCXjxMGXkw,42587
87
88
  spacr/resources/icons/logo_spacr_1.png,sha256=g9y2ZmnV3hab8r1idDfytm8AaHbBiQdu_93Jd7YKzwA,610892
@@ -101,9 +102,9 @@ spacr/resources/icons/umap.png,sha256=dOLF3DeLYy9k0nkUybiZMe1wzHQwLJFRmgccppw-8b
101
102
  spacr/resources/images/plate1_E01_T0001F001L01A01Z01C02.tif,sha256=Tl0ZUfZ_AYAbu0up_nO0tPRtF1BxXhWQ3T3pURBCCRo,7958528
102
103
  spacr/resources/images/plate1_E01_T0001F001L01A02Z01C01.tif,sha256=m8N-V71rA1TT4dFlENNg8s0Q0YEXXs8slIn7yObmZJQ,7958528
103
104
  spacr/resources/images/plate1_E01_T0001F001L01A03Z01C03.tif,sha256=Pbhk7xn-KUP6RSIhJsxQcrHFImBm3GEpLkzx7WOc-5M,7958528
104
- spacr-0.9.0.dist-info/LICENSE,sha256=SR-2MeGc6SCM1UORJYyarSWY_A-JaOMFDj7ReSs9tRM,1083
105
- spacr-0.9.0.dist-info/METADATA,sha256=mk6s6xqwjy95ZgAc3fxWVNAoK_XsAceqHtB6e2PaRwE,6208
106
- spacr-0.9.0.dist-info/WHEEL,sha256=R0nc6qTxuoLk7ShA2_Y-UWkN8ZdfDBG2B6Eqpz2WXbs,91
107
- spacr-0.9.0.dist-info/entry_points.txt,sha256=BMC0ql9aNNpv8lUZ8sgDLQMsqaVnX5L535gEhKUP5ho,296
108
- spacr-0.9.0.dist-info/top_level.txt,sha256=GJPU8FgwRXGzKeut6JopsSRY2R8T3i9lDgya42tLInY,6
109
- spacr-0.9.0.dist-info/RECORD,,
105
+ spacr-0.9.2.dist-info/LICENSE,sha256=SR-2MeGc6SCM1UORJYyarSWY_A-JaOMFDj7ReSs9tRM,1083
106
+ spacr-0.9.2.dist-info/METADATA,sha256=vfXoB5rq-qP1Yq4Hj4noXrPKrI-Fs1Q09RN_WTRkQLw,9336
107
+ spacr-0.9.2.dist-info/WHEEL,sha256=R0nc6qTxuoLk7ShA2_Y-UWkN8ZdfDBG2B6Eqpz2WXbs,91
108
+ spacr-0.9.2.dist-info/entry_points.txt,sha256=BMC0ql9aNNpv8lUZ8sgDLQMsqaVnX5L535gEhKUP5ho,296
109
+ spacr-0.9.2.dist-info/top_level.txt,sha256=GJPU8FgwRXGzKeut6JopsSRY2R8T3i9lDgya42tLInY,6
110
+ spacr-0.9.2.dist-info/RECORD,,
File without changes
File without changes