flimanalyzer 0.6.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- flim/__init__.py +2 -0
- flim/_version.py +5 -0
- flim/analysis/__init__.py +1 -0
- flim/analysis/ml/__init__.py +0 -0
- flim/analysis/ml/autoencoder.py +188 -0
- flim/analyzerapp.py +131 -0
- flim/batch.py +235 -0
- flim/configs/__init__.py +77 -0
- flim/configs/analysis.py +554 -0
- flim/configs/data.py +278 -0
- flim/configs/plot.py +543 -0
- flim/configs/workflow.py +204 -0
- flim/core/__init__.py +0 -0
- flim/core/calculator.py +0 -0
- flim/core/config.py +305 -0
- flim/core/execution.py +110 -0
- flim/core/fields.py +226 -0
- flim/core/filter.py +179 -0
- flim/core/graph.py +111 -0
- flim/core/headers.py +72 -0
- flim/core/importer.py +269 -0
- flim/core/parser.py +305 -0
- flim/core/paths.py +66 -0
- flim/core/prefect_server.py +152 -0
- flim/core/preprocessor.py +287 -0
- flim/core/registry.py +108 -0
- flim/core/runner.py +703 -0
- flim/core/schema.py +114 -0
- flim/core/settings.py +419 -0
- flim/core/tool.py +284 -0
- flim/core/tools.py +76 -0
- flim/core/types.py +113 -0
- flim/core/validation.py +272 -0
- flim/core/workflow.py +148 -0
- flim/gui/__init__.py +12 -0
- flim/gui/app.py +953 -0
- flim/gui/configdialog.py +495 -0
- flim/gui/datapanel.py +841 -0
- flim/gui/delimpanel.py +56 -0
- flim/gui/dialogs/__init__.py +473 -0
- flim/gui/dialogs/autoencoder.py +89 -0
- flim/gui/dialogs/categoryorder.py +130 -0
- flim/gui/dialogs/filter.py +86 -0
- flim/gui/dialogs/unpivot.py +72 -0
- flim/gui/dicttablepanel.py +178 -0
- flim/gui/editors.py +1578 -0
- flim/gui/events.py +162 -0
- flim/gui/icons.py +16 -0
- flim/gui/importdlg.py +391 -0
- flim/gui/listcontrol.py +472 -0
- flim/gui/macos.py +77 -0
- flim/gui/plotframe.py +73 -0
- flim/gui/progressdlg.py +290 -0
- flim/resources/aeaugment.png +0 -0
- flim/resources/aefeatureworkflow.png +0 -0
- flim/resources/aerun.png +0 -0
- flim/resources/aetrain.png +0 -0
- flim/resources/aetuneworkflow.png +0 -0
- flim/resources/barplot.png +0 -0
- flim/resources/boxplot.png +0 -0
- flim/resources/categorize.png +0 -0
- flim/resources/concatenate.png +0 -0
- flim/resources/fileopen.png +0 -0
- flim/resources/filter.png +0 -0
- flim/resources/flimanalyzer.ico +0 -0
- flim/resources/flimanalyzer.png +0 -0
- flim/resources/flirrplot.png +0 -0
- flim/resources/flirrworkflow.png +0 -0
- flim/resources/gmm.png +0 -0
- flim/resources/heatmap.png +0 -0
- flim/resources/histogram.png +0 -0
- flim/resources/import.png +0 -0
- flim/resources/kde.png +0 -0
- flim/resources/kmeans.png +0 -0
- flim/resources/ks.png +0 -0
- flim/resources/lineplot.png +0 -0
- flim/resources/merge.png +0 -0
- flim/resources/order.png +0 -0
- flim/resources/pairplot.png +0 -0
- flim/resources/pca.png +0 -0
- flim/resources/pivot.png +0 -0
- flim/resources/randomforest.png +0 -0
- flim/resources/relchange.png +0 -0
- flim/resources/scatter.png +0 -0
- flim/resources/seriesanalysis.png +0 -0
- flim/resources/sort.png +0 -0
- flim/resources/split.png +0 -0
- flim/resources/summary.png +0 -0
- flim/resources/swarmplot.png +0 -0
- flim/resources/unpivot.png +0 -0
- flim/resources/violinplot.png +0 -0
- flim/resources/workflow.png +0 -0
- flim/results.py +70 -0
- flim/tools/__init__.py +7 -0
- flim/tools/autoencoder.py +662 -0
- flim/tools/common.py +62 -0
- flim/tools/data.py +301 -0
- flim/tools/ml.py +311 -0
- flim/tools/plot.py +1439 -0
- flim/tools/statistics.py +348 -0
- flim/utils.py +43 -0
- flim/workflow/__init__.py +3 -0
- flim/workflow/aefeature.py +145 -0
- flim/workflow/aetune.py +163 -0
- flim/workflow/basicflow.py +99 -0
- flimanalyzer-0.6.0.dist-info/METADATA +262 -0
- flimanalyzer-0.6.0.dist-info/RECORD +110 -0
- flimanalyzer-0.6.0.dist-info/WHEEL +4 -0
- flimanalyzer-0.6.0.dist-info/entry_points.txt +5 -0
- flimanalyzer-0.6.0.dist-info/licenses/LICENSE +19 -0
flim/configs/analysis.py
ADDED
|
@@ -0,0 +1,554 @@
|
|
|
1
|
+
"""Configs of the Analysis tools: statistics, machine learning and autoencoders."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Annotated, Literal, Optional, Union
|
|
5
|
+
|
|
6
|
+
from pydantic import AfterValidator, Field, field_validator, model_validator
|
|
7
|
+
|
|
8
|
+
from flim.core.config import (
|
|
9
|
+
BaseConfig,
|
|
10
|
+
CategoryColumn,
|
|
11
|
+
Device,
|
|
12
|
+
FeatureColumn,
|
|
13
|
+
Probability,
|
|
14
|
+
Resources,
|
|
15
|
+
TaskConfig,
|
|
16
|
+
ValueOf,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
def _not_zero(threads: int) -> int:
|
|
20
|
+
if threads == 0:
|
|
21
|
+
raise ValueError("0 threads: use -1 (all cores) or a positive number")
|
|
22
|
+
return threads
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
# -1: all cores allocated to the task
|
|
26
|
+
Threads = Annotated[int, Field(ge=-1), AfterValidator(_not_zero)]
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class AnalysisConfig(TaskConfig):
|
|
30
|
+
pass
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
# --- statistics ---
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class StatisticsConfig(AnalysisConfig):
|
|
37
|
+
pass
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
Aggregate = Literal[
|
|
41
|
+
"count",
|
|
42
|
+
"min",
|
|
43
|
+
"max",
|
|
44
|
+
"mean",
|
|
45
|
+
"std",
|
|
46
|
+
"sem",
|
|
47
|
+
"median",
|
|
48
|
+
"percentile(25)",
|
|
49
|
+
"percentile(75)",
|
|
50
|
+
"sum",
|
|
51
|
+
]
|
|
52
|
+
AGGREGATES: tuple[str, ...] = Aggregate.__args__
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class SummaryConfig(StatisticsConfig):
|
|
56
|
+
legacy_keys = {
|
|
57
|
+
"aggs": "aggs",
|
|
58
|
+
"single_table": "singledf",
|
|
59
|
+
}
|
|
60
|
+
# flat headers ("FLIRR\nmean") always: other tools name columns by text
|
|
61
|
+
legacy_dropped = frozenset({"flattenindex"})
|
|
62
|
+
|
|
63
|
+
aggs: list[Aggregate] = Field(
|
|
64
|
+
default_factory=lambda: list(AGGREGATES), title="Statistics"
|
|
65
|
+
)
|
|
66
|
+
single_table: bool = Field(
|
|
67
|
+
default=True,
|
|
68
|
+
title="Single table",
|
|
69
|
+
description="One table for all features; otherwise one table per feature.",
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
# alpha levels of the KS critical value table (flim.tools.statistics.KS_CRITICAL)
|
|
74
|
+
KSAlpha = Literal[0.1, 0.05, 0.025, 0.01, 0.005, 0.001]
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class KSStatsConfig(StatisticsConfig):
|
|
78
|
+
comparison: CategoryColumn = Field(
|
|
79
|
+
default="Treatment",
|
|
80
|
+
title="Compare",
|
|
81
|
+
description=(
|
|
82
|
+
"Its values are compared pairwise, separately within each group of the"
|
|
83
|
+
" other grouping columns."
|
|
84
|
+
),
|
|
85
|
+
)
|
|
86
|
+
alpha: KSAlpha = Field(default=0.05, title="Significance level")
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
class RelativeChangeConfig(StatisticsConfig):
|
|
90
|
+
method: Literal["mean", "median"] = Field(
|
|
91
|
+
default="mean", title="Reference statistic"
|
|
92
|
+
)
|
|
93
|
+
reference_group: CategoryColumn = Field(
|
|
94
|
+
default="Treatment",
|
|
95
|
+
title="Reference column",
|
|
96
|
+
description="The category column that holds the reference value.",
|
|
97
|
+
)
|
|
98
|
+
reference_value: Annotated[str, ValueOf("reference_group")] = Field(
|
|
99
|
+
default="Ctrl",
|
|
100
|
+
title="Reference value",
|
|
101
|
+
description="Features are divided by their mean or median in these rows.",
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
SeriesStatistic = Literal[
|
|
106
|
+
"series_min",
|
|
107
|
+
"series_max",
|
|
108
|
+
"series_range",
|
|
109
|
+
"series_mean",
|
|
110
|
+
"series_median",
|
|
111
|
+
"delta",
|
|
112
|
+
"delta_min",
|
|
113
|
+
"delta_max",
|
|
114
|
+
"delta_sum",
|
|
115
|
+
"delta_cum",
|
|
116
|
+
"delta_norm",
|
|
117
|
+
]
|
|
118
|
+
SERIES_STATISTICS: tuple[str, ...] = SeriesStatistic.__args__
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
class SeriesAnalysisConfig(StatisticsConfig):
|
|
122
|
+
legacy_keys = {"statistics": None}
|
|
123
|
+
|
|
124
|
+
statistics: set[SeriesStatistic] = Field(
|
|
125
|
+
default_factory=lambda: set(SERIES_STATISTICS)
|
|
126
|
+
- {"series_mean", "series_median"},
|
|
127
|
+
title="Statistics",
|
|
128
|
+
)
|
|
129
|
+
merge_input: bool = Field(
|
|
130
|
+
default=False,
|
|
131
|
+
title="Include the input",
|
|
132
|
+
description="Also include the series' features in the result.",
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
@classmethod
|
|
136
|
+
def _from_legacy(cls, params):
|
|
137
|
+
# legacy: one boolean per statistic
|
|
138
|
+
if not any(s in params for s in SERIES_STATISTICS):
|
|
139
|
+
return {}
|
|
140
|
+
defaults = cls.model_fields["statistics"].default_factory()
|
|
141
|
+
return {
|
|
142
|
+
"statistics": {s for s in SERIES_STATISTICS if params.pop(s, s in defaults)}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
def _to_legacy(self):
|
|
146
|
+
return {s: s in self.statistics for s in SERIES_STATISTICS}
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
# --- machine learning ---
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
class MLConfig(AnalysisConfig):
|
|
153
|
+
legacy_keys = {"random_seed": None}
|
|
154
|
+
hidden_fields = frozenset({"grouping"})
|
|
155
|
+
|
|
156
|
+
random_seed: Optional[int] = Field(
|
|
157
|
+
default=0,
|
|
158
|
+
title="Random seed",
|
|
159
|
+
description="The same results on every run; empty: different on every run.",
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
class ParallelResources(Resources):
|
|
164
|
+
"""For tools using scikit-learn's ``n_jobs``."""
|
|
165
|
+
|
|
166
|
+
preferred_threads: Threads = Field(
|
|
167
|
+
default=-1,
|
|
168
|
+
title="Threads",
|
|
169
|
+
description="Threads within the task (1 or more); -1: all cores allocated to the task.",
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
class PCAConfig(MLConfig):
|
|
174
|
+
legacy_keys = {
|
|
175
|
+
**MLConfig.legacy_keys,
|
|
176
|
+
"keep_original": "keeporig",
|
|
177
|
+
"keep_standardized": "keepstd",
|
|
178
|
+
"explained_plot": "explainedhisto",
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
keep_original: bool = Field(
|
|
182
|
+
default=False,
|
|
183
|
+
title="Include the features",
|
|
184
|
+
description="Also include the original features in the result.",
|
|
185
|
+
)
|
|
186
|
+
keep_standardized: bool = Field(
|
|
187
|
+
default=True,
|
|
188
|
+
title="Include standardized features",
|
|
189
|
+
description="Also include the standardized features in the result.",
|
|
190
|
+
)
|
|
191
|
+
explained_plot: bool = Field(
|
|
192
|
+
default=False,
|
|
193
|
+
title="Explained variance plot",
|
|
194
|
+
description="A bar plot of the variance each component explains.",
|
|
195
|
+
)
|
|
196
|
+
n_components: Union[Annotated[int, Field(ge=1)], Probability, None] = Field(
|
|
197
|
+
default=0.999,
|
|
198
|
+
title="Components",
|
|
199
|
+
description=(
|
|
200
|
+
"A number of components, or the fraction of the variance to explain"
|
|
201
|
+
" (0 to 1); empty: all components."
|
|
202
|
+
),
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
class KMeansConfig(MLConfig):
|
|
207
|
+
n_clusters: int = Field(default=2, ge=1, title="Clusters")
|
|
208
|
+
cluster_prefix: str = Field(
|
|
209
|
+
default="Cluster",
|
|
210
|
+
title="Cluster column",
|
|
211
|
+
description='Name of the new column; clusters are named "<name> 1", ...',
|
|
212
|
+
)
|
|
213
|
+
init: Literal["k-means++", "random"] = Field(
|
|
214
|
+
default="k-means++", title="Initialization"
|
|
215
|
+
)
|
|
216
|
+
algorithm: Literal["lloyd", "elkan"] = Field(default="lloyd", title="Algorithm")
|
|
217
|
+
n_init: int = Field(
|
|
218
|
+
default=4,
|
|
219
|
+
ge=1,
|
|
220
|
+
title="Runs",
|
|
221
|
+
description="Runs with different starting centers; the best is kept.",
|
|
222
|
+
)
|
|
223
|
+
max_iter: int = Field(default=300, ge=1, title="Max iterations")
|
|
224
|
+
tolerance: float = Field(default=1e-4, gt=0, title="Tolerance")
|
|
225
|
+
resources: ParallelResources = Field(
|
|
226
|
+
default_factory=ParallelResources, title="Resources"
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
@classmethod
|
|
230
|
+
def _from_legacy(cls, params):
|
|
231
|
+
# names removed in scikit-learn 1.3; both meant "lloyd"
|
|
232
|
+
if params.get("algorithm") in ("auto", "full"):
|
|
233
|
+
params["algorithm"] = "lloyd"
|
|
234
|
+
return {}
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
class GMMConfig(MLConfig):
|
|
238
|
+
legacy_keys = {**MLConfig.legacy_keys, "covariance": "covar_type"}
|
|
239
|
+
|
|
240
|
+
method: Literal["EM", "Variational Bayesian"] = Field(default="EM", title="Method")
|
|
241
|
+
n_components: int = Field(
|
|
242
|
+
default=2,
|
|
243
|
+
ge=1,
|
|
244
|
+
title="Components",
|
|
245
|
+
description="Components of the model; BIC and AIC are reported for 1 to n.",
|
|
246
|
+
)
|
|
247
|
+
covariance: Literal["full", "tied", "diag", "spherical"] = Field(
|
|
248
|
+
default="full", title="Covariance"
|
|
249
|
+
)
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
class RandomForestConfig(MLConfig):
|
|
253
|
+
legacy_keys = {
|
|
254
|
+
**MLConfig.legacy_keys,
|
|
255
|
+
"target": None,
|
|
256
|
+
"importance_plot": "importancehisto",
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
# the grouping columns are left out of the features
|
|
260
|
+
hidden_fields = frozenset()
|
|
261
|
+
|
|
262
|
+
target: Optional[CategoryColumn] = Field(
|
|
263
|
+
default=None, title="Predict", description="The category column to predict."
|
|
264
|
+
)
|
|
265
|
+
n_estimators: int = Field(default=100, ge=1, title="Trees")
|
|
266
|
+
test_size: Probability = Field(
|
|
267
|
+
default=0.3,
|
|
268
|
+
title="Test fraction",
|
|
269
|
+
description="Fraction of the rows kept for testing the accuracy.",
|
|
270
|
+
)
|
|
271
|
+
importance_plot: bool = Field(default=True, title="Importance plot")
|
|
272
|
+
resources: ParallelResources = Field(
|
|
273
|
+
default_factory=ParallelResources, title="Resources"
|
|
274
|
+
)
|
|
275
|
+
|
|
276
|
+
@classmethod
|
|
277
|
+
def _from_legacy(cls, params):
|
|
278
|
+
return {"target": params.pop("classifier", "") or None}
|
|
279
|
+
|
|
280
|
+
def _to_legacy(self):
|
|
281
|
+
return {"classifier": self.target or ""}
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
class CategoryRule(BaseConfig):
|
|
285
|
+
"""Rows whose ``feature`` lies in (min, max] get the category ``value``."""
|
|
286
|
+
|
|
287
|
+
value: str = Field(title="Value")
|
|
288
|
+
feature: FeatureColumn = Field(title="Feature")
|
|
289
|
+
min: float = Field(title="Min")
|
|
290
|
+
max: float = Field(title="Max")
|
|
291
|
+
|
|
292
|
+
@model_validator(mode="after")
|
|
293
|
+
def _check_range(self):
|
|
294
|
+
if self.min >= self.max:
|
|
295
|
+
raise ValueError(f"'{self.value}': min ({self.min}) >= max ({self.max})")
|
|
296
|
+
return self
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
def _default_rules():
|
|
300
|
+
return [
|
|
301
|
+
CategoryRule(value="negative", feature="FLIRR", min=-100000.0, max=-0.5),
|
|
302
|
+
CategoryRule(value="neutral", feature="FLIRR", min=-0.5, max=0.5),
|
|
303
|
+
CategoryRule(value="positive", feature="FLIRR", min=0.5, max=100000),
|
|
304
|
+
]
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
class CategorizerConfig(AnalysisConfig):
|
|
308
|
+
legacy_keys = {"rules": None}
|
|
309
|
+
hidden_fields = frozenset({"grouping"})
|
|
310
|
+
|
|
311
|
+
name: str = Field(
|
|
312
|
+
default="New Cat",
|
|
313
|
+
title="New column",
|
|
314
|
+
description="Name of the new category column.",
|
|
315
|
+
)
|
|
316
|
+
rules: list[CategoryRule] = Field(
|
|
317
|
+
default_factory=_default_rules,
|
|
318
|
+
title="Rules",
|
|
319
|
+
description=(
|
|
320
|
+
"Rows whose feature lies in a rule's range (min < value <= max) get the"
|
|
321
|
+
" rule's value; the first matching rule wins."
|
|
322
|
+
),
|
|
323
|
+
)
|
|
324
|
+
default: str = Field(
|
|
325
|
+
default="unassigned",
|
|
326
|
+
title="Default value",
|
|
327
|
+
description="The value of rows that no rule matches.",
|
|
328
|
+
)
|
|
329
|
+
merge_input: bool = Field(
|
|
330
|
+
default=True,
|
|
331
|
+
title="Include the input",
|
|
332
|
+
description="Keep all input columns; otherwise only categories and features.",
|
|
333
|
+
)
|
|
334
|
+
|
|
335
|
+
@classmethod
|
|
336
|
+
def allowed_values(cls, values):
|
|
337
|
+
# the features kept: only without the input's columns (dialogs hide them)
|
|
338
|
+
if values.get("merge_input", True):
|
|
339
|
+
return {"features": (values.get("features"),)}
|
|
340
|
+
return {}
|
|
341
|
+
|
|
342
|
+
@field_validator("rules")
|
|
343
|
+
@classmethod
|
|
344
|
+
def _check_overlaps(cls, rules):
|
|
345
|
+
for i, a in enumerate(rules):
|
|
346
|
+
for b in rules[i + 1 :]:
|
|
347
|
+
if a.feature == b.feature and a.min < b.max and b.min < a.max:
|
|
348
|
+
raise ValueError(f"Rules '{a.value}' and '{b.value}' overlap")
|
|
349
|
+
return rules
|
|
350
|
+
|
|
351
|
+
@classmethod
|
|
352
|
+
def _from_legacy(cls, params):
|
|
353
|
+
if "categories" not in params:
|
|
354
|
+
return {}
|
|
355
|
+
return {
|
|
356
|
+
"rules": [
|
|
357
|
+
CategoryRule(value=c["value"], **c["criteria"])
|
|
358
|
+
for c in params.pop("categories")
|
|
359
|
+
]
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
def _to_legacy(self):
|
|
363
|
+
return {
|
|
364
|
+
"categories": [
|
|
365
|
+
{
|
|
366
|
+
"value": r.value,
|
|
367
|
+
"criteria": {"feature": r.feature, "min": r.min, "max": r.max},
|
|
368
|
+
}
|
|
369
|
+
for r in self.rules
|
|
370
|
+
]
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
|
|
374
|
+
# --- autoencoders ---
|
|
375
|
+
|
|
376
|
+
# names of the classes in flim.analysis.ml.autoencoder (not imported: needs torch)
|
|
377
|
+
AutoencoderName = Literal[
|
|
378
|
+
"AE Simulator 1-6",
|
|
379
|
+
"AE Simulator 1-3",
|
|
380
|
+
"AE Dimensionality Reduction 2-10",
|
|
381
|
+
"AE Dimensionality Reduction 2-5",
|
|
382
|
+
]
|
|
383
|
+
|
|
384
|
+
AE_FEATURES = [
|
|
385
|
+
"FAD a1",
|
|
386
|
+
"FAD a2",
|
|
387
|
+
"FAD t1",
|
|
388
|
+
"FAD t2",
|
|
389
|
+
"FAD photons",
|
|
390
|
+
"NAD(P)H a1",
|
|
391
|
+
"NAD(P)H a2",
|
|
392
|
+
"NAD(P)H t1",
|
|
393
|
+
"NAD(P)H t2",
|
|
394
|
+
"NAD(P)H photons",
|
|
395
|
+
]
|
|
396
|
+
|
|
397
|
+
|
|
398
|
+
DEFAULT_DEVICE = "auto" # see flim.core.tool.resolve_device
|
|
399
|
+
|
|
400
|
+
|
|
401
|
+
class AutoencoderResources(Resources):
|
|
402
|
+
preferred_device: Device = Field(
|
|
403
|
+
default=DEFAULT_DEVICE,
|
|
404
|
+
title="Device",
|
|
405
|
+
description="auto: a GPU if there is one where the task runs, else the CPU.",
|
|
406
|
+
)
|
|
407
|
+
preferred_threads: Threads = Field(
|
|
408
|
+
default=-1,
|
|
409
|
+
title="Threads",
|
|
410
|
+
description="Threads within the task (1 or more); -1: all cores allocated to the task.",
|
|
411
|
+
)
|
|
412
|
+
|
|
413
|
+
|
|
414
|
+
class AutoencoderConfig(AnalysisConfig):
|
|
415
|
+
resources: AutoencoderResources = Field(default_factory=AutoencoderResources)
|
|
416
|
+
|
|
417
|
+
@classmethod
|
|
418
|
+
def _from_legacy(cls, params):
|
|
419
|
+
if "device" in params:
|
|
420
|
+
return {"resources": {"preferred_device": params.pop("device")}}
|
|
421
|
+
return {}
|
|
422
|
+
|
|
423
|
+
def _to_legacy(self):
|
|
424
|
+
return {"device": self.resources.preferred_device}
|
|
425
|
+
|
|
426
|
+
|
|
427
|
+
PositiveFloat = Annotated[float, Field(gt=0)]
|
|
428
|
+
NonNegativeFloat = Annotated[float, Field(ge=0)]
|
|
429
|
+
PositiveInt = Annotated[int, Field(ge=1)]
|
|
430
|
+
|
|
431
|
+
|
|
432
|
+
class AETrainConfig(AutoencoderConfig):
|
|
433
|
+
legacy_keys = {
|
|
434
|
+
"epochs": "epoches",
|
|
435
|
+
"learning_rates": "learning_rate",
|
|
436
|
+
"weight_decays": "weight_decay",
|
|
437
|
+
"batch_sizes": "batch_size",
|
|
438
|
+
"model_name": "modelfile",
|
|
439
|
+
"architecture": "model",
|
|
440
|
+
"random_seed": None,
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
grouping: list[CategoryColumn] = Field(
|
|
444
|
+
default=["FOV", "Cell"],
|
|
445
|
+
title="Grouping",
|
|
446
|
+
description=(
|
|
447
|
+
"Training and validation sets are split by these groups (e.g. cells)."
|
|
448
|
+
),
|
|
449
|
+
)
|
|
450
|
+
features: list[FeatureColumn] = Field(
|
|
451
|
+
default_factory=lambda: list(AE_FEATURES), title="Features"
|
|
452
|
+
)
|
|
453
|
+
epochs: PositiveInt = Field(default=20, title="Epochs")
|
|
454
|
+
# each combination of learning rate, weight decay and batch size trains a model
|
|
455
|
+
learning_rates: list[PositiveFloat] = Field(
|
|
456
|
+
default=[1e-4],
|
|
457
|
+
min_length=1,
|
|
458
|
+
title="Learning rates",
|
|
459
|
+
description="Comma-separated; one model per combination with the others.",
|
|
460
|
+
)
|
|
461
|
+
weight_decays: list[NonNegativeFloat] = Field(
|
|
462
|
+
default=[1e-8],
|
|
463
|
+
min_length=1,
|
|
464
|
+
title="Weight decays",
|
|
465
|
+
description="Comma-separated.",
|
|
466
|
+
)
|
|
467
|
+
batch_sizes: list[PositiveInt] = Field(
|
|
468
|
+
default=[128], min_length=1, title="Batch sizes", description="Comma-separated."
|
|
469
|
+
)
|
|
470
|
+
timeseries: CategoryColumn = Field(
|
|
471
|
+
default="Treatment",
|
|
472
|
+
title="Time series",
|
|
473
|
+
description="Not used to split the training and validation sets.",
|
|
474
|
+
)
|
|
475
|
+
model_name: str = Field(
|
|
476
|
+
default="AEModel",
|
|
477
|
+
title="Model name",
|
|
478
|
+
description="Base name of the saved model files.",
|
|
479
|
+
)
|
|
480
|
+
architecture: AutoencoderName = Field(
|
|
481
|
+
default="AE Dimensionality Reduction 2-10", title="Architecture"
|
|
482
|
+
)
|
|
483
|
+
rescale: bool = Field(
|
|
484
|
+
default=False,
|
|
485
|
+
title="Rescale",
|
|
486
|
+
description="Reconstructed values in the units of the input (not 0..1).",
|
|
487
|
+
)
|
|
488
|
+
create_plots: bool = Field(default=True, title="Loss plots")
|
|
489
|
+
train_size: Probability = Field(
|
|
490
|
+
default=0.7,
|
|
491
|
+
title="Training fraction",
|
|
492
|
+
description="Fraction of the groups used for training; the rest validates.",
|
|
493
|
+
)
|
|
494
|
+
checkpoint_interval: PositiveInt = Field(
|
|
495
|
+
default=20,
|
|
496
|
+
title="Checkpoint every",
|
|
497
|
+
description="Save the model every n epochs (and after the last).",
|
|
498
|
+
)
|
|
499
|
+
random_seed: Optional[int] = Field(
|
|
500
|
+
default=None,
|
|
501
|
+
title="Random seed",
|
|
502
|
+
description="The same split and starting weights on every run; empty: random.",
|
|
503
|
+
)
|
|
504
|
+
|
|
505
|
+
@classmethod
|
|
506
|
+
def _from_legacy(cls, params):
|
|
507
|
+
# workflows pass one value of each grid when they map over the combinations
|
|
508
|
+
for key in ("learning_rate", "weight_decay", "batch_size"):
|
|
509
|
+
if key in params and not isinstance(params[key], (list, tuple)):
|
|
510
|
+
params[key] = [params[key]]
|
|
511
|
+
return super()._from_legacy(params)
|
|
512
|
+
|
|
513
|
+
|
|
514
|
+
class _ModelFilesConfig(AutoencoderConfig):
|
|
515
|
+
legacy_keys = {"model_files": None}
|
|
516
|
+
|
|
517
|
+
model_files: list[Path] = Field(
|
|
518
|
+
default=[],
|
|
519
|
+
title="Model files",
|
|
520
|
+
description="Models saved by Autoencoder: Train (one result per model).",
|
|
521
|
+
)
|
|
522
|
+
|
|
523
|
+
@classmethod
|
|
524
|
+
def _from_legacy(cls, params):
|
|
525
|
+
values = super()._from_legacy(params)
|
|
526
|
+
files = params.pop("modelfile", "") # "", one path or a list
|
|
527
|
+
if isinstance(files, (str, Path)):
|
|
528
|
+
files = [files] if files else []
|
|
529
|
+
return {**values, "model_files": files}
|
|
530
|
+
|
|
531
|
+
def _to_legacy(self):
|
|
532
|
+
files = [str(f) for f in self.model_files]
|
|
533
|
+
return {**super()._to_legacy(), "modelfile": files if files else ""}
|
|
534
|
+
|
|
535
|
+
|
|
536
|
+
class AERunConfig(_ModelFilesConfig):
|
|
537
|
+
hidden_fields = frozenset({"grouping"})
|
|
538
|
+
|
|
539
|
+
|
|
540
|
+
class AEAugmentConfig(_ModelFilesConfig):
|
|
541
|
+
grouping: list[CategoryColumn] = Field(
|
|
542
|
+
default=[],
|
|
543
|
+
title="Grouping",
|
|
544
|
+
description="The last column (e.g. Cell) is renumbered in each copy.",
|
|
545
|
+
)
|
|
546
|
+
sets: PositiveInt = Field(
|
|
547
|
+
default=1, title="Copies", description="Simulated copies."
|
|
548
|
+
)
|
|
549
|
+
add_noise: bool = Field(
|
|
550
|
+
default=True,
|
|
551
|
+
title="Add noise",
|
|
552
|
+
description="Add white noise before simulating.",
|
|
553
|
+
)
|
|
554
|
+
snr_db: float = Field(default=0.0, title="Signal-to-noise (dB)")
|