spacr 0.4.60__py3-none-any.whl → 0.5.0__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.
Files changed (47) hide show
  1. spacr/__init__.py +2 -2
  2. spacr/core.py +0 -1
  3. spacr/gui.py +0 -1
  4. spacr/gui_utils.py +5 -2
  5. spacr/io.py +168 -178
  6. spacr/resources/MEDIAR/__pycache__/SetupDict.cpython-39.pyc +0 -0
  7. spacr/resources/MEDIAR/__pycache__/evaluate.cpython-39.pyc +0 -0
  8. spacr/resources/MEDIAR/__pycache__/generate_mapping.cpython-39.pyc +0 -0
  9. spacr/resources/MEDIAR/__pycache__/main.cpython-39.pyc +0 -0
  10. spacr/resources/MEDIAR/core/Baseline/__pycache__/Predictor.cpython-39.pyc +0 -0
  11. spacr/resources/MEDIAR/core/Baseline/__pycache__/Trainer.cpython-39.pyc +0 -0
  12. spacr/resources/MEDIAR/core/Baseline/__pycache__/__init__.cpython-39.pyc +0 -0
  13. spacr/resources/MEDIAR/core/Baseline/__pycache__/utils.cpython-39.pyc +0 -0
  14. spacr/resources/MEDIAR/core/MEDIAR/__pycache__/EnsemblePredictor.cpython-39.pyc +0 -0
  15. spacr/resources/MEDIAR/core/MEDIAR/__pycache__/Predictor.cpython-39.pyc +0 -0
  16. spacr/resources/MEDIAR/core/MEDIAR/__pycache__/Trainer.cpython-39.pyc +0 -0
  17. spacr/resources/MEDIAR/core/MEDIAR/__pycache__/__init__.cpython-39.pyc +0 -0
  18. spacr/resources/MEDIAR/core/MEDIAR/__pycache__/utils.cpython-39.pyc +0 -0
  19. spacr/resources/MEDIAR/core/__pycache__/BasePredictor.cpython-39.pyc +0 -0
  20. spacr/resources/MEDIAR/core/__pycache__/BaseTrainer.cpython-39.pyc +0 -0
  21. spacr/resources/MEDIAR/core/__pycache__/__init__.cpython-39.pyc +0 -0
  22. spacr/resources/MEDIAR/core/__pycache__/utils.cpython-39.pyc +0 -0
  23. spacr/resources/MEDIAR/train_tools/__pycache__/__init__.cpython-39.pyc +0 -0
  24. spacr/resources/MEDIAR/train_tools/__pycache__/measures.cpython-39.pyc +0 -0
  25. spacr/resources/MEDIAR/train_tools/__pycache__/utils.cpython-39.pyc +0 -0
  26. spacr/resources/MEDIAR/train_tools/data_utils/__pycache__/__init__.cpython-39.pyc +0 -0
  27. spacr/resources/MEDIAR/train_tools/data_utils/__pycache__/datasetter.cpython-39.pyc +0 -0
  28. spacr/resources/MEDIAR/train_tools/data_utils/__pycache__/transforms.cpython-39.pyc +0 -0
  29. spacr/resources/MEDIAR/train_tools/data_utils/__pycache__/utils.cpython-39.pyc +0 -0
  30. spacr/resources/MEDIAR/train_tools/data_utils/custom/__pycache__/CellAware.cpython-39.pyc +0 -0
  31. spacr/resources/MEDIAR/train_tools/data_utils/custom/__pycache__/LoadImage.cpython-39.pyc +0 -0
  32. spacr/resources/MEDIAR/train_tools/data_utils/custom/__pycache__/NormalizeImage.cpython-39.pyc +0 -0
  33. spacr/resources/MEDIAR/train_tools/data_utils/custom/__pycache__/__init__.cpython-39.pyc +0 -0
  34. spacr/resources/MEDIAR/train_tools/models/__pycache__/MEDIARFormer.cpython-39.pyc +0 -0
  35. spacr/resources/MEDIAR/train_tools/models/__pycache__/__init__.cpython-39.pyc +0 -0
  36. spacr/settings.py +26 -8
  37. spacr/submodules.py +10 -13
  38. spacr/timelapse.py +172 -4
  39. spacr/utils.py +26 -41
  40. {spacr-0.4.60.dist-info → spacr-0.5.0.dist-info}/METADATA +4 -2
  41. {spacr-0.4.60.dist-info → spacr-0.5.0.dist-info}/RECORD +46 -17
  42. spacr/stats.py +0 -221
  43. /spacr/{cellpose.py → spacr_cellpose.py} +0 -0
  44. {spacr-0.4.60.dist-info → spacr-0.5.0.dist-info}/LICENSE +0 -0
  45. {spacr-0.4.60.dist-info → spacr-0.5.0.dist-info}/WHEEL +0 -0
  46. {spacr-0.4.60.dist-info → spacr-0.5.0.dist-info}/entry_points.txt +0 -0
  47. {spacr-0.4.60.dist-info → spacr-0.5.0.dist-info}/top_level.txt +0 -0
spacr/settings.py CHANGED
@@ -95,14 +95,12 @@ def set_default_settings_preprocess_generate_masks(settings={}):
95
95
 
96
96
  # Misc settings
97
97
  settings.setdefault('all_to_mip', False)
98
- settings.setdefault('pick_slice', False)
99
- settings.setdefault('skip_mode', '01')
100
98
  settings.setdefault('upscale', False)
101
99
  settings.setdefault('upscale_factor', 2.0)
102
100
  settings.setdefault('adjust_cells', False)
103
101
  return settings
104
102
 
105
- def set_default_settings_preprocess_img_data(settings):
103
+ def set_default_settings_preprocess_img_data_v1(settings):
106
104
 
107
105
  metadata_type = settings.setdefault('metadata_type', 'cellvoyager')
108
106
  custom_regex = settings.setdefault('custom_regex', None)
@@ -127,6 +125,27 @@ def set_default_settings_preprocess_img_data(settings):
127
125
 
128
126
  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
129
127
 
128
+ def set_default_settings_preprocess_img_data(settings):
129
+
130
+ settings.setdefault('metadata_type', 'cellvoyager')
131
+ settings.setdefault('custom_regex', None)
132
+ settings.setdefault('nr', 1)
133
+ settings.setdefault('plot', True)
134
+ settings.setdefault('batch_size', 50)
135
+ settings.setdefault('timelapse', False)
136
+ settings.setdefault('lower_percentile', 2)
137
+ settings.setdefault('randomize', True)
138
+ settings.setdefault('all_to_mip', False)
139
+ settings.setdefault('cmap', 'inferno')
140
+ settings.setdefault('figuresize', 10)
141
+ settings.setdefault('normalize', True)
142
+ settings.setdefault('save_dtype', 'uint16')
143
+ settings.setdefault('test_mode', False)
144
+ settings.setdefault('test_images', 10)
145
+ settings.setdefault('random_test', True)
146
+ settings.setdefault('fps', 2)
147
+ return settings
148
+
130
149
  def _get_object_settings(object_type, settings):
131
150
 
132
151
  from .utils import _get_diam
@@ -278,7 +297,7 @@ def get_measure_crop_settings(settings={}):
278
297
 
279
298
  # Timelapsed settings
280
299
  settings.setdefault('timelapse', False)
281
- settings.setdefault('timelapse_objects', 'cell')
300
+ settings.setdefault('timelapse_objects', ['cell'])
282
301
 
283
302
  # Operational settings
284
303
  settings.setdefault('plot',False)
@@ -715,7 +734,7 @@ expected_types = {
715
734
  #"timelapse_frame_limits": (list, type(None)), # This can be a list of lists
716
735
  "timelapse_remove_transient": bool,
717
736
  "timelapse_mode": str,
718
- "timelapse_objects": list,
737
+ "timelapse_objects": (list, type(None)),
719
738
  "fps": int,
720
739
  "remove_background": bool,
721
740
  "lower_percentile": (int, float),
@@ -1005,7 +1024,7 @@ categories = {"Paths":[ "src", "grna", "barcodes", "custom_model_path", "dataset
1005
1024
  "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"],
1006
1025
  "Timelapse": ["timelapse", "fps", "timelapse_displacement", "timelapse_memory", "timelapse_frame_limits", "timelapse_remove_transient", "timelapse_mode", "timelapse_objects", "compartments"],
1007
1026
  "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"],
1008
- "Beta": ["all_to_mip", "pick_slice", "skip_mode", "upscale", "upscale_factor", "consolidate"]
1027
+ "Beta": ["all_to_mip", "upscale", "upscale_factor", "consolidate"]
1009
1028
  }
1010
1029
 
1011
1030
 
@@ -1033,7 +1052,7 @@ def check_settings(vars_dict, expected_types, q=None):
1033
1052
  expected_type = expected_types.get(key, str)
1034
1053
 
1035
1054
  try:
1036
- if key in ["cell_plate_metadata", "timelapse_frame_limits", "png_size", "png_dims", "pathogen_plate_metadata", "treatment_plate_metadata", "class_metadata", "crop_mode"]:
1055
+ if key in ["cell_plate_metadata", "timelapse_frame_limits", "png_size", "png_dims", "pathogen_plate_metadata", "treatment_plate_metadata", "timelapse_objects", "class_metadata", "crop_mode"]:
1037
1056
  if value is None:
1038
1057
  parsed_value = None
1039
1058
  else:
@@ -1274,7 +1293,6 @@ def generate_fields(variables, scrollable_frame):
1274
1293
  "pc": "(str) - Positive control identifier.",
1275
1294
  "pc_loc": "(str) - Location of the positive control in the images.",
1276
1295
  "percentiles": "(list) - Percentiles to use for normalizing the images.",
1277
- "pick_slice": "(bool) - Whether to pick a single slice from the z-stack images. If False, the maximum intensity projection will be used.",
1278
1296
  "pin_memory": "(bool) - Whether to pin memory for the data loader.",
1279
1297
  "plate": "(str) - Plate identifier for the experiment.",
1280
1298
  "plate_dict": "(dict) - Dictionary of plate metadata.",
spacr/submodules.py CHANGED
@@ -28,10 +28,7 @@ from skimage.measure import regionprops, label as sklabel
28
28
  import matplotlib.pyplot as plt
29
29
  from natsort import natsorted
30
30
 
31
- import torch
32
31
  from torch.utils.data import Dataset
33
- from spacr.settings import get_train_cellpose_default_settings
34
- from spacr.utils import save_settings, invert_image
35
32
 
36
33
  class CellposeLazyDataset(Dataset):
37
34
  def __init__(self, image_files, label_files, settings, randomize=True, augment=False):
@@ -91,8 +88,8 @@ class CellposeLazyDataset(Dataset):
91
88
 
92
89
  def train_cellpose(settings):
93
90
 
94
- from spacr.settings import get_train_cellpose_default_settings
95
- from spacr.utils import save_settings
91
+ from .settings import get_train_cellpose_default_settings
92
+ from .utils import save_settings
96
93
 
97
94
  settings = get_train_cellpose_default_settings(settings)
98
95
  img_src = os.path.join(settings['src'], 'train', 'images')
@@ -161,11 +158,11 @@ def train_cellpose(settings):
161
158
 
162
159
  def test_cellpose_model(settings):
163
160
 
164
- from spacr.utils import save_settings, print_progress
161
+ from .utils import save_settings, print_progress
165
162
  from .settings import get_default_test_cellpose_model_settings
166
163
 
167
164
  def plot_cellpose_resilts(i, j, results_dir, img, lbl, pred, flow):
168
- from spacr. plot import generate_mask_random_cmap
165
+ from . plot import generate_mask_random_cmap
169
166
  fig, axs = plt.subplots(1, 5, figsize=(16, 4), gridspec_kw={'wspace': 0.1, 'hspace': 0.1})
170
167
  cmap_lbl = generate_mask_random_cmap(lbl)
171
168
  cmap_pred = generate_mask_random_cmap(pred)
@@ -348,7 +345,7 @@ def test_cellpose_model(settings):
348
345
  def apply_cellpose_model(settings):
349
346
 
350
347
  from .settings import get_default_apply_cellpose_model_settings
351
- from spacr.utils import save_settings, print_progress
348
+ from .utils import save_settings, print_progress
352
349
 
353
350
  def plot_cellpose_result(i, j, results_dir, img, pred, flow):
354
351
 
@@ -466,7 +463,7 @@ def apply_cellpose_model(settings):
466
463
  print("Saved object count and average area to summary.csv")
467
464
 
468
465
  def plot_cellpose_batch(images, labels):
469
- from spacr.plot import generate_mask_random_cmap
466
+ from .plot import generate_mask_random_cmap
470
467
 
471
468
  cmap_lbl = generate_mask_random_cmap(labels)
472
469
  batch_size = len(images)
@@ -481,8 +478,8 @@ def plot_cellpose_batch(images, labels):
481
478
  plt.show()
482
479
 
483
480
  def analyze_percent_positive(settings):
484
- from spacr.io import _read_and_merge_data
485
- from spacr.utils import save_settings
481
+ from .io import _read_and_merge_data
482
+ from .utils import save_settings
486
483
  from .settings import default_settings_analyze_percent_positive
487
484
 
488
485
  settings = default_settings_analyze_percent_positive(settings)
@@ -681,7 +678,7 @@ def analyze_recruitment(settings):
681
678
 
682
679
  def analyze_plaques(settings):
683
680
 
684
- from .cellpose import identify_masks_finetune
681
+ from .spacr_cellpose import identify_masks_finetune
685
682
  from .settings import get_analyze_plaque_settings
686
683
  from .utils import save_settings, download_models
687
684
  from spacr import __file__ as spacr_path
@@ -1020,7 +1017,7 @@ def interperate_vision_model(settings={}):
1020
1017
  else:
1021
1018
  return None
1022
1019
 
1023
- from spacr.plot import spacrGraph
1020
+ from .plot import spacrGraph
1024
1021
 
1025
1022
  df[name] = df['feature'].apply(lambda x: find_feature_class(x, feature_groups))
1026
1023
 
spacr/timelapse.py CHANGED
@@ -7,9 +7,9 @@ from IPython.display import display
7
7
  from IPython.display import Image as ipyimage
8
8
  import trackpy as tp
9
9
  from btrack import datasets as btrack_datasets
10
- from skimage.measure import regionprops
10
+ from skimage.measure import regionprops, regionprops_table
11
11
  from scipy.signal import find_peaks
12
- from scipy.optimize import curve_fit
12
+ from scipy.optimize import curve_fit, linear_sum_assignment
13
13
  from scipy.integrate import trapz
14
14
  import matplotlib.pyplot as plt
15
15
 
@@ -255,7 +255,7 @@ def _relabel_masks_based_on_tracks(masks, tracks, mode='btrack'):
255
255
 
256
256
  return relabeled_masks
257
257
 
258
- def _prepare_for_tracking(mask_array):
258
+ def _prepare_for_tracking_v1(mask_array):
259
259
  """
260
260
  Prepare the mask array for object tracking.
261
261
 
@@ -286,6 +286,87 @@ def _prepare_for_tracking(mask_array):
286
286
  })
287
287
  return pd.DataFrame(frames)
288
288
 
289
+ def _prepare_for_tracking(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
+
309
+ def _track_by_iou(masks, iou_threshold=0.1):
310
+ """
311
+ Build a track table by linking masks frame→frame via IoU.
312
+ Returns a DataFrame with columns [frame, original_label, track_id].
313
+ """
314
+ n_frames = masks.shape[0]
315
+ # 1) initialize: every label in frame 0 starts its own track
316
+ labels0 = np.unique(masks[0])[1:]
317
+ next_track = 1
318
+ track_map = {} # (frame,label) -> track_id
319
+ for L in labels0:
320
+ track_map[(0, L)] = next_track
321
+ next_track += 1
322
+
323
+ # 2) iterate through frames
324
+ for t in range(1, n_frames):
325
+ prev, curr = masks[t-1], masks[t]
326
+ matches = link_by_iou(prev, curr, iou_threshold=iou_threshold)
327
+ used_curr = set()
328
+ # a) assign matched labels to existing tracks
329
+ for L_prev, L_curr in matches:
330
+ tid = track_map[(t-1, L_prev)]
331
+ track_map[(t, L_curr)] = tid
332
+ used_curr.add(L_curr)
333
+ # b) any label in curr not matched → new track
334
+ for L in np.unique(curr)[1:]:
335
+ if L not in used_curr:
336
+ track_map[(t, L)] = next_track
337
+ next_track += 1
338
+
339
+ # 3) flatten into DataFrame
340
+ records = []
341
+ for (frame, label), tid in track_map.items():
342
+ records.append({'frame': frame, 'original_label': label, 'track_id': tid})
343
+ return pd.DataFrame(records)
344
+
345
+ def link_by_iou(mask_prev, mask_next, iou_threshold=0.1):
346
+ # Get labels
347
+ labels_prev = np.unique(mask_prev)[1:]
348
+ labels_next = np.unique(mask_next)[1:]
349
+ # Precompute masks as boolean
350
+ bool_prev = {L: mask_prev==L for L in labels_prev}
351
+ bool_next = {L: mask_next==L for L in labels_next}
352
+ # Cost matrix = 1 - IoU
353
+ cost = np.ones((len(labels_prev), len(labels_next)), dtype=float)
354
+ for i, L1 in enumerate(labels_prev):
355
+ m1 = bool_prev[L1]
356
+ for j, L2 in enumerate(labels_next):
357
+ m2 = bool_next[L2]
358
+ inter = np.logical_and(m1, m2).sum()
359
+ union = np.logical_or(m1, m2).sum()
360
+ if union > 0:
361
+ cost[i, j] = 1 - inter/union
362
+ # Solve assignment
363
+ row_ind, col_ind = linear_sum_assignment(cost)
364
+ matches = []
365
+ for i, j in zip(row_ind, col_ind):
366
+ if cost[i,j] <= 1 - iou_threshold:
367
+ matches.append((labels_prev[i], labels_next[j]))
368
+ return matches
369
+
289
370
  def _find_optimal_search_range(features, initial_search_range=500, increment=10, max_attempts=49, memory=3):
290
371
  """
291
372
  Find the optimal search range for linking features.
@@ -336,7 +417,94 @@ def _remove_objects_from_first_frame(masks, percentage=10):
336
417
  masks[0][first_frame == label] = 0
337
418
  return masks
338
419
 
339
- def _facilitate_trackin_with_adaptive_removal(masks, search_range=500, max_attempts=100, memory=3):
420
+ def _track_by_iou(masks, iou_threshold=0.1):
421
+ """
422
+ Build a track table by linking masks frame→frame via IoU.
423
+ Returns a DataFrame with columns [frame, original_label, track_id].
424
+ """
425
+ n_frames = masks.shape[0]
426
+ # 1) initialize: every label in frame 0 starts its own track
427
+ labels0 = np.unique(masks[0])[1:]
428
+ next_track = 1
429
+ track_map = {} # (frame,label) -> track_id
430
+ for L in labels0:
431
+ track_map[(0, L)] = next_track
432
+ next_track += 1
433
+
434
+ # 2) iterate through frames
435
+ for t in range(1, n_frames):
436
+ prev, curr = masks[t-1], masks[t]
437
+ matches = link_by_iou(prev, curr, iou_threshold=iou_threshold)
438
+ used_curr = set()
439
+ # a) assign matched labels to existing tracks
440
+ for L_prev, L_curr in matches:
441
+ tid = track_map[(t-1, L_prev)]
442
+ track_map[(t, L_curr)] = tid
443
+ used_curr.add(L_curr)
444
+ # b) any label in curr not matched → new track
445
+ for L in np.unique(curr)[1:]:
446
+ if L not in used_curr:
447
+ track_map[(t, L)] = next_track
448
+ next_track += 1
449
+
450
+ # 3) flatten into DataFrame
451
+ records = []
452
+ for (frame, label), tid in track_map.items():
453
+ records.append({'frame': frame, 'original_label': label, 'track_id': tid})
454
+ return pd.DataFrame(records)
455
+
456
+
457
+ def _facilitate_trackin_with_adaptive_removal(masks, search_range=None, max_attempts=5, memory=3, min_mass=50, track_by_iou=False):
458
+ """
459
+ Facilitates object tracking with deterministic initial filtering and
460
+ trackpy’s constant-velocity prediction.
461
+
462
+ Args:
463
+ masks (np.ndarray): integer‐labeled masks (frames × H × W).
464
+ search_range (int|None): max displacement; if None, auto‐computed.
465
+ max_attempts (int): how many times to retry with smaller search_range.
466
+ memory (int): trackpy memory parameter.
467
+ min_mass (float): drop any object in frame 0 with area < min_mass.
468
+
469
+ Returns:
470
+ masks, features_df, tracks_df
471
+
472
+ Raises:
473
+ RuntimeError if linking fails after max_attempts.
474
+ """
475
+ # 1) initial features & filter frame 0 by area
476
+ features = _prepare_for_tracking(masks)
477
+ f0 = features[features['frame'] == 0]
478
+ valid = f0.loc[f0['mass'] >= min_mass, 'original_label'].unique()
479
+ masks[0] = np.where(np.isin(masks[0], valid), masks[0], 0)
480
+
481
+ # 2) recompute features on filtered masks
482
+ features = _prepare_for_tracking(masks)
483
+
484
+ # 3) default search_range = 2×sqrt(99th‑pct area)
485
+ if search_range is None:
486
+ a99 = f0['mass'].quantile(0.99)
487
+ search_range = max(1, int(2 * np.sqrt(a99)))
488
+
489
+ # 4) attempt linking, shrinking search_range on failure
490
+ for attempt in range(1, max_attempts + 1):
491
+ try:
492
+ if track_by_iou:
493
+ tracks_df = _track_by_iou(masks, iou_threshold=0.1)
494
+ else:
495
+ tracks_df = tp.link_df(features,search_range=search_range, memory=memory, predict=True)
496
+ print(f"Linked on attempt {attempt} with search_range={search_range}")
497
+ return masks, features, tracks_df
498
+
499
+ except Exception as e:
500
+ search_range = max(1, int(search_range * 0.8))
501
+ print(f"Attempt {attempt} failed ({e}); reducing search_range to {search_range}")
502
+
503
+ raise RuntimeError(
504
+ f"Failed to track after {max_attempts} attempts; last search_range={search_range}"
505
+ )
506
+
507
+ def _facilitate_trackin_with_adaptive_removal_v1(masks, search_range=500, max_attempts=100, memory=3):
340
508
  """
341
509
  Facilitates object tracking with adaptive removal.
342
510
 
spacr/utils.py CHANGED
@@ -1,5 +1,4 @@
1
1
  import os, re, sqlite3, torch, torchvision, random, string, shutil, cv2, tarfile, glob, psutil, platform, gzip, subprocess, time, requests, ast, traceback
2
-
3
2
  import numpy as np
4
3
  import pandas as pd
5
4
  from cellpose import models as cp_models
@@ -554,7 +553,7 @@ def _get_cellpose_batch_size():
554
553
  except Exception as e:
555
554
  return 8
556
555
 
557
- def _extract_filename_metadata(filenames, src, regular_expression, metadata_type='cellvoyager', pick_slice=False, skip_mode='01'):
556
+ def _extract_filename_metadata(filenames, src, regular_expression, metadata_type='cellvoyager'):
558
557
 
559
558
  images_by_key = defaultdict(list)
560
559
 
@@ -568,33 +567,38 @@ def _extract_filename_metadata(filenames, src, regular_expression, metadata_type
568
567
  plate = os.path.basename(src)
569
568
 
570
569
  well = match.group('wellID')
571
- field = match.group('fieldID')
572
- channel = match.group('chanID')
573
- mode = None
574
-
575
570
  if well[0].isdigit():
576
571
  well = str(_safe_int_convert(well))
572
+
573
+ field = match.group('fieldID')
577
574
  if field[0].isdigit():
578
575
  field = str(_safe_int_convert(field))
576
+
577
+ channel = match.group('chanID')
579
578
  if channel[0].isdigit():
580
579
  channel = str(_safe_int_convert(channel))
581
-
580
+
581
+ if 'timeID' in match.groupdict():
582
+ timeID = match.group('timeID')
583
+ if timeID[0].isdigit():
584
+ timeID = str(_safe_int_convert(timeID))
585
+ else:
586
+ timeID = None
587
+
588
+ if 'sliceID' in match.groupdict():
589
+ sliceID = match.group('sliceID')
590
+ if sliceID[0].isdigit():
591
+ sliceID = str(_safe_int_convert(sliceID))
592
+ else:
593
+ sliceID = None
594
+
582
595
  if metadata_type =='cq1':
583
596
  orig_wellID = wellID
584
597
  wellID = _convert_cq1_well_id(wellID)
585
598
  print(f'Converted Well ID: {orig_wellID} to {wellID}', end='\r', flush=True)
586
599
 
587
- if pick_slice:
588
- try:
589
- mode = match.group('AID')
590
- except IndexError:
591
- sliceid = '00'
592
-
593
- if mode == skip_mode:
594
- continue
595
-
596
- key = (plate, well, field, channel, mode)
597
- file_path = os.path.join(src, filename) # Store the full path
600
+ key = (plate, well, field, channel, timeID, sliceID)
601
+ file_path = os.path.join(src, filename)
598
602
  images_by_key[key].append(file_path)
599
603
 
600
604
  except IndexError:
@@ -3279,15 +3283,6 @@ class SaliencyMapGenerator:
3279
3283
  return fig
3280
3284
 
3281
3285
  def percentile_normalize(self, img, lower_percentile=2, upper_percentile=98):
3282
- """
3283
- Normalize each channel of the image to the given percentiles.
3284
- Args:
3285
- img: Input image as numpy array with shape (H, W, C)
3286
- lower_percentile: Lower percentile for normalization (default 2)
3287
- upper_percentile: Upper percentile for normalization (default 98)
3288
- Returns:
3289
- img: Normalized image
3290
- """
3291
3286
  img_normalized = np.zeros_like(img)
3292
3287
 
3293
3288
  for c in range(img.shape[2]): # Iterate over each channel
@@ -3297,7 +3292,6 @@ class SaliencyMapGenerator:
3297
3292
 
3298
3293
  return img_normalized
3299
3294
 
3300
-
3301
3295
  class GradCAMGenerator:
3302
3296
  def __init__(self, model, target_layer, cam_type='gradcam'):
3303
3297
  self.model = model
@@ -3402,15 +3396,6 @@ class GradCAMGenerator:
3402
3396
  return fig
3403
3397
 
3404
3398
  def percentile_normalize(self, img, lower_percentile=2, upper_percentile=98):
3405
- """
3406
- Normalize each channel of the image to the given percentiles.
3407
- Args:
3408
- img: Input image as numpy array with shape (H, W, C)
3409
- lower_percentile: Lower percentile for normalization (default 2)
3410
- upper_percentile: Upper percentile for normalization (default 98)
3411
- Returns:
3412
- img: Normalized image
3413
- """
3414
3399
  img_normalized = np.zeros_like(img)
3415
3400
 
3416
3401
  for c in range(img.shape[2]): # Iterate over each channel
@@ -4698,10 +4683,10 @@ def get_ml_results_paths(src, model_type='xgboost', channel_of_interest=1):
4698
4683
  elif isinstance(channel_of_interest, int):
4699
4684
  feature_string = f"channel_{channel_of_interest}"
4700
4685
 
4701
- elif channel_of_interest is 'morphology':
4686
+ elif channel_of_interest == 'morphology':
4702
4687
  feature_string = 'morphology'
4703
4688
 
4704
- elif channel_of_interest is None:
4689
+ elif channel_of_interest == None:
4705
4690
  feature_string = 'all_features'
4706
4691
  else:
4707
4692
  raise ValueError(f"Unsupported channel_of_interest: {channel_of_interest}. Supported values are 'int', 'list', 'None', or 'morphology'.")
@@ -5115,9 +5100,9 @@ def correct_metadata_column_names(df):
5115
5100
 
5116
5101
  def control_filelist(folder, mode='columnID', values=['01','02']):
5117
5102
  files = os.listdir(folder)
5118
- if mode is 'columnID':
5103
+ if mode == 'columnID':
5119
5104
  filtered_files = [file for file in files if file.split('_')[1][1:] in values]
5120
- if mode is 'rowID':
5105
+ if mode == 'rowID':
5121
5106
  filtered_files = [file for file in files if file.split('_')[1][:1] in values]
5122
5107
  return filtered_files
5123
5108
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: spacr
3
- Version: 0.4.60
3
+ Version: 0.5.0
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
@@ -41,6 +41,8 @@ Requires-Dist: pillow<11.0,>=10.2.0
41
41
  Requires-Dist: tifffile>=2023.4.12
42
42
  Requires-Dist: nd2reader<4.0,>=3.3.0
43
43
  Requires-Dist: czifile
44
+ Requires-Dist: pylibCZIrw<6.0,>=5.0.0
45
+ Requires-Dist: aicspylibczi
44
46
  Requires-Dist: readlif
45
47
  Requires-Dist: imageio<3.0,>=2.34.0
46
48
  Requires-Dist: pingouin<1.0,>=0.5.5
@@ -79,7 +81,7 @@ Provides-Extra: headless
79
81
  Requires-Dist: opencv-python-headless; extra == "headless"
80
82
 
81
83
  .. |Documentation Status| image:: https://readthedocs.org/projects/spacr/badge/?version=latest
82
- :target: https://spacr.readthedocs.io/en/latest/?badge=latest
84
+ :target: https://einarolafsson.github.io/spacr
83
85
  .. |PyPI version| image:: https://badge.fury.io/py/spacr.svg
84
86
  :target: https://badge.fury.io/py/spacr
85
87
  .. |Python version| image:: https://img.shields.io/pypi/pyversions/spacr