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/__init__.py
ADDED
flim/_version.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Machine learning models of the autoencoder tools (``flim.analysis.ml``)."""
|
|
File without changes
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
# @Time : 10/27/20 3:22 PM
|
|
4
|
+
# @Author: Jiaxin_Zhang
|
|
5
|
+
|
|
6
|
+
import os
|
|
7
|
+
import pkgutil
|
|
8
|
+
import importlib
|
|
9
|
+
import inspect
|
|
10
|
+
import logging
|
|
11
|
+
import torch
|
|
12
|
+
import torch.nn as nn
|
|
13
|
+
import numpy as np
|
|
14
|
+
from sklearn.base import BaseEstimator, TransformerMixin
|
|
15
|
+
from abc import abstractmethod
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def get_autoencoder_classes():
|
|
19
|
+
pkdir = os.path.dirname(__file__)
|
|
20
|
+
for module_loader, name, ispkg in pkgutil.iter_modules([pkdir]):
|
|
21
|
+
importlib.import_module("." + name, __package__)
|
|
22
|
+
available_aes = {
|
|
23
|
+
create_instance(cls).get_name(): cls for cls in AbsAutoencoder.__subclasses__()
|
|
24
|
+
}
|
|
25
|
+
return available_aes
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def init_autoencoders():
|
|
29
|
+
tools = get_autoencoder_classes()
|
|
30
|
+
aes = [create_instance(tools[aname]) for aname in tools]
|
|
31
|
+
return aes
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def create_instance(clazz, **kwargs):
|
|
35
|
+
if isinstance(clazz, str):
|
|
36
|
+
modulename, _, classname = clazz.rpartition(".")
|
|
37
|
+
elif inspect.isclass(clazz):
|
|
38
|
+
modulename = clazz.__module__
|
|
39
|
+
classname = clazz.__name__
|
|
40
|
+
else:
|
|
41
|
+
logging.error(f"Error instantiating {clazz} autoencoder.")
|
|
42
|
+
logging.debug(
|
|
43
|
+
f"Autoencoder modulename={modulename}, classname={classname}, {kwargs}"
|
|
44
|
+
)
|
|
45
|
+
try:
|
|
46
|
+
module = importlib.import_module(modulename)
|
|
47
|
+
class_ = getattr(module, classname)
|
|
48
|
+
toolinstance = class_(**kwargs)
|
|
49
|
+
except Exception as err:
|
|
50
|
+
logging.error(f"Error: {err}")
|
|
51
|
+
logging.error(
|
|
52
|
+
f"Error instantiating {modulename}.{classname} autoencoder, {kwargs}"
|
|
53
|
+
)
|
|
54
|
+
toolinstance = None
|
|
55
|
+
|
|
56
|
+
return toolinstance
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class AbsAutoencoder(nn.Module, BaseEstimator, TransformerMixin):
|
|
60
|
+
def __init__(self):
|
|
61
|
+
super(AbsAutoencoder, self).__init__()
|
|
62
|
+
self.name = __name__
|
|
63
|
+
|
|
64
|
+
def get_name(self):
|
|
65
|
+
return self.name
|
|
66
|
+
|
|
67
|
+
def fit(self, X):
|
|
68
|
+
return self
|
|
69
|
+
|
|
70
|
+
def __sklearn_is_fitted__(self):
|
|
71
|
+
# trained with PyTorch before it is put into a scikit-learn pipeline
|
|
72
|
+
return True
|
|
73
|
+
|
|
74
|
+
def transform(self, X):
|
|
75
|
+
"""Runs X through the model on the model's device.
|
|
76
|
+
|
|
77
|
+
Returns the encoder and decoder outputs as CPU tensors, wherever the
|
|
78
|
+
model runs (cpu, cuda or mps).
|
|
79
|
+
"""
|
|
80
|
+
device = next(self.parameters()).device
|
|
81
|
+
X = torch.as_tensor(np.asarray(X), dtype=torch.float32, device=device)
|
|
82
|
+
with torch.no_grad():
|
|
83
|
+
encoder_out, decoder_out = self(X)
|
|
84
|
+
return encoder_out.cpu(), decoder_out.cpu()
|
|
85
|
+
|
|
86
|
+
@abstractmethod
|
|
87
|
+
def get_description(self):
|
|
88
|
+
return None
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
class Autoencoder_One_Six(AbsAutoencoder): # used for simulation
|
|
92
|
+
def __init__(self, nb_param=2, hidden_size=6):
|
|
93
|
+
super().__init__()
|
|
94
|
+
self.name = "AE Simulator 1-6"
|
|
95
|
+
self.fc1 = nn.Linear(nb_param, hidden_size)
|
|
96
|
+
self.fc4 = nn.Linear(hidden_size, nb_param)
|
|
97
|
+
self.activation2 = nn.ReLU()
|
|
98
|
+
|
|
99
|
+
def fit(self, loaders):
|
|
100
|
+
return self
|
|
101
|
+
|
|
102
|
+
def forward(self, x):
|
|
103
|
+
encoder_out = self.activation2(self.fc1(x))
|
|
104
|
+
decoder_out = self.fc4(encoder_out)
|
|
105
|
+
return encoder_out, decoder_out
|
|
106
|
+
|
|
107
|
+
def get_description(self):
|
|
108
|
+
return (
|
|
109
|
+
"This Autoencoder can be used to simulate new data based on an existing"
|
|
110
|
+
" dataset."
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
class Autoencoder_One_Three(AbsAutoencoder): # also for simulation but isnt used
|
|
115
|
+
def __init__(self, nb_param=2, hidden_size=3):
|
|
116
|
+
super().__init__()
|
|
117
|
+
self.name = "AE Simulator 1-3"
|
|
118
|
+
self.fc1 = nn.Linear(nb_param, hidden_size)
|
|
119
|
+
self.fc4 = nn.Linear(hidden_size, nb_param)
|
|
120
|
+
self.activation2 = nn.ReLU()
|
|
121
|
+
|
|
122
|
+
def fit(self, loaders):
|
|
123
|
+
return self
|
|
124
|
+
|
|
125
|
+
def forward(self, x):
|
|
126
|
+
encoder_out = self.activation2(self.fc1(x))
|
|
127
|
+
decoder_out = self.fc4(encoder_out)
|
|
128
|
+
return encoder_out, decoder_out
|
|
129
|
+
|
|
130
|
+
def get_description(self):
|
|
131
|
+
return (
|
|
132
|
+
"This Autoencoder can be used to simulate new data based on an existing"
|
|
133
|
+
" dataset."
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
class Autoencoder_Two_Ten(AbsAutoencoder):
|
|
138
|
+
def __init__(self, nb_param=2, hidden_size_1=10, hidden_size_2=1):
|
|
139
|
+
super().__init__()
|
|
140
|
+
self.name = "AE Dimensionality Reduction 2-10"
|
|
141
|
+
self.fc1 = nn.Linear(nb_param, hidden_size_1)
|
|
142
|
+
self.fc2 = nn.Linear(hidden_size_1, hidden_size_2)
|
|
143
|
+
self.fc3 = nn.Linear(hidden_size_2, hidden_size_1)
|
|
144
|
+
self.fc4 = nn.Linear(hidden_size_1, nb_param)
|
|
145
|
+
self.activation2 = nn.ReLU()
|
|
146
|
+
|
|
147
|
+
def fit(self, loaders):
|
|
148
|
+
return self
|
|
149
|
+
|
|
150
|
+
def forward(self, x):
|
|
151
|
+
x_out = self.activation2(self.fc1(x))
|
|
152
|
+
encoder_out = self.activation2(self.fc2(x_out))
|
|
153
|
+
y_out = self.activation2(self.fc3(encoder_out))
|
|
154
|
+
decoder_out = self.fc4(y_out)
|
|
155
|
+
return encoder_out, decoder_out
|
|
156
|
+
|
|
157
|
+
def get_description(self):
|
|
158
|
+
return (
|
|
159
|
+
"This Autoencoder is used for dimensionality reduction of the feature space"
|
|
160
|
+
" of an existing dataset."
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
class Autoencoder_Two_Five(AbsAutoencoder): # also not really used
|
|
165
|
+
def __init__(self, nb_param=2, hidden_size_1=5, hidden_size_2=1):
|
|
166
|
+
super().__init__()
|
|
167
|
+
self.name = "AE Dimensionality Reduction 2-5"
|
|
168
|
+
self.fc1 = nn.Linear(nb_param, hidden_size_1)
|
|
169
|
+
self.fc2 = nn.Linear(hidden_size_1, hidden_size_2)
|
|
170
|
+
self.fc3 = nn.Linear(hidden_size_2, hidden_size_1)
|
|
171
|
+
self.fc4 = nn.Linear(hidden_size_1, nb_param)
|
|
172
|
+
self.activation2 = nn.ReLU()
|
|
173
|
+
|
|
174
|
+
def fit(self, loaders):
|
|
175
|
+
return self
|
|
176
|
+
|
|
177
|
+
def forward(self, x):
|
|
178
|
+
x_out = self.activation2(self.fc1(x))
|
|
179
|
+
encoder_out = self.activation2(self.fc2(x_out))
|
|
180
|
+
y_out = self.activation2(self.fc3(encoder_out))
|
|
181
|
+
decoder_out = self.fc4(y_out)
|
|
182
|
+
return encoder_out, decoder_out
|
|
183
|
+
|
|
184
|
+
def get_description(self):
|
|
185
|
+
return (
|
|
186
|
+
"This Autoencoder is used for dimensionality reduction of the feature space"
|
|
187
|
+
" of an existing dataset."
|
|
188
|
+
)
|
flim/analyzerapp.py
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""
|
|
4
|
+
Created on Mon May 7 14:38:04 2018
|
|
5
|
+
|
|
6
|
+
@author: khs3z
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import logging
|
|
10
|
+
import argparse
|
|
11
|
+
import sys
|
|
12
|
+
from flim._version import __version__
|
|
13
|
+
from flim.core.tools import FLIMAnalyzer, check_executor_available
|
|
14
|
+
from flim.core.prefect_server import use_prefect_server
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def parse_arguments():
|
|
18
|
+
"""Parses command line arguments."""
|
|
19
|
+
parser = argparse.ArgumentParser(
|
|
20
|
+
epilog="flimanalyzer --batch ARGS runs flimbatch ARGS (batch mode, no GUI)."
|
|
21
|
+
)
|
|
22
|
+
parser.add_argument(
|
|
23
|
+
"-i",
|
|
24
|
+
"--input",
|
|
25
|
+
nargs="+",
|
|
26
|
+
help="data files to open in the GUI, each in its own window",
|
|
27
|
+
)
|
|
28
|
+
parser.add_argument(
|
|
29
|
+
"-e",
|
|
30
|
+
"--executor",
|
|
31
|
+
default="LocalExecutor",
|
|
32
|
+
help=(
|
|
33
|
+
"executor to run pipeline. Options: LocalExecutor, LocalDaskExecutor,"
|
|
34
|
+
" DaskExecutor"
|
|
35
|
+
),
|
|
36
|
+
)
|
|
37
|
+
parser.add_argument(
|
|
38
|
+
"-a",
|
|
39
|
+
"--execargs",
|
|
40
|
+
default="",
|
|
41
|
+
help=(
|
|
42
|
+
"comma separated list of arguments to pass to executor instance, e.g."
|
|
43
|
+
" num_workers=4 (LocalExecutor: number of tasks run at the same time)"
|
|
44
|
+
),
|
|
45
|
+
)
|
|
46
|
+
parser.add_argument(
|
|
47
|
+
"--no-prefect-server",
|
|
48
|
+
action="store_true",
|
|
49
|
+
help=(
|
|
50
|
+
"don't start a local Prefect server if none is running; flows then use a"
|
|
51
|
+
" temporary server"
|
|
52
|
+
),
|
|
53
|
+
)
|
|
54
|
+
parser.add_argument(
|
|
55
|
+
"-c", "--config", default=None, help="settings file to start with (any version)"
|
|
56
|
+
)
|
|
57
|
+
parser.add_argument(
|
|
58
|
+
"-l",
|
|
59
|
+
"--log",
|
|
60
|
+
choices=["info", "debug", "warning", "error", "critical"],
|
|
61
|
+
default="info",
|
|
62
|
+
help="logging level",
|
|
63
|
+
)
|
|
64
|
+
return parser.parse_args()
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def interactive_run(fa, files=None, settings=None):
|
|
68
|
+
try:
|
|
69
|
+
import wx # noqa: F401
|
|
70
|
+
except ImportError:
|
|
71
|
+
sys.exit(
|
|
72
|
+
"flimanalyzer: the GUI needs wxPython, which is not installed:"
|
|
73
|
+
' pip install "flimanalyzer[gui]". Without it, use flimbatch.'
|
|
74
|
+
)
|
|
75
|
+
from flim.gui.app import FLIMAnalyzerApp # here: batch runs never import wx
|
|
76
|
+
|
|
77
|
+
app = FLIMAnalyzerApp(fa, config=settings, files=files)
|
|
78
|
+
app.MainLoop()
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def init_logger(loglevel):
|
|
82
|
+
for handler in logging.root.handlers[:]:
|
|
83
|
+
logging.root.removeHandler(handler)
|
|
84
|
+
numeric_level = getattr(logging, loglevel, None)
|
|
85
|
+
if not isinstance(numeric_level, int):
|
|
86
|
+
raise ValueError(f"Invalid log level: {loglevel}")
|
|
87
|
+
logging.basicConfig(
|
|
88
|
+
level=numeric_level,
|
|
89
|
+
filename="flimanalyzer.log",
|
|
90
|
+
filemode="w",
|
|
91
|
+
format="%(levelname)s \t %(asctime)-15s - %(module)s.%(funcName)s: %(message)s",
|
|
92
|
+
)
|
|
93
|
+
logging.info(f"Log level: {numeric_level}")
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def main():
|
|
97
|
+
argv = sys.argv[1:]
|
|
98
|
+
if "--batch" in argv: # the rest is flimbatch's (flim/batch.py)
|
|
99
|
+
from flim import batch
|
|
100
|
+
|
|
101
|
+
argv.remove("--batch")
|
|
102
|
+
sys.exit(batch.main(argv))
|
|
103
|
+
# with open(os.path.join(os.path.dirname(__file__),"..","VERSION"), "r") as vf:
|
|
104
|
+
# __version__ = vf.read().strip()
|
|
105
|
+
|
|
106
|
+
args = parse_arguments()
|
|
107
|
+
try:
|
|
108
|
+
check_executor_available(args.executor)
|
|
109
|
+
except RuntimeError as e:
|
|
110
|
+
sys.exit(f"flimanalyzer: {e}")
|
|
111
|
+
init_logger(args.log.upper())
|
|
112
|
+
version = __version__
|
|
113
|
+
logging.info(f"FLIMAnalyzer version {version}")
|
|
114
|
+
|
|
115
|
+
logging.debug(args)
|
|
116
|
+
fa = FLIMAnalyzer(executor=args.executor, execargs=args.execargs)
|
|
117
|
+
settings = None
|
|
118
|
+
if args.config:
|
|
119
|
+
from flim.core.settings import load_settings
|
|
120
|
+
|
|
121
|
+
settings = load_settings(args.config)
|
|
122
|
+
with use_prefect_server(start=not args.no_prefect_server):
|
|
123
|
+
interactive_run(fa, files=args.input, settings=settings)
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
if __name__ == "__main__":
|
|
127
|
+
import platform, multiprocessing
|
|
128
|
+
|
|
129
|
+
if platform.system() == "Darwin":
|
|
130
|
+
multiprocessing.set_start_method("spawn")
|
|
131
|
+
main()
|
flim/batch.py
ADDED
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
"""Batch mode: import data files and run tools or workflows without the GUI.
|
|
2
|
+
|
|
3
|
+
``flimbatch -i <files or folders> -o <folder> [-c settings.json] [--run NAME]``
|
|
4
|
+
|
|
5
|
+
1. Reads the settings (any version, see flim.core.settings), else the defaults.
|
|
6
|
+
2. Imports the files as the GUI's Import does: delimiter, filename parser and its
|
|
7
|
+
patterns, renamed and dropped columns, and the range filters if they are used.
|
|
8
|
+
The table is saved as ``Imported.txt`` in the output folder.
|
|
9
|
+
3. Runs each ``--run`` tool or workflow on it, with its settings from the settings
|
|
10
|
+
file; the results are saved in ``<output>/<name>/``. Workflow columns that are not
|
|
11
|
+
in the data are errors unless ``--allow-guesses`` is given.
|
|
12
|
+
|
|
13
|
+
The exit code is 0 if everything ran, 1 otherwise. Only the core is imported (no wx).
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
import argparse
|
|
17
|
+
import logging
|
|
18
|
+
import os
|
|
19
|
+
import sys
|
|
20
|
+
from typing import Optional
|
|
21
|
+
|
|
22
|
+
import numpy as np
|
|
23
|
+
import pandas as pd
|
|
24
|
+
|
|
25
|
+
from flim.core.execution import ExecutionOptions
|
|
26
|
+
from flim.core.filter import RangeFilter
|
|
27
|
+
from flim.core.importer import dataimporter
|
|
28
|
+
from flim.core.parser import instantiate_parser
|
|
29
|
+
from flim.core.paths import canonical_folder, folder_problem
|
|
30
|
+
from flim.core.preprocessor import defaultpreprocessor
|
|
31
|
+
from flim.core.registry import TOOLS_GROUP, discover, tools
|
|
32
|
+
from flim.core.schema import TableSchema
|
|
33
|
+
from flim.core.settings import AppSettings, load_settings
|
|
34
|
+
|
|
35
|
+
IMPORTED = "Imported.txt"
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class BatchError(Exception):
|
|
39
|
+
"""A batch step that cannot be done, with the reason for the user."""
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def import_table(files, settings: AppSettings, exclude=()) -> pd.DataFrame:
|
|
43
|
+
"""Imports data files and folders with the import, preprocess and filter
|
|
44
|
+
settings."""
|
|
45
|
+
imported = settings.import_
|
|
46
|
+
parser = instantiate_parser(f"flim.core.parser.{imported.filename_parser.parser}")
|
|
47
|
+
if parser is None:
|
|
48
|
+
raise BatchError(f"Unknown filename parser {imported.filename_parser.parser}")
|
|
49
|
+
if imported.filename_parser.patterns:
|
|
50
|
+
parser.set_regexpatterns(imported.filename_parser.patterns)
|
|
51
|
+
preprocessor = defaultpreprocessor()
|
|
52
|
+
preprocessor.set_replacementheaders(settings.preprocess.rename)
|
|
53
|
+
preprocessor.set_dropcolumns(settings.preprocess.drop)
|
|
54
|
+
|
|
55
|
+
importer = dataimporter()
|
|
56
|
+
importer.set_delimiter(imported.delimiter)
|
|
57
|
+
importer.add_files(list(files), exclude=list(exclude) + imported.exclude_files)
|
|
58
|
+
if not importer.get_files():
|
|
59
|
+
raise BatchError("No data files found")
|
|
60
|
+
importer.set_parser(parser)
|
|
61
|
+
importer.set_column_combos(imported.category_combinations)
|
|
62
|
+
importer.set_preprocessor(preprocessor)
|
|
63
|
+
data, filenames, _ = importer.import_data()
|
|
64
|
+
if data is None or not filenames:
|
|
65
|
+
raise BatchError("None of the files could be read")
|
|
66
|
+
logging.info(f"Imported {len(filenames)} file(s): {data.shape[0]} rows")
|
|
67
|
+
if settings.filters.use:
|
|
68
|
+
data = filtered(data, settings.filters.range_filters)
|
|
69
|
+
return data
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def filtered(data: pd.DataFrame, range_filters) -> pd.DataFrame:
|
|
73
|
+
"""The rows that pass the selected range filters (as the data windows show)."""
|
|
74
|
+
dropped = [RangeFilter(params=p).get_dropped(data) for p in range_filters]
|
|
75
|
+
dropped = [rows for rows, p in zip(dropped, range_filters) if p.get("selected")]
|
|
76
|
+
rows = np.unique(np.concatenate([np.asarray(r, dtype=int) for r in dropped] or [[]]))
|
|
77
|
+
if len(rows):
|
|
78
|
+
logging.info(f"Range filters dropped {len(rows)} rows")
|
|
79
|
+
return data.drop(data.index[rows.astype(int)]).reset_index(drop=True)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def run_tool(
|
|
83
|
+
name: str,
|
|
84
|
+
data: pd.DataFrame,
|
|
85
|
+
settings: AppSettings,
|
|
86
|
+
output_dir: str,
|
|
87
|
+
execution: Optional[ExecutionOptions] = None,
|
|
88
|
+
allow_guesses: bool = False,
|
|
89
|
+
):
|
|
90
|
+
"""Runs a tool or workflow on ``data`` with its saved settings; its results are
|
|
91
|
+
saved in ``<output_dir>/<name>/``.
|
|
92
|
+
|
|
93
|
+
Returns:
|
|
94
|
+
flim.core.runner.FlowRun
|
|
95
|
+
"""
|
|
96
|
+
from flim.core.runner import run_flow
|
|
97
|
+
from flim.core.workflow import Workflow, WorkflowValidationError
|
|
98
|
+
|
|
99
|
+
if name not in tools:
|
|
100
|
+
raise BatchError(f"No tool or workflow named '{name}'")
|
|
101
|
+
tool = tools.get(name)
|
|
102
|
+
required = [spec for spec in tool.inputs if not spec.optional]
|
|
103
|
+
if len(required) > 1 and not issubclass(tool, Workflow):
|
|
104
|
+
raise BatchError(f"{name} needs {len(required)} input tables; batch runs give one")
|
|
105
|
+
config = settings.tool_settings.config(tool)
|
|
106
|
+
execution = execution or ExecutionOptions()
|
|
107
|
+
autosave_dir = os.path.join(output_dir, tool.name)
|
|
108
|
+
task_runner = execution.task_runner()
|
|
109
|
+
if issubclass(tool, Workflow):
|
|
110
|
+
inputs = [data] * len(tool.inputs) # e.g. training data and data: the same
|
|
111
|
+
try:
|
|
112
|
+
return tool().run_flow(
|
|
113
|
+
*inputs,
|
|
114
|
+
config=config,
|
|
115
|
+
task_runner=task_runner,
|
|
116
|
+
autosave_dir=autosave_dir,
|
|
117
|
+
strict=not allow_guesses,
|
|
118
|
+
)
|
|
119
|
+
except WorkflowValidationError as err:
|
|
120
|
+
raise BatchError(f"{name}:\n{err}") from None
|
|
121
|
+
many = tool.inputs[0].many # e.g. Concatenate: a dict of tables
|
|
122
|
+
schema = TableSchema.from_frame(data)
|
|
123
|
+
schemas = [{"Imported": schema} if many else schema]
|
|
124
|
+
errors = [i for i in tool.validate(config, schemas) if i.severity == "error"]
|
|
125
|
+
if errors:
|
|
126
|
+
lines = [f"{i.field}: {i.message}" if i.field else i.message for i in errors]
|
|
127
|
+
raise BatchError(f"{name}:\n" + "\n".join(lines))
|
|
128
|
+
|
|
129
|
+
def build():
|
|
130
|
+
table = {"Imported": data} if many else data
|
|
131
|
+
for each in tool().map_configs(config):
|
|
132
|
+
tool()(table, config=each, task_tags=tool.name)
|
|
133
|
+
|
|
134
|
+
return run_flow(tool.name, build, task_runner=task_runner, autosave_dir=autosave_dir)
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def parse_arguments(argv=None):
|
|
138
|
+
parser = argparse.ArgumentParser(
|
|
139
|
+
prog="flimbatch",
|
|
140
|
+
description="Imports FLIM data files and runs tools or workflows on them.",
|
|
141
|
+
)
|
|
142
|
+
parser.add_argument(
|
|
143
|
+
"-i", "--input", nargs="+", required=True, help="data files or folders"
|
|
144
|
+
)
|
|
145
|
+
parser.add_argument(
|
|
146
|
+
"-x", "--exclude", nargs="+", default=[], help="files not to import"
|
|
147
|
+
)
|
|
148
|
+
parser.add_argument("-o", "--output", required=True, help="folder for the results")
|
|
149
|
+
parser.add_argument(
|
|
150
|
+
"-c",
|
|
151
|
+
"--settings",
|
|
152
|
+
"--config",
|
|
153
|
+
dest="settings",
|
|
154
|
+
help="settings file (saved in the GUI; any version)",
|
|
155
|
+
)
|
|
156
|
+
parser.add_argument(
|
|
157
|
+
"-r",
|
|
158
|
+
"--run",
|
|
159
|
+
action="append",
|
|
160
|
+
default=[],
|
|
161
|
+
metavar="NAME",
|
|
162
|
+
help="tool or workflow to run on the imported data (repeatable)",
|
|
163
|
+
)
|
|
164
|
+
parser.add_argument(
|
|
165
|
+
"--allow-guesses",
|
|
166
|
+
action="store_true",
|
|
167
|
+
help="workflows: use the closest columns for columns not in the data",
|
|
168
|
+
)
|
|
169
|
+
parser.add_argument(
|
|
170
|
+
"-e",
|
|
171
|
+
"--executor",
|
|
172
|
+
default="LocalExecutor",
|
|
173
|
+
help="LocalExecutor, LocalDaskExecutor or DaskExecutor",
|
|
174
|
+
)
|
|
175
|
+
parser.add_argument(
|
|
176
|
+
"-a", "--execargs", default="", help="executor arguments, e.g. num_workers=4"
|
|
177
|
+
)
|
|
178
|
+
parser.add_argument(
|
|
179
|
+
"--no-prefect-server",
|
|
180
|
+
action="store_true",
|
|
181
|
+
help="don't start a local Prefect server; flows use a temporary one",
|
|
182
|
+
)
|
|
183
|
+
parser.add_argument(
|
|
184
|
+
"-l",
|
|
185
|
+
"--log",
|
|
186
|
+
choices=["debug", "info", "warning", "error"],
|
|
187
|
+
default="info",
|
|
188
|
+
help="logging level",
|
|
189
|
+
)
|
|
190
|
+
return parser.parse_args(argv)
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def main(argv=None) -> int:
|
|
194
|
+
args = parse_arguments(argv)
|
|
195
|
+
logging.basicConfig(
|
|
196
|
+
level=args.log.upper(), format="%(levelname)s %(message)s", force=True
|
|
197
|
+
)
|
|
198
|
+
import flim.tools # noqa: F401 (registers the tools)
|
|
199
|
+
import flim.workflow # noqa: F401 (registers the workflows)
|
|
200
|
+
from flim.core.prefect_server import use_prefect_server
|
|
201
|
+
from flim.core.tools import check_executor_available
|
|
202
|
+
|
|
203
|
+
discover(TOOLS_GROUP) # tools of other packages
|
|
204
|
+
try:
|
|
205
|
+
check_executor_available(args.executor)
|
|
206
|
+
settings = load_settings(args.settings) if args.settings else AppSettings.default()
|
|
207
|
+
output = canonical_folder(args.output)
|
|
208
|
+
if problem := folder_problem(output):
|
|
209
|
+
raise BatchError(f"--output: {problem}")
|
|
210
|
+
os.makedirs(output, exist_ok=True)
|
|
211
|
+
data = import_table(args.input, settings, exclude=args.exclude)
|
|
212
|
+
path = os.path.join(output, IMPORTED)
|
|
213
|
+
data.to_csv(path, sep="\t", index=False)
|
|
214
|
+
logging.info(f"Saved {path}")
|
|
215
|
+
execution = ExecutionOptions.from_legacy(args.executor, args.execargs)
|
|
216
|
+
failed = []
|
|
217
|
+
with use_prefect_server(start=not args.no_prefect_server):
|
|
218
|
+
for name in args.run:
|
|
219
|
+
run = run_tool(
|
|
220
|
+
name, data, settings, output, execution, args.allow_guesses
|
|
221
|
+
)
|
|
222
|
+
if run.failed or run.state != "completed":
|
|
223
|
+
failed.append(f"{name}: {', '.join(run.failed) or run.state}")
|
|
224
|
+
logging.info(f"{name}: {run.state}; results in {run.autosave_dir}")
|
|
225
|
+
except (BatchError, RuntimeError, OSError, ValueError) as err:
|
|
226
|
+
print(f"flimbatch: {err}", file=sys.stderr)
|
|
227
|
+
return 1
|
|
228
|
+
if failed:
|
|
229
|
+
print("flimbatch: failed tasks\n" + "\n".join(failed), file=sys.stderr)
|
|
230
|
+
return 1
|
|
231
|
+
return 0
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
if __name__ == "__main__":
|
|
235
|
+
sys.exit(main())
|
flim/configs/__init__.py
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"""Configs of the built-in tools and workflows (no wx, so usable without the GUI)."""
|
|
2
|
+
|
|
3
|
+
from flim.configs.analysis import (
|
|
4
|
+
AEAugmentConfig,
|
|
5
|
+
AERunConfig,
|
|
6
|
+
AETrainConfig,
|
|
7
|
+
CategorizerConfig,
|
|
8
|
+
GMMConfig,
|
|
9
|
+
KMeansConfig,
|
|
10
|
+
KSStatsConfig,
|
|
11
|
+
PCAConfig,
|
|
12
|
+
RandomForestConfig,
|
|
13
|
+
RelativeChangeConfig,
|
|
14
|
+
SeriesAnalysisConfig,
|
|
15
|
+
SummaryConfig,
|
|
16
|
+
)
|
|
17
|
+
from flim.configs.data import (
|
|
18
|
+
CategoryOrderConfig,
|
|
19
|
+
ConcatConfig,
|
|
20
|
+
FilterConfig,
|
|
21
|
+
MergeConfig,
|
|
22
|
+
PivotConfig,
|
|
23
|
+
SortConfig,
|
|
24
|
+
UnpivotConfig,
|
|
25
|
+
)
|
|
26
|
+
from flim.configs.data import SplitConfig # noqa: F401 (re-exported)
|
|
27
|
+
from flim.configs.plot import (
|
|
28
|
+
BarPlotConfig,
|
|
29
|
+
BoxPlotConfig,
|
|
30
|
+
FLIRRPlotConfig,
|
|
31
|
+
HeatmapConfig,
|
|
32
|
+
HistogramConfig,
|
|
33
|
+
KDEConfig,
|
|
34
|
+
LinePlotConfig,
|
|
35
|
+
PairPlotConfig,
|
|
36
|
+
ScatterPlotConfig,
|
|
37
|
+
SwarmPlotConfig,
|
|
38
|
+
ViolinPlotConfig,
|
|
39
|
+
)
|
|
40
|
+
from flim.configs.workflow import AEFeatureConfig, AETuneConfig, BasicFLIRRConfig
|
|
41
|
+
|
|
42
|
+
# config class of each plugin, by the plugin's name (the key of its saved settings)
|
|
43
|
+
LEGACY_TOOL_CONFIGS = {
|
|
44
|
+
"Order Categories": CategoryOrderConfig,
|
|
45
|
+
"Concat Data": ConcatConfig,
|
|
46
|
+
"Filter": FilterConfig,
|
|
47
|
+
"Merge": MergeConfig,
|
|
48
|
+
"Pivot": PivotConfig,
|
|
49
|
+
"Sort": SortConfig,
|
|
50
|
+
"Unpivot": UnpivotConfig,
|
|
51
|
+
"Summarize": SummaryConfig,
|
|
52
|
+
"KS-Statistics": KSStatsConfig,
|
|
53
|
+
"Relative Change": RelativeChangeConfig,
|
|
54
|
+
"Series Analysis": SeriesAnalysisConfig,
|
|
55
|
+
"PCA": PCAConfig,
|
|
56
|
+
"K-Means": KMeansConfig,
|
|
57
|
+
"Gaussian Mixed Model": GMMConfig,
|
|
58
|
+
"Random Forest": RandomForestConfig,
|
|
59
|
+
"Categorize Data": CategorizerConfig,
|
|
60
|
+
"Autoencoder: Train": AETrainConfig,
|
|
61
|
+
"Autoencoder: Run": AERunConfig,
|
|
62
|
+
"Autoencoder: Augment Data": AEAugmentConfig,
|
|
63
|
+
"Bar Plot": BarPlotConfig,
|
|
64
|
+
"Box Plot": BoxPlotConfig,
|
|
65
|
+
"Swarm Plot": SwarmPlotConfig,
|
|
66
|
+
"Violin Plot": ViolinPlotConfig,
|
|
67
|
+
"Line Plot": LinePlotConfig,
|
|
68
|
+
"KDE": KDEConfig,
|
|
69
|
+
"Frequency Histogram": HistogramConfig,
|
|
70
|
+
"Heatmap": HeatmapConfig,
|
|
71
|
+
"Pair Plot": PairPlotConfig,
|
|
72
|
+
"Scatter Plot": ScatterPlotConfig,
|
|
73
|
+
"FLIRR Plot": FLIRRPlotConfig,
|
|
74
|
+
"Basic FLIRR Analysis": BasicFLIRRConfig,
|
|
75
|
+
"FLIM Data Augmentation Tuning": AETuneConfig,
|
|
76
|
+
"FLIM Feature Analysis": AEFeatureConfig,
|
|
77
|
+
}
|