spacr 0.9.1__py3-none-any.whl → 0.9.3__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
spacr/app_annotate.py CHANGED
@@ -30,6 +30,10 @@ def initiate_annotation_app(parent_frame):
30
30
  settings['percentiles'] = list(map(convert_to_number, settings['percentiles'].split(','))) if settings['percentiles'] else [2, 98]
31
31
  settings['normalize'] = settings['normalize'].lower() == 'true'
32
32
  settings['normalize_channels'] = settings['normalize_channels'].split(',')
33
+ settings['outline'] = settings['outline'].split(',') if settings['outline'] else None
34
+ settings['outline_threshold_factor'] = float(settings['outline_threshold_factor']) if settings['outline_threshold_factor'] else 1.0
35
+ settings['outline_sigma'] = float(settings['outline_threshold_factor']) if settings['outline_threshold_factor'] else 1.0
36
+
33
37
  try:
34
38
  settings['measurement'] = settings['measurement'].split(',') if settings['measurement'] else None
35
39
  settings['threshold'] = None if settings['threshold'].lower() == 'none' else int(settings['threshold'])
spacr/gui_core.py CHANGED
@@ -361,8 +361,8 @@ def setup_plot_section(vertical_container, settings_type):
361
361
  if settings_type == 'map_barcodes':
362
362
  current_dir = os.path.dirname(__file__)
363
363
  resources_path = os.path.join(current_dir, 'resources', 'icons')
364
- gif_path = os.path.join(resources_path, 'dna_matrix.mp4')
365
- display_media_in_plot_frame(gif_path, plot_frame)
364
+ #gif_path = os.path.join(resources_path, 'dna_matrix.mp4')
365
+ #display_media_in_plot_frame(gif_path, plot_frame)
366
366
 
367
367
  canvas = FigureCanvasTkAgg(figure, master=plot_frame)
368
368
  canvas.get_tk_widget().configure(cursor='arrow', highlightthickness=0)
spacr/gui_elements.py CHANGED
@@ -10,18 +10,23 @@ import numpy as np
10
10
  import pandas as pd
11
11
  from PIL import Image, ImageOps, ImageTk, ImageDraw, ImageFont, ImageEnhance
12
12
  from concurrent.futures import ThreadPoolExecutor
13
- from skimage.exposure import rescale_intensity
14
13
  from IPython.display import display, HTML
15
14
  import imageio.v2 as imageio
16
15
  from collections import deque
16
+ from skimage.filters import threshold_otsu
17
+ from skimage.exposure import rescale_intensity
17
18
  from skimage.draw import polygon, line
18
19
  from skimage.transform import resize
19
- from scipy.ndimage import binary_fill_holes, label
20
+ from skimage.morphology import dilation, disk
21
+ from skimage.segmentation import find_boundaries
22
+ from skimage.util import img_as_ubyte
23
+ from scipy.ndimage import binary_fill_holes, label, gaussian_filter
20
24
  from tkinter import ttk, scrolledtext
21
25
  from sklearn.model_selection import train_test_split
22
26
  from xgboost import XGBClassifier
23
27
  from sklearn.metrics import classification_report, confusion_matrix
24
28
 
29
+
25
30
  fig = None
26
31
 
27
32
  def restart_gui_app(root):
@@ -2209,7 +2214,7 @@ class ModifyMaskApp:
2209
2214
  self.update_display()
2210
2215
 
2211
2216
  class AnnotateApp:
2212
- def __init__(self, root, db_path, src, image_type=None, channels=None, image_size=200, annotation_column='annotate', normalize=False, percentiles=(1, 99), measurement=None, threshold=None, normalize_channels=None):
2217
+ def __init__(self, root, db_path, src, image_type=None, channels=None, image_size=200, annotation_column='annotate', normalize=False, percentiles=(1, 99), measurement=None, threshold=None, normalize_channels=None, outline=None, outline_threshold_factor=1, outline_sigma=1):
2213
2218
  self.root = root
2214
2219
  self.db_path = db_path
2215
2220
  self.src = src
@@ -2237,7 +2242,10 @@ class AnnotateApp:
2237
2242
  self.measurement = measurement
2238
2243
  self.threshold = threshold
2239
2244
  self.normalize_channels = normalize_channels
2240
- print('self.normalize_channels',self.normalize_channels)
2245
+ self.outline = outline #([s.strip().lower() for s in outline.split(',') if s.strip()]if isinstance(outline, str) and outline else None)
2246
+ self.outline_threshold_factor = outline_threshold_factor
2247
+ self.outline_sigma = outline_sigma
2248
+
2241
2249
  style_out = set_dark_style(ttk.Style())
2242
2250
  self.font_loader = style_out['font_loader']
2243
2251
  self.font_size = style_out['font_size']
@@ -2337,7 +2345,12 @@ class AnnotateApp:
2337
2345
  'percentiles': ','.join(map(str, self.percentiles)),
2338
2346
  'measurement': ','.join(self.measurement) if self.measurement else '',
2339
2347
  'threshold': str(self.threshold) if self.threshold is not None else '',
2340
- 'normalize_channels': ','.join(self.normalize_channels) if self.normalize_channels else ''
2348
+ 'normalize_channels': ','.join(self.normalize_channels) if self.normalize_channels else '',
2349
+ 'outline': ','.join(self.outline) if self.outline else '',
2350
+ 'outline_threshold_factor': str(self.outline_threshold_factor) if hasattr(self, 'outline_threshold_factor') else '1.0',
2351
+ 'outline_sigma': str(self.outline_sigma) if hasattr(self, 'outline_sigma') else '1.0',
2352
+ 'src': self.src,
2353
+ 'db_path': self.db_path,
2341
2354
  }
2342
2355
 
2343
2356
  for key, data in vars_dict.items():
@@ -2354,7 +2367,10 @@ class AnnotateApp:
2354
2367
  settings['percentiles'] = list(map(convert_to_number, settings['percentiles'].split(','))) if settings['percentiles'] else [1, 99]
2355
2368
  settings['normalize'] = settings['normalize'].lower() == 'true'
2356
2369
  settings['normalize_channels'] = settings['normalize_channels'].split(',') if settings['normalize_channels'] else None
2357
-
2370
+ settings['outline'] = settings['outline'].split(',') if settings['outline'] else None
2371
+ settings['outline_threshold_factor'] = float(settings['outline_threshold_factor'].replace(',', '.')) if settings['outline_threshold_factor'] else 1.0
2372
+ settings['outline_sigma'] = float(settings['outline_sigma'].replace(',', '.')) if settings['outline_sigma'] else 1.0
2373
+
2358
2374
  try:
2359
2375
  settings['measurement'] = settings['measurement'].split(',') if settings['measurement'] else None
2360
2376
  settings['threshold'] = None if settings['threshold'].lower() == 'none' else int(settings['threshold'])
@@ -2379,7 +2395,12 @@ class AnnotateApp:
2379
2395
  'percentiles': settings.get('percentiles'),
2380
2396
  'measurement': settings.get('measurement'),
2381
2397
  'threshold': settings.get('threshold'),
2382
- 'normalize_channels': settings.get('normalize_channels')
2398
+ 'normalize_channels': settings.get('normalize_channels'),
2399
+ 'outline': settings.get('outline'),
2400
+ 'outline_threshold_factor': settings.get('outline_threshold_factor'),
2401
+ 'outline_sigma': settings.get('outline_sigma'),
2402
+ 'src': self.src,
2403
+ 'db_path': self.db_path
2383
2404
  })
2384
2405
 
2385
2406
  settings_window.destroy()
@@ -2389,22 +2410,32 @@ class AnnotateApp:
2389
2410
 
2390
2411
  def update_settings(self, **kwargs):
2391
2412
  allowed_attributes = {
2392
- 'image_type', 'channels', 'image_size', 'annotation_column',
2393
- 'normalize', 'percentiles', 'measurement', 'threshold', 'normalize_channels'
2413
+ 'image_type', 'channels', 'image_size', 'annotation_column', 'src', 'db_path',
2414
+ 'normalize', 'percentiles', 'measurement', 'threshold', 'normalize_channels', 'outline', 'outline_threshold_factor', 'outline_sigma'
2394
2415
  }
2395
2416
 
2396
2417
  updated = False
2397
-
2418
+
2398
2419
  for attr, value in kwargs.items():
2399
2420
  if attr in allowed_attributes and value is not None:
2421
+ if attr == 'outline':
2422
+ if isinstance(value, str):
2423
+ value = [s.strip().lower() for s in value.split(',') if s.strip()]
2424
+ elif attr == 'outline_threshold_factor':
2425
+ value = float(value)
2426
+ elif attr == 'outline_sigma':
2427
+ value = float(value)
2400
2428
  setattr(self, attr, value)
2401
2429
  updated = True
2402
2430
 
2431
+
2403
2432
  if 'image_size' in kwargs:
2404
2433
  if isinstance(self.image_size, list):
2405
2434
  self.image_size = (int(self.image_size[0]), int(self.image_size[0]))
2406
2435
  elif isinstance(self.image_size, int):
2407
2436
  self.image_size = (self.image_size, self.image_size)
2437
+ elif isinstance(self.image_size, tuple) and len(self.image_size) == 2:
2438
+ self.image_size = tuple(map(int, self.image_size))
2408
2439
  else:
2409
2440
  raise ValueError("Invalid image size")
2410
2441
 
@@ -2599,9 +2630,47 @@ class AnnotateApp:
2599
2630
  img = self.normalize_image(img, self.normalize, self.percentiles, self.normalize_channels)
2600
2631
  img = img.convert('RGB')
2601
2632
  img = self.filter_channels(img)
2633
+
2634
+ if self.outline:
2635
+ img = self.outline_image(img, self.outline_sigma)
2636
+
2602
2637
  img = img.resize(self.image_size)
2603
2638
  return img, annotation
2604
2639
 
2640
+ def outline_image(self, img, edge_sigma=1, edge_thickness=1):
2641
+ """
2642
+ For each selected channel, compute a continuous outline from the intensity landscape
2643
+ using Otsu threshold scaled by a correction factor. Replace only that channel.
2644
+ """
2645
+ arr = np.asarray(img)
2646
+ if arr.ndim != 3 or arr.shape[2] != 3:
2647
+ return img # not RGB
2648
+
2649
+ out_img = arr.copy()
2650
+ channel_map = {'r': 0, 'g': 1, 'b': 2}
2651
+ factor = getattr(self, 'outline_threshold_factor', 1.0)
2652
+
2653
+ for ch in self.outline:
2654
+ if ch not in channel_map:
2655
+ continue
2656
+ idx = channel_map[ch]
2657
+ channel_data = arr[:, :, idx]
2658
+
2659
+ try:
2660
+ channel_data = gaussian_filter(channel_data, sigma=edge_sigma)
2661
+ otsu_thresh = threshold_otsu(channel_data)
2662
+ corrected_thresh = min(255, otsu_thresh * factor)
2663
+ fg_mask = channel_data > corrected_thresh
2664
+ except Exception:
2665
+ continue
2666
+
2667
+ edge = find_boundaries(fg_mask, mode='inner')
2668
+ thick_edge = dilation(edge, disk(edge_thickness))
2669
+
2670
+ out_img[:, :, idx] = (thick_edge * 255).astype(np.uint8)
2671
+
2672
+ return Image.fromarray(out_img)
2673
+
2605
2674
  @staticmethod
2606
2675
  def normalize_image(img, normalize=False, percentiles=(1, 99), normalize_channels=None):
2607
2676
  """
spacr/gui_utils.py CHANGED
@@ -252,7 +252,7 @@ def annotate(settings):
252
252
  app.load_images()
253
253
  root.mainloop()
254
254
 
255
- def generate_annotate_fields(frame):
255
+ def generate_annotate_fields_v1(frame):
256
256
  from .settings import set_annotate_default_settings
257
257
  from .gui_elements import set_dark_style
258
258
 
@@ -281,6 +281,48 @@ def generate_annotate_fields(frame):
281
281
 
282
282
  return vars_dict
283
283
 
284
+ def generate_annotate_fields(frame):
285
+ from .settings import set_annotate_default_settings
286
+ from .gui_elements import set_dark_style
287
+
288
+ style_out = set_dark_style(ttk.Style())
289
+ font_loader = style_out['font_loader']
290
+ font_size = style_out['font_size'] - 2
291
+
292
+ vars_dict = {}
293
+ settings = set_annotate_default_settings(settings={})
294
+
295
+ for setting in settings:
296
+ vars_dict[setting] = {
297
+ 'entry': ttk.Entry(frame),
298
+ 'value': settings[setting]
299
+ }
300
+
301
+ # Arrange input fields and labels
302
+ for row, (name, data) in enumerate(vars_dict.items()):
303
+ tk.Label(
304
+ frame,
305
+ text=f"{name.replace('_', ' ').capitalize()}:",
306
+ bg=style_out['bg_color'],
307
+ fg=style_out['fg_color'],
308
+ font=font_loader.get_font(size=font_size)
309
+ ).grid(row=row, column=0)
310
+
311
+ value = data['value']
312
+ if isinstance(value, list):
313
+ string_value = ','.join(map(str, value))
314
+ elif isinstance(value, (int, float, bool)):
315
+ string_value = str(value)
316
+ elif value is None:
317
+ string_value = ''
318
+ else:
319
+ string_value = value
320
+
321
+ data['entry'].insert(0, string_value)
322
+ data['entry'].grid(row=row, column=1)
323
+
324
+ return vars_dict
325
+
284
326
  def run_annotate_app(vars_dict, parent_frame):
285
327
  settings = {key: data['entry'].get() for key, data in vars_dict.items()}
286
328
  settings['channels'] = settings['channels'].split(',')
@@ -349,7 +391,7 @@ def annotate_with_image_refs(settings, root, shutdown_callback):
349
391
  screen_height = root.winfo_screenheight()
350
392
  root.geometry(f"{screen_width}x{screen_height}")
351
393
 
352
- app = AnnotateApp(root, db, src, image_type=settings['image_type'], channels=settings['channels'], image_size=settings['img_size'], annotation_column=settings['annotation_column'], normalize=settings['normalize'], percentiles=settings['percentiles'], measurement=settings['measurement'], threshold=settings['threshold'], normalize_channels=settings['normalize_channels'])
394
+ app = AnnotateApp(root, db, src, image_type=settings['image_type'], channels=settings['channels'], image_size=settings['img_size'], annotation_column=settings['annotation_column'], normalize=settings['normalize'], percentiles=settings['percentiles'], measurement=settings['measurement'], threshold=settings['threshold'], normalize_channels=settings['normalize_channels'], outline=settings['outline'], outline_threshold_factor=settings['outline_threshold_factor'], outline_sigma=settings['outline_sigma'])
353
395
 
354
396
  # Set the canvas background to black
355
397
  root.configure(bg='black')
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.
@@ -251,25 +249,95 @@ def _create_dataframe(radial_distributions, object_type):
251
249
 
252
250
  def _extended_regionprops_table(labels, image, intensity_props):
253
251
  """
254
- Calculate extended region properties table.
255
-
256
- Args:
257
- labels (ndarray): Labeled image.
258
- image (ndarray): Input image.
259
- intensity_props (list): List of intensity properties to calculate.
260
-
261
- Returns:
262
- DataFrame: Extended region properties table.
263
-
252
+ Calculate extended region properties table, adding a suite of advanced quantitative features.
264
253
  """
265
- regions = regionprops(labels, image)
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
+
266
265
  props = regionprops_table(labels, image, properties=intensity_props)
267
- percentiles = [5, 10, 25, 50, 75, 85, 95]
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, 75, 85, 95]
268
335
  for p in percentiles:
269
- props[f'percentile_{p}'] = [
270
- np.percentile(region.intensity_image.flatten()[~np.isnan(region.intensity_image.flatten())], p)
271
- for region in regions]
272
- return pd.DataFrame(props)
336
+ df[f'percentile_{p}'] = [
337
+ np.percentile(region.intensity_image[region.image], p)
338
+ for region in regions
339
+ ]
340
+ return df
273
341
 
274
342
  def _calculate_homogeneity(label, channel, distances=[2,4,8,16,32,64]):
275
343
  """
@@ -495,8 +563,75 @@ def _estimate_blur(image):
495
563
  # Compute and return the variance of the Laplacian
496
564
  return lap.var()
497
565
 
566
+ def _measure_intensity_distance(cell_mask, nucleus_mask, pathogen_mask, channel_arrays, settings):
567
+ """
568
+ Compute Gaussian-smoothed intensity-weighted centroid distances for each cell object.
569
+ """
570
+
571
+ sigma = settings.get('distance_gaussian_sigma', 1.0)
572
+ cell_labels = np.unique(cell_mask)
573
+ cell_labels = cell_labels[cell_labels > 0]
574
+
575
+ dfs = []
576
+ nucleus_dt = distance_transform_edt(nucleus_mask == 0)
577
+ pathogen_dt = distance_transform_edt(pathogen_mask == 0)
578
+
579
+ for ch in range(channel_arrays.shape[-1]):
580
+ channel_img = channel_arrays[:, :, ch]
581
+ blurred_img = gaussian_filter(channel_img, sigma=sigma)
582
+
583
+ data = []
584
+ for label in cell_labels:
585
+ cell_coords = np.argwhere(cell_mask == label)
586
+ if cell_coords.size == 0:
587
+ data.append([label, np.nan, np.nan])
588
+ continue
589
+
590
+ minr, minc = np.min(cell_coords, axis=0)
591
+ maxr, maxc = np.max(cell_coords, axis=0) + 1
592
+
593
+ cell_submask = (cell_mask[minr:maxr, minc:maxc] == label)
594
+ blurred_subimg = blurred_img[minr:maxr, minc:maxc]
595
+
596
+ if np.sum(cell_submask) == 0:
597
+ data.append([label, np.nan, np.nan])
598
+ continue
599
+
600
+ masked_intensity = blurred_subimg * cell_submask
601
+ com_local = center_of_mass(masked_intensity)
602
+ if np.isnan(com_local[0]):
603
+ data.append([label, np.nan, np.nan])
604
+ continue
605
+
606
+ com_global = (com_local[0] + minr, com_local[1] + minc)
607
+ com_global_int = tuple(np.round(com_global).astype(int))
608
+
609
+ x, y = com_global_int
610
+ if not (0 <= x < cell_mask.shape[0] and 0 <= y < cell_mask.shape[1]):
611
+ data.append([label, np.nan, np.nan])
612
+ continue
613
+
614
+ nucleus_dist = nucleus_dt[x, y]
615
+ pathogen_dist = pathogen_dt[x, y]
616
+
617
+ data.append([label, nucleus_dist, pathogen_dist])
618
+
619
+ df = pd.DataFrame(data, columns=['label',
620
+ f'cell_channel_{ch}_distance_to_nucleus',
621
+ f'cell_channel_{ch}_distance_to_pathogen'])
622
+ dfs.append(df)
623
+
624
+ # Merge all channel dataframes on label
625
+ merged_df = dfs[0]
626
+ for df in dfs[1:]:
627
+ merged_df = merged_df.merge(df, on='label', how='outer')
628
+
629
+ return merged_df
630
+
631
+
498
632
  #@log_function_call
499
633
  def _intensity_measurements(cell_mask, nucleus_mask, pathogen_mask, cytoplasm_mask, channel_arrays, settings, sizes=[3, 6, 12, 24], periphery=True, outside=True):
634
+
500
635
  """
501
636
  Calculate various intensity measurements for different regions in the image.
502
637
 
@@ -536,8 +671,8 @@ def _intensity_measurements(cell_mask, nucleus_mask, pathogen_mask, cytoplasm_ma
536
671
  df.append(empty_df)
537
672
  continue
538
673
 
539
- mask_intensity_df = _extended_regionprops_table(label, channel, intensity_props)
540
- mask_intensity_df['shannon_entropy'] = shannon_entropy(channel, base=2)
674
+ mask_intensity_df = _extended_regionprops_table(label, channel, intensity_props)
675
+ #mask_intensity_df['shannon_entropy'] = shannon_entropy(channel, base=2)
541
676
 
542
677
  if homogeneity:
543
678
  homogeneity_df = _calculate_homogeneity(label, channel, distances)
@@ -558,6 +693,13 @@ def _intensity_measurements(cell_mask, nucleus_mask, pathogen_mask, cytoplasm_ma
558
693
 
559
694
  mask_intensity_df.columns = [f'{ls[j]}_channel_{i}_{col}' if col != 'label' else col for col in mask_intensity_df.columns]
560
695
  df.append(mask_intensity_df)
696
+
697
+ if isinstance(settings['distance_gaussian_sigma'], int):
698
+ if settings['distance_gaussian_sigma'] != 0:
699
+ if settings['cell_mask_dim'] != None:
700
+ if settings['nucleus_mask_dim'] != None or settings['pathogen_mask_dim'] != None:
701
+ intensity_distance_df = _measure_intensity_distance(cell_mask, nucleus_mask, pathogen_mask, channel_arrays, settings)
702
+ cell_dfs.append(intensity_distance_df)
561
703
 
562
704
  if radial_dist:
563
705
  if np.max(nucleus_mask) != 0:
@@ -565,7 +707,7 @@ def _intensity_measurements(cell_mask, nucleus_mask, pathogen_mask, cytoplasm_ma
565
707
  nucleus_df = _create_dataframe(nucleus_radial_distributions, 'nucleus')
566
708
  dfs[1].append(nucleus_df)
567
709
 
568
- if np.max(nucleus_mask) != 0:
710
+ if np.max(pathogen_mask) != 0:
569
711
  pathogen_radial_distributions = _calculate_radial_distribution(cell_mask, pathogen_mask, channel_arrays, num_bins=6)
570
712
  pathogen_df = _create_dataframe(pathogen_radial_distributions, 'pathogen')
571
713
  dfs[2].append(pathogen_df)
@@ -785,6 +927,7 @@ def _measure_crop_core(index, time_ls, file, settings):
785
927
  #merge skeleton_df with cell_df here
786
928
 
787
929
  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)
930
+
788
931
  if settings['cell_mask_dim'] is not None:
789
932
  cell_merged_df = _merge_and_save_to_database(cell_df, cell_intensity_df, 'cell', source_folder, file_name, settings['experiment'], settings['timelapse'])
790
933
  if settings['nucleus_mask_dim'] is not None:
spacr/plot.py CHANGED
@@ -1154,7 +1154,7 @@ def _plot_cropped_arrays(stack, filename, figuresize=10, cmap='inferno', thresho
1154
1154
  for channel in range(num_channels):
1155
1155
  plot_single_array(stack[:, :, channel], axs[channel], f'C. {channel}', plt.get_cmap(cmap))
1156
1156
  fig.tight_layout()
1157
- print(f'{filename}')
1157
+ #print(f'{filename}')
1158
1158
  return fig
1159
1159
 
1160
1160
  def _visualize_and_save_timelapse_stack_with_tracks(masks, tracks_df, save, src, name, plot, filenames, object_type, mode='btrack', interactive=False):
Binary file
Binary file
spacr/settings.py CHANGED
@@ -313,7 +313,9 @@ def get_measure_crop_settings(settings={}):
313
313
  settings.setdefault('pathogen_min_size',0)
314
314
  settings.setdefault('cytoplasm_min_size',0)
315
315
  settings.setdefault('merge_edge_pathogen_cells', True)
316
-
316
+
317
+ settings.setdefault('distance_gaussian_sigma', 10)
318
+
317
319
  if settings['test_mode']:
318
320
  settings['verbose'] = True
319
321
  settings['plot'] = True
@@ -1000,7 +1002,8 @@ expected_types = {
1000
1002
  "cell_diamiter":int,
1001
1003
  "nucleus_diamiter":int,
1002
1004
  "pathogen_diamiter":int,
1003
- "consolidate":bool
1005
+ "consolidate":bool,
1006
+ "distance_gaussian_sigma": (int, type(None))
1004
1007
  }
1005
1008
 
1006
1009
  categories = {"Paths":[ "src", "grna", "barcodes", "custom_model_path", "dataset","model_path","grna_csv","row_csv","column_csv", "metadata_files", "score_data","count_data"],
@@ -1022,7 +1025,7 @@ categories = {"Paths":[ "src", "grna", "barcodes", "custom_model_path", "dataset
1022
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"],
1023
1026
  "Timelapse": ["timelapse", "fps", "timelapse_displacement", "timelapse_memory", "timelapse_frame_limits", "timelapse_remove_transient", "timelapse_mode", "timelapse_objects", "compartments"],
1024
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"],
1025
- "Beta": ["all_to_mip", "upscale", "upscale_factor", "consolidate"]
1028
+ "Beta": ["all_to_mip", "upscale", "upscale_factor", "consolidate", "distance_gaussian_sigma"]
1026
1029
  }
1027
1030
 
1028
1031
 
@@ -1464,6 +1467,9 @@ def set_annotate_default_settings(settings):
1464
1467
  settings.setdefault('annotation_column', 'test')
1465
1468
  settings.setdefault('normalize', 'False')
1466
1469
  settings.setdefault('normalize_channels', "r,g,b")
1470
+ settings.setdefault('outline', None)
1471
+ settings.setdefault('outline_threshold_factor', 1)
1472
+ settings.setdefault('outline_sigma', 1)
1467
1473
  settings.setdefault('percentiles', [2, 98])
1468
1474
  settings.setdefault('measurement', '') #'cytoplasm_channel_3_mean_intensity,pathogen_channel_3_mean_intensity')
1469
1475
  settings.setdefault('threshold', '') #'2')
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
 
@@ -1,6 +1,6 @@
1
1
  The MIT License (MIT)
2
2
 
3
- Copyright (c) <year> Adam Veldhousen
3
+ Copyright (c) 2025 Adam Veldhousen
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
@@ -0,0 +1,207 @@
1
+ Metadata-Version: 2.1
2
+ Name: spacr
3
+ Version: 0.9.3
4
+ Summary: Spatial phenotype analysis of crisp screens (SpaCr)
5
+ Home-page: https://github.com/EinarOlafsson/spacr
6
+ Author: Einar Birnir Olafsson
7
+ Author-email: olafsson@med.umich.com
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Classifier: Operating System :: OS Independent
11
+ Description-Content-Type: text/x-rst
12
+ License-File: LICENSE
13
+ Requires-Dist: numpy <2.0,>=1.26.4
14
+ Requires-Dist: pandas <3.0,>=2.2.1
15
+ Requires-Dist: scipy <2.0,>=1.12.0
16
+ Requires-Dist: cellpose <4.0,>=3.0.6
17
+ Requires-Dist: scikit-image <1.0,>=0.22.0
18
+ Requires-Dist: scikit-learn <2.0,>=1.4.1
19
+ Requires-Dist: scikit-posthocs <0.20,>=0.10.0
20
+ Requires-Dist: mahotas <2.0,>=1.4.13
21
+ Requires-Dist: btrack <1.0,>=0.6.5
22
+ Requires-Dist: trackpy <1.0,>=0.6.2
23
+ Requires-Dist: statsmodels <1.0,>=0.14.1
24
+ Requires-Dist: shap <1.0,>=0.45.0
25
+ Requires-Dist: torch <3.0,>=2.0
26
+ Requires-Dist: torchvision <1.0,>=0.1
27
+ Requires-Dist: torch-geometric <3.0,>=2.5
28
+ Requires-Dist: torchcam <1.0,>=0.4.0
29
+ Requires-Dist: transformers <5.0,>=4.45.2
30
+ Requires-Dist: segmentation-models-pytorch >=0.3.3
31
+ Requires-Dist: monai >=1.3.0
32
+ Requires-Dist: captum <1.0,>=0.7.0
33
+ Requires-Dist: seaborn <1.0,>=0.13.2
34
+ Requires-Dist: matplotlib <4.0,>=3.8.3
35
+ Requires-Dist: matplotlib-venn <2.0,>=1.1
36
+ Requires-Dist: adjustText <2.0,>=1.2.0
37
+ Requires-Dist: bottleneck <2.0,>=1.3.6
38
+ Requires-Dist: numexpr <3.0,>=2.8.4
39
+ Requires-Dist: opencv-python-headless <5.0,>=4.9.0.80
40
+ Requires-Dist: pillow <11.0,>=10.2.0
41
+ Requires-Dist: tifffile >=2023.4.12
42
+ Requires-Dist: nd2reader <4.0,>=3.3.0
43
+ Requires-Dist: czifile
44
+ Requires-Dist: pylibCZIrw <6.0,>=5.0.0
45
+ Requires-Dist: aicspylibczi
46
+ Requires-Dist: readlif
47
+ Requires-Dist: imageio <3.0,>=2.34.0
48
+ Requires-Dist: pingouin <1.0,>=0.5.5
49
+ Requires-Dist: umap-learn <1.0,>=0.5.6
50
+ Requires-Dist: ttkthemes <4.0,>=3.2.2
51
+ Requires-Dist: xgboost <3.0,>=2.0.3
52
+ Requires-Dist: PyWavelets <2.0,>=1.6.0
53
+ Requires-Dist: ttf-opensans >=2020.10.30
54
+ Requires-Dist: customtkinter <6.0,>=5.2.2
55
+ Requires-Dist: biopython <2.0,>=1.80
56
+ Requires-Dist: lxml <6.0,>=5.1.0
57
+ Requires-Dist: psutil <6.0,>=5.9.8
58
+ Requires-Dist: gputil <2.0,>=1.4.0
59
+ Requires-Dist: gpustat <2.0,>=1.1.1
60
+ Requires-Dist: pyautogui <1.0,>=0.9.54
61
+ Requires-Dist: tables <4.0,>=3.8.0
62
+ Requires-Dist: rapidfuzz <4.0,>=3.9
63
+ Requires-Dist: keyring <16.0,>=15.1
64
+ Requires-Dist: screeninfo <1.0,>=0.8.1
65
+ Requires-Dist: fastremap >=1.14.1
66
+ Requires-Dist: pytz >=2023.3.post1
67
+ Requires-Dist: tqdm >=4.65.0
68
+ Requires-Dist: wandb >=0.16.2
69
+ Requires-Dist: openai <2.0,>=1.50.2
70
+ Requires-Dist: gdown
71
+ Requires-Dist: IPython <9.0,>=8.18.1
72
+ Requires-Dist: ipykernel
73
+ Requires-Dist: ipywidgets <9.0,>=8.1.2
74
+ Requires-Dist: brokenaxes <1.0,>=0.6.2
75
+ Requires-Dist: huggingface-hub <0.25,>=0.24.0
76
+ Provides-Extra: dev
77
+ Requires-Dist: pytest <3.11,>=3.9 ; extra == 'dev'
78
+ Provides-Extra: full
79
+ Requires-Dist: opencv-python ; extra == 'full'
80
+ Provides-Extra: headless
81
+ Requires-Dist: opencv-python-headless ; extra == 'headless'
82
+
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
+ .. |PyPI version| image:: https://badge.fury.io/py/spacr.svg
86
+ :target: https://badge.fury.io/py/spacr
87
+ .. |Python version| image:: https://img.shields.io/pypi/pyversions/spacr
88
+ :target: https://pypistats.org/packages/spacr
89
+ .. |Licence: MIT| image:: https://img.shields.io/github/license/EinarOlafsson/spacr
90
+ :target: https://github.com/EinarOlafsson/spacr/blob/main/LICENSE
91
+ .. |repo size| image:: https://img.shields.io/github/repo-size/EinarOlafsson/spacr
92
+ :target: https://github.com/EinarOlafsson/spacr/
93
+
94
+ .. _docs: https://einarolafsson.github.io/spacr/index.html
95
+
96
+ Badges
97
+ ------
98
+ |Docs| |PyPI version| |Python version| |Licence: MIT| |repo size|
99
+
100
+ SpaCr
101
+ =====
102
+
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.
106
+
107
+ Features
108
+ --------
109
+
110
+ - **Generate Masks:** Generate cellpose masks of cell, nuclei, and pathogen objects.
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.
112
+ - **Crop Images:** Save objects (cells, nuclei, pathogen, cytoplasm) as images. Object image paths are saved in a SQL database.
113
+ - **Train CNNs or Transformers:** Train Torch models to classify single object images.
114
+ - **Manual Annotation:** Supports manual annotation of single-cell images and segmentation to refine training datasets for training CNNs/Transformers or cellpose, respectively.
115
+ - **Finetune Cellpose Models:** Adjust pre-existing Cellpose models to your specific dataset for improved performance.
116
+ - **Timelapse Data Support:** Track objects in timelapse image data.
117
+ - **Simulations:** Simulate spatial phenotype screens.
118
+ - **Sequencing:** Map FASTQ reads to barcode and gRNA barcode metadata.
119
+ - **Misc:** Analyze Ca oscillation, recruitment, infection rate, plaque size/count.
120
+
121
+ .. image:: https://github.com/EinarOlafsson/spacr/raw/main/spacr/resources/icons/flow_chart_v3.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
+
133
+ Installation
134
+ ------------
135
+
136
+ **Linux recommended.**
137
+ If using Windows, switch to Linux—it's free, open-source, and better.
138
+
139
+ **macOS prerequisites (before install):**
140
+
141
+ ::
142
+
143
+ brew install libomp
144
+ brew install hdf5
145
+
146
+ **Linux GUI requirement:**
147
+ SpaCr GUI requires Tkinter.
148
+
149
+ ::
150
+
151
+ sudo apt-get install python3-tk
152
+
153
+ **Installation:**
154
+
155
+ ::
156
+
157
+ pip install spacr
158
+
159
+ **Run SpaCr GUI:**
160
+
161
+ ::
162
+
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:
171
+ `S-BIAD2076 <https://www.ebi.ac.uk/biostudies/studies/S-BIAD2076>`_
172
+ *(If the link redirects to the main BioStudies portal, copy and paste it directly into your browser.)*
173
+
174
+
175
+ Example Notebooks
176
+ -----------------
177
+
178
+ The following example Jupyter notebooks illustrate common workflows using spaCR.
179
+
180
+ - `Generate masks <https://github.com/EinarOlafsson/spacr/blob/main/Notebooks/1_spacr_generate_masks.ipynb>`_
181
+ *Generate cell, nuclei, and pathogen segmentation masks from microscopy images using Cellpose.*
182
+
183
+ - `Capture single cell images and measurements <https://github.com/EinarOlafsson/spacr/blob/main/Notebooks/2_spacr_generate_mesurments_crop_images.ipynb>`_
184
+ *Extract object-level measurements and crop single-cell images for downstream analysis.*
185
+
186
+ - `Machine learning based object classification <https://github.com/EinarOlafsson/spacr/blob/main/Notebooks/3a_spacr_machine_learning.ipynb>`_
187
+ *Train traditional machine learning models (e.g., XGBoost) to classify cell phenotypes based on extracted features.*
188
+
189
+ - `Computer vision based object classification <https://github.com/EinarOlafsson/spacr/blob/main/Notebooks/3b_spacr_computer_vision.ipynb>`_
190
+ *Train and evaluate deep learning models (PyTorch CNNs/Transformers) on cropped object images.*
191
+
192
+ - `Map sequencing barcodes <https://github.com/EinarOlafsson/spacr/blob/main/Notebooks/4_spacr_map_barecodes.ipynb>`_
193
+ *Map sequencing reads to row, column, and gRNA barcodes for CRISPR screen genotype-phenotype mapping.*
194
+
195
+ - `Finetune cellpose models <https://github.com/EinarOlafsson/spacr/blob/main/Notebooks/5_spacr_train_cellpose.ipynb>`_
196
+ *Finetune Cellpose models using your own annotated training data for improved segmentation accuracy.*
197
+
198
+ License
199
+ -------
200
+ spaCR is distributed under the terms of the MIT License.
201
+ See the `LICENSE <https://github.com/EinarOlafsson/spacr/blob/main/LICENSE>`_ file for details.
202
+
203
+ How to Cite
204
+ -----------
205
+ If you use spaCR in your research, please cite:
206
+ Olafsson EB, et al. SpaCr: Spatial phenotype analysis of CRISPR-Cas9 screens. *Manuscript in preparation*.
207
+
@@ -1,6 +1,6 @@
1
1
  spacr/__init__.py,sha256=EoGInYks0M4foZElYNhksrQK6aEO1au7cncWexWNhRw,1376
2
2
  spacr/__main__.py,sha256=H4MjaMF9ohZL6xfl1kTxVn1Nt_vEhhZArENMMBv8f4E,77
3
- spacr/app_annotate.py,sha256=W9eLPa_LZIvXsXx_-0iDFEU938LBDvRy6prXo0qF4KQ,2533
3
+ spacr/app_annotate.py,sha256=p0OyvgFycIug7RcLfejFmc4HWB7yQskCBxxy3Sdq_Y0,2905
4
4
  spacr/app_classify.py,sha256=urTP_wlZ58hSyM5a19slYlBxN0PdC-9-ga0hvq8CGWc,165
5
5
  spacr/app_make_masks.py,sha256=pqDhRpluiHZz-kPX2Zh_KbYe4TsU43qYBa_7f-rsjpw,1694
6
6
  spacr/app_mask.py,sha256=l-dBY8ftzCMdDe6-pXc2Nh_u-idNL9G7UOARiLJBtds,153
@@ -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=m-AYUmaSXqsGFj9EzPf4fp1irZGN-X-21S4FBO8PoXc,52727
15
- spacr/gui_elements.py,sha256=5a3BOpctBPklsT1NungqS72h1Bg1FArUndE0OfvWD8Y,152646
16
- spacr/gui_utils.py,sha256=vv_uBOA0n-04KCCicYHhNt3sRbm0IPLM5r8QX5EkJ1Q,40867
14
+ spacr/gui_core.py,sha256=RtpdB8S8yF9WARRsUjrZ1szZi4ZMfG7R_W34BTBEGYo,52729
15
+ spacr/gui_elements.py,sha256=OTU7aeLrPiMUTnyCT-J7ygng3beI9tdA0MmypOavEkw,156123
16
+ spacr/gui_utils.py,sha256=F6KfNY3OqNkvfkOP1rxwBha5IOdLVyBgqZYPw3xPLes,42293
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=nYvrfVfCIqD1AUk4QBE2jtpeSFtLdfUcnkhkqf9G4xQ,60877
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=M2w9ytR8iMFtsVPhmQ5tzIWTQDmbtCzs1-7hALUIQtg,167339
23
+ spacr/plot.py,sha256=Y1ON8Bu-FsZZZasXIK7nvnOohFzucCvFhyPE2bDGz1A,167340
24
24
  spacr/sequencing.py,sha256=EY12RdW5QRKpHDRQCw1QoAlxCq8FK2v6WoVa5uuDBXQ,26745
25
- spacr/settings.py,sha256=Fd5RbNxjBmWG6bCCDprVvy8dTjBzSK7L71k_-mJXBsk,87380
25
+ spacr/settings.py,sha256=gT0FEP6anfhM6sbFofmLRhOwaQptgpcI18VX6nRqmtQ,87661
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
30
  spacr/timelapse.py,sha256=-5ZupTsCCpbenIQ2zsUmnwXh45B82fO-gPrSXOxu2s8,42980
31
31
  spacr/toxo.py,sha256=GoNfgyH-NJx3WOzNQPgzODir7Jp65fs7UM46XpzcrUo,26056
32
- spacr/utils.py,sha256=RA4V1EDXsU_Gs6QgplvOA4lVIRFQJVJ1JAzQsaunHiU,234010
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,8 @@ 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
86
+ spacr/resources/icons/flow_chart_v3.png,sha256=Vw7ykdgmXEpA5BLUpDEnp_bAaJ6gPz94EVYqMHXmn1k,638047
85
87
  spacr/resources/icons/logo.pdf,sha256=VB4cS41V3VV_QxD7l6CwdQKQiYLErugLBxWoCoxjQU0,377925
86
88
  spacr/resources/icons/logo_spacr.png,sha256=qG3e3bdrAefhl1281rfo0R2XP0qA-c-oaBCXjxMGXkw,42587
87
89
  spacr/resources/icons/logo_spacr_1.png,sha256=g9y2ZmnV3hab8r1idDfytm8AaHbBiQdu_93Jd7YKzwA,610892
@@ -101,9 +103,9 @@ spacr/resources/icons/umap.png,sha256=dOLF3DeLYy9k0nkUybiZMe1wzHQwLJFRmgccppw-8b
101
103
  spacr/resources/images/plate1_E01_T0001F001L01A01Z01C02.tif,sha256=Tl0ZUfZ_AYAbu0up_nO0tPRtF1BxXhWQ3T3pURBCCRo,7958528
102
104
  spacr/resources/images/plate1_E01_T0001F001L01A02Z01C01.tif,sha256=m8N-V71rA1TT4dFlENNg8s0Q0YEXXs8slIn7yObmZJQ,7958528
103
105
  spacr/resources/images/plate1_E01_T0001F001L01A03Z01C03.tif,sha256=Pbhk7xn-KUP6RSIhJsxQcrHFImBm3GEpLkzx7WOc-5M,7958528
104
- spacr-0.9.1.dist-info/LICENSE,sha256=SR-2MeGc6SCM1UORJYyarSWY_A-JaOMFDj7ReSs9tRM,1083
105
- spacr-0.9.1.dist-info/METADATA,sha256=Lh02YCW7LiHlegDZDIIascNP_XrFD421ewydkNNjjSY,6208
106
- spacr-0.9.1.dist-info/WHEEL,sha256=R0nc6qTxuoLk7ShA2_Y-UWkN8ZdfDBG2B6Eqpz2WXbs,91
107
- spacr-0.9.1.dist-info/entry_points.txt,sha256=BMC0ql9aNNpv8lUZ8sgDLQMsqaVnX5L535gEhKUP5ho,296
108
- spacr-0.9.1.dist-info/top_level.txt,sha256=GJPU8FgwRXGzKeut6JopsSRY2R8T3i9lDgya42tLInY,6
109
- spacr-0.9.1.dist-info/RECORD,,
106
+ spacr-0.9.3.dist-info/LICENSE,sha256=t0Pov6pnK8thLteoF4xZGmdCwe5mhNwl3OXxLYTGD9U,1081
107
+ spacr-0.9.3.dist-info/METADATA,sha256=67z09Wghste6IUXWFchysvsqH38M6GVnr9IgHYixVNg,10088
108
+ spacr-0.9.3.dist-info/WHEEL,sha256=R0nc6qTxuoLk7ShA2_Y-UWkN8ZdfDBG2B6Eqpz2WXbs,91
109
+ spacr-0.9.3.dist-info/entry_points.txt,sha256=BMC0ql9aNNpv8lUZ8sgDLQMsqaVnX5L535gEhKUP5ho,296
110
+ spacr-0.9.3.dist-info/top_level.txt,sha256=GJPU8FgwRXGzKeut6JopsSRY2R8T3i9lDgya42tLInY,6
111
+ spacr-0.9.3.dist-info/RECORD,,
@@ -1,144 +0,0 @@
1
- Metadata-Version: 2.1
2
- Name: spacr
3
- Version: 0.9.1
4
- Summary: Spatial phenotype analysis of crisp screens (SpaCr)
5
- Home-page: https://github.com/EinarOlafsson/spacr
6
- Author: Einar Birnir Olafsson
7
- Author-email: olafsson@med.umich.com
8
- Classifier: Programming Language :: Python :: 3
9
- Classifier: License :: OSI Approved :: MIT License
10
- Classifier: Operating System :: OS Independent
11
- Description-Content-Type: text/x-rst
12
- License-File: LICENSE
13
- Requires-Dist: numpy <2.0,>=1.26.4
14
- Requires-Dist: pandas <3.0,>=2.2.1
15
- Requires-Dist: scipy <2.0,>=1.12.0
16
- Requires-Dist: cellpose <4.0,>=3.0.6
17
- Requires-Dist: scikit-image <1.0,>=0.22.0
18
- Requires-Dist: scikit-learn <2.0,>=1.4.1
19
- Requires-Dist: scikit-posthocs <0.20,>=0.10.0
20
- Requires-Dist: mahotas <2.0,>=1.4.13
21
- Requires-Dist: btrack <1.0,>=0.6.5
22
- Requires-Dist: trackpy <1.0,>=0.6.2
23
- Requires-Dist: statsmodels <1.0,>=0.14.1
24
- Requires-Dist: shap <1.0,>=0.45.0
25
- Requires-Dist: torch <3.0,>=2.0
26
- Requires-Dist: torchvision <1.0,>=0.1
27
- Requires-Dist: torch-geometric <3.0,>=2.5
28
- Requires-Dist: torchcam <1.0,>=0.4.0
29
- Requires-Dist: transformers <5.0,>=4.45.2
30
- Requires-Dist: segmentation-models-pytorch >=0.3.3
31
- Requires-Dist: monai >=1.3.0
32
- Requires-Dist: captum <1.0,>=0.7.0
33
- Requires-Dist: seaborn <1.0,>=0.13.2
34
- Requires-Dist: matplotlib <4.0,>=3.8.3
35
- Requires-Dist: matplotlib-venn <2.0,>=1.1
36
- Requires-Dist: adjustText <2.0,>=1.2.0
37
- Requires-Dist: bottleneck <2.0,>=1.3.6
38
- Requires-Dist: numexpr <3.0,>=2.8.4
39
- Requires-Dist: opencv-python-headless <5.0,>=4.9.0.80
40
- Requires-Dist: pillow <11.0,>=10.2.0
41
- Requires-Dist: tifffile >=2023.4.12
42
- Requires-Dist: nd2reader <4.0,>=3.3.0
43
- Requires-Dist: czifile
44
- Requires-Dist: pylibCZIrw <6.0,>=5.0.0
45
- Requires-Dist: aicspylibczi
46
- Requires-Dist: readlif
47
- Requires-Dist: imageio <3.0,>=2.34.0
48
- Requires-Dist: pingouin <1.0,>=0.5.5
49
- Requires-Dist: umap-learn <1.0,>=0.5.6
50
- Requires-Dist: ttkthemes <4.0,>=3.2.2
51
- Requires-Dist: xgboost <3.0,>=2.0.3
52
- Requires-Dist: PyWavelets <2.0,>=1.6.0
53
- Requires-Dist: ttf-opensans >=2020.10.30
54
- Requires-Dist: customtkinter <6.0,>=5.2.2
55
- Requires-Dist: biopython <2.0,>=1.80
56
- Requires-Dist: lxml <6.0,>=5.1.0
57
- Requires-Dist: psutil <6.0,>=5.9.8
58
- Requires-Dist: gputil <2.0,>=1.4.0
59
- Requires-Dist: gpustat <2.0,>=1.1.1
60
- Requires-Dist: pyautogui <1.0,>=0.9.54
61
- Requires-Dist: tables <4.0,>=3.8.0
62
- Requires-Dist: rapidfuzz <4.0,>=3.9
63
- Requires-Dist: keyring <16.0,>=15.1
64
- Requires-Dist: screeninfo <1.0,>=0.8.1
65
- Requires-Dist: fastremap >=1.14.1
66
- Requires-Dist: pytz >=2023.3.post1
67
- Requires-Dist: tqdm >=4.65.0
68
- Requires-Dist: wandb >=0.16.2
69
- Requires-Dist: openai <2.0,>=1.50.2
70
- Requires-Dist: gdown
71
- Requires-Dist: IPython <9.0,>=8.18.1
72
- Requires-Dist: ipykernel
73
- Requires-Dist: ipywidgets <9.0,>=8.1.2
74
- Requires-Dist: brokenaxes <1.0,>=0.6.2
75
- Requires-Dist: huggingface-hub <0.25,>=0.24.0
76
- Provides-Extra: dev
77
- Requires-Dist: pytest <3.11,>=3.9 ; extra == 'dev'
78
- Provides-Extra: full
79
- Requires-Dist: opencv-python ; extra == 'full'
80
- Provides-Extra: headless
81
- Requires-Dist: opencv-python-headless ; extra == 'headless'
82
-
83
- .. |Documentation Status| image:: https://readthedocs.org/projects/spacr/badge/?version=latest
84
- :target: https://einarolafsson.github.io/spacr
85
- .. |PyPI version| image:: https://badge.fury.io/py/spacr.svg
86
- :target: https://badge.fury.io/py/spacr
87
- .. |Python version| image:: https://img.shields.io/pypi/pyversions/spacr
88
- :target: https://pypistats.org/packages/spacr
89
- .. |Licence: GPL v3| image:: https://img.shields.io/github/license/EinarOlafsson/spacr
90
- :target: https://github.com/EinarOlafsson/spacr/blob/master/LICENSE
91
- .. |repo size| image:: https://img.shields.io/github/repo-size/EinarOlafsson/spacr
92
- :target: https://github.com/EinarOlafsson/spacr/
93
-
94
- |Documentation Status| |PyPI version| |Python version| |Licence: GPL v3| |repo size|
95
-
96
- SpaCr
97
- =====
98
-
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.
100
-
101
- Features
102
- --------
103
-
104
- - **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
-
108
- - **Crop Images:** Save objects (cells, nuclei, pathogen, cytoplasm) as images. Object image paths are saved in a SQL database.
109
-
110
- - **Train CNNs or Transformers:** Train Torch models to classify single object images.
111
-
112
- - **Manual Annotation:** Supports manual annotation of single-cell images and segmentation to refine training datasets for training CNNs/Transformers or cellpose, respectively.
113
-
114
- - **Finetune Cellpose Models:** Adjust pre-existing Cellpose models to your specific dataset for improved performance.
115
-
116
- - **Timelapse Data Support:** Track objects in timelapse image data.
117
-
118
- - **Simulations:** Simulate spatial phenotype screens.
119
-
120
- - **Sequencing:** Map FASTQ reads to barcode and gRNA barcode metadata.
121
-
122
- - **Misc:** Analyze Ca oscillation, recruitment, infection rate, plaque size/count.
123
-
124
- Installation
125
- ------------
126
-
127
- If using Windows, switch to Linux—it's free, open-source, and better.
128
-
129
- Before installing SpaCr on OSX ensure OpenMP is installed::
130
-
131
- brew install libomp
132
- brew install hdf5
133
-
134
- SpaCr GUI requires Tkinter. On Linux, ensure Tkinter is installed. (Tkinter is included with the standard Python installation on macOS and Windows)::
135
-
136
- sudo apt-get install python3-tk
137
-
138
- Install SpaCr with pip::
139
-
140
- pip install spacr
141
-
142
- Run SpaCr GUI::
143
-
144
- spacr
File without changes