celldetective 1.4.2__py3-none-any.whl → 1.5.0b1__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.
- celldetective/__init__.py +25 -0
- celldetective/__main__.py +62 -43
- celldetective/_version.py +1 -1
- celldetective/extra_properties.py +477 -399
- celldetective/filters.py +192 -97
- celldetective/gui/InitWindow.py +541 -411
- celldetective/gui/__init__.py +0 -15
- celldetective/gui/about.py +44 -39
- celldetective/gui/analyze_block.py +120 -84
- celldetective/gui/base/__init__.py +0 -0
- celldetective/gui/base/channel_norm_generator.py +335 -0
- celldetective/gui/base/components.py +249 -0
- celldetective/gui/base/feature_choice.py +92 -0
- celldetective/gui/base/figure_canvas.py +52 -0
- celldetective/gui/base/list_widget.py +133 -0
- celldetective/gui/{styles.py → base/styles.py} +92 -36
- celldetective/gui/base/utils.py +33 -0
- celldetective/gui/base_annotator.py +900 -767
- celldetective/gui/classifier_widget.py +6 -22
- celldetective/gui/configure_new_exp.py +777 -671
- celldetective/gui/control_panel.py +635 -524
- celldetective/gui/dynamic_progress.py +449 -0
- celldetective/gui/event_annotator.py +2023 -1662
- celldetective/gui/generic_signal_plot.py +1292 -944
- celldetective/gui/gui_utils.py +899 -1289
- celldetective/gui/interactions_block.py +658 -0
- celldetective/gui/interactive_timeseries_viewer.py +447 -0
- celldetective/gui/json_readers.py +48 -15
- celldetective/gui/layouts/__init__.py +5 -0
- celldetective/gui/layouts/background_model_free_layout.py +537 -0
- celldetective/gui/layouts/channel_offset_layout.py +134 -0
- celldetective/gui/layouts/local_correction_layout.py +91 -0
- celldetective/gui/layouts/model_fit_layout.py +372 -0
- celldetective/gui/layouts/operation_layout.py +68 -0
- celldetective/gui/layouts/protocol_designer_layout.py +96 -0
- celldetective/gui/pair_event_annotator.py +3130 -2435
- celldetective/gui/plot_measurements.py +586 -267
- celldetective/gui/plot_signals_ui.py +724 -506
- celldetective/gui/preprocessing_block.py +395 -0
- celldetective/gui/process_block.py +1678 -1831
- celldetective/gui/seg_model_loader.py +580 -473
- celldetective/gui/settings/__init__.py +0 -7
- celldetective/gui/settings/_cellpose_model_params.py +181 -0
- celldetective/gui/settings/_event_detection_model_params.py +95 -0
- celldetective/gui/settings/_segmentation_model_params.py +159 -0
- celldetective/gui/settings/_settings_base.py +77 -65
- celldetective/gui/settings/_settings_event_model_training.py +752 -526
- celldetective/gui/settings/_settings_measurements.py +1133 -964
- celldetective/gui/settings/_settings_neighborhood.py +574 -488
- celldetective/gui/settings/_settings_segmentation_model_training.py +779 -564
- celldetective/gui/settings/_settings_signal_annotator.py +329 -305
- celldetective/gui/settings/_settings_tracking.py +1304 -1094
- celldetective/gui/settings/_stardist_model_params.py +98 -0
- celldetective/gui/survival_ui.py +422 -312
- celldetective/gui/tableUI.py +1665 -1701
- celldetective/gui/table_ops/_maths.py +295 -0
- celldetective/gui/table_ops/_merge_groups.py +140 -0
- celldetective/gui/table_ops/_merge_one_hot.py +95 -0
- celldetective/gui/table_ops/_query_table.py +43 -0
- celldetective/gui/table_ops/_rename_col.py +44 -0
- celldetective/gui/thresholds_gui.py +382 -179
- celldetective/gui/viewers/__init__.py +0 -0
- celldetective/gui/viewers/base_viewer.py +700 -0
- celldetective/gui/viewers/channel_offset_viewer.py +331 -0
- celldetective/gui/viewers/contour_viewer.py +394 -0
- celldetective/gui/viewers/size_viewer.py +153 -0
- celldetective/gui/viewers/spot_detection_viewer.py +341 -0
- celldetective/gui/viewers/threshold_viewer.py +309 -0
- celldetective/gui/workers.py +403 -126
- celldetective/log_manager.py +92 -0
- celldetective/measure.py +1895 -1478
- celldetective/napari/__init__.py +0 -0
- celldetective/napari/utils.py +1025 -0
- celldetective/neighborhood.py +1914 -1448
- celldetective/preprocessing.py +1620 -1220
- celldetective/processes/__init__.py +0 -0
- celldetective/processes/background_correction.py +271 -0
- celldetective/processes/compute_neighborhood.py +894 -0
- celldetective/processes/detect_events.py +246 -0
- celldetective/processes/downloader.py +137 -0
- celldetective/processes/measure_cells.py +565 -0
- celldetective/processes/segment_cells.py +760 -0
- celldetective/processes/track_cells.py +435 -0
- celldetective/processes/train_segmentation_model.py +694 -0
- celldetective/processes/train_signal_model.py +265 -0
- celldetective/processes/unified_process.py +292 -0
- celldetective/regionprops/_regionprops.py +358 -317
- celldetective/relative_measurements.py +987 -710
- celldetective/scripts/measure_cells.py +313 -212
- celldetective/scripts/measure_relative.py +90 -46
- celldetective/scripts/segment_cells.py +165 -104
- celldetective/scripts/segment_cells_thresholds.py +96 -68
- celldetective/scripts/track_cells.py +198 -149
- celldetective/scripts/train_segmentation_model.py +324 -201
- celldetective/scripts/train_signal_model.py +87 -45
- celldetective/segmentation.py +844 -749
- celldetective/signals.py +3514 -2861
- celldetective/tracking.py +30 -15
- celldetective/utils/__init__.py +0 -0
- celldetective/utils/cellpose_utils/__init__.py +133 -0
- celldetective/utils/color_mappings.py +42 -0
- celldetective/utils/data_cleaning.py +630 -0
- celldetective/utils/data_loaders.py +450 -0
- celldetective/utils/dataset_helpers.py +207 -0
- celldetective/utils/downloaders.py +235 -0
- celldetective/utils/event_detection/__init__.py +8 -0
- celldetective/utils/experiment.py +1782 -0
- celldetective/utils/image_augmenters.py +308 -0
- celldetective/utils/image_cleaning.py +74 -0
- celldetective/utils/image_loaders.py +926 -0
- celldetective/utils/image_transforms.py +335 -0
- celldetective/utils/io.py +62 -0
- celldetective/utils/mask_cleaning.py +348 -0
- celldetective/utils/mask_transforms.py +5 -0
- celldetective/utils/masks.py +184 -0
- celldetective/utils/maths.py +351 -0
- celldetective/utils/model_getters.py +325 -0
- celldetective/utils/model_loaders.py +296 -0
- celldetective/utils/normalization.py +380 -0
- celldetective/utils/parsing.py +465 -0
- celldetective/utils/plots/__init__.py +0 -0
- celldetective/utils/plots/regression.py +53 -0
- celldetective/utils/resources.py +34 -0
- celldetective/utils/stardist_utils/__init__.py +104 -0
- celldetective/utils/stats.py +90 -0
- celldetective/utils/types.py +21 -0
- {celldetective-1.4.2.dist-info → celldetective-1.5.0b1.dist-info}/METADATA +1 -1
- celldetective-1.5.0b1.dist-info/RECORD +187 -0
- {celldetective-1.4.2.dist-info → celldetective-1.5.0b1.dist-info}/WHEEL +1 -1
- tests/gui/test_new_project.py +129 -117
- tests/gui/test_project.py +127 -79
- tests/test_filters.py +39 -15
- tests/test_notebooks.py +8 -0
- tests/test_tracking.py +232 -13
- tests/test_utils.py +123 -77
- celldetective/gui/base_components.py +0 -23
- celldetective/gui/layouts.py +0 -1602
- celldetective/gui/processes/compute_neighborhood.py +0 -594
- celldetective/gui/processes/downloader.py +0 -111
- celldetective/gui/processes/measure_cells.py +0 -360
- celldetective/gui/processes/segment_cells.py +0 -499
- celldetective/gui/processes/track_cells.py +0 -303
- celldetective/gui/processes/train_segmentation_model.py +0 -270
- celldetective/gui/processes/train_signal_model.py +0 -108
- celldetective/gui/table_ops/merge_groups.py +0 -118
- celldetective/gui/viewers.py +0 -1354
- celldetective/io.py +0 -3663
- celldetective/utils.py +0 -3108
- celldetective-1.4.2.dist-info/RECORD +0 -123
- {celldetective-1.4.2.dist-info → celldetective-1.5.0b1.dist-info}/entry_points.txt +0 -0
- {celldetective-1.4.2.dist-info → celldetective-1.5.0b1.dist-info}/licenses/LICENSE +0 -0
- {celldetective-1.4.2.dist-info → celldetective-1.5.0b1.dist-info}/top_level.txt +0 -0
celldetective/preprocessing.py
CHANGED
|
@@ -1,1243 +1,1643 @@
|
|
|
1
1
|
"""
|
|
2
2
|
Copright © 2024 Laboratoire Adhesion et Inflammation, Authored by Remy Torro & Ksenija Dervanova.
|
|
3
3
|
"""
|
|
4
|
-
from typing import List
|
|
5
4
|
|
|
6
|
-
from
|
|
5
|
+
from typing import List
|
|
7
6
|
import numpy as np
|
|
8
7
|
import os
|
|
9
|
-
from celldetective.
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
8
|
+
from celldetective.utils.image_loaders import (
|
|
9
|
+
auto_load_number_of_frames,
|
|
10
|
+
load_frames,
|
|
11
|
+
_get_img_num_per_channel,
|
|
12
|
+
)
|
|
13
|
+
from celldetective.utils.image_cleaning import interpolate_nan
|
|
14
|
+
from celldetective.utils.experiment import (
|
|
15
|
+
get_experiment_wells,
|
|
16
|
+
extract_well_name_and_number,
|
|
17
|
+
extract_position_name,
|
|
18
|
+
get_config,
|
|
19
|
+
interpret_wells_and_positions,
|
|
20
|
+
get_position_movie_path,
|
|
21
|
+
get_positions_in_well,
|
|
22
|
+
)
|
|
23
|
+
from celldetective.utils.image_transforms import (
|
|
24
|
+
estimate_unreliable_edge,
|
|
25
|
+
unpad,
|
|
26
|
+
threshold_image,
|
|
27
|
+
)
|
|
28
|
+
from celldetective.utils.parsing import (
|
|
29
|
+
config_section_to_dict,
|
|
30
|
+
_extract_channel_indices_from_config,
|
|
31
|
+
_extract_nbr_channels_from_config,
|
|
32
|
+
)
|
|
13
33
|
from gc import collect
|
|
14
|
-
from
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
34
|
+
from tqdm import tqdm
|
|
35
|
+
from celldetective import get_logger
|
|
36
|
+
|
|
37
|
+
logger = get_logger(__name__)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def estimate_background_per_condition(
|
|
41
|
+
experiment,
|
|
42
|
+
threshold_on_std=1,
|
|
43
|
+
well_option="*",
|
|
44
|
+
target_channel="channel_name",
|
|
45
|
+
frame_range=[0, 5],
|
|
46
|
+
mode="timeseries",
|
|
47
|
+
activation_protocol=[["gauss", 2], ["std", 4]],
|
|
48
|
+
show_progress_per_pos=False,
|
|
49
|
+
show_progress_per_well=True,
|
|
50
|
+
offset=None,
|
|
51
|
+
fix_nan: bool = False,
|
|
52
|
+
progress_callback=None,
|
|
53
|
+
):
|
|
54
|
+
"""
|
|
55
|
+
Estimate the background for each condition in an experiment.
|
|
56
|
+
|
|
57
|
+
This function calculates the background for each well within
|
|
58
|
+
a given experiment by processing image frames using a specified activation
|
|
59
|
+
protocol. It supports time-series and tile-based modes for background
|
|
60
|
+
estimation.
|
|
61
|
+
|
|
62
|
+
Parameters
|
|
63
|
+
----------
|
|
64
|
+
experiment : str
|
|
65
|
+
The path to the experiment directory.
|
|
66
|
+
threshold_on_std : float, optional
|
|
67
|
+
The threshold value on the standard deviation for masking (default is 1).
|
|
68
|
+
well_option : str, optional
|
|
69
|
+
The option to select specific wells (default is '*').
|
|
70
|
+
target_channel : str, optional
|
|
71
|
+
The name of the target channel for background estimation (default is "channel_name").
|
|
72
|
+
frame_range : list of int, optional
|
|
73
|
+
The range of frames to consider for background estimation (default is [0, 5]).
|
|
74
|
+
mode : str, optional
|
|
75
|
+
The mode of background estimation, either "timeseries" or "tiles" (default is "timeseries").
|
|
76
|
+
activation_protocol : list of list, optional
|
|
77
|
+
The activation protocol consisting of filters and their respective parameters (default is [['gauss', 2], ['std', 4]]).
|
|
78
|
+
show_progress_per_pos : bool, optional
|
|
79
|
+
Whether to show progress for each position (default is False).
|
|
80
|
+
show_progress_per_well : bool, optional
|
|
81
|
+
Whether to show progress for each well (default is True).
|
|
82
|
+
progress_callback : callable, optional
|
|
83
|
+
A callback function to be called at each step of the process (default is None).
|
|
84
|
+
|
|
85
|
+
Returns
|
|
86
|
+
-------
|
|
87
|
+
list of dict
|
|
88
|
+
A list of dictionaries, each containing the background image (`bg`) and the corresponding well path (`well`).
|
|
89
|
+
|
|
90
|
+
See Also
|
|
91
|
+
--------
|
|
92
|
+
estimate_unreliable_edge : Estimates the unreliable edge value from the activation protocol.
|
|
93
|
+
threshold_image : Thresholds an image based on the specified criteria.
|
|
94
|
+
|
|
95
|
+
Notes
|
|
96
|
+
-----
|
|
97
|
+
This function assumes that the experiment directory structure and the configuration
|
|
98
|
+
files follow a specific format expected by the helper functions used within.
|
|
99
|
+
|
|
100
|
+
Examples
|
|
101
|
+
--------
|
|
102
|
+
>>> experiment_path = "path/to/experiment"
|
|
103
|
+
>>> backgrounds = estimate_background_per_condition(experiment_path, threshold_on_std=1.5, target_channel="GFP", frame_range=[0, 10], mode="tiles")
|
|
104
|
+
>>> for bg in backgrounds:
|
|
105
|
+
... print(bg["well"], bg["bg"].shape)
|
|
106
|
+
"""
|
|
107
|
+
|
|
108
|
+
config = get_config(experiment)
|
|
109
|
+
wells = get_experiment_wells(experiment)
|
|
110
|
+
len_movie = float(config_section_to_dict(config, "MovieSettings")["len_movie"])
|
|
111
|
+
movie_prefix = config_section_to_dict(config, "MovieSettings")["movie_prefix"]
|
|
112
|
+
|
|
113
|
+
well_indices, position_indices = interpret_wells_and_positions(
|
|
114
|
+
experiment, well_option, "*"
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
channel_indices = _extract_channel_indices_from_config(config, [target_channel])
|
|
118
|
+
nbr_channels = _extract_nbr_channels_from_config(config)
|
|
119
|
+
img_num_channels = _get_img_num_per_channel(
|
|
120
|
+
channel_indices, int(len_movie), nbr_channels
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
backgrounds = []
|
|
124
|
+
|
|
125
|
+
for k, well_path in enumerate(
|
|
126
|
+
tqdm(wells[well_indices], disable=not show_progress_per_well)
|
|
127
|
+
):
|
|
128
|
+
|
|
129
|
+
well_name, _ = extract_well_name_and_number(well_path)
|
|
130
|
+
well_idx = well_indices[k]
|
|
131
|
+
|
|
132
|
+
positions = get_positions_in_well(well_path)
|
|
133
|
+
logger.info(
|
|
134
|
+
f"Reconstruct a background in well {well_name} from positions: {[extract_position_name(p) for p in positions]}..."
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
frame_mean_per_position = []
|
|
138
|
+
|
|
139
|
+
for l, pos_path in enumerate(
|
|
140
|
+
tqdm(positions, disable=not show_progress_per_pos)
|
|
141
|
+
):
|
|
142
|
+
if progress_callback is not None:
|
|
143
|
+
should_continue = progress_callback(
|
|
144
|
+
level="position", iter=l, total=len(positions)
|
|
145
|
+
)
|
|
146
|
+
if should_continue is False:
|
|
147
|
+
logger.info("Background estimation cancelled by user.")
|
|
148
|
+
return None
|
|
149
|
+
|
|
150
|
+
stack_path = get_position_movie_path(pos_path, prefix=movie_prefix)
|
|
151
|
+
if stack_path is not None:
|
|
152
|
+
len_movie_auto = auto_load_number_of_frames(stack_path)
|
|
153
|
+
if len_movie_auto is not None:
|
|
154
|
+
len_movie = len_movie_auto
|
|
155
|
+
img_num_channels = _get_img_num_per_channel(
|
|
156
|
+
channel_indices, int(len_movie), nbr_channels
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
from celldetective.filters import filter_image
|
|
160
|
+
|
|
161
|
+
if mode == "timeseries":
|
|
162
|
+
|
|
163
|
+
frames = load_frames(
|
|
164
|
+
img_num_channels[0, frame_range[0] : frame_range[1]],
|
|
165
|
+
stack_path,
|
|
166
|
+
normalize_input=False,
|
|
167
|
+
)
|
|
168
|
+
frames = np.moveaxis(frames, -1, 0).astype(float)
|
|
169
|
+
|
|
170
|
+
for i in range(len(frames)):
|
|
171
|
+
if np.all(frames[i].flatten() == 0):
|
|
172
|
+
frames[i, :, :] = np.nan
|
|
173
|
+
|
|
174
|
+
frame_mean = np.nanmean(frames, axis=0)
|
|
175
|
+
|
|
176
|
+
frame = frame_mean.copy().astype(float)
|
|
177
|
+
|
|
178
|
+
std_frame = filter_image(frame.copy(), filters=activation_protocol)
|
|
179
|
+
edge = estimate_unreliable_edge(activation_protocol)
|
|
180
|
+
mask = threshold_image(
|
|
181
|
+
std_frame,
|
|
182
|
+
threshold_on_std,
|
|
183
|
+
np.inf,
|
|
184
|
+
foreground_value=1,
|
|
185
|
+
edge_exclusion=edge,
|
|
186
|
+
)
|
|
187
|
+
frame[np.where(mask.astype(int) == 1)] = np.nan
|
|
188
|
+
|
|
189
|
+
elif mode == "tiles":
|
|
190
|
+
|
|
191
|
+
frames = load_frames(
|
|
192
|
+
img_num_channels[0, :], stack_path, normalize_input=False
|
|
193
|
+
).astype(float)
|
|
194
|
+
frames = np.moveaxis(frames, -1, 0).astype(float)
|
|
195
|
+
|
|
196
|
+
new_frames = []
|
|
197
|
+
for i in range(len(frames)):
|
|
198
|
+
|
|
199
|
+
if np.all(frames[i].flatten() == 0):
|
|
200
|
+
empty_frame = np.zeros_like(frames[i])
|
|
201
|
+
empty_frame[:, :] = np.nan
|
|
202
|
+
new_frames.append(empty_frame)
|
|
203
|
+
continue
|
|
204
|
+
|
|
205
|
+
f = frames[i].copy()
|
|
206
|
+
std_frame = filter_image(f.copy(), filters=activation_protocol)
|
|
207
|
+
edge = estimate_unreliable_edge(activation_protocol)
|
|
208
|
+
mask = threshold_image(
|
|
209
|
+
std_frame,
|
|
210
|
+
threshold_on_std,
|
|
211
|
+
np.inf,
|
|
212
|
+
foreground_value=1,
|
|
213
|
+
edge_exclusion=edge,
|
|
214
|
+
)
|
|
215
|
+
f[np.where(mask.astype(int) == 1)] = np.nan
|
|
216
|
+
new_frames.append(f.copy())
|
|
217
|
+
|
|
218
|
+
frame = np.nanmedian(new_frames, axis=0)
|
|
219
|
+
else:
|
|
220
|
+
print(f"Stack not found for position {pos_path}...")
|
|
221
|
+
frame = []
|
|
222
|
+
|
|
223
|
+
# store
|
|
224
|
+
frame_mean_per_position.append(frame)
|
|
225
|
+
|
|
226
|
+
if progress_callback:
|
|
227
|
+
progress_callback(
|
|
228
|
+
level="position", iter=l, total=len(positions), stage="estimating"
|
|
229
|
+
)
|
|
230
|
+
|
|
231
|
+
try:
|
|
232
|
+
background = np.nanmedian(frame_mean_per_position, axis=0)
|
|
233
|
+
if progress_callback:
|
|
234
|
+
progress_callback(image_preview=background)
|
|
235
|
+
|
|
236
|
+
if offset is not None:
|
|
237
|
+
# print("The offset is applied to background...")
|
|
238
|
+
background -= offset
|
|
239
|
+
if fix_nan:
|
|
240
|
+
background = interpolate_nan(background.copy().astype(float))
|
|
241
|
+
backgrounds.append({"bg": background, "well": well_path})
|
|
242
|
+
logger.info(f"Background successfully computed for well {well_name}...")
|
|
243
|
+
except Exception as e:
|
|
244
|
+
logger.error(e)
|
|
245
|
+
backgrounds.append(None)
|
|
246
|
+
|
|
247
|
+
return backgrounds
|
|
248
|
+
|
|
165
249
|
|
|
166
250
|
def correct_background_model_free(
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
251
|
+
experiment,
|
|
252
|
+
well_option="*",
|
|
253
|
+
position_option="*",
|
|
254
|
+
target_channel="channel_name",
|
|
255
|
+
mode="timeseries",
|
|
256
|
+
threshold_on_std=1,
|
|
257
|
+
frame_range=[0, 5],
|
|
258
|
+
optimize_option=False,
|
|
259
|
+
opt_coef_range=[0.95, 1.05],
|
|
260
|
+
opt_coef_nbr=100,
|
|
261
|
+
operation="divide",
|
|
262
|
+
clip=False,
|
|
263
|
+
offset=None,
|
|
264
|
+
show_progress_per_well=True,
|
|
265
|
+
show_progress_per_pos=False,
|
|
266
|
+
export=False,
|
|
267
|
+
return_stacks=False,
|
|
268
|
+
movie_prefix=None,
|
|
269
|
+
fix_nan=False,
|
|
270
|
+
activation_protocol=[["gauss", 2], ["std", 4]],
|
|
271
|
+
export_prefix="Corrected",
|
|
272
|
+
progress_callback=None,
|
|
273
|
+
**kwargs,
|
|
274
|
+
):
|
|
275
|
+
"""
|
|
276
|
+
Correct the background of image stacks for a given experiment.
|
|
277
|
+
|
|
278
|
+
This function processes image stacks by estimating and correcting the background
|
|
279
|
+
for each well and position in the experiment. It supports different modes, such
|
|
280
|
+
as timeseries or tiles, and offers options for optimization and exporting the results.
|
|
281
|
+
|
|
282
|
+
Parameters
|
|
283
|
+
----------
|
|
284
|
+
experiment : str
|
|
285
|
+
Path to the experiment configuration.
|
|
286
|
+
well_option : str, int, or list of int, optional
|
|
287
|
+
Selection of wells to process. '*' indicates all wells. Defaults to '*'.
|
|
288
|
+
position_option : str, int, or list of int, optional
|
|
289
|
+
Selection of positions to process within each well. '*' indicates all positions. Defaults to '*'.
|
|
290
|
+
target_channel : str, optional
|
|
291
|
+
The name of the target channel to be corrected. Defaults to "channel_name".
|
|
292
|
+
mode : {'timeseries', 'tiles'}, optional
|
|
293
|
+
The mode of processing. Defaults to "timeseries".
|
|
294
|
+
threshold_on_std : float, optional
|
|
295
|
+
The threshold for the standard deviation filter to identify high-variance areas. Defaults to 1.
|
|
296
|
+
frame_range : list of int, optional
|
|
297
|
+
The range of frames to consider for background estimation. Defaults to [0, 5].
|
|
298
|
+
optimize_option : bool, optional
|
|
299
|
+
If True, optimize the correction coefficient. Defaults to False.
|
|
300
|
+
opt_coef_range : list of float, optional
|
|
301
|
+
The range of coefficients to try for optimization. Defaults to [0.95, 1.05].
|
|
302
|
+
opt_coef_nbr : int, optional
|
|
303
|
+
The number of coefficients to test within the optimization range. Defaults to 100.
|
|
304
|
+
operation : {'divide', 'subtract'}, optional
|
|
305
|
+
The operation to apply for background correction. Defaults to 'divide'.
|
|
306
|
+
clip : bool, optional
|
|
307
|
+
If True, clip the corrected values to be non-negative when using subtraction. Defaults to False.
|
|
308
|
+
show_progress_per_well : bool, optional
|
|
309
|
+
If True, show progress bar for each well. Defaults to True.
|
|
310
|
+
show_progress_per_pos : bool, optional
|
|
311
|
+
If True, show progress bar for each position. Defaults to False.
|
|
312
|
+
export : bool, optional
|
|
313
|
+
If True, export the corrected stacks to files. Defaults to False.
|
|
314
|
+
return_stacks : bool, optional
|
|
315
|
+
If True, return the corrected stacks as a list of numpy arrays. Defaults to False.
|
|
316
|
+
progress_callback : callable, optional
|
|
317
|
+
A callback function to be called at each step of the process (default is None).
|
|
318
|
+
|
|
319
|
+
Returns
|
|
320
|
+
-------
|
|
321
|
+
list of numpy.ndarray, optional
|
|
322
|
+
A list of corrected image stacks if `return_stacks` is True.
|
|
323
|
+
|
|
324
|
+
Notes
|
|
325
|
+
-----
|
|
326
|
+
The function uses several helper functions, including `interpret_wells_and_positions`,
|
|
327
|
+
`estimate_background_per_condition`, and `apply_background_to_stack`.
|
|
328
|
+
|
|
329
|
+
Examples
|
|
330
|
+
--------
|
|
331
|
+
>>> experiment = "path/to/experiment/config"
|
|
332
|
+
>>> corrected_stacks = correct_background(experiment, well_option=[0, 1], position_option='*', target_channel="DAPI", mode="timeseries", threshold_on_std=2, frame_range=[0, 10], optimize_option=True, operation='subtract', clip=True, return_stacks=True)
|
|
333
|
+
>>> print(len(corrected_stacks))
|
|
334
|
+
2
|
|
335
|
+
|
|
336
|
+
"""
|
|
337
|
+
|
|
338
|
+
config = get_config(experiment)
|
|
339
|
+
wells = get_experiment_wells(experiment)
|
|
340
|
+
len_movie = float(config_section_to_dict(config, "MovieSettings")["len_movie"])
|
|
341
|
+
if movie_prefix is None:
|
|
342
|
+
movie_prefix = config_section_to_dict(config, "MovieSettings")["movie_prefix"]
|
|
343
|
+
|
|
344
|
+
well_indices, position_indices = interpret_wells_and_positions(
|
|
345
|
+
experiment, well_option, position_option
|
|
346
|
+
)
|
|
347
|
+
channel_indices = _extract_channel_indices_from_config(config, [target_channel])
|
|
348
|
+
nbr_channels = _extract_nbr_channels_from_config(config)
|
|
349
|
+
img_num_channels = _get_img_num_per_channel(
|
|
350
|
+
channel_indices, int(len_movie), nbr_channels
|
|
351
|
+
)
|
|
352
|
+
|
|
353
|
+
stacks = []
|
|
354
|
+
|
|
355
|
+
total_wells = len(wells[well_indices])
|
|
356
|
+
|
|
357
|
+
for k, well_path in enumerate(
|
|
358
|
+
tqdm(wells[well_indices], disable=not show_progress_per_well)
|
|
359
|
+
):
|
|
360
|
+
if progress_callback:
|
|
361
|
+
progress_callback(level="well", iter=k, total=total_wells)
|
|
362
|
+
|
|
363
|
+
well_name, _ = extract_well_name_and_number(well_path)
|
|
364
|
+
|
|
365
|
+
if progress_callback:
|
|
366
|
+
progress_callback(status="Reconstructing background...")
|
|
367
|
+
|
|
368
|
+
try:
|
|
369
|
+
# Estimate background
|
|
370
|
+
background = estimate_background_per_condition(
|
|
371
|
+
experiment,
|
|
372
|
+
threshold_on_std=threshold_on_std,
|
|
373
|
+
well_option=int(well_indices[k]),
|
|
374
|
+
target_channel=target_channel,
|
|
375
|
+
frame_range=frame_range,
|
|
376
|
+
mode=mode,
|
|
377
|
+
show_progress_per_pos=True,
|
|
378
|
+
show_progress_per_well=False,
|
|
379
|
+
activation_protocol=activation_protocol,
|
|
380
|
+
offset=offset,
|
|
381
|
+
fix_nan=fix_nan,
|
|
382
|
+
progress_callback=progress_callback,
|
|
383
|
+
)
|
|
384
|
+
background = background[0]
|
|
385
|
+
background = background["bg"]
|
|
386
|
+
except Exception as e:
|
|
387
|
+
logger.error(
|
|
388
|
+
f'Background could not be estimated due to error "{e}"... Skipping well {well_name}...'
|
|
389
|
+
)
|
|
390
|
+
if progress_callback:
|
|
391
|
+
progress_callback(level="well", iter=k + 1, total=total_wells)
|
|
392
|
+
if progress_callback:
|
|
393
|
+
progress_callback(level="well", iter=k + 1, total=total_wells)
|
|
394
|
+
continue
|
|
395
|
+
|
|
396
|
+
if progress_callback:
|
|
397
|
+
progress_callback(
|
|
398
|
+
level="position", iter=-1, total=1, status="Applying background..."
|
|
399
|
+
)
|
|
400
|
+
|
|
401
|
+
positions = get_positions_in_well(well_path)
|
|
402
|
+
selection = positions[position_indices]
|
|
403
|
+
if isinstance(selection[0], np.ndarray):
|
|
404
|
+
selection = selection[0]
|
|
405
|
+
|
|
406
|
+
total_pos_in_well = len(selection)
|
|
407
|
+
|
|
408
|
+
for pidx, pos_path in enumerate(
|
|
409
|
+
tqdm(selection, disable=not show_progress_per_pos)
|
|
410
|
+
):
|
|
411
|
+
|
|
412
|
+
stack_path = get_position_movie_path(pos_path, prefix=movie_prefix)
|
|
413
|
+
logger.info(
|
|
414
|
+
f"Applying the correction to position {extract_position_name(pos_path)}..."
|
|
415
|
+
)
|
|
416
|
+
if stack_path is not None:
|
|
417
|
+
len_movie_auto = auto_load_number_of_frames(stack_path)
|
|
418
|
+
if len_movie_auto is not None:
|
|
419
|
+
len_movie = len_movie_auto
|
|
420
|
+
img_num_channels = _get_img_num_per_channel(
|
|
421
|
+
channel_indices, int(len_movie), nbr_channels
|
|
422
|
+
)
|
|
423
|
+
|
|
424
|
+
corrected_stack = apply_background_to_stack(
|
|
425
|
+
stack_path,
|
|
426
|
+
background,
|
|
427
|
+
target_channel_index=channel_indices[0],
|
|
428
|
+
nbr_channels=nbr_channels,
|
|
429
|
+
stack_length=len_movie,
|
|
430
|
+
threshold_on_std=threshold_on_std,
|
|
431
|
+
optimize_option=optimize_option,
|
|
432
|
+
opt_coef_range=opt_coef_range,
|
|
433
|
+
opt_coef_nbr=opt_coef_nbr,
|
|
434
|
+
operation=operation,
|
|
435
|
+
clip=clip,
|
|
436
|
+
offset=offset,
|
|
437
|
+
export=export,
|
|
438
|
+
fix_nan=fix_nan,
|
|
439
|
+
activation_protocol=activation_protocol,
|
|
440
|
+
prefix=export_prefix,
|
|
441
|
+
progress_callback=progress_callback,
|
|
442
|
+
)
|
|
443
|
+
logger.info("Correction successful.")
|
|
444
|
+
if return_stacks:
|
|
445
|
+
stacks.append(corrected_stack)
|
|
446
|
+
else:
|
|
447
|
+
del corrected_stack
|
|
448
|
+
collect()
|
|
449
|
+
else:
|
|
450
|
+
stacks.append(None)
|
|
451
|
+
|
|
452
|
+
if progress_callback:
|
|
453
|
+
progress_callback(
|
|
454
|
+
level="position",
|
|
455
|
+
iter=pidx,
|
|
456
|
+
total=total_pos_in_well,
|
|
457
|
+
stage="correcting",
|
|
458
|
+
)
|
|
459
|
+
|
|
460
|
+
if progress_callback:
|
|
461
|
+
progress_callback(level="well", iter=k + 1, total=total_wells)
|
|
462
|
+
|
|
463
|
+
if return_stacks:
|
|
464
|
+
return stacks
|
|
465
|
+
|
|
466
|
+
|
|
467
|
+
def apply_background_to_stack(
|
|
468
|
+
stack_path,
|
|
469
|
+
background,
|
|
470
|
+
target_channel_index=0,
|
|
471
|
+
nbr_channels=1,
|
|
472
|
+
stack_length=45,
|
|
473
|
+
offset=None,
|
|
474
|
+
activation_protocol=[["gauss", 2], ["std", 4]],
|
|
475
|
+
threshold_on_std=1,
|
|
476
|
+
optimize_option=True,
|
|
477
|
+
opt_coef_range=(0.95, 1.05),
|
|
478
|
+
opt_coef_nbr=100,
|
|
479
|
+
operation="divide",
|
|
480
|
+
clip=False,
|
|
481
|
+
export=False,
|
|
482
|
+
prefix="Corrected",
|
|
483
|
+
fix_nan=False,
|
|
484
|
+
progress_callback=None,
|
|
485
|
+
):
|
|
486
|
+
"""
|
|
487
|
+
Apply background correction to an image stack.
|
|
488
|
+
|
|
489
|
+
This function corrects the background of an image stack by applying a specified operation
|
|
490
|
+
(either division or subtraction) between the image stack and the background. It also supports
|
|
491
|
+
optimization of the correction coefficient through brute-force regression.
|
|
492
|
+
|
|
493
|
+
Parameters
|
|
494
|
+
----------
|
|
495
|
+
stack_path : str
|
|
496
|
+
The path to the image stack file.
|
|
497
|
+
background : numpy.ndarray
|
|
498
|
+
The background image to be applied for correction.
|
|
499
|
+
target_channel_index : int, optional
|
|
500
|
+
The index of the target channel to be corrected. Defaults to 0.
|
|
501
|
+
nbr_channels : int, optional
|
|
502
|
+
The number of channels in the image stack. Defaults to 1.
|
|
503
|
+
stack_length : int, optional
|
|
504
|
+
The length of the image stack (number of frames). If None, the length is auto-detected. Defaults to 45.
|
|
505
|
+
threshold_on_std : float, optional
|
|
506
|
+
The threshold for the standard deviation filter to identify high-variance areas. Defaults to 1.
|
|
507
|
+
optimize_option : bool, optional
|
|
508
|
+
If True, optimize the correction coefficient using a range of values. Defaults to True.
|
|
509
|
+
opt_coef_range : tuple of float, optional
|
|
510
|
+
The range of coefficients to try for optimization. Defaults to (0.95, 1.05).
|
|
511
|
+
opt_coef_nbr : int, optional
|
|
512
|
+
The number of coefficients to test within the optimization range. Defaults to 100.
|
|
513
|
+
operation : {'divide', 'subtract'}, optional
|
|
514
|
+
The operation to apply for background correction. Defaults to 'divide'.
|
|
515
|
+
clip : bool, optional
|
|
516
|
+
If True, clip the corrected values to be non-negative when using subtraction. Defaults to False.
|
|
517
|
+
export : bool, optional
|
|
518
|
+
If True, export the corrected stack to a file. Defaults to False.
|
|
519
|
+
prefix : str, optional
|
|
520
|
+
The prefix for the exported file name. Defaults to "Corrected".
|
|
521
|
+
progress_callback : callable, optional
|
|
522
|
+
A callback function to be called at each step of the process (default is None).
|
|
523
|
+
|
|
524
|
+
Returns
|
|
525
|
+
-------
|
|
526
|
+
corrected_stack : numpy.ndarray
|
|
527
|
+
The background-corrected image stack.
|
|
528
|
+
|
|
529
|
+
Examples
|
|
530
|
+
--------
|
|
531
|
+
>>> stack_path = "path/to/stack.tif"
|
|
532
|
+
>>> background = np.zeros((512, 512)) # Example background
|
|
533
|
+
>>> corrected_stack = apply_background_to_stack(stack_path, background, target_channel_index=0, nbr_channels=3, stack_length=45, optimize_option=False, operation='subtract', clip=True)
|
|
534
|
+
>>> print(corrected_stack.shape)
|
|
535
|
+
(44, 512, 512, 3)
|
|
536
|
+
|
|
537
|
+
"""
|
|
538
|
+
import os
|
|
539
|
+
import numpy as np
|
|
540
|
+
|
|
541
|
+
if stack_length is None:
|
|
542
|
+
stack_length = auto_load_number_of_frames(stack_path)
|
|
543
|
+
if stack_length is None:
|
|
544
|
+
logger.error("stack length not provided")
|
|
545
|
+
return None
|
|
546
|
+
|
|
547
|
+
if optimize_option:
|
|
548
|
+
coefficients = np.linspace(
|
|
549
|
+
opt_coef_range[0], opt_coef_range[1], int(opt_coef_nbr)
|
|
550
|
+
)
|
|
551
|
+
coefficients = np.append(coefficients, [1.0])
|
|
552
|
+
if export:
|
|
553
|
+
path, file = os.path.split(stack_path)
|
|
554
|
+
if prefix is None:
|
|
555
|
+
newfile = file
|
|
556
|
+
else:
|
|
557
|
+
newfile = "_".join([prefix, file])
|
|
558
|
+
|
|
559
|
+
corrected_stack = []
|
|
560
|
+
|
|
561
|
+
for i in range(0, int(stack_length * nbr_channels), nbr_channels):
|
|
562
|
+
|
|
563
|
+
frames = load_frames(
|
|
564
|
+
list(np.arange(i, (i + nbr_channels))), stack_path, normalize_input=False
|
|
565
|
+
).astype(float)
|
|
566
|
+
target_img = frames[:, :, target_channel_index].copy()
|
|
567
|
+
if offset is not None:
|
|
568
|
+
# print(f"The offset is applied to image...")
|
|
569
|
+
target_img -= offset
|
|
570
|
+
|
|
571
|
+
if optimize_option:
|
|
572
|
+
|
|
573
|
+
target_copy = target_img.copy()
|
|
574
|
+
|
|
575
|
+
from celldetective.segmentation import threshold_image
|
|
576
|
+
from celldetective.filters import filter_image
|
|
577
|
+
|
|
578
|
+
std_frame = filter_image(target_copy.copy(), filters=activation_protocol)
|
|
579
|
+
edge = estimate_unreliable_edge(activation_protocol)
|
|
580
|
+
mask = threshold_image(
|
|
581
|
+
std_frame,
|
|
582
|
+
threshold_on_std,
|
|
583
|
+
np.inf,
|
|
584
|
+
foreground_value=1,
|
|
585
|
+
edge_exclusion=edge,
|
|
586
|
+
)
|
|
587
|
+
target_copy[np.where(mask.astype(int) == 1)] = np.nan
|
|
588
|
+
|
|
589
|
+
loss = []
|
|
590
|
+
|
|
591
|
+
# brute-force regression, could do gradient descent instead
|
|
592
|
+
for c in coefficients:
|
|
593
|
+
|
|
594
|
+
target_crop = unpad(target_copy, edge)
|
|
595
|
+
bg_crop = unpad(background, edge)
|
|
596
|
+
|
|
597
|
+
roi = np.zeros_like(target_crop).astype(int)
|
|
598
|
+
roi[target_crop != target_crop] = 1
|
|
599
|
+
roi[bg_crop != bg_crop] = 1
|
|
600
|
+
|
|
601
|
+
diff = np.subtract(target_crop, c * bg_crop, where=roi == 0)
|
|
602
|
+
s = np.sum(np.abs(diff, where=roi == 0), where=roi == 0)
|
|
603
|
+
loss.append(s)
|
|
604
|
+
|
|
605
|
+
c = coefficients[np.argmin(loss)]
|
|
606
|
+
logger.info(f"IFD {i}; optimal coefficient: {c}...")
|
|
607
|
+
# if c==min(coefficients) or c==max(coefficients):
|
|
608
|
+
# print('Warning... The optimal coefficient is beyond the range provided... Please adjust your coefficient range...')
|
|
609
|
+
else:
|
|
610
|
+
c = 1
|
|
611
|
+
|
|
612
|
+
if operation == "divide":
|
|
613
|
+
correction = np.divide(
|
|
614
|
+
target_img, background * c, where=background == background
|
|
615
|
+
)
|
|
616
|
+
correction[background != background] = np.nan
|
|
617
|
+
correction[target_img != target_img] = np.nan
|
|
618
|
+
|
|
619
|
+
elif operation == "subtract":
|
|
620
|
+
correction = np.subtract(
|
|
621
|
+
target_img, background * c, where=background == background
|
|
622
|
+
)
|
|
623
|
+
correction[background != background] = np.nan
|
|
624
|
+
correction[target_img != target_img] = np.nan
|
|
625
|
+
if clip:
|
|
626
|
+
correction[correction <= 0.0] = 0.0
|
|
627
|
+
else:
|
|
628
|
+
logger.error("Operation not supported... Abort.")
|
|
629
|
+
return
|
|
630
|
+
|
|
631
|
+
correction[~np.isfinite(correction)] = np.nan
|
|
632
|
+
if fix_nan:
|
|
633
|
+
correction = interpolate_nan(correction.copy())
|
|
634
|
+
frames[:, :, target_channel_index] = correction
|
|
635
|
+
corrected_stack.append(frames)
|
|
636
|
+
|
|
637
|
+
if progress_callback:
|
|
638
|
+
progress_callback(
|
|
639
|
+
level="frame",
|
|
640
|
+
iter=i,
|
|
641
|
+
total=int(stack_length * nbr_channels),
|
|
642
|
+
stage="correcting",
|
|
643
|
+
)
|
|
644
|
+
|
|
645
|
+
corrected_stack = np.array(corrected_stack)
|
|
646
|
+
|
|
647
|
+
if export:
|
|
648
|
+
from celldetective.utils.io import save_tiff_imagej_compatible
|
|
649
|
+
|
|
650
|
+
save_tiff_imagej_compatible(
|
|
651
|
+
os.sep.join([path, newfile]), corrected_stack, axes="TYXC"
|
|
652
|
+
)
|
|
653
|
+
|
|
654
|
+
return corrected_stack
|
|
461
655
|
|
|
462
|
-
def paraboloid(x, y, a, b, c, d, e, g):
|
|
463
656
|
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
657
|
+
def paraboloid(x, y, a, b, c, d, e, g):
|
|
658
|
+
"""
|
|
659
|
+
Compute the value of a 2D paraboloid function.
|
|
660
|
+
|
|
661
|
+
This function evaluates a paraboloid defined by the equation:
|
|
662
|
+
`a * x ** 2 + b * y ** 2 + c * x * y + d * x + e * y + g`.
|
|
663
|
+
|
|
664
|
+
Parameters
|
|
665
|
+
----------
|
|
666
|
+
x : float or ndarray
|
|
667
|
+
The x-coordinate(s) at which to evaluate the paraboloid.
|
|
668
|
+
y : float or ndarray
|
|
669
|
+
The y-coordinate(s) at which to evaluate the paraboloid.
|
|
670
|
+
a : float
|
|
671
|
+
The coefficient of the x^2 term.
|
|
672
|
+
b : float
|
|
673
|
+
The coefficient of the y^2 term.
|
|
674
|
+
c : float
|
|
675
|
+
The coefficient of the x*y term.
|
|
676
|
+
d : float
|
|
677
|
+
The coefficient of the x term.
|
|
678
|
+
e : float
|
|
679
|
+
The coefficient of the y term.
|
|
680
|
+
g : float
|
|
681
|
+
The constant term.
|
|
682
|
+
|
|
683
|
+
Returns
|
|
684
|
+
-------
|
|
685
|
+
float or ndarray
|
|
686
|
+
The value of the paraboloid at the given (x, y) coordinates. If `x` and
|
|
687
|
+
`y` are arrays, the result is an array of the same shape.
|
|
688
|
+
|
|
689
|
+
Examples
|
|
690
|
+
--------
|
|
691
|
+
>>> paraboloid(1, 2, 1, 1, 0, 0, 0, 0)
|
|
692
|
+
5
|
|
693
|
+
>>> paraboloid(np.array([1, 2]), np.array([3, 4]), 1, 1, 0, 0, 0, 0)
|
|
694
|
+
array([10, 20])
|
|
695
|
+
|
|
696
|
+
Notes
|
|
697
|
+
-----
|
|
698
|
+
The paraboloid function is a quadratic function in two variables, commonly used
|
|
699
|
+
to model surfaces in three-dimensional space.
|
|
700
|
+
"""
|
|
701
|
+
|
|
702
|
+
return a * x**2 + b * y**2 + c * x * y + d * x + e * y + g
|
|
509
703
|
|
|
510
704
|
|
|
511
705
|
def plane(x, y, a, b, c):
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
return a * x + b * y + c
|
|
706
|
+
"""
|
|
707
|
+
Compute the value of a plane function.
|
|
708
|
+
|
|
709
|
+
This function evaluates a plane defined by the equation:
|
|
710
|
+
`a * x + b * y + c`.
|
|
711
|
+
|
|
712
|
+
Parameters
|
|
713
|
+
----------
|
|
714
|
+
x : float or ndarray
|
|
715
|
+
The x-coordinate(s) at which to evaluate the plane.
|
|
716
|
+
y : float or ndarray
|
|
717
|
+
The y-coordinate(s) at which to evaluate the plane.
|
|
718
|
+
a : float
|
|
719
|
+
The coefficient of the x term.
|
|
720
|
+
b : float
|
|
721
|
+
The coefficient of the y term.
|
|
722
|
+
c : float
|
|
723
|
+
The constant term.
|
|
724
|
+
|
|
725
|
+
Returns
|
|
726
|
+
-------
|
|
727
|
+
float or ndarray
|
|
728
|
+
The value of the plane at the given (x, y) coordinates. If `x` and
|
|
729
|
+
`y` are arrays, the result is an array of the same shape.
|
|
730
|
+
|
|
731
|
+
Examples
|
|
732
|
+
--------
|
|
733
|
+
>>> plane(1, 2, 3, 4, 5)
|
|
734
|
+
16
|
|
735
|
+
>>> plane(np.array([1, 2]), np.array([3, 4]), 3, 4, 5)
|
|
736
|
+
array([20, 27])
|
|
737
|
+
|
|
738
|
+
Notes
|
|
739
|
+
-----
|
|
740
|
+
The plane function is a linear function in two variables, commonly used
|
|
741
|
+
to model flat surfaces in three-dimensional space.
|
|
742
|
+
"""
|
|
743
|
+
|
|
744
|
+
return a * x + b * y + c
|
|
552
745
|
|
|
553
746
|
|
|
554
747
|
def fit_plane(image, cell_masks=None, edge_exclusion=None):
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
748
|
+
"""
|
|
749
|
+
Fit a plane to the given image data.
|
|
750
|
+
|
|
751
|
+
This function fits a plane to the provided image data using least squares
|
|
752
|
+
regression. It constructs a mesh grid based on the dimensions of the image
|
|
753
|
+
and fits a plane model to the data points. If cell masks are provided,
|
|
754
|
+
areas covered by cell masks will be excluded from the fitting process.
|
|
755
|
+
|
|
756
|
+
Parameters
|
|
757
|
+
----------
|
|
758
|
+
image : numpy.ndarray
|
|
759
|
+
The input image data.
|
|
760
|
+
cell_masks : numpy.ndarray, optional
|
|
761
|
+
An array specifying cell masks. If provided, areas covered by cell masks
|
|
762
|
+
will be excluded from the fitting process (default is None).
|
|
763
|
+
edge_exclusion : int, optional
|
|
764
|
+
The size of the edge to exclude from the fitting process (default is None).
|
|
765
|
+
|
|
766
|
+
Returns
|
|
767
|
+
-------
|
|
768
|
+
numpy.ndarray
|
|
769
|
+
The fitted plane.
|
|
770
|
+
|
|
771
|
+
Notes
|
|
772
|
+
-----
|
|
773
|
+
- The `cell_masks` parameter allows excluding areas covered by cell masks from
|
|
774
|
+
the fitting process.
|
|
775
|
+
- The `edge_exclusion` parameter allows excluding edges of the specified size
|
|
776
|
+
from the fitting process to avoid boundary effects.
|
|
777
|
+
|
|
778
|
+
See Also
|
|
779
|
+
--------
|
|
780
|
+
plane : The plane function used for fitting.
|
|
781
|
+
"""
|
|
782
|
+
|
|
783
|
+
data = np.empty(image.shape)
|
|
784
|
+
x = np.arange(0, image.shape[1])
|
|
785
|
+
y = np.arange(0, image.shape[0])
|
|
786
|
+
xx, yy = np.meshgrid(x, y)
|
|
787
|
+
|
|
788
|
+
from lmfit import Parameters, Model
|
|
789
|
+
|
|
790
|
+
params = Parameters()
|
|
791
|
+
params.add("a", value=1)
|
|
792
|
+
params.add("b", value=1)
|
|
793
|
+
params.add("c", value=1)
|
|
794
|
+
|
|
795
|
+
model = Model(plane, independent_vars=["x", "y"])
|
|
796
|
+
|
|
797
|
+
weights = np.ones_like(xx, dtype=float)
|
|
798
|
+
if cell_masks is not None:
|
|
799
|
+
weights[np.where(cell_masks > 0)] = 0.0
|
|
800
|
+
|
|
801
|
+
if edge_exclusion is not None:
|
|
802
|
+
xx = unpad(xx, edge_exclusion)
|
|
803
|
+
yy = unpad(yy, edge_exclusion)
|
|
804
|
+
weights = unpad(weights, edge_exclusion)
|
|
805
|
+
image = unpad(image, edge_exclusion)
|
|
806
|
+
|
|
807
|
+
result = model.fit(image, x=xx, y=yy, weights=weights, params=params, max_nfev=3000)
|
|
808
|
+
del model
|
|
809
|
+
collect()
|
|
810
|
+
|
|
811
|
+
xx, yy = np.meshgrid(x, y)
|
|
812
|
+
|
|
813
|
+
return plane(xx, yy, **result.params)
|
|
814
|
+
|
|
815
|
+
|
|
816
|
+
def fit_paraboloid(image, cell_masks=None, edge_exclusion=None, downsample=10):
|
|
817
|
+
"""
|
|
818
|
+
Fit a paraboloid to the given image data.
|
|
819
|
+
|
|
820
|
+
This function fits a paraboloid to the provided image data using least squares
|
|
821
|
+
regression. It constructs a mesh grid based on the dimensions of the image
|
|
822
|
+
and fits a paraboloid model to the data points. If cell masks are provided,
|
|
823
|
+
areas covered by cell masks will be excluded from the fitting process.
|
|
824
|
+
|
|
825
|
+
Parameters
|
|
826
|
+
----------
|
|
827
|
+
image : numpy.ndarray
|
|
828
|
+
The input image data.
|
|
829
|
+
cell_masks : numpy.ndarray, optional
|
|
830
|
+
An array specifying cell masks. If provided, areas covered by cell masks
|
|
831
|
+
will be excluded from the fitting process (default is None).
|
|
832
|
+
edge_exclusion : int, optional
|
|
833
|
+
The size of the edge to exclude from the fitting process (default is None).
|
|
834
|
+
downsample : int, optional
|
|
835
|
+
The downsampling factor to reduce the number of points used for fitting.
|
|
836
|
+
Default is 10.
|
|
837
|
+
|
|
838
|
+
Returns
|
|
839
|
+
-------
|
|
840
|
+
numpy.ndarray
|
|
841
|
+
The fitted paraboloid.
|
|
842
|
+
|
|
843
|
+
Notes
|
|
844
|
+
-----
|
|
845
|
+
- The `cell_masks` parameter allows excluding areas covered by cell masks from
|
|
846
|
+
the fitting process.
|
|
847
|
+
- The `edge_exclusion` parameter allows excluding edges of the specified size
|
|
848
|
+
from the fitting process to avoid boundary effects.
|
|
849
|
+
- Downsampling significantly speeds up the fitting process for large images
|
|
850
|
+
without compromising the accuracy of the low-frequency background estimate.
|
|
851
|
+
|
|
852
|
+
See Also
|
|
853
|
+
--------
|
|
854
|
+
paraboloid : The paraboloid function used for fitting.
|
|
855
|
+
"""
|
|
856
|
+
|
|
857
|
+
data = np.empty(image.shape)
|
|
858
|
+
x = np.arange(0, image.shape[1])
|
|
859
|
+
y = np.arange(0, image.shape[0])
|
|
860
|
+
xx, yy = np.meshgrid(x, y)
|
|
861
|
+
|
|
862
|
+
from lmfit import Parameters, Model
|
|
863
|
+
|
|
864
|
+
params = Parameters()
|
|
865
|
+
params.add("a", value=1.0e-05)
|
|
866
|
+
params.add("b", value=1.0e-05)
|
|
867
|
+
params.add("c", value=1.0e-06)
|
|
868
|
+
params.add("d", value=0.01)
|
|
869
|
+
params.add("e", value=0.01)
|
|
870
|
+
params.add("g", value=100)
|
|
871
|
+
|
|
872
|
+
model = Model(paraboloid, independent_vars=["x", "y"])
|
|
873
|
+
|
|
874
|
+
weights = np.ones_like(xx, dtype=float)
|
|
875
|
+
if cell_masks is not None:
|
|
876
|
+
weights[np.where(cell_masks > 0)] = 0.0
|
|
877
|
+
|
|
878
|
+
if edge_exclusion is not None:
|
|
879
|
+
xx = unpad(xx, edge_exclusion)
|
|
880
|
+
yy = unpad(yy, edge_exclusion)
|
|
881
|
+
weights = unpad(weights, edge_exclusion)
|
|
882
|
+
image = unpad(image, edge_exclusion)
|
|
883
|
+
|
|
884
|
+
# Downsample for faster fitting
|
|
885
|
+
if downsample > 1:
|
|
886
|
+
image_fit = image[::downsample, ::downsample]
|
|
887
|
+
xx_fit = xx[::downsample, ::downsample]
|
|
888
|
+
yy_fit = yy[::downsample, ::downsample]
|
|
889
|
+
weights_fit = weights[::downsample, ::downsample]
|
|
890
|
+
else:
|
|
891
|
+
image_fit = image
|
|
892
|
+
xx_fit = xx
|
|
893
|
+
yy_fit = yy
|
|
894
|
+
weights_fit = weights
|
|
895
|
+
|
|
896
|
+
result = model.fit(
|
|
897
|
+
image_fit, x=xx_fit, y=yy_fit, weights=weights_fit, params=params, max_nfev=3000
|
|
898
|
+
)
|
|
899
|
+
|
|
900
|
+
del model
|
|
901
|
+
collect()
|
|
902
|
+
|
|
903
|
+
xx, yy = np.meshgrid(x, y)
|
|
904
|
+
|
|
905
|
+
return paraboloid(xx, yy, **result.params)
|
|
700
906
|
|
|
701
907
|
|
|
702
908
|
def correct_background_model(
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
909
|
+
experiment,
|
|
910
|
+
well_option="*",
|
|
911
|
+
position_option="*",
|
|
912
|
+
target_channel="channel_name",
|
|
913
|
+
threshold_on_std=1,
|
|
914
|
+
model="paraboloid",
|
|
915
|
+
operation="divide",
|
|
916
|
+
clip=False,
|
|
917
|
+
show_progress_per_well=True,
|
|
918
|
+
show_progress_per_pos=False,
|
|
919
|
+
export=False,
|
|
920
|
+
return_stacks=False,
|
|
921
|
+
movie_prefix=None,
|
|
922
|
+
activation_protocol=[["gauss", 2], ["std", 4]],
|
|
923
|
+
export_prefix="Corrected",
|
|
924
|
+
return_stack=True,
|
|
925
|
+
progress_callback=None,
|
|
926
|
+
downsample=10,
|
|
927
|
+
**kwargs,
|
|
928
|
+
):
|
|
929
|
+
"""
|
|
930
|
+
Correct background in image stacks using a specified model.
|
|
931
|
+
|
|
932
|
+
This function corrects the background in image stacks obtained from an experiment
|
|
933
|
+
using a specified background correction model. It supports various options for
|
|
934
|
+
specifying wells, positions, target channel, and background correction parameters.
|
|
935
|
+
|
|
936
|
+
Parameters
|
|
937
|
+
----------
|
|
938
|
+
experiment : str
|
|
939
|
+
The path to the experiment directory.
|
|
940
|
+
well_option : str, optional
|
|
941
|
+
The option to select specific wells (default is '*').
|
|
942
|
+
position_option : str, optional
|
|
943
|
+
The option to select specific positions (default is '*').
|
|
944
|
+
target_channel : str, optional
|
|
945
|
+
The name of the target channel for background correction (default is "channel_name").
|
|
946
|
+
threshold_on_std : float, optional
|
|
947
|
+
The threshold value on the standard deviation for masking (default is 1).
|
|
948
|
+
model : str, optional
|
|
949
|
+
The background correction model to use, either 'paraboloid' or 'plane' (default is 'paraboloid').
|
|
950
|
+
operation : str, optional
|
|
951
|
+
The operation to apply for background correction, either 'divide' or 'subtract' (default is 'divide').
|
|
952
|
+
clip : bool, optional
|
|
953
|
+
Whether to clip the corrected image to ensure non-negative values (default is False).
|
|
954
|
+
show_progress_per_well : bool, optional
|
|
955
|
+
Whether to show progress for each well (default is True).
|
|
956
|
+
show_progress_per_pos : bool, optional
|
|
957
|
+
Whether to show progress for each position (default is False).
|
|
958
|
+
export : bool, optional
|
|
959
|
+
Whether to export the corrected stacks (default is False).
|
|
960
|
+
return_stacks : bool, optional
|
|
961
|
+
Whether to return the corrected stacks (default is False).
|
|
962
|
+
movie_prefix : str, optional
|
|
963
|
+
The prefix for the movie files (default is None).
|
|
964
|
+
activation_protocol : list of list, optional
|
|
965
|
+
The activation protocol consisting of filters and their respective parameters (default is [['gauss',2],['std',4]]).
|
|
966
|
+
export_prefix : str, optional
|
|
967
|
+
The prefix for exported corrected stacks (default is 'Corrected').
|
|
968
|
+
**kwargs : dict
|
|
969
|
+
Additional keyword arguments to be passed to the underlying correction function.
|
|
970
|
+
|
|
971
|
+
Returns
|
|
972
|
+
-------
|
|
973
|
+
list of numpy.ndarray
|
|
974
|
+
A list of corrected image stacks if `return_stacks` is True, otherwise None.
|
|
975
|
+
|
|
976
|
+
Notes
|
|
977
|
+
-----
|
|
978
|
+
- This function assumes that the experiment directory structure and the configuration
|
|
979
|
+
files follow a specific format expected by the helper functions used within.
|
|
980
|
+
- Supported background correction models are 'paraboloid' and 'plane'.
|
|
981
|
+
- Supported background correction operations are 'divide' and 'subtract'.
|
|
982
|
+
|
|
983
|
+
See Also
|
|
984
|
+
--------
|
|
985
|
+
fit_and_apply_model_background_to_stack : Function to fit and apply background correction to an image stack.
|
|
986
|
+
"""
|
|
987
|
+
|
|
988
|
+
config = get_config(experiment)
|
|
989
|
+
wells = get_experiment_wells(experiment)
|
|
990
|
+
len_movie = float(config_section_to_dict(config, "MovieSettings")["len_movie"])
|
|
991
|
+
if movie_prefix is None:
|
|
992
|
+
movie_prefix = config_section_to_dict(config, "MovieSettings")["movie_prefix"]
|
|
993
|
+
|
|
994
|
+
well_indices, position_indices = interpret_wells_and_positions(
|
|
995
|
+
experiment, well_option, position_option
|
|
996
|
+
)
|
|
997
|
+
channel_indices = _extract_channel_indices_from_config(config, [target_channel])
|
|
998
|
+
nbr_channels = _extract_nbr_channels_from_config(config)
|
|
999
|
+
img_num_channels = _get_img_num_per_channel(
|
|
1000
|
+
channel_indices, int(len_movie), nbr_channels
|
|
1001
|
+
)
|
|
1002
|
+
|
|
1003
|
+
stacks = []
|
|
1004
|
+
|
|
1005
|
+
total_wells = len(wells[well_indices])
|
|
1006
|
+
for k, well_path in enumerate(
|
|
1007
|
+
tqdm(wells[well_indices], disable=not show_progress_per_well)
|
|
1008
|
+
):
|
|
1009
|
+
if progress_callback:
|
|
1010
|
+
progress_callback(level="well", iter=k, total=total_wells)
|
|
1011
|
+
|
|
1012
|
+
well_name, _ = extract_well_name_and_number(well_path)
|
|
1013
|
+
positions = get_positions_in_well(well_path)
|
|
1014
|
+
selection = positions[position_indices]
|
|
1015
|
+
if isinstance(selection[0], np.ndarray):
|
|
1016
|
+
selection = selection[0]
|
|
1017
|
+
|
|
1018
|
+
total_pos_in_well = len(selection)
|
|
1019
|
+
|
|
1020
|
+
for pidx, pos_path in enumerate(
|
|
1021
|
+
tqdm(selection, disable=not show_progress_per_pos)
|
|
1022
|
+
):
|
|
1023
|
+
|
|
1024
|
+
stack_path = get_position_movie_path(pos_path, prefix=movie_prefix)
|
|
1025
|
+
if stack_path is None:
|
|
1026
|
+
logger.warning(f"No stack could be found in {pos_path}... Skip...")
|
|
1027
|
+
continue
|
|
1028
|
+
|
|
1029
|
+
logger.info(
|
|
1030
|
+
f"Applying the correction to position {extract_position_name(pos_path)}..."
|
|
1031
|
+
)
|
|
1032
|
+
len_movie_auto = auto_load_number_of_frames(stack_path)
|
|
1033
|
+
if len_movie_auto is not None:
|
|
1034
|
+
len_movie = len_movie_auto
|
|
1035
|
+
img_num_channels = _get_img_num_per_channel(
|
|
1036
|
+
channel_indices, int(len_movie), nbr_channels
|
|
1037
|
+
)
|
|
1038
|
+
|
|
1039
|
+
corrected_stack = fit_and_apply_model_background_to_stack(
|
|
1040
|
+
stack_path,
|
|
1041
|
+
target_channel_index=channel_indices[0],
|
|
1042
|
+
model=model,
|
|
1043
|
+
nbr_channels=nbr_channels,
|
|
1044
|
+
stack_length=len_movie,
|
|
1045
|
+
threshold_on_std=threshold_on_std,
|
|
1046
|
+
operation=operation,
|
|
1047
|
+
clip=clip,
|
|
1048
|
+
export=export,
|
|
1049
|
+
prefix=export_prefix,
|
|
1050
|
+
activation_protocol=activation_protocol,
|
|
1051
|
+
return_stacks=return_stacks,
|
|
1052
|
+
progress_callback=progress_callback,
|
|
1053
|
+
downsample=downsample,
|
|
1054
|
+
subset_indices=kwargs.get("subset_indices", None),
|
|
1055
|
+
)
|
|
1056
|
+
logger.info("Correction successful.")
|
|
1057
|
+
if return_stacks:
|
|
1058
|
+
stacks.append(corrected_stack)
|
|
1059
|
+
else:
|
|
1060
|
+
del corrected_stack
|
|
1061
|
+
collect()
|
|
1062
|
+
|
|
1063
|
+
if progress_callback:
|
|
1064
|
+
progress_callback(
|
|
1065
|
+
level="position",
|
|
1066
|
+
iter=pidx,
|
|
1067
|
+
total=total_pos_in_well,
|
|
1068
|
+
stage="correcting",
|
|
1069
|
+
)
|
|
1070
|
+
|
|
1071
|
+
if progress_callback:
|
|
1072
|
+
progress_callback(level="well", iter=k + 1, total=total_wells)
|
|
1073
|
+
|
|
1074
|
+
if return_stacks:
|
|
1075
|
+
return stacks
|
|
1076
|
+
|
|
1077
|
+
|
|
1078
|
+
def fit_and_apply_model_background_to_stack(
|
|
1079
|
+
stack_path,
|
|
1080
|
+
target_channel_index=0,
|
|
1081
|
+
nbr_channels=1,
|
|
1082
|
+
stack_length=45,
|
|
1083
|
+
threshold_on_std=1,
|
|
1084
|
+
operation="divide",
|
|
1085
|
+
model="paraboloid",
|
|
1086
|
+
clip=False,
|
|
1087
|
+
export=False,
|
|
1088
|
+
activation_protocol=[["gauss", 2], ["std", 4]],
|
|
1089
|
+
prefix="Corrected",
|
|
1090
|
+
return_stacks=True,
|
|
1091
|
+
progress_callback=None,
|
|
1092
|
+
downsample=10,
|
|
1093
|
+
subset_indices=None,
|
|
1094
|
+
):
|
|
1095
|
+
"""
|
|
1096
|
+
Fit and apply a background correction model to an image stack.
|
|
1097
|
+
|
|
1098
|
+
This function fits a background correction model to each frame of the image stack
|
|
1099
|
+
and applies the correction accordingly. It supports various options for specifying
|
|
1100
|
+
the target channel, number of channels, stack length, threshold on standard deviation,
|
|
1101
|
+
correction operation, correction model, clipping, and export.
|
|
1102
|
+
|
|
1103
|
+
Parameters
|
|
1104
|
+
----------
|
|
1105
|
+
stack_path : str
|
|
1106
|
+
The path to the image stack.
|
|
1107
|
+
target_channel_index : int, optional
|
|
1108
|
+
The index of the target channel for background correction (default is 0).
|
|
1109
|
+
nbr_channels : int, optional
|
|
1110
|
+
The number of channels in the image stack (default is 1).
|
|
1111
|
+
subset_indices : list of int, optional
|
|
1112
|
+
List of absolute frame indices to process (default is None).
|
|
1113
|
+
stack_length : int, optional
|
|
1114
|
+
The length of the stack (default is 45).
|
|
1115
|
+
threshold_on_std : float, optional
|
|
1116
|
+
The threshold value on the standard deviation for masking (default is 1).
|
|
1117
|
+
operation : str, optional
|
|
1118
|
+
The operation to apply for background correction, either 'divide' or 'subtract' (default is 'divide').
|
|
1119
|
+
model : str, optional
|
|
1120
|
+
The background correction model to use, either 'paraboloid' or 'plane' (default is 'paraboloid').
|
|
1121
|
+
clip : bool, optional
|
|
1122
|
+
Whether to clip the corrected image to ensure non-negative values (default is False).
|
|
1123
|
+
export : bool, optional
|
|
1124
|
+
Whether to export the corrected image stack (default is False).
|
|
1125
|
+
activation_protocol : list of list, optional
|
|
1126
|
+
The activation protocol consisting of filters and their respective parameters (default is [['gauss',2],['std',4]]).
|
|
1127
|
+
prefix : str, optional
|
|
1128
|
+
The prefix for exported corrected stacks (default is 'Corrected').
|
|
1129
|
+
subset_indices : list of int, optional
|
|
1130
|
+
List of absolute frame indices to process (default is None).
|
|
1131
|
+
|
|
1132
|
+
Returns
|
|
1133
|
+
-------
|
|
1134
|
+
numpy.ndarray
|
|
1135
|
+
The corrected image stack.
|
|
1136
|
+
|
|
1137
|
+
Notes
|
|
1138
|
+
-----
|
|
1139
|
+
- The function loads frames from the image stack, applies background correction to each frame,
|
|
1140
|
+
and stores the corrected frames in a new stack.
|
|
1141
|
+
- Supported background correction models are 'paraboloid' and 'plane'.
|
|
1142
|
+
- Supported background correction operations are 'divide' and 'subtract'.
|
|
1143
|
+
|
|
1144
|
+
See Also
|
|
1145
|
+
--------
|
|
1146
|
+
field_correction : Function to apply background correction to an image.
|
|
1147
|
+
"""
|
|
1148
|
+
|
|
1149
|
+
from tqdm import tqdm
|
|
1150
|
+
|
|
1151
|
+
stack_length_auto = auto_load_number_of_frames(stack_path)
|
|
1152
|
+
if stack_length_auto is None and stack_length is None:
|
|
1153
|
+
logger.error("Stack length not provided...")
|
|
1154
|
+
return None
|
|
1155
|
+
if stack_length_auto is not None:
|
|
1156
|
+
stack_length = stack_length_auto
|
|
1157
|
+
|
|
1158
|
+
corrected_stack = []
|
|
1159
|
+
|
|
1160
|
+
if export:
|
|
1161
|
+
path, file = os.path.split(stack_path)
|
|
1162
|
+
if prefix is None:
|
|
1163
|
+
newfile = "temp_" + file
|
|
1164
|
+
else:
|
|
1165
|
+
newfile = "_".join([prefix, file])
|
|
1166
|
+
|
|
1167
|
+
import tifffile.tifffile as tiff
|
|
1168
|
+
|
|
1169
|
+
with tiff.TiffWriter(
|
|
1170
|
+
os.sep.join([path, newfile]), imagej=True, bigtiff=True
|
|
1171
|
+
) as tif:
|
|
1172
|
+
|
|
1173
|
+
for i in tqdm(range(0, int(stack_length * nbr_channels), nbr_channels)):
|
|
1174
|
+
|
|
1175
|
+
frames = load_frames(
|
|
1176
|
+
list(np.arange(i, (i + nbr_channels))),
|
|
1177
|
+
stack_path,
|
|
1178
|
+
normalize_input=False,
|
|
1179
|
+
).astype(float)
|
|
1180
|
+
target_img = frames[:, :, target_channel_index].copy()
|
|
1181
|
+
|
|
1182
|
+
correction = field_correction(
|
|
1183
|
+
target_img,
|
|
1184
|
+
threshold=threshold_on_std,
|
|
1185
|
+
operation=operation,
|
|
1186
|
+
model=model,
|
|
1187
|
+
clip=clip,
|
|
1188
|
+
activation_protocol=activation_protocol,
|
|
1189
|
+
downsample=downsample,
|
|
1190
|
+
)
|
|
1191
|
+
frames[:, :, target_channel_index] = correction.copy()
|
|
1192
|
+
|
|
1193
|
+
if return_stacks:
|
|
1194
|
+
corrected_stack.append(frames)
|
|
1195
|
+
|
|
1196
|
+
if export:
|
|
1197
|
+
tif.write(
|
|
1198
|
+
np.moveaxis(frames, -1, 0).astype(np.dtype("f")),
|
|
1199
|
+
contiguous=True,
|
|
1200
|
+
)
|
|
1201
|
+
del frames
|
|
1202
|
+
del target_img
|
|
1203
|
+
del correction
|
|
1204
|
+
collect()
|
|
1205
|
+
|
|
1206
|
+
if progress_callback:
|
|
1207
|
+
progress_callback(
|
|
1208
|
+
level="frame",
|
|
1209
|
+
iter=int(i // nbr_channels),
|
|
1210
|
+
total=stack_length,
|
|
1211
|
+
stage="correcting",
|
|
1212
|
+
)
|
|
1213
|
+
|
|
1214
|
+
if prefix is None:
|
|
1215
|
+
os.replace(os.sep.join([path, newfile]), os.sep.join([path, file]))
|
|
1216
|
+
else:
|
|
1217
|
+
|
|
1218
|
+
if subset_indices is None:
|
|
1219
|
+
iterator = range(0, int(stack_length * nbr_channels), nbr_channels)
|
|
1220
|
+
else:
|
|
1221
|
+
iterator = subset_indices
|
|
1222
|
+
|
|
1223
|
+
for i in tqdm(iterator):
|
|
1224
|
+
|
|
1225
|
+
frames = load_frames(
|
|
1226
|
+
list(np.arange(i, (i + nbr_channels))),
|
|
1227
|
+
stack_path,
|
|
1228
|
+
normalize_input=False,
|
|
1229
|
+
).astype(float)
|
|
1230
|
+
target_img = frames[:, :, target_channel_index].copy()
|
|
1231
|
+
|
|
1232
|
+
correction = field_correction(
|
|
1233
|
+
target_img,
|
|
1234
|
+
threshold=threshold_on_std,
|
|
1235
|
+
operation=operation,
|
|
1236
|
+
model=model,
|
|
1237
|
+
clip=clip,
|
|
1238
|
+
activation_protocol=activation_protocol,
|
|
1239
|
+
downsample=downsample,
|
|
1240
|
+
)
|
|
1241
|
+
frames[:, :, target_channel_index] = correction.copy()
|
|
1242
|
+
|
|
1243
|
+
corrected_stack.append(frames)
|
|
1244
|
+
|
|
1245
|
+
del frames
|
|
1246
|
+
del target_img
|
|
1247
|
+
del correction
|
|
1248
|
+
collect()
|
|
1249
|
+
|
|
1250
|
+
if progress_callback:
|
|
1251
|
+
progress_callback(
|
|
1252
|
+
level="frame",
|
|
1253
|
+
iter=int(i // nbr_channels),
|
|
1254
|
+
total=stack_length,
|
|
1255
|
+
stage="correcting",
|
|
1256
|
+
)
|
|
1257
|
+
|
|
1258
|
+
if return_stacks:
|
|
1259
|
+
return np.array(corrected_stack)
|
|
1260
|
+
else:
|
|
1261
|
+
return None
|
|
1262
|
+
|
|
1263
|
+
|
|
1264
|
+
def field_correction(
|
|
1265
|
+
img: np.ndarray,
|
|
1266
|
+
threshold: float = 1,
|
|
1267
|
+
operation: str = "divide",
|
|
1268
|
+
model: str = "paraboloid",
|
|
1269
|
+
clip: bool = False,
|
|
1270
|
+
return_bg: bool = False,
|
|
1271
|
+
activation_protocol: List[List] = [["gauss", 2], ["std", 4]],
|
|
1272
|
+
downsample: int = 10,
|
|
1273
|
+
):
|
|
1274
|
+
"""
|
|
1275
|
+
Apply field correction to an image.
|
|
1276
|
+
|
|
1277
|
+
This function applies field correction to the given image based on the specified parameters
|
|
1278
|
+
including the threshold on standard deviation, operation, background correction model, clipping,
|
|
1279
|
+
and activation protocol.
|
|
1280
|
+
|
|
1281
|
+
Parameters
|
|
1282
|
+
----------
|
|
1283
|
+
img : numpy.ndarray
|
|
1284
|
+
The input image to be corrected.
|
|
1285
|
+
threshold : float, optional
|
|
1286
|
+
The threshold value on the image, post activation protocol for masking out cells (default is 1).
|
|
1287
|
+
operation : str, optional
|
|
1288
|
+
The operation to apply for background correction, either 'divide' or 'subtract' (default is 'divide').
|
|
1289
|
+
model : str, optional
|
|
1290
|
+
The background correction model to use, either 'paraboloid' or 'plane' (default is 'paraboloid').
|
|
1291
|
+
clip : bool, optional
|
|
1292
|
+
Whether to clip the corrected image to ensure non-negative values (default is False).
|
|
1293
|
+
return_bg : bool, optional
|
|
1294
|
+
Whether to return the background along with the corrected image (default is False).
|
|
1295
|
+
activation_protocol : list of list, optional
|
|
1296
|
+
The activation protocol consisting of filters and their respective parameters (default is [['gauss',2],['std',4]]).
|
|
1297
|
+
|
|
1298
|
+
Returns
|
|
1299
|
+
-------
|
|
1300
|
+
numpy.ndarray or tuple
|
|
1301
|
+
The corrected image or a tuple containing the corrected image and the background, depending on the value of `return_bg`.
|
|
1302
|
+
|
|
1303
|
+
Notes
|
|
1304
|
+
-----
|
|
1305
|
+
- This function first estimates the unreliable edge based on the activation protocol.
|
|
1306
|
+
- It then applies thresholding to obtain a mask for the background.
|
|
1307
|
+
- Next, it fits a background model to the image using the specified model.
|
|
1308
|
+
- Depending on the operation specified, it either divides or subtracts the background from the image.
|
|
1309
|
+
- If `clip` is True and operation is 'subtract', negative values in the corrected image are clipped to 0.
|
|
1310
|
+
- If `return_bg` is True, the function returns a tuple containing the corrected image and the background.
|
|
1311
|
+
|
|
1312
|
+
See Also
|
|
1313
|
+
--------
|
|
1314
|
+
fit_background_model : Function to fit a background model to an image.
|
|
1315
|
+
threshold_image : Function to apply thresholding to an image.
|
|
1316
|
+
"""
|
|
1317
|
+
|
|
1318
|
+
target_copy = img.copy().astype(float)
|
|
1319
|
+
if np.percentile(target_copy.flatten(), 99.9) == 0.0:
|
|
1320
|
+
return target_copy
|
|
1321
|
+
|
|
1322
|
+
from celldetective.filters import filter_image
|
|
1323
|
+
|
|
1324
|
+
std_frame = filter_image(target_copy, filters=activation_protocol)
|
|
1325
|
+
edge = estimate_unreliable_edge(activation_protocol)
|
|
1326
|
+
mask = threshold_image(
|
|
1327
|
+
std_frame, threshold, np.inf, foreground_value=1, edge_exclusion=edge
|
|
1328
|
+
).astype(int)
|
|
1329
|
+
background = fit_background_model(
|
|
1330
|
+
img, cell_masks=mask, model=model, edge_exclusion=edge, downsample=downsample
|
|
1331
|
+
)
|
|
1332
|
+
|
|
1333
|
+
if operation == "divide":
|
|
1334
|
+
correction = np.divide(img, background, where=background == background)
|
|
1335
|
+
correction[background != background] = np.nan
|
|
1336
|
+
correction[img != img] = np.nan
|
|
1337
|
+
fill_val = 1.0
|
|
1338
|
+
|
|
1339
|
+
elif operation == "subtract":
|
|
1340
|
+
correction = np.subtract(img, background, where=background == background)
|
|
1341
|
+
correction[background != background] = np.nan
|
|
1342
|
+
correction[img != img] = np.nan
|
|
1343
|
+
fill_val = 0.0
|
|
1344
|
+
if clip:
|
|
1345
|
+
correction[correction <= 0.0] = 0.0
|
|
1346
|
+
|
|
1347
|
+
if return_bg:
|
|
1348
|
+
return correction.copy(), background
|
|
1349
|
+
else:
|
|
1350
|
+
return correction.copy()
|
|
1351
|
+
|
|
1352
|
+
return correction.copy()
|
|
1353
|
+
|
|
1354
|
+
|
|
1355
|
+
def fit_background_model(
|
|
1356
|
+
img, cell_masks=None, model="paraboloid", edge_exclusion=None, downsample=10
|
|
1357
|
+
):
|
|
1358
|
+
"""
|
|
1359
|
+
Fit a background model to the given image.
|
|
1360
|
+
|
|
1361
|
+
This function fits a background model to the given image using either a paraboloid or plane model.
|
|
1362
|
+
It supports optional cell masks and edge exclusion for fitting.
|
|
1363
|
+
|
|
1364
|
+
Parameters
|
|
1365
|
+
----------
|
|
1366
|
+
img : numpy.ndarray
|
|
1367
|
+
The input image data.
|
|
1368
|
+
cell_masks : numpy.ndarray, optional
|
|
1369
|
+
An array specifying cell masks. If provided, areas covered by cell masks will be excluded from the fitting process.
|
|
1370
|
+
model : str, optional
|
|
1371
|
+
The background model to fit, either 'paraboloid' or 'plane' (default is 'paraboloid').
|
|
1372
|
+
edge_exclusion : int or None, optional
|
|
1373
|
+
The size of the border to exclude from fitting (default is None).
|
|
1374
|
+
|
|
1375
|
+
Returns
|
|
1376
|
+
-------
|
|
1377
|
+
numpy.ndarray or None
|
|
1378
|
+
The fitted background model as a numpy array if successful, otherwise None.
|
|
1379
|
+
|
|
1380
|
+
Notes
|
|
1381
|
+
-----
|
|
1382
|
+
- This function fits a background model to the image using either a paraboloid or plane model based on the specified `model`.
|
|
1383
|
+
- If `cell_masks` are provided, areas covered by cell masks will be excluded from the fitting process.
|
|
1384
|
+
- If `edge_exclusion` is provided, a border of the specified size will be excluded from fitting.
|
|
1385
|
+
|
|
1386
|
+
See Also
|
|
1387
|
+
--------
|
|
1388
|
+
fit_paraboloid : Function to fit a paraboloid model to an image.
|
|
1389
|
+
fit_plane : Function to fit a plane model to an image.
|
|
1390
|
+
"""
|
|
1391
|
+
|
|
1392
|
+
if model == "paraboloid":
|
|
1393
|
+
bg = fit_paraboloid(
|
|
1394
|
+
img.astype(float),
|
|
1395
|
+
cell_masks=cell_masks,
|
|
1396
|
+
edge_exclusion=edge_exclusion,
|
|
1397
|
+
downsample=downsample,
|
|
1398
|
+
).astype(float)
|
|
1399
|
+
elif model == "plane":
|
|
1400
|
+
bg = fit_plane(
|
|
1401
|
+
img.astype(float), cell_masks=cell_masks, edge_exclusion=edge_exclusion
|
|
1402
|
+
).astype(float)
|
|
1403
|
+
|
|
1404
|
+
if bg is not None:
|
|
1405
|
+
bg = np.array(bg)
|
|
1406
|
+
|
|
1407
|
+
return bg
|
|
1080
1408
|
|
|
1081
1409
|
|
|
1082
1410
|
def correct_channel_offset(
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1411
|
+
experiment,
|
|
1412
|
+
well_option="*",
|
|
1413
|
+
position_option="*",
|
|
1414
|
+
target_channel="channel_name",
|
|
1415
|
+
correction_horizontal=0,
|
|
1416
|
+
correction_vertical=0,
|
|
1417
|
+
show_progress_per_well=True,
|
|
1418
|
+
show_progress_per_pos=True,
|
|
1419
|
+
export=False,
|
|
1420
|
+
return_stacks=False,
|
|
1421
|
+
movie_prefix=None,
|
|
1422
|
+
export_prefix="Corrected",
|
|
1423
|
+
progress_callback=None,
|
|
1424
|
+
**kwargs,
|
|
1425
|
+
):
|
|
1426
|
+
|
|
1427
|
+
config = get_config(experiment)
|
|
1428
|
+
wells = get_experiment_wells(experiment)
|
|
1429
|
+
len_movie = float(config_section_to_dict(config, "MovieSettings")["len_movie"])
|
|
1430
|
+
if movie_prefix is None:
|
|
1431
|
+
movie_prefix = config_section_to_dict(config, "MovieSettings")["movie_prefix"]
|
|
1432
|
+
|
|
1433
|
+
well_indices, position_indices = interpret_wells_and_positions(
|
|
1434
|
+
experiment, well_option, position_option
|
|
1435
|
+
)
|
|
1436
|
+
channel_indices = _extract_channel_indices_from_config(config, [target_channel])
|
|
1437
|
+
nbr_channels = _extract_nbr_channels_from_config(config)
|
|
1438
|
+
img_num_channels = _get_img_num_per_channel(
|
|
1439
|
+
channel_indices, int(len_movie), nbr_channels
|
|
1440
|
+
)
|
|
1441
|
+
|
|
1442
|
+
stacks = []
|
|
1443
|
+
|
|
1444
|
+
# Well loop with progress reporting
|
|
1445
|
+
total_wells = len(well_indices)
|
|
1446
|
+
for k, well_path in enumerate(wells[well_indices]):
|
|
1447
|
+
if progress_callback:
|
|
1448
|
+
progress_callback(level="well", iter=k, total=total_wells)
|
|
1449
|
+
elif show_progress_per_well:
|
|
1450
|
+
print(f"Processing well {k+1}/{total_wells}...")
|
|
1451
|
+
|
|
1452
|
+
well_name, _ = extract_well_name_and_number(well_path)
|
|
1453
|
+
positions = get_positions_in_well(well_path)
|
|
1454
|
+
selection = positions[position_indices]
|
|
1455
|
+
if isinstance(selection[0], np.ndarray):
|
|
1456
|
+
selection = selection[0]
|
|
1457
|
+
|
|
1458
|
+
total_pos = len(selection)
|
|
1459
|
+
for pidx, pos_path in enumerate(selection):
|
|
1460
|
+
if progress_callback:
|
|
1461
|
+
progress_callback(
|
|
1462
|
+
level="position",
|
|
1463
|
+
iter=pidx,
|
|
1464
|
+
total=total_pos,
|
|
1465
|
+
stage=f"Pos {extract_position_name(pos_path)}",
|
|
1466
|
+
)
|
|
1467
|
+
elif show_progress_per_pos:
|
|
1468
|
+
print(f" Processing position {pidx+1}/{total_pos}...")
|
|
1469
|
+
|
|
1470
|
+
stack_path = get_position_movie_path(pos_path, prefix=movie_prefix)
|
|
1471
|
+
logger.info(
|
|
1472
|
+
f"Applying the correction to position {extract_position_name(pos_path)}..."
|
|
1473
|
+
)
|
|
1474
|
+
len_movie_auto = auto_load_number_of_frames(stack_path)
|
|
1475
|
+
if len_movie_auto is not None:
|
|
1476
|
+
len_movie = len_movie_auto
|
|
1477
|
+
img_num_channels = _get_img_num_per_channel(
|
|
1478
|
+
channel_indices, int(len_movie), nbr_channels
|
|
1479
|
+
)
|
|
1480
|
+
|
|
1481
|
+
corrected_stack = correct_channel_offset_single_stack(
|
|
1482
|
+
stack_path,
|
|
1483
|
+
target_channel_index=channel_indices[0],
|
|
1484
|
+
nbr_channels=nbr_channels,
|
|
1485
|
+
stack_length=len_movie,
|
|
1486
|
+
correction_vertical=correction_vertical,
|
|
1487
|
+
correction_horizontal=correction_horizontal,
|
|
1488
|
+
export=export,
|
|
1489
|
+
prefix=export_prefix,
|
|
1490
|
+
return_stacks=return_stacks,
|
|
1491
|
+
progress_callback=progress_callback,
|
|
1492
|
+
)
|
|
1493
|
+
|
|
1494
|
+
logger.info("Correction successful.")
|
|
1495
|
+
if return_stacks:
|
|
1496
|
+
stacks.append(corrected_stack)
|
|
1497
|
+
else:
|
|
1498
|
+
del corrected_stack
|
|
1499
|
+
collect()
|
|
1500
|
+
|
|
1501
|
+
if return_stacks:
|
|
1502
|
+
return stacks
|
|
1503
|
+
|
|
1504
|
+
|
|
1505
|
+
def correct_channel_offset_single_stack(
|
|
1506
|
+
stack_path,
|
|
1507
|
+
target_channel_index=0,
|
|
1508
|
+
nbr_channels=1,
|
|
1509
|
+
stack_length=45,
|
|
1510
|
+
correction_vertical=0,
|
|
1511
|
+
correction_horizontal=0,
|
|
1512
|
+
export=False,
|
|
1513
|
+
prefix="Corrected",
|
|
1514
|
+
return_stacks=True,
|
|
1515
|
+
progress_callback=None,
|
|
1516
|
+
):
|
|
1517
|
+
|
|
1518
|
+
assert os.path.exists(
|
|
1519
|
+
stack_path
|
|
1520
|
+
), f"The stack {stack_path} does not exist... Abort."
|
|
1521
|
+
|
|
1522
|
+
from tqdm import tqdm
|
|
1523
|
+
import tifffile.tifffile as tiff
|
|
1524
|
+
from scipy.ndimage import shift
|
|
1525
|
+
|
|
1526
|
+
stack_length_auto = auto_load_number_of_frames(stack_path)
|
|
1527
|
+
if stack_length_auto is None and stack_length is None:
|
|
1528
|
+
logger.error("Stack length not provided...")
|
|
1529
|
+
return None
|
|
1530
|
+
if stack_length_auto is not None:
|
|
1531
|
+
stack_length = stack_length_auto
|
|
1532
|
+
|
|
1533
|
+
corrected_stack = []
|
|
1534
|
+
|
|
1535
|
+
if export:
|
|
1536
|
+
path, file = os.path.split(stack_path)
|
|
1537
|
+
if prefix is None:
|
|
1538
|
+
newfile = "temp_" + file
|
|
1539
|
+
else:
|
|
1540
|
+
newfile = "_".join([prefix, file])
|
|
1541
|
+
|
|
1542
|
+
with tiff.TiffWriter(
|
|
1543
|
+
os.sep.join([path, newfile]), bigtiff=True, imagej=True
|
|
1544
|
+
) as tif:
|
|
1545
|
+
frames_indices = range(0, int(stack_length * nbr_channels), nbr_channels)
|
|
1546
|
+
total_frames = len(frames_indices)
|
|
1547
|
+
for k, i in enumerate(tqdm(frames_indices)):
|
|
1548
|
+
if progress_callback:
|
|
1549
|
+
progress_callback(level="frame", iter=k, total=total_frames)
|
|
1550
|
+
|
|
1551
|
+
frames = load_frames(
|
|
1552
|
+
list(np.arange(i, (i + nbr_channels))),
|
|
1553
|
+
stack_path,
|
|
1554
|
+
normalize_input=False,
|
|
1555
|
+
).astype(float)
|
|
1556
|
+
target_img = frames[:, :, target_channel_index].copy()
|
|
1557
|
+
|
|
1558
|
+
if np.percentile(target_img.flatten(), 99.9) == 0.0:
|
|
1559
|
+
correction = target_img
|
|
1560
|
+
elif np.any(target_img.flatten() != target_img.flatten()):
|
|
1561
|
+
# Routine to interpolate NaN for the spline filter then mask it again
|
|
1562
|
+
target_interp = interpolate_nan(target_img)
|
|
1563
|
+
from scipy.ndimage import shift
|
|
1564
|
+
|
|
1565
|
+
correction = shift(
|
|
1566
|
+
target_interp, [correction_vertical, correction_horizontal]
|
|
1567
|
+
)
|
|
1568
|
+
correction_nan = shift(
|
|
1569
|
+
target_img,
|
|
1570
|
+
[correction_vertical, correction_horizontal],
|
|
1571
|
+
prefilter=False,
|
|
1572
|
+
)
|
|
1573
|
+
nan_i, nan_j = np.where(correction_nan != correction_nan)
|
|
1574
|
+
correction[nan_i, nan_j] = np.nan
|
|
1575
|
+
else:
|
|
1576
|
+
correction = shift(
|
|
1577
|
+
target_img, [correction_vertical, correction_horizontal]
|
|
1578
|
+
)
|
|
1579
|
+
|
|
1580
|
+
frames[:, :, target_channel_index] = correction.copy()
|
|
1581
|
+
|
|
1582
|
+
if return_stacks:
|
|
1583
|
+
corrected_stack.append(frames)
|
|
1584
|
+
|
|
1585
|
+
if export:
|
|
1586
|
+
tif.write(
|
|
1587
|
+
np.moveaxis(frames, -1, 0).astype(np.dtype("f")),
|
|
1588
|
+
contiguous=True,
|
|
1589
|
+
)
|
|
1590
|
+
del frames
|
|
1591
|
+
del target_img
|
|
1592
|
+
del correction
|
|
1593
|
+
collect()
|
|
1594
|
+
|
|
1595
|
+
if prefix is None:
|
|
1596
|
+
os.replace(os.sep.join([path, newfile]), os.sep.join([path, file]))
|
|
1597
|
+
else:
|
|
1598
|
+
frames_indices = range(0, int(stack_length * nbr_channels), nbr_channels)
|
|
1599
|
+
total_frames = len(frames_indices)
|
|
1600
|
+
for k, i in enumerate(tqdm(frames_indices)):
|
|
1601
|
+
if progress_callback:
|
|
1602
|
+
progress_callback(level="frame", iter=k, total=total_frames)
|
|
1603
|
+
|
|
1604
|
+
frames = load_frames(
|
|
1605
|
+
list(np.arange(i, (i + nbr_channels))),
|
|
1606
|
+
stack_path,
|
|
1607
|
+
normalize_input=False,
|
|
1608
|
+
).astype(float)
|
|
1609
|
+
target_img = frames[:, :, target_channel_index].copy()
|
|
1610
|
+
|
|
1611
|
+
if np.percentile(target_img.flatten(), 99.9) == 0.0:
|
|
1612
|
+
correction = target_img
|
|
1613
|
+
elif np.any(target_img.flatten() != target_img.flatten()):
|
|
1614
|
+
# Routine to interpolate NaN for the spline filter then mask it again
|
|
1615
|
+
target_interp = interpolate_nan(target_img)
|
|
1616
|
+
correction = shift(
|
|
1617
|
+
target_interp, [correction_vertical, correction_horizontal]
|
|
1618
|
+
)
|
|
1619
|
+
correction_nan = shift(
|
|
1620
|
+
target_img,
|
|
1621
|
+
[correction_vertical, correction_horizontal],
|
|
1622
|
+
prefilter=False,
|
|
1623
|
+
)
|
|
1624
|
+
nan_i, nan_j = np.where(correction_nan != correction_nan)
|
|
1625
|
+
correction[nan_i, nan_j] = np.nan
|
|
1626
|
+
else:
|
|
1627
|
+
correction = shift(
|
|
1628
|
+
target_img, [correction_vertical, correction_horizontal]
|
|
1629
|
+
)
|
|
1630
|
+
|
|
1631
|
+
frames[:, :, target_channel_index] = correction.copy()
|
|
1632
|
+
|
|
1633
|
+
corrected_stack.append(frames)
|
|
1634
|
+
|
|
1635
|
+
del frames
|
|
1636
|
+
del target_img
|
|
1637
|
+
del correction
|
|
1638
|
+
collect()
|
|
1639
|
+
|
|
1640
|
+
if return_stacks:
|
|
1641
|
+
return np.array(corrected_stack)
|
|
1642
|
+
else:
|
|
1643
|
+
return None
|