pytme 0.2.9.post1__cp311-cp311-macosx_15_0_arm64.whl → 0.3.0__cp311-cp311-macosx_15_0_arm64.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.
- pytme-0.3.0.data/scripts/estimate_memory_usage.py +76 -0
- pytme-0.3.0.data/scripts/match_template.py +1106 -0
- {pytme-0.2.9.post1.data → pytme-0.3.0.data}/scripts/postprocess.py +320 -190
- {pytme-0.2.9.post1.data → pytme-0.3.0.data}/scripts/preprocess.py +21 -31
- {pytme-0.2.9.post1.data → pytme-0.3.0.data}/scripts/preprocessor_gui.py +85 -19
- pytme-0.3.0.data/scripts/pytme_runner.py +771 -0
- {pytme-0.2.9.post1.dist-info → pytme-0.3.0.dist-info}/METADATA +21 -20
- pytme-0.3.0.dist-info/RECORD +126 -0
- {pytme-0.2.9.post1.dist-info → pytme-0.3.0.dist-info}/entry_points.txt +2 -1
- pytme-0.3.0.dist-info/licenses/LICENSE +339 -0
- scripts/estimate_memory_usage.py +76 -0
- scripts/eval.py +93 -0
- scripts/extract_candidates.py +224 -0
- scripts/match_template.py +349 -378
- pytme-0.2.9.post1.data/scripts/match_template.py → scripts/match_template_filters.py +213 -148
- scripts/postprocess.py +320 -190
- scripts/preprocess.py +21 -31
- scripts/preprocessor_gui.py +85 -19
- scripts/pytme_runner.py +771 -0
- scripts/refine_matches.py +625 -0
- tests/preprocessing/test_frequency_filters.py +28 -14
- tests/test_analyzer.py +41 -36
- tests/test_backends.py +1 -0
- tests/test_matching_cli.py +109 -54
- tests/test_matching_data.py +5 -5
- tests/test_matching_exhaustive.py +1 -2
- tests/test_matching_optimization.py +4 -9
- tests/test_matching_utils.py +1 -1
- tests/test_orientations.py +0 -1
- tme/__version__.py +1 -1
- tme/analyzer/__init__.py +2 -0
- tme/analyzer/_utils.py +26 -21
- tme/analyzer/aggregation.py +395 -222
- tme/analyzer/base.py +127 -0
- tme/analyzer/peaks.py +189 -204
- tme/analyzer/proxy.py +123 -0
- tme/backends/__init__.py +4 -3
- tme/backends/_cupy_utils.py +25 -24
- tme/backends/_jax_utils.py +20 -18
- tme/backends/cupy_backend.py +13 -26
- tme/backends/jax_backend.py +24 -23
- tme/backends/matching_backend.py +4 -3
- tme/backends/mlx_backend.py +4 -3
- tme/backends/npfftw_backend.py +34 -30
- tme/backends/pytorch_backend.py +18 -4
- tme/cli.py +126 -0
- tme/density.py +9 -7
- tme/extensions.cpython-311-darwin.so +0 -0
- tme/filters/__init__.py +3 -3
- tme/filters/_utils.py +36 -10
- tme/filters/bandpass.py +229 -188
- tme/filters/compose.py +5 -4
- tme/filters/ctf.py +516 -254
- tme/filters/reconstruction.py +91 -32
- tme/filters/wedge.py +196 -135
- tme/filters/whitening.py +37 -42
- tme/matching_data.py +28 -39
- tme/matching_exhaustive.py +31 -27
- tme/matching_optimization.py +5 -4
- tme/matching_scores.py +25 -15
- tme/matching_utils.py +193 -27
- tme/memory.py +4 -3
- tme/orientations.py +22 -9
- tme/parser.py +114 -33
- tme/preprocessor.py +6 -5
- tme/rotations.py +10 -7
- tme/structure.py +4 -3
- pytme-0.2.9.post1.data/scripts/estimate_ram_usage.py +0 -97
- pytme-0.2.9.post1.dist-info/RECORD +0 -119
- pytme-0.2.9.post1.dist-info/licenses/LICENSE +0 -153
- scripts/estimate_ram_usage.py +0 -97
- tests/data/Maps/.DS_Store +0 -0
- tests/data/Structures/.DS_Store +0 -0
- {pytme-0.2.9.post1.dist-info → pytme-0.3.0.dist-info}/WHEEL +0 -0
- {pytme-0.2.9.post1.dist-info → pytme-0.3.0.dist-info}/top_level.txt +0 -0
tme/filters/bandpass.py
CHANGED
@@ -1,230 +1,271 @@
|
|
1
|
-
"""
|
1
|
+
"""
|
2
|
+
Implements class BandPassFilter to create Fourier filter representations.
|
2
3
|
|
3
|
-
|
4
|
+
Copyright (c) 2024 European Molecular Biology Laboratory
|
4
5
|
|
5
|
-
|
6
|
+
Author: Valentin Maurer <valentin.maurer@embl-hamburg.de>
|
6
7
|
"""
|
7
8
|
|
8
9
|
from typing import Tuple
|
9
10
|
from math import log, sqrt
|
11
|
+
from dataclasses import dataclass
|
12
|
+
|
13
|
+
import numpy as np
|
10
14
|
|
11
15
|
from ..types import BackendArray
|
12
16
|
from ..backends import backend as be
|
13
17
|
from .compose import ComposableFilter
|
14
|
-
from ._utils import
|
18
|
+
from ._utils import (
|
19
|
+
crop_real_fourier,
|
20
|
+
shift_fourier,
|
21
|
+
pad_to_length,
|
22
|
+
frequency_grid_at_angle,
|
23
|
+
fftfreqn,
|
24
|
+
)
|
15
25
|
|
16
|
-
__all__ = ["
|
26
|
+
__all__ = ["BandPass", "BandPassReconstructed"]
|
17
27
|
|
18
28
|
|
19
|
-
|
29
|
+
@dataclass
|
30
|
+
class BandPass(ComposableFilter):
|
20
31
|
"""
|
21
|
-
Generate
|
22
|
-
|
23
|
-
Parameters
|
24
|
-
----------
|
25
|
-
lowpass : float, optional
|
26
|
-
The lowpass cutoff, defaults to None.
|
27
|
-
highpass : float, optional
|
28
|
-
The highpass cutoff, defaults to None.
|
29
|
-
sampling_rate : Tuple[float], optional
|
30
|
-
The sampling r_position_to_molmapate in Fourier space, defaults to 1.
|
31
|
-
use_gaussian : bool, optional
|
32
|
-
Whether to use Gaussian bandpass filter, defaults to True.
|
33
|
-
return_real_fourier : bool, optional
|
34
|
-
Whether to return only the real Fourier space, defaults to False.
|
35
|
-
shape_is_real_fourier : bool, optional
|
36
|
-
Whether the shape represents the real Fourier space, defaults to False.
|
32
|
+
Generate per-slice Fourier Bandpass filter
|
37
33
|
"""
|
38
34
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
)
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
highpass: float,
|
60
|
-
sampling_rate: Tuple[float],
|
61
|
-
return_real_fourier: bool = False,
|
62
|
-
shape_is_real_fourier: bool = False,
|
63
|
-
**kwargs,
|
64
|
-
) -> BackendArray:
|
35
|
+
#: The tilt angles.
|
36
|
+
angles: Tuple[float]
|
37
|
+
#: The lowpass cutoffs. Either one or one per angle, defaults to None.
|
38
|
+
lowpass: Tuple[float] = None
|
39
|
+
#: The highpass cutoffs. Either one or one per angle, defaults to None.
|
40
|
+
highpass: Tuple[float] = None
|
41
|
+
#: The shape of the to-be created mask.
|
42
|
+
shape: Tuple[int] = None
|
43
|
+
#: Axis the plane is tilted over, defaults to 0 (x).
|
44
|
+
tilt_axis: int = 0
|
45
|
+
#: The projection axis, defaults to 2 (z).
|
46
|
+
opening_axis: int = 2
|
47
|
+
#: The sampling rate, defaults to 1 Ångstrom / voxel.
|
48
|
+
sampling_rate: Tuple[float] = 1
|
49
|
+
#: Whether to use Gaussian bandpass filter, defaults to True.
|
50
|
+
use_gaussian: bool = True
|
51
|
+
#: Whether to return a mask for rfft
|
52
|
+
return_real_fourier: bool = False
|
53
|
+
|
54
|
+
def __call__(self, **kwargs):
|
65
55
|
"""
|
66
|
-
|
67
|
-
|
68
|
-
Parameters
|
69
|
-
----------
|
70
|
-
shape : tuple of int
|
71
|
-
The shape of the bandpass filter.
|
72
|
-
lowpass : float
|
73
|
-
The lowpass cutoff in units of sampling rate.
|
74
|
-
highpass : float
|
75
|
-
The highpass cutoff in units of sampling rate.
|
76
|
-
return_real_fourier : bool, optional
|
77
|
-
Whether to return only the real Fourier space, defaults to False.
|
78
|
-
sampling_rate : float
|
79
|
-
The sampling rate in Fourier space.
|
80
|
-
shape_is_real_fourier : bool, optional
|
81
|
-
Whether the shape represents the real Fourier space, defaults to False.
|
82
|
-
**kwargs : dict
|
83
|
-
Additional keyword arguments.
|
84
|
-
|
85
|
-
Returns
|
86
|
-
-------
|
87
|
-
BackendArray
|
88
|
-
The bandpass filter in Fourier space.
|
56
|
+
Returns a Bandpass stack of chosen parameters with DC component in the center.
|
89
57
|
"""
|
58
|
+
func_args = vars(self).copy()
|
59
|
+
func_args.update(kwargs)
|
60
|
+
|
61
|
+
func = discrete_bandpass
|
62
|
+
if func_args.get("use_gaussian"):
|
63
|
+
func = gaussian_bandpass
|
64
|
+
|
65
|
+
return_real_fourier = kwargs.get("return_real_fourier", True)
|
66
|
+
shape_is_real_fourier = kwargs.get("shape_is_real_fourier", False)
|
90
67
|
if shape_is_real_fourier:
|
91
68
|
return_real_fourier = False
|
92
69
|
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
)
|
99
|
-
|
100
|
-
|
70
|
+
angles = np.atleast_1d(func_args["angles"])
|
71
|
+
_lowpass = pad_to_length(func_args["lowpass"], angles.size)
|
72
|
+
_highpass = pad_to_length(func_args["highpass"], angles.size)
|
73
|
+
|
74
|
+
masks = []
|
75
|
+
for index, angle in enumerate(angles):
|
76
|
+
frequency_grid = frequency_grid_at_angle(
|
77
|
+
shape=func_args["shape"],
|
78
|
+
tilt_axis=func_args["tilt_axis"],
|
79
|
+
opening_axis=func_args["opening_axis"],
|
80
|
+
angle=angle,
|
81
|
+
sampling_rate=1,
|
82
|
+
)
|
83
|
+
func_args["lowpass"] = _lowpass[index]
|
84
|
+
func_args["highpass"] = _highpass[index]
|
85
|
+
mask = func(grid=frequency_grid, **func_args)
|
101
86
|
|
102
|
-
|
103
|
-
|
104
|
-
|
87
|
+
mask = shift_fourier(data=mask, shape_is_real_fourier=shape_is_real_fourier)
|
88
|
+
if return_real_fourier:
|
89
|
+
mask = crop_real_fourier(mask)
|
90
|
+
masks.append(mask[None])
|
105
91
|
|
106
|
-
|
107
|
-
|
108
|
-
|
92
|
+
masks = be.concatenate(masks, axis=0)
|
93
|
+
return {
|
94
|
+
"data": be.to_backend_array(masks),
|
95
|
+
"shape": func_args["shape"],
|
96
|
+
"return_real_fourier": return_real_fourier,
|
97
|
+
"is_multiplicative_filter": True,
|
98
|
+
}
|
109
99
|
|
110
|
-
bandpass_filter = ((grid <= highcut) & (grid >= lowcut)) * 1.0
|
111
|
-
bandpass_filter = shift_fourier(
|
112
|
-
data=bandpass_filter, shape_is_real_fourier=shape_is_real_fourier
|
113
|
-
)
|
114
100
|
|
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
|
-
Additional keyword arguments.
|
149
|
-
|
150
|
-
Returns
|
151
|
-
-------
|
152
|
-
BackendArray
|
153
|
-
The bandpass filter in Fourier space.
|
154
|
-
"""
|
101
|
+
@dataclass
|
102
|
+
class BandPassReconstructed(ComposableFilter):
|
103
|
+
"""
|
104
|
+
Generate reconstructed bandpass filters in Fourier space.
|
105
|
+
"""
|
106
|
+
|
107
|
+
#: The lowpass cutoff, defaults to None.
|
108
|
+
lowpass: float = None
|
109
|
+
#: The highpass cutoff, defaults to None.
|
110
|
+
highpass: float = None
|
111
|
+
#: The shape of the to-be created mask.
|
112
|
+
shape: Tuple[int] = None
|
113
|
+
#: Axis the plane is tilted over, defaults to 0 (x).
|
114
|
+
tilt_axis: int = 0
|
115
|
+
#: The projection axis, defaults to 2 (z).
|
116
|
+
opening_axis: int = 2
|
117
|
+
#: The sampling rate, defaults to 1 Ångstrom / voxel.
|
118
|
+
sampling_rate: Tuple[float] = 1
|
119
|
+
#: Whether to use Gaussian bandpass filter, defaults to True.
|
120
|
+
use_gaussian: bool = True
|
121
|
+
#: Whether to return a mask for rfft
|
122
|
+
return_real_fourier: bool = False
|
123
|
+
|
124
|
+
def __call__(self, **kwargs):
|
125
|
+
func_args = vars(self).copy()
|
126
|
+
func_args.update(kwargs)
|
127
|
+
|
128
|
+
func = discrete_bandpass
|
129
|
+
if func_args.get("use_gaussian"):
|
130
|
+
func = gaussian_bandpass
|
131
|
+
|
132
|
+
return_real_fourier = func_args.get("return_real_fourier", True)
|
133
|
+
shape_is_real_fourier = func_args.get("shape_is_real_fourier", False)
|
155
134
|
if shape_is_real_fourier:
|
156
135
|
return_real_fourier = False
|
157
136
|
|
158
137
|
grid = fftfreqn(
|
159
|
-
shape=shape,
|
138
|
+
shape=func_args["shape"],
|
160
139
|
sampling_rate=0.5,
|
161
140
|
shape_is_real_fourier=shape_is_real_fourier,
|
162
141
|
compute_euclidean_norm=True,
|
163
142
|
)
|
164
|
-
|
165
|
-
grid = -be.square(grid, out=grid)
|
143
|
+
mask = func(grid=grid, **func_args)
|
166
144
|
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
be.max(be.multiply(2, be.to_backend_array(sampling_rate)))
|
171
|
-
)
|
145
|
+
mask = shift_fourier(data=mask, shape_is_real_fourier=shape_is_real_fourier)
|
146
|
+
if return_real_fourier:
|
147
|
+
mask = crop_real_fourier(mask)
|
172
148
|
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
if has_lowpass:
|
181
|
-
lowpass = upper_sampling / (lowpass * norm)
|
182
|
-
lowpass = be.multiply(2, be.square(lowpass))
|
183
|
-
if not has_highpass:
|
184
|
-
lowpass_filter = be.divide(grid, lowpass, out=grid)
|
185
|
-
else:
|
186
|
-
lowpass_filter = be.divide(grid, lowpass)
|
187
|
-
lowpass_filter = be.exp(lowpass_filter, out=lowpass_filter)
|
188
|
-
|
189
|
-
if has_highpass:
|
190
|
-
highpass = upper_sampling / (highpass * norm)
|
191
|
-
highpass = be.multiply(2, be.square(highpass))
|
192
|
-
highpass_filter = be.divide(grid, highpass, out=grid)
|
193
|
-
highpass_filter = be.exp(highpass_filter, out=highpass_filter)
|
194
|
-
highpass_filter = be.subtract(1, highpass_filter, out=highpass_filter)
|
195
|
-
|
196
|
-
if has_lowpass and not has_highpass:
|
197
|
-
bandpass_filter = lowpass_filter
|
198
|
-
elif not has_lowpass and has_highpass:
|
199
|
-
bandpass_filter = highpass_filter
|
200
|
-
elif has_lowpass and has_highpass:
|
201
|
-
bandpass_filter = be.multiply(
|
202
|
-
lowpass_filter, highpass_filter, out=lowpass_filter
|
203
|
-
)
|
204
|
-
else:
|
205
|
-
bandpass_filter = be.full(shape, fill_value=1, dtype=be._float_dtype)
|
149
|
+
return {
|
150
|
+
"data": be.to_backend_array(mask),
|
151
|
+
"shape": func_args["shape"],
|
152
|
+
"return_real_fourier": return_real_fourier,
|
153
|
+
"is_multiplicative_filter": True,
|
154
|
+
}
|
206
155
|
|
207
|
-
bandpass_filter = shift_fourier(
|
208
|
-
data=bandpass_filter, shape_is_real_fourier=shape_is_real_fourier
|
209
|
-
)
|
210
156
|
|
211
|
-
|
212
|
-
|
157
|
+
def discrete_bandpass(
|
158
|
+
grid: BackendArray,
|
159
|
+
lowpass: float,
|
160
|
+
highpass: float,
|
161
|
+
sampling_rate: Tuple[float],
|
162
|
+
**kwargs,
|
163
|
+
) -> BackendArray:
|
164
|
+
"""
|
165
|
+
Generate a bandpass filter using discrete frequency cutoffs.
|
213
166
|
|
214
|
-
|
167
|
+
Parameters
|
168
|
+
----------
|
169
|
+
grid : BackendArray
|
170
|
+
Frequencies in Fourier space.
|
171
|
+
lowpass : float
|
172
|
+
The lowpass cutoff in units of sampling rate.
|
173
|
+
highpass : float
|
174
|
+
The highpass cutoff in units of sampling rate.
|
175
|
+
return_real_fourier : bool, optional
|
176
|
+
Whether to return only the real Fourier space, defaults to False.
|
177
|
+
sampling_rate : float
|
178
|
+
The sampling rate in Fourier space.
|
179
|
+
**kwargs : dict
|
180
|
+
Additional keyword arguments.
|
181
|
+
|
182
|
+
Returns
|
183
|
+
-------
|
184
|
+
BackendArray
|
185
|
+
The bandpass filter in Fourier space.
|
186
|
+
"""
|
187
|
+
grid = be.astype(be.to_backend_array(grid), be._float_dtype)
|
188
|
+
sampling_rate = be.to_backend_array(sampling_rate)
|
215
189
|
|
216
|
-
|
217
|
-
|
218
|
-
|
190
|
+
highcut = grid.max()
|
191
|
+
if lowpass is not None:
|
192
|
+
highcut = be.max(2 * sampling_rate / lowpass)
|
219
193
|
|
220
|
-
|
221
|
-
|
222
|
-
|
194
|
+
lowcut = 0
|
195
|
+
if highpass is not None:
|
196
|
+
lowcut = be.max(2 * sampling_rate / highpass)
|
223
197
|
|
224
|
-
|
198
|
+
bandpass_filter = ((grid <= highcut) & (grid >= lowcut)) * 1.0
|
199
|
+
return bandpass_filter
|
225
200
|
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
201
|
+
|
202
|
+
def gaussian_bandpass(
|
203
|
+
grid: BackendArray,
|
204
|
+
lowpass: float = None,
|
205
|
+
highpass: float = None,
|
206
|
+
sampling_rate: float = 1,
|
207
|
+
**kwargs,
|
208
|
+
) -> BackendArray:
|
209
|
+
"""
|
210
|
+
Generate a bandpass filter using Gaussians.
|
211
|
+
|
212
|
+
Parameters
|
213
|
+
----------
|
214
|
+
grid : BackendArray
|
215
|
+
Frequency grid in Fourier space.
|
216
|
+
lowpass : float, optional
|
217
|
+
The lowpass cutoff in units of sampling rate, defaults to None.
|
218
|
+
highpass : float, optional
|
219
|
+
The highpass cutoff in units of sampling rate, defaults to None.
|
220
|
+
sampling_rate : float, optional
|
221
|
+
The sampling rate in Fourier space, defaults to one.
|
222
|
+
**kwargs : dict
|
223
|
+
Additional keyword arguments.
|
224
|
+
|
225
|
+
Returns
|
226
|
+
-------
|
227
|
+
BackendArray
|
228
|
+
The bandpass filter in Fourier space.
|
229
|
+
"""
|
230
|
+
grid = be.astype(be.to_backend_array(grid), be._float_dtype)
|
231
|
+
grid = -be.square(grid, out=grid)
|
232
|
+
|
233
|
+
has_lowpass, has_highpass = False, False
|
234
|
+
norm = float(sqrt(2 * log(2)))
|
235
|
+
upper_sampling = float(be.max(be.multiply(2, be.to_backend_array(sampling_rate))))
|
236
|
+
|
237
|
+
if lowpass is not None:
|
238
|
+
lowpass, has_lowpass = float(lowpass), True
|
239
|
+
lowpass = be.maximum(lowpass, be.eps(be._float_dtype))
|
240
|
+
if highpass is not None:
|
241
|
+
highpass, has_highpass = float(highpass), True
|
242
|
+
highpass = be.maximum(highpass, be.eps(be._float_dtype))
|
243
|
+
|
244
|
+
if has_lowpass:
|
245
|
+
lowpass = upper_sampling / (lowpass * norm)
|
246
|
+
lowpass = be.multiply(2, be.square(lowpass))
|
247
|
+
if not has_highpass:
|
248
|
+
lowpass_filter = be.divide(grid, lowpass, out=grid)
|
249
|
+
else:
|
250
|
+
lowpass_filter = be.divide(grid, lowpass)
|
251
|
+
lowpass_filter = be.exp(lowpass_filter, out=lowpass_filter)
|
252
|
+
|
253
|
+
if has_highpass:
|
254
|
+
highpass = upper_sampling / (highpass * norm)
|
255
|
+
highpass = be.multiply(2, be.square(highpass))
|
256
|
+
highpass_filter = be.divide(grid, highpass, out=grid)
|
257
|
+
highpass_filter = be.exp(highpass_filter, out=highpass_filter)
|
258
|
+
highpass_filter = be.subtract(1, highpass_filter, out=highpass_filter)
|
259
|
+
|
260
|
+
if has_lowpass and not has_highpass:
|
261
|
+
bandpass_filter = lowpass_filter
|
262
|
+
elif not has_lowpass and has_highpass:
|
263
|
+
bandpass_filter = highpass_filter
|
264
|
+
elif has_lowpass and has_highpass:
|
265
|
+
bandpass_filter = be.multiply(
|
266
|
+
lowpass_filter, highpass_filter, out=lowpass_filter
|
267
|
+
)
|
268
|
+
else:
|
269
|
+
bandpass_filter = be.full(grid.shape, fill_value=1, dtype=be._float_dtype)
|
270
|
+
|
271
|
+
return bandpass_filter
|
tme/filters/compose.py
CHANGED
@@ -1,8 +1,9 @@
|
|
1
|
-
"""
|
1
|
+
"""
|
2
|
+
Combine filters using an interface analogous to pytorch's Compose.
|
2
3
|
|
3
|
-
|
4
|
+
Copyright (c) 2024 European Molecular Biology Laboratory
|
4
5
|
|
5
|
-
|
6
|
+
Author: Valentin Maurer <valentin.maurer@embl-hamburg.de>
|
6
7
|
"""
|
7
8
|
|
8
9
|
from typing import Tuple, Dict
|
@@ -60,7 +61,7 @@ class Compose:
|
|
60
61
|
|
61
62
|
class ComposableFilter(ABC):
|
62
63
|
"""
|
63
|
-
|
64
|
+
Base class for composable filters.
|
64
65
|
"""
|
65
66
|
|
66
67
|
@abstractmethod
|