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.
Files changed (110) hide show
  1. flim/__init__.py +2 -0
  2. flim/_version.py +5 -0
  3. flim/analysis/__init__.py +1 -0
  4. flim/analysis/ml/__init__.py +0 -0
  5. flim/analysis/ml/autoencoder.py +188 -0
  6. flim/analyzerapp.py +131 -0
  7. flim/batch.py +235 -0
  8. flim/configs/__init__.py +77 -0
  9. flim/configs/analysis.py +554 -0
  10. flim/configs/data.py +278 -0
  11. flim/configs/plot.py +543 -0
  12. flim/configs/workflow.py +204 -0
  13. flim/core/__init__.py +0 -0
  14. flim/core/calculator.py +0 -0
  15. flim/core/config.py +305 -0
  16. flim/core/execution.py +110 -0
  17. flim/core/fields.py +226 -0
  18. flim/core/filter.py +179 -0
  19. flim/core/graph.py +111 -0
  20. flim/core/headers.py +72 -0
  21. flim/core/importer.py +269 -0
  22. flim/core/parser.py +305 -0
  23. flim/core/paths.py +66 -0
  24. flim/core/prefect_server.py +152 -0
  25. flim/core/preprocessor.py +287 -0
  26. flim/core/registry.py +108 -0
  27. flim/core/runner.py +703 -0
  28. flim/core/schema.py +114 -0
  29. flim/core/settings.py +419 -0
  30. flim/core/tool.py +284 -0
  31. flim/core/tools.py +76 -0
  32. flim/core/types.py +113 -0
  33. flim/core/validation.py +272 -0
  34. flim/core/workflow.py +148 -0
  35. flim/gui/__init__.py +12 -0
  36. flim/gui/app.py +953 -0
  37. flim/gui/configdialog.py +495 -0
  38. flim/gui/datapanel.py +841 -0
  39. flim/gui/delimpanel.py +56 -0
  40. flim/gui/dialogs/__init__.py +473 -0
  41. flim/gui/dialogs/autoencoder.py +89 -0
  42. flim/gui/dialogs/categoryorder.py +130 -0
  43. flim/gui/dialogs/filter.py +86 -0
  44. flim/gui/dialogs/unpivot.py +72 -0
  45. flim/gui/dicttablepanel.py +178 -0
  46. flim/gui/editors.py +1578 -0
  47. flim/gui/events.py +162 -0
  48. flim/gui/icons.py +16 -0
  49. flim/gui/importdlg.py +391 -0
  50. flim/gui/listcontrol.py +472 -0
  51. flim/gui/macos.py +77 -0
  52. flim/gui/plotframe.py +73 -0
  53. flim/gui/progressdlg.py +290 -0
  54. flim/resources/aeaugment.png +0 -0
  55. flim/resources/aefeatureworkflow.png +0 -0
  56. flim/resources/aerun.png +0 -0
  57. flim/resources/aetrain.png +0 -0
  58. flim/resources/aetuneworkflow.png +0 -0
  59. flim/resources/barplot.png +0 -0
  60. flim/resources/boxplot.png +0 -0
  61. flim/resources/categorize.png +0 -0
  62. flim/resources/concatenate.png +0 -0
  63. flim/resources/fileopen.png +0 -0
  64. flim/resources/filter.png +0 -0
  65. flim/resources/flimanalyzer.ico +0 -0
  66. flim/resources/flimanalyzer.png +0 -0
  67. flim/resources/flirrplot.png +0 -0
  68. flim/resources/flirrworkflow.png +0 -0
  69. flim/resources/gmm.png +0 -0
  70. flim/resources/heatmap.png +0 -0
  71. flim/resources/histogram.png +0 -0
  72. flim/resources/import.png +0 -0
  73. flim/resources/kde.png +0 -0
  74. flim/resources/kmeans.png +0 -0
  75. flim/resources/ks.png +0 -0
  76. flim/resources/lineplot.png +0 -0
  77. flim/resources/merge.png +0 -0
  78. flim/resources/order.png +0 -0
  79. flim/resources/pairplot.png +0 -0
  80. flim/resources/pca.png +0 -0
  81. flim/resources/pivot.png +0 -0
  82. flim/resources/randomforest.png +0 -0
  83. flim/resources/relchange.png +0 -0
  84. flim/resources/scatter.png +0 -0
  85. flim/resources/seriesanalysis.png +0 -0
  86. flim/resources/sort.png +0 -0
  87. flim/resources/split.png +0 -0
  88. flim/resources/summary.png +0 -0
  89. flim/resources/swarmplot.png +0 -0
  90. flim/resources/unpivot.png +0 -0
  91. flim/resources/violinplot.png +0 -0
  92. flim/resources/workflow.png +0 -0
  93. flim/results.py +70 -0
  94. flim/tools/__init__.py +7 -0
  95. flim/tools/autoencoder.py +662 -0
  96. flim/tools/common.py +62 -0
  97. flim/tools/data.py +301 -0
  98. flim/tools/ml.py +311 -0
  99. flim/tools/plot.py +1439 -0
  100. flim/tools/statistics.py +348 -0
  101. flim/utils.py +43 -0
  102. flim/workflow/__init__.py +3 -0
  103. flim/workflow/aefeature.py +145 -0
  104. flim/workflow/aetune.py +163 -0
  105. flim/workflow/basicflow.py +99 -0
  106. flimanalyzer-0.6.0.dist-info/METADATA +262 -0
  107. flimanalyzer-0.6.0.dist-info/RECORD +110 -0
  108. flimanalyzer-0.6.0.dist-info/WHEEL +4 -0
  109. flimanalyzer-0.6.0.dist-info/entry_points.txt +5 -0
  110. flimanalyzer-0.6.0.dist-info/licenses/LICENSE +19 -0
flim/__init__.py ADDED
@@ -0,0 +1,2 @@
1
+ # Importing flim does not load the tools or the GUI: tools register when flim.tools
2
+ # is imported (the application does that), so flim.core can be used without wxPython.
flim/_version.py ADDED
@@ -0,0 +1,5 @@
1
+ # Generated by versioningit
2
+ __version__ = "0.6.0"
3
+ __build_date__ = "2026-11-03 13:58:33.016407+00:00"
4
+ __git_commit__ = "835f0b5776d57eda8214ed7da58ee09df86784ed"
5
+ __dirty__ = False
@@ -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())
@@ -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
+ }