celldetective 1.1.0__py3-none-any.whl → 1.1.1.post1__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 (34) hide show
  1. celldetective/__main__.py +5 -19
  2. celldetective/extra_properties.py +63 -53
  3. celldetective/filters.py +39 -11
  4. celldetective/gui/classifier_widget.py +56 -7
  5. celldetective/gui/control_panel.py +5 -0
  6. celldetective/gui/layouts.py +3 -2
  7. celldetective/gui/measurement_options.py +13 -109
  8. celldetective/gui/plot_signals_ui.py +1 -0
  9. celldetective/gui/process_block.py +1 -1
  10. celldetective/gui/survival_ui.py +7 -1
  11. celldetective/gui/tableUI.py +294 -28
  12. celldetective/gui/thresholds_gui.py +51 -10
  13. celldetective/gui/viewers.py +169 -22
  14. celldetective/io.py +41 -17
  15. celldetective/measure.py +13 -238
  16. celldetective/models/segmentation_effectors/primNK_cfse/config_input.json +29 -0
  17. celldetective/models/segmentation_effectors/primNK_cfse/cp-cfse-transfer +0 -0
  18. celldetective/models/segmentation_effectors/primNK_cfse/training_instructions.json +37 -0
  19. celldetective/neighborhood.py +4 -1
  20. celldetective/preprocessing.py +483 -143
  21. celldetective/scripts/segment_cells.py +26 -7
  22. celldetective/scripts/train_segmentation_model.py +35 -34
  23. celldetective/segmentation.py +29 -20
  24. celldetective/signals.py +13 -231
  25. celldetective/tracking.py +2 -1
  26. celldetective/utils.py +440 -26
  27. {celldetective-1.1.0.dist-info → celldetective-1.1.1.post1.dist-info}/METADATA +1 -1
  28. {celldetective-1.1.0.dist-info → celldetective-1.1.1.post1.dist-info}/RECORD +34 -30
  29. {celldetective-1.1.0.dist-info → celldetective-1.1.1.post1.dist-info}/WHEEL +1 -1
  30. tests/test_preprocessing.py +37 -0
  31. tests/test_utils.py +48 -1
  32. {celldetective-1.1.0.dist-info → celldetective-1.1.1.post1.dist-info}/LICENSE +0 -0
  33. {celldetective-1.1.0.dist-info → celldetective-1.1.1.post1.dist-info}/entry_points.txt +0 -0
  34. {celldetective-1.1.0.dist-info → celldetective-1.1.1.post1.dist-info}/top_level.txt +0 -0
@@ -6,65 +6,69 @@ from tqdm import tqdm
6
6
  import numpy as np
7
7
  import os
8
8
  from celldetective.io import get_config, get_experiment_wells, interpret_wells_and_positions, extract_well_name_and_number, get_positions_in_well, extract_position_name, get_position_movie_path, load_frames, auto_load_number_of_frames
9
- from celldetective.utils import ConfigSectionMap, _extract_channel_indices_from_config, _extract_nbr_channels_from_config, _get_img_num_per_channel
9
+ from celldetective.utils import estimate_unreliable_edge, unpad, mask_edges, ConfigSectionMap, _extract_channel_indices_from_config, _extract_nbr_channels_from_config, _get_img_num_per_channel
10
10
  from celldetective.filters import std_filter, gauss_filter
11
+ from celldetective.segmentation import filter_image, threshold_image
11
12
  from stardist import fill_label_holes
12
13
  from csbdeep.io import save_tiff_imagej_compatible
13
14
  from gc import collect
14
15
  from lmfit import Parameters, Model, models
15
- import matplotlib.pyplot as plt
16
+ import tifffile.tifffile as tiff
16
17
 
17
- def estimate_background_per_condition(experiment, threshold_on_std=1, well_option='*', target_channel="channel_name", frame_range=[0,5], mode="timeseries", show_progress_per_pos=False, show_progress_per_well=True):
18
+ def estimate_background_per_condition(experiment, threshold_on_std=1, well_option='*', target_channel="channel_name", frame_range=[0,5], mode="timeseries", activation_protocol=[['gauss',2],['std',4]], show_progress_per_pos=False, show_progress_per_well=True):
18
19
 
19
20
  """
20
- Estimate the background per condition in an experiment.
21
+ Estimate the background for each condition in an experiment.
21
22
 
22
- This function calculates the background of each well in the given experiment
23
- by analyzing frames from the specified range. It supports two modes: "timeseries"
24
- and "tiles". The function applies Gaussian and standard deviation filters to
25
- identify and mask out high-variance areas, and computes the median background
26
- across positions within each well.
23
+ This function calculates the background for each well within
24
+ a given experiment by processing image frames using a specified activation
25
+ protocol. It supports time-series and tile-based modes for background
26
+ estimation.
27
27
 
28
28
  Parameters
29
29
  ----------
30
- experiment : object
31
- The experiment object containing well and position information.
30
+ experiment : str
31
+ The path to the experiment directory.
32
32
  threshold_on_std : float, optional
33
- The threshold for the standard deviation filter to identify high-variance areas. Defaults to 1.
34
- well_option : str, int, or list of int, optional
35
- The well selection option:
36
- - '*' : Select all wells.
37
- - int : Select a specific well by its index.
38
- - list of int : Select multiple wells by their indices. Defaults to '*'.
39
- target_channel : str
40
- The specific channel to be analyzed.
33
+ The threshold value on the standard deviation for masking (default is 1).
34
+ well_option : str, optional
35
+ The option to select specific wells (default is '*').
36
+ target_channel : str, optional
37
+ The name of the target channel for background estimation (default is "channel_name").
41
38
  frame_range : list of int, optional
42
- The range of frames to be analyzed, specified as [start, end]. Defaults to [0, 5].
43
- mode : {'timeseries', 'tiles'}, optional
44
- The mode of analysis. "timeseries" averages frames before filtering, while "tiles" filters each frame individually. Defaults to "timeseries".
39
+ The range of frames to consider for background estimation (default is [0, 5]).
40
+ mode : str, optional
41
+ The mode of background estimation, either "timeseries" or "tiles" (default is "timeseries").
42
+ activation_protocol : list of list, optional
43
+ The activation protocol consisting of filters and their respective parameters (default is [['gauss', 2], ['std', 4]]).
45
44
  show_progress_per_pos : bool, optional
46
- If True, display a progress bar for position processing. Defaults to False.
45
+ Whether to show progress for each position (default is False).
47
46
  show_progress_per_well : bool, optional
48
- If True, display a progress bar for well processing. Defaults to True.
47
+ Whether to show progress for each well (default is True).
49
48
 
50
49
  Returns
51
50
  -------
52
- backgrounds : list of dict
53
- A list of dictionaries, each containing:
54
- - 'bg' : numpy.ndarray
55
- The computed background for the well.
56
- - 'well' : str
57
- The path to the well.
51
+ list of dict
52
+ A list of dictionaries, each containing the background image (`bg`) and the corresponding well path (`well`).
53
+
54
+ See Also
55
+ --------
56
+ estimate_unreliable_edge : Estimates the unreliable edge value from the activation protocol.
57
+ threshold_image : Thresholds an image based on the specified criteria.
58
+
59
+ Notes
60
+ -----
61
+ This function assumes that the experiment directory structure and the configuration
62
+ files follow a specific format expected by the helper functions used within.
58
63
 
59
64
  Examples
60
65
  --------
61
- >>> experiment = ... # Some experiment object
62
- >>> backgrounds = estimate_background_per_condition(experiment, threshold_on_std=1.5, well_option=[0, 1, 2], target_channel='DAPI', frame_range=[0, 10], mode="tiles")
63
- >>> print(backgrounds[0]['bg']) # The background array for the first well
64
- >>> print(backgrounds[0]['well']) # The path to the first well
66
+ >>> experiment_path = "path/to/experiment"
67
+ >>> backgrounds = estimate_background_per_condition(experiment_path, threshold_on_std=1.5, target_channel="GFP", frame_range=[0, 10], mode="tiles")
68
+ >>> for bg in backgrounds:
69
+ ... print(bg["well"], bg["bg"].shape)
65
70
  """
66
71
 
67
-
68
72
  config = get_config(experiment)
69
73
  wells = get_experiment_wells(experiment)
70
74
  len_movie = float(ConfigSectionMap(config,"MovieSettings")["len_movie"])
@@ -104,12 +108,10 @@ def estimate_background_per_condition(experiment, threshold_on_std=1, well_optio
104
108
  frame_mean = np.nanmean(frames, axis=0)
105
109
 
106
110
  frame = frame_mean.copy().astype(float)
107
- frame = gauss_filter(frame, 2)
108
- std_frame = std_filter(frame, 4)
109
-
110
- mask = std_frame > threshold_on_std
111
- mask = fill_label_holes(mask)
112
- frame[np.where(mask==1)] = np.nan
111
+ std_frame = filter_image(frame.copy(),filters=activation_protocol)
112
+ edge = estimate_unreliable_edge(activation_protocol)
113
+ mask = threshold_image(std_frame, threshold_on_std, 1.0E06, foreground_value=1, edge_exclusion=edge)
114
+ frame[np.where(mask.astype(int)==1)] = np.nan
113
115
 
114
116
  elif mode=="tiles":
115
117
 
@@ -123,12 +125,10 @@ def estimate_background_per_condition(experiment, threshold_on_std=1, well_optio
123
125
  continue
124
126
 
125
127
  f = frames[i].copy()
126
- f = gauss_filter(f, 2)
127
- std_frame = std_filter(f, 4)
128
-
129
- mask = std_frame > threshold_on_std
130
- mask = fill_label_holes(mask)
131
- f[np.where(mask==1)] = np.nan
128
+ std_frame = filter_image(f.copy(),filters=activation_protocol)
129
+ edge = estimate_unreliable_edge(activation_protocol)
130
+ mask = threshold_image(std_frame, threshold_on_std, 1.0E06, foreground_value=1, edge_exclusion=edge)
131
+ f[np.where(mask.astype(int)==1)] = np.nan
132
132
 
133
133
  frames[i,:,:] = f
134
134
 
@@ -161,6 +161,7 @@ def correct_background_model_free(
161
161
  export = False,
162
162
  return_stacks = False,
163
163
  movie_prefix=None,
164
+ activation_protocol=[['gauss',2],['std',4]],
164
165
  export_prefix='Corrected',
165
166
  **kwargs,
166
167
  ):
@@ -244,7 +245,7 @@ def correct_background_model_free(
244
245
  well_name, _ = extract_well_name_and_number(well_path)
245
246
 
246
247
  try:
247
- background = estimate_background_per_condition(experiment, threshold_on_std=threshold_on_std, well_option=int(well_indices[k]), target_channel=target_channel, frame_range=frame_range, mode=mode, show_progress_per_pos=True, show_progress_per_well=False)
248
+ background = estimate_background_per_condition(experiment, threshold_on_std=threshold_on_std, well_option=int(well_indices[k]), target_channel=target_channel, frame_range=frame_range, mode=mode, show_progress_per_pos=True, show_progress_per_well=False, activation_protocol=activation_protocol)
248
249
  background = background[0]
249
250
  background = background['bg']
250
251
  except Exception as e:
@@ -273,6 +274,7 @@ def correct_background_model_free(
273
274
  operation=operation,
274
275
  clip=clip,
275
276
  export=export,
277
+ activation_protocol=activation_protocol,
276
278
  prefix=export_prefix,
277
279
  )
278
280
  print('Correction successful.')
@@ -287,7 +289,7 @@ def correct_background_model_free(
287
289
 
288
290
 
289
291
 
290
- def apply_background_to_stack(stack_path, background, target_channel_index=0, nbr_channels=1, stack_length=45, threshold_on_std=1, optimize_option=True, opt_coef_range=(0.95,1.05), opt_coef_nbr=100, operation='divide', clip=False, export=False, prefix="Corrected"):
292
+ def apply_background_to_stack(stack_path, background, target_channel_index=0, nbr_channels=1, stack_length=45,activation_protocol=[['gauss',2],['std',4]], threshold_on_std=1, optimize_option=True, opt_coef_range=(0.95,1.05), opt_coef_nbr=100, operation='divide', clip=False, export=False, prefix="Corrected"):
291
293
 
292
294
  """
293
295
  Apply background correction to an image stack.
@@ -366,16 +368,19 @@ def apply_background_to_stack(stack_path, background, target_channel_index=0, nb
366
368
  if optimize_option:
367
369
 
368
370
  target_copy = target_img.copy()
369
- f = gauss_filter(target_img.copy(), 2)
370
- std_frame = std_filter(f, 4)
371
- mask = std_frame > threshold_on_std
372
- mask = fill_label_holes(mask)
373
- target_copy[np.where(mask==1)] = np.nan
371
+
372
+ std_frame = filter_image(target_copy.copy(),filters=activation_protocol)
373
+ edge = estimate_unreliable_edge(activation_protocol)
374
+ mask = threshold_image(std_frame, threshold_on_std, 1.0E06, foreground_value=1, edge_exclusion=edge)
375
+ target_copy[np.where(mask.astype(int)==1)] = np.nan
376
+
374
377
  loss = []
375
378
 
376
379
  # brute-force regression, could do gradient descent instead
377
380
  for c in coefficients:
378
- diff = np.subtract(target_copy, c*background, where=target_copy==target_copy)
381
+ target_crop = unpad(target_copy,edge)
382
+ bg_crop = unpad(background, edge)
383
+ diff = np.subtract(target_crop, c*bg_crop, where=target_crop==target_crop)
379
384
  s = np.sum(np.abs(diff, where=diff==diff), where=diff==diff)
380
385
  loss.append(s)
381
386
  c = coefficients[np.argmin(loss)]
@@ -410,40 +415,134 @@ def apply_background_to_stack(stack_path, background, target_channel_index=0, nb
410
415
  return corrected_stack
411
416
 
412
417
  def paraboloid(x, y, a, b, c, d, e, g):
418
+
419
+ """
420
+ Compute the value of a 2D paraboloid function.
421
+
422
+ This function evaluates a paraboloid defined by the equation:
423
+ `a * x ** 2 + b * y ** 2 + c * x * y + d * x + e * y + g`.
424
+
425
+ Parameters
426
+ ----------
427
+ x : float or ndarray
428
+ The x-coordinate(s) at which to evaluate the paraboloid.
429
+ y : float or ndarray
430
+ The y-coordinate(s) at which to evaluate the paraboloid.
431
+ a : float
432
+ The coefficient of the x^2 term.
433
+ b : float
434
+ The coefficient of the y^2 term.
435
+ c : float
436
+ The coefficient of the x*y term.
437
+ d : float
438
+ The coefficient of the x term.
439
+ e : float
440
+ The coefficient of the y term.
441
+ g : float
442
+ The constant term.
443
+
444
+ Returns
445
+ -------
446
+ float or ndarray
447
+ The value of the paraboloid at the given (x, y) coordinates. If `x` and
448
+ `y` are arrays, the result is an array of the same shape.
449
+
450
+ Examples
451
+ --------
452
+ >>> paraboloid(1, 2, 1, 1, 0, 0, 0, 0)
453
+ 5
454
+ >>> paraboloid(np.array([1, 2]), np.array([3, 4]), 1, 1, 0, 0, 0, 0)
455
+ array([10, 20])
456
+
457
+ Notes
458
+ -----
459
+ The paraboloid function is a quadratic function in two variables, commonly used
460
+ to model surfaces in three-dimensional space.
461
+ """
462
+
413
463
  return a * x ** 2 + b * y ** 2 + c * x * y + d * x + e * y + g
414
464
 
415
465
 
416
466
  def plane(x, y, a, b, c):
467
+
468
+ """
469
+ Compute the value of a plane function.
470
+
471
+ This function evaluates a plane defined by the equation:
472
+ `a * x + b * y + c`.
473
+
474
+ Parameters
475
+ ----------
476
+ x : float or ndarray
477
+ The x-coordinate(s) at which to evaluate the plane.
478
+ y : float or ndarray
479
+ The y-coordinate(s) at which to evaluate the plane.
480
+ a : float
481
+ The coefficient of the x term.
482
+ b : float
483
+ The coefficient of the y term.
484
+ c : float
485
+ The constant term.
486
+
487
+ Returns
488
+ -------
489
+ float or ndarray
490
+ The value of the plane at the given (x, y) coordinates. If `x` and
491
+ `y` are arrays, the result is an array of the same shape.
492
+
493
+ Examples
494
+ --------
495
+ >>> plane(1, 2, 3, 4, 5)
496
+ 16
497
+ >>> plane(np.array([1, 2]), np.array([3, 4]), 3, 4, 5)
498
+ array([20, 27])
499
+
500
+ Notes
501
+ -----
502
+ The plane function is a linear function in two variables, commonly used
503
+ to model flat surfaces in three-dimensional space.
504
+ """
505
+
417
506
  return a * x + b * y + c
418
507
 
419
508
 
420
- def fit_plane(image, cell_masks=None):
509
+ def fit_plane(image, cell_masks=None, edge_exclusion=None):
510
+
421
511
  """
422
512
  Fit a plane to the given image data.
423
513
 
424
- Parameters:
425
- - image (numpy.ndarray): The input image data.
426
- - cell_masks (numpy.ndarray, optional): An array specifying cell masks. If provided, areas covered by
427
- cell masks will be excluded from the fitting process.
514
+ This function fits a plane to the provided image data using least squares
515
+ regression. It constructs a mesh grid based on the dimensions of the image
516
+ and fits a plane model to the data points. If cell masks are provided,
517
+ areas covered by cell masks will be excluded from the fitting process.
428
518
 
429
- Returns:
430
- - numpy.ndarray: The fitted plane.
519
+ Parameters
520
+ ----------
521
+ image : numpy.ndarray
522
+ The input image data.
523
+ cell_masks : numpy.ndarray, optional
524
+ An array specifying cell masks. If provided, areas covered by cell masks
525
+ will be excluded from the fitting process (default is None).
526
+ edge_exclusion : int, optional
527
+ The size of the edge to exclude from the fitting process (default is None).
431
528
 
432
- This function fits a plane to the given image data using least squares regression. It constructs a mesh
433
- grid based on the dimensions of the image and fits a plane model to the data points. If cell masks are
434
- provided, areas covered by cell masks will be excluded from the fitting process.
529
+ Returns
530
+ -------
531
+ numpy.ndarray
532
+ The fitted plane.
435
533
 
436
- Example:
437
- >>> image = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
438
- >>> result = fit_plane(image)
439
- >>> print(result)
440
- [[1. 2. 3.]
441
- [4. 5. 6.]
442
- [7. 8. 9.]]
534
+ Notes
535
+ -----
536
+ - The `cell_masks` parameter allows excluding areas covered by cell masks from
537
+ the fitting process.
538
+ - The `edge_exclusion` parameter allows excluding edges of the specified size
539
+ from the fitting process to avoid boundary effects.
443
540
 
444
- Note:
445
- - The 'cell_masks' parameter allows excluding areas covered by cell masks from the fitting process.
541
+ See Also
542
+ --------
543
+ plane : The plane function used for fitting.
446
544
  """
545
+
447
546
  data = np.empty(image.shape)
448
547
  x = np.arange(0, image.shape[1])
449
548
  y = np.arange(0, image.shape[0])
@@ -457,7 +556,14 @@ def fit_plane(image, cell_masks=None):
457
556
  model = Model(plane, independent_vars=['x', 'y'])
458
557
 
459
558
  weights = np.ones_like(xx, dtype=float)
460
- weights[np.where(cell_masks > 0)] = 0.
559
+ if cell_masks is not None:
560
+ weights[np.where(cell_masks > 0)] = 0.
561
+
562
+ if edge_exclusion is not None:
563
+ xx = unpad(xx, edge_exclusion)
564
+ yy = unpad(yy, edge_exclusion)
565
+ weights = unpad(weights, edge_exclusion)
566
+ image = unpad(image, edge_exclusion)
461
567
 
462
568
  result = model.fit(image,
463
569
  x=xx,
@@ -467,36 +573,48 @@ def fit_plane(image, cell_masks=None):
467
573
  del model
468
574
  collect()
469
575
 
470
- return result.best_fit
576
+ xx, yy = np.meshgrid(x, y)
577
+
578
+ return plane(xx, yy, **result.params)
579
+
471
580
 
581
+ def fit_paraboloid(image, cell_masks=None, edge_exclusion=None):
472
582
 
473
- def fit_paraboloid(image, cell_masks=None):
474
583
  """
475
584
  Fit a paraboloid to the given image data.
476
585
 
477
- Parameters:
478
- - image (numpy.ndarray): The input image data.
479
- - cell_masks (numpy.ndarray, optional): An array specifying cell masks. If provided, areas covered by
480
- cell masks will be excluded from the fitting process.
586
+ This function fits a paraboloid to the provided image data using least squares
587
+ regression. It constructs a mesh grid based on the dimensions of the image
588
+ and fits a paraboloid model to the data points. If cell masks are provided,
589
+ areas covered by cell masks will be excluded from the fitting process.
481
590
 
482
- Returns:
483
- - numpy.ndarray: The fitted paraboloid.
591
+ Parameters
592
+ ----------
593
+ image : numpy.ndarray
594
+ The input image data.
595
+ cell_masks : numpy.ndarray, optional
596
+ An array specifying cell masks. If provided, areas covered by cell masks
597
+ will be excluded from the fitting process (default is None).
598
+ edge_exclusion : int, optional
599
+ The size of the edge to exclude from the fitting process (default is None).
484
600
 
485
- This function fits a paraboloid to the given image data using least squares regression. It constructs
486
- a mesh grid based on the dimensions of the image and fits a paraboloid model to the data points. If cell
487
- masks are provided, areas covered by cell masks will be excluded from the fitting process.
601
+ Returns
602
+ -------
603
+ numpy.ndarray
604
+ The fitted paraboloid.
488
605
 
489
- Example:
490
- >>> image = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
491
- >>> result = fit_paraboloid(image)
492
- >>> print(result)
493
- [[1. 2. 3.]
494
- [4. 5. 6.]
495
- [7. 8. 9.]]
606
+ Notes
607
+ -----
608
+ - The `cell_masks` parameter allows excluding areas covered by cell masks from
609
+ the fitting process.
610
+ - The `edge_exclusion` parameter allows excluding edges of the specified size
611
+ from the fitting process to avoid boundary effects.
496
612
 
497
- Note:
498
- - The 'cell_masks' parameter allows excluding areas covered by cell masks from the fitting process.
613
+ See Also
614
+ --------
615
+ paraboloid : The paraboloid function used for fitting.
499
616
  """
617
+
500
618
  data = np.empty(image.shape)
501
619
  x = np.arange(0, image.shape[1])
502
620
  y = np.arange(0, image.shape[0])
@@ -513,7 +631,14 @@ def fit_paraboloid(image, cell_masks=None):
513
631
  model = Model(paraboloid, independent_vars=['x', 'y'])
514
632
 
515
633
  weights = np.ones_like(xx, dtype=float)
516
- weights[np.where(cell_masks > 0)] = 0.
634
+ if cell_masks is not None:
635
+ weights[np.where(cell_masks > 0)] = 0.
636
+
637
+ if edge_exclusion is not None:
638
+ xx = unpad(xx, edge_exclusion)
639
+ yy = unpad(yy, edge_exclusion)
640
+ weights = unpad(weights, edge_exclusion)
641
+ image = unpad(image, edge_exclusion)
517
642
 
518
643
  result = model.fit(image,
519
644
  x=xx,
@@ -521,10 +646,12 @@ def fit_paraboloid(image, cell_masks=None):
521
646
  weights=weights,
522
647
  params=params, max_nfev=3000)
523
648
 
524
- #print(result.fit_report())
525
649
  del model
526
650
  collect()
527
- return result.best_fit
651
+
652
+ xx, yy = np.meshgrid(x, y)
653
+
654
+ return paraboloid(xx, yy, **result.params)
528
655
 
529
656
 
530
657
  def correct_background_model(
@@ -541,10 +668,71 @@ def correct_background_model(
541
668
  export = False,
542
669
  return_stacks = False,
543
670
  movie_prefix=None,
671
+ activation_protocol=[['gauss',2],['std',4]],
544
672
  export_prefix='Corrected',
673
+ return_stack = True,
545
674
  **kwargs,
546
675
  ):
547
676
 
677
+ """
678
+ Correct background in image stacks using a specified model.
679
+
680
+ This function corrects the background in image stacks obtained from an experiment
681
+ using a specified background correction model. It supports various options for
682
+ specifying wells, positions, target channel, and background correction parameters.
683
+
684
+ Parameters
685
+ ----------
686
+ experiment : str
687
+ The path to the experiment directory.
688
+ well_option : str, optional
689
+ The option to select specific wells (default is '*').
690
+ position_option : str, optional
691
+ The option to select specific positions (default is '*').
692
+ target_channel : str, optional
693
+ The name of the target channel for background correction (default is "channel_name").
694
+ threshold_on_std : float, optional
695
+ The threshold value on the standard deviation for masking (default is 1).
696
+ model : str, optional
697
+ The background correction model to use, either 'paraboloid' or 'plane' (default is 'paraboloid').
698
+ operation : str, optional
699
+ The operation to apply for background correction, either 'divide' or 'subtract' (default is 'divide').
700
+ clip : bool, optional
701
+ Whether to clip the corrected image to ensure non-negative values (default is False).
702
+ show_progress_per_well : bool, optional
703
+ Whether to show progress for each well (default is True).
704
+ show_progress_per_pos : bool, optional
705
+ Whether to show progress for each position (default is False).
706
+ export : bool, optional
707
+ Whether to export the corrected stacks (default is False).
708
+ return_stacks : bool, optional
709
+ Whether to return the corrected stacks (default is False).
710
+ movie_prefix : str, optional
711
+ The prefix for the movie files (default is None).
712
+ activation_protocol : list of list, optional
713
+ The activation protocol consisting of filters and their respective parameters (default is [['gauss',2],['std',4]]).
714
+ export_prefix : str, optional
715
+ The prefix for exported corrected stacks (default is 'Corrected').
716
+ **kwargs : dict
717
+ Additional keyword arguments to be passed to the underlying correction function.
718
+
719
+ Returns
720
+ -------
721
+ list of numpy.ndarray
722
+ A list of corrected image stacks if `return_stacks` is True, otherwise None.
723
+
724
+ Notes
725
+ -----
726
+ - This function assumes that the experiment directory structure and the configuration
727
+ files follow a specific format expected by the helper functions used within.
728
+ - Supported background correction models are 'paraboloid' and 'plane'.
729
+ - Supported background correction operations are 'divide' and 'subtract'.
730
+
731
+ See Also
732
+ --------
733
+ fit_and_apply_model_background_to_stack : Function to fit and apply background correction to an image stack.
734
+ """
735
+
548
736
  config = get_config(experiment)
549
737
  wells = get_experiment_wells(experiment)
550
738
  len_movie = float(ConfigSectionMap(config,"MovieSettings")["len_movie"])
@@ -582,6 +770,8 @@ def correct_background_model(
582
770
  clip=clip,
583
771
  export=export,
584
772
  prefix=export_prefix,
773
+ activation_protocol=activation_protocol,
774
+ return_stacks = return_stacks,
585
775
  )
586
776
  print('Correction successful.')
587
777
  if return_stacks:
@@ -602,9 +792,61 @@ def fit_and_apply_model_background_to_stack(stack_path,
602
792
  model='paraboloid',
603
793
  clip=False,
604
794
  export=False,
605
- prefix="Corrected"
795
+ activation_protocol=[['gauss',2],['std',4]],
796
+ prefix="Corrected",
797
+ return_stacks=True,
606
798
  ):
607
799
 
800
+ """
801
+ Fit and apply a background correction model to an image stack.
802
+
803
+ This function fits a background correction model to each frame of the image stack
804
+ and applies the correction accordingly. It supports various options for specifying
805
+ the target channel, number of channels, stack length, threshold on standard deviation,
806
+ correction operation, correction model, clipping, and export.
807
+
808
+ Parameters
809
+ ----------
810
+ stack_path : str
811
+ The path to the image stack.
812
+ target_channel_index : int, optional
813
+ The index of the target channel for background correction (default is 0).
814
+ nbr_channels : int, optional
815
+ The number of channels in the image stack (default is 1).
816
+ stack_length : int, optional
817
+ The length of the stack (default is 45).
818
+ threshold_on_std : float, optional
819
+ The threshold value on the standard deviation for masking (default is 1).
820
+ operation : str, optional
821
+ The operation to apply for background correction, either 'divide' or 'subtract' (default is 'divide').
822
+ model : str, optional
823
+ The background correction model to use, either 'paraboloid' or 'plane' (default is 'paraboloid').
824
+ clip : bool, optional
825
+ Whether to clip the corrected image to ensure non-negative values (default is False).
826
+ export : bool, optional
827
+ Whether to export the corrected image stack (default is False).
828
+ activation_protocol : list of list, optional
829
+ The activation protocol consisting of filters and their respective parameters (default is [['gauss',2],['std',4]]).
830
+ prefix : str, optional
831
+ The prefix for exported corrected stacks (default is 'Corrected').
832
+
833
+ Returns
834
+ -------
835
+ numpy.ndarray
836
+ The corrected image stack.
837
+
838
+ Notes
839
+ -----
840
+ - The function loads frames from the image stack, applies background correction to each frame,
841
+ and stores the corrected frames in a new stack.
842
+ - Supported background correction models are 'paraboloid' and 'plane'.
843
+ - Supported background correction operations are 'divide' and 'subtract'.
844
+
845
+ See Also
846
+ --------
847
+ field_correction : Function to apply background correction to an image.
848
+ """
849
+
608
850
  stack_length_auto = auto_load_number_of_frames(stack_path)
609
851
  if stack_length_auto is None and stack_length is None:
610
852
  print('stack length not provided')
@@ -612,70 +854,168 @@ def fit_and_apply_model_background_to_stack(stack_path,
612
854
  if stack_length_auto is not None:
613
855
  stack_length = stack_length_auto
614
856
 
857
+ corrected_stack = []
858
+
615
859
  if export:
616
860
  path,file = os.path.split(stack_path)
617
861
  if prefix is None:
618
- newfile = file
862
+ newfile = 'temp_'+file
619
863
  else:
620
864
  newfile = '_'.join([prefix,file])
865
+
866
+ with tiff.TiffWriter(os.sep.join([path,newfile]),imagej=True) as tif:
621
867
 
622
- corrected_stack = []
868
+ for i in tqdm(range(0,int(stack_length*nbr_channels),nbr_channels)):
869
+
870
+ frames = load_frames(list(np.arange(i,(i+nbr_channels))), stack_path, normalize_input=False).astype(float)
871
+ target_img = frames[:,:,target_channel_index].copy()
872
+ correction = field_correction(target_img, threshold_on_std=threshold_on_std, operation=operation, model=model, clip=clip, activation_protocol=activation_protocol)
873
+ frames[:,:,target_channel_index] = correction.copy()
623
874
 
624
- for i in tqdm(range(0,int(stack_length*nbr_channels),nbr_channels)):
625
-
626
- frames = load_frames(list(np.arange(i,(i+nbr_channels))), stack_path, normalize_input=False).astype(float)
627
- target_img = frames[:,:,target_channel_index].copy()
628
- correction = field_correction(target_img, threshold_on_std=threshold_on_std, operation=operation, model=model, clip=clip)
629
- frames[:,:,target_channel_index] = correction.copy()
630
- corrected_stack.append(frames)
875
+ if return_stacks:
876
+ corrected_stack.append(frames)
631
877
 
632
- del frames
633
- del target_img
634
- del correction
635
- collect()
878
+ if export:
879
+ tif.write(np.moveaxis(frames,-1,0).astype(np.dtype('f')), contiguous=True)
880
+ del frames
881
+ del target_img
882
+ del correction
883
+ collect()
636
884
 
637
- corrected_stack = np.array(corrected_stack)
885
+ if prefix is None:
886
+ os.replace(os.sep.join([path,newfile]), os.sep.join([path,file]))
887
+ else:
888
+ for i in tqdm(range(0,int(stack_length*nbr_channels),nbr_channels)):
889
+
890
+ frames = load_frames(list(np.arange(i,(i+nbr_channels))), stack_path, normalize_input=False).astype(float)
891
+ target_img = frames[:,:,target_channel_index].copy()
892
+ correction = field_correction(target_img, threshold_on_std=threshold_on_std, operation=operation, model=model, clip=clip, activation_protocol=activation_protocol)
893
+ frames[:,:,target_channel_index] = correction.copy()
638
894
 
639
- if export:
640
- save_tiff_imagej_compatible(os.sep.join([path,newfile]), corrected_stack, axes='TYXC')
895
+ corrected_stack.append(frames)
641
896
 
642
- return corrected_stack
897
+ del frames
898
+ del target_img
899
+ del correction
900
+ collect()
643
901
 
644
- def field_correction(img, threshold_on_std=1, operation='divide', model='paraboloid', clip=False, return_bg=False):
645
-
646
- target_copy = img.copy().astype(float)
647
- f = gauss_filter(target_copy, 2)
648
- std_frame = std_filter(f, 4)
649
- masks = std_frame > threshold_on_std
650
- masks = fill_label_holes(masks).astype(int)
902
+ if return_stacks:
903
+ return np.array(corrected_stack)
904
+ else:
905
+ return None
651
906
 
652
- background = fit_background_model(img, cell_masks=masks, model=model)
907
+ def field_correction(img, threshold_on_std=1, operation='divide', model='paraboloid', clip=False, return_bg=False, activation_protocol=[['gauss',2],['std',4]]):
908
+
909
+ """
910
+ Apply field correction to an image.
653
911
 
654
- if operation=="divide":
655
- correction = np.divide(img, background, where=background==background)
656
- correction[background!=background] = np.nan
657
- correction[img!=img] = np.nan
658
- fill_val = 1.0
912
+ This function applies field correction to the given image based on the specified parameters
913
+ including the threshold on standard deviation, operation, background correction model, clipping,
914
+ and activation protocol.
659
915
 
660
- elif operation=="subtract":
661
- correction = np.subtract(img, background, where=background==background)
662
- correction[background!=background] = np.nan
663
- correction[img!=img] = np.nan
664
- fill_val = 0.0
665
- if clip:
666
- correction[correction<=0.] = 0.
916
+ Parameters
917
+ ----------
918
+ img : numpy.ndarray
919
+ The input image to be corrected.
920
+ threshold_on_std : float, optional
921
+ The threshold value on the standard deviation for masking (default is 1).
922
+ operation : str, optional
923
+ The operation to apply for background correction, either 'divide' or 'subtract' (default is 'divide').
924
+ model : str, optional
925
+ The background correction model to use, either 'paraboloid' or 'plane' (default is 'paraboloid').
926
+ clip : bool, optional
927
+ Whether to clip the corrected image to ensure non-negative values (default is False).
928
+ return_bg : bool, optional
929
+ Whether to return the background along with the corrected image (default is False).
930
+ activation_protocol : list of list, optional
931
+ The activation protocol consisting of filters and their respective parameters (default is [['gauss',2],['std',4]]).
667
932
 
668
- if return_bg:
669
- return correction.copy(), background
670
- else:
671
- return correction.copy()
933
+ Returns
934
+ -------
935
+ numpy.ndarray or tuple
936
+ The corrected image or a tuple containing the corrected image and the background, depending on the value of `return_bg`.
672
937
 
673
- def fit_background_model(img, cell_masks=None, model='paraboloid'):
938
+ Notes
939
+ -----
940
+ - This function first estimates the unreliable edge based on the activation protocol.
941
+ - It then applies thresholding to obtain a mask for the background.
942
+ - Next, it fits a background model to the image using the specified model.
943
+ - Depending on the operation specified, it either divides or subtracts the background from the image.
944
+ - If `clip` is True and operation is 'subtract', negative values in the corrected image are clipped to 0.
945
+ - If `return_bg` is True, the function returns a tuple containing the corrected image and the background.
946
+
947
+ See Also
948
+ --------
949
+ fit_background_model : Function to fit a background model to an image.
950
+ threshold_image : Function to apply thresholding to an image.
951
+ """
952
+
953
+ target_copy = img.copy().astype(float)
954
+
955
+ std_frame = filter_image(target_copy,filters=activation_protocol)
956
+ edge = estimate_unreliable_edge(activation_protocol)
957
+ mask = threshold_image(std_frame, threshold_on_std, 1.0E06, foreground_value=1, edge_exclusion=edge).astype(int)
958
+ background = fit_background_model(img, cell_masks=mask, model=model, edge_exclusion=edge)
959
+
960
+ if operation=="divide":
961
+ correction = np.divide(img, background, where=background==background)
962
+ correction[background!=background] = np.nan
963
+ correction[img!=img] = np.nan
964
+ fill_val = 1.0
965
+
966
+ elif operation=="subtract":
967
+ correction = np.subtract(img, background, where=background==background)
968
+ correction[background!=background] = np.nan
969
+ correction[img!=img] = np.nan
970
+ fill_val = 0.0
971
+ if clip:
972
+ correction[correction<=0.] = 0.
973
+
974
+ if return_bg:
975
+ return correction.copy(), background
976
+ else:
977
+ return correction.copy()
978
+
979
+ def fit_background_model(img, cell_masks=None, model='paraboloid', edge_exclusion=None):
674
980
 
981
+ """
982
+ Fit a background model to the given image.
983
+
984
+ This function fits a background model to the given image using either a paraboloid or plane model.
985
+ It supports optional cell masks and edge exclusion for fitting.
986
+
987
+ Parameters
988
+ ----------
989
+ img : numpy.ndarray
990
+ The input image data.
991
+ cell_masks : numpy.ndarray, optional
992
+ An array specifying cell masks. If provided, areas covered by cell masks will be excluded from the fitting process.
993
+ model : str, optional
994
+ The background model to fit, either 'paraboloid' or 'plane' (default is 'paraboloid').
995
+ edge_exclusion : int or None, optional
996
+ The size of the border to exclude from fitting (default is None).
997
+
998
+ Returns
999
+ -------
1000
+ numpy.ndarray or None
1001
+ The fitted background model as a numpy array if successful, otherwise None.
1002
+
1003
+ Notes
1004
+ -----
1005
+ - This function fits a background model to the image using either a paraboloid or plane model based on the specified `model`.
1006
+ - If `cell_masks` are provided, areas covered by cell masks will be excluded from the fitting process.
1007
+ - If `edge_exclusion` is provided, a border of the specified size will be excluded from fitting.
1008
+
1009
+ See Also
1010
+ --------
1011
+ fit_paraboloid : Function to fit a paraboloid model to an image.
1012
+ fit_plane : Function to fit a plane model to an image.
1013
+ """
1014
+
675
1015
  if model == "paraboloid":
676
- bg = fit_paraboloid(img.astype(float), cell_masks=cell_masks).astype(float)
1016
+ bg = fit_paraboloid(img.astype(float), cell_masks=cell_masks, edge_exclusion=edge_exclusion).astype(float)
677
1017
  elif model == "plane":
678
- bg = fit_plane(img.astype(float), cell_masks=cell_masks).astype(float)
1018
+ bg = fit_plane(img.astype(float), cell_masks=cell_masks, edge_exclusion=edge_exclusion).astype(float)
679
1019
 
680
1020
  if bg is not None:
681
1021
  bg = np.array(bg)