1
|
|
|
""" |
2
|
|
|
Command-line interface for mandos. |
3
|
|
|
""" |
4
|
|
|
|
5
|
|
|
from __future__ import annotations |
6
|
|
|
|
7
|
|
|
from pathlib import Path |
8
|
|
|
from typing import Optional, List |
9
|
|
|
|
10
|
|
|
from mandos.entry._entry_utils import EntryUtils |
11
|
|
|
from mandos.entry.searchers import InputFrame |
12
|
|
|
|
13
|
|
|
from mandos.model.utils.setup import logger, MANDOS_SETUP |
14
|
|
|
from mandos.analysis.io_defns import ( |
15
|
|
|
SimilarityDfLongForm, |
16
|
|
|
PsiProjectedDf, |
17
|
|
|
SimilarityDfShortForm, |
18
|
|
|
EnrichmentDf, |
19
|
|
|
ConcordanceDf, |
20
|
|
|
) |
21
|
|
|
from mandos.analysis.concordance import ConcordanceCalculation |
22
|
|
|
from mandos.analysis.distances import MatrixCalculation |
23
|
|
|
from mandos.analysis.enrichment import EnrichmentCalculation, RealAlg, BoolAlg |
24
|
|
|
from mandos.analysis.io_defns import ScoreDf |
25
|
|
|
from mandos.analysis.prepping import MatrixPrep |
26
|
|
|
from mandos.entry._common_args import CommonArgs |
27
|
|
|
from mandos.entry._arg_utils import Arg, Opt, ArgUtils |
28
|
|
|
from mandos.entry._common_args import CommonArgs as Ca |
|
|
|
|
29
|
|
|
from mandos.model.hits import HitFrame |
30
|
|
|
from mandos.model.settings import MANDOS_SETTINGS |
31
|
|
|
from mandos.analysis.projection import UMAP |
32
|
|
|
|
33
|
|
|
|
34
|
|
|
DEF_SUFFIX = MANDOS_SETTINGS.default_table_suffix |
35
|
|
|
|
36
|
|
|
if UMAP is None: |
37
|
|
|
_umap_params = {} |
38
|
|
|
else: |
39
|
|
|
_umap_params = { |
40
|
|
|
k: v |
41
|
|
|
for k, v in UMAP().get_params(deep=False).items() |
42
|
|
|
if k not in {"random_state", "metric"} |
43
|
|
|
} |
44
|
|
|
|
45
|
|
|
|
46
|
|
|
class Aa: |
|
|
|
|
47
|
|
|
|
48
|
|
|
in_scores_table: Path = Opt.in_file( |
49
|
|
|
rf""" |
50
|
|
|
Path to a table containing scores. |
51
|
|
|
|
52
|
|
|
{Ca.input_formats} |
53
|
|
|
|
54
|
|
|
Required columns are 'inchikey', 'score_name', and 'score_value'. |
55
|
|
|
The InChI Keys must match those provided for the search. |
56
|
|
|
Each score must start with either 'score_' or 'is_'. |
57
|
|
|
|
58
|
|
|
{ArgUtils.df_description(ScoreDf)} |
59
|
|
|
""" |
60
|
|
|
) |
61
|
|
|
|
62
|
|
|
out_enrichment: Optional[Path] = Opt.out_file( |
63
|
|
|
rf""" |
64
|
|
|
Path to write enrichment info. |
65
|
|
|
|
66
|
|
|
{Ca.output_formats} |
67
|
|
|
|
68
|
|
|
One row will be included per predicate/object pair (or list of them), per bootstrap sample. |
69
|
|
|
Rows with a null bootstrap sample are not sub-sampled. |
70
|
|
|
Columns will correspond to the columns you provided. |
71
|
|
|
|
72
|
|
|
{ArgUtils.df_description(EnrichmentDf)} |
73
|
|
|
|
74
|
|
|
[default: <path>-correlation-<scores.filename>{MANDOS_SETTINGS.default_table_suffix}] |
75
|
|
|
""" |
76
|
|
|
) |
77
|
|
|
|
78
|
|
|
in_matrix_long_form: Path = Arg.in_file( |
79
|
|
|
rf""" |
80
|
|
|
The path to a file with compound/compound similarity matrices. |
81
|
|
|
|
82
|
|
|
{Ca.input_formats} |
83
|
|
|
|
84
|
|
|
The matrix is "long-form" so that multiple matrices can be included. |
85
|
|
|
Columns are "inchikey_1", "inchikey_2", "key", and "type". |
86
|
|
|
The key is the specific similarity matrix; it is usually the search_key |
87
|
|
|
for psi matrices (computed from annotations from :search), and |
88
|
|
|
a user-provided value for phi matrices (typically of phenotypic similarity). |
89
|
|
|
The "type" column should contain either "phi" or "psi" accordingly. |
90
|
|
|
|
91
|
|
|
{ArgUtils.df_description(SimilarityDfLongForm)} |
92
|
|
|
""" |
93
|
|
|
) |
94
|
|
|
|
95
|
|
|
out_matrix_long_form: Path = Opt.out_file( |
96
|
|
|
rf""" |
97
|
|
|
The path to a file with compound/compound similarity matrices. |
98
|
|
|
|
99
|
|
|
{Ca.output_formats} |
100
|
|
|
|
101
|
|
|
The matrix is "long-form" so that multiple matrices can be included. |
102
|
|
|
You can provide just a filename suffix to change the format and suffix |
103
|
|
|
but otherwise use the default path. |
104
|
|
|
|
105
|
|
|
{ArgUtils.df_description(SimilarityDfLongForm)} |
106
|
|
|
|
107
|
|
|
[default: inferred from input path(s)] |
108
|
|
|
""", |
109
|
|
|
) |
110
|
|
|
|
111
|
|
|
in_matrix_short_form = Arg.in_file( |
112
|
|
|
rf""" |
113
|
|
|
The path to a file with a compound/compound similarity matrix. |
114
|
|
|
|
115
|
|
|
{Ca.input_formats} |
116
|
|
|
|
117
|
|
|
The matrix is "short-form": the first row and first column list the InChI Keys. |
118
|
|
|
The table must be symmetric (the rows and columns listed in the same order). |
119
|
|
|
|
120
|
|
|
{ArgUtils.df_description(SimilarityDfShortForm)} |
121
|
|
|
""" |
122
|
|
|
) |
123
|
|
|
|
124
|
|
|
out_tau = Arg.out_file( |
125
|
|
|
rf""" |
126
|
|
|
Output file. |
127
|
|
|
|
128
|
|
|
{Ca.output_formats} |
129
|
|
|
|
130
|
|
|
{ArgUtils.df_description(ConcordanceDf)} |
131
|
|
|
""" |
132
|
|
|
) |
133
|
|
|
|
134
|
|
|
out_projection: Optional[Path] = Opt.out_file( |
135
|
|
|
rf""" |
136
|
|
|
Path to the output table. |
137
|
|
|
|
138
|
|
|
{ArgUtils.df_description(PsiProjectedDf)} |
139
|
|
|
|
140
|
|
|
[default: <path>-<algorithm>{DEF_SUFFIX}], |
141
|
|
|
""" |
142
|
|
|
) |
143
|
|
|
|
144
|
|
|
seed = Opt.val(r"Random seed (integer).", default=0) |
145
|
|
|
|
146
|
|
|
boot = Opt.val( |
147
|
|
|
r""" |
148
|
|
|
Also generate results for <b> bootstrapped samples. |
149
|
|
|
|
150
|
|
|
Number of bootstrap samples (positive integer). |
151
|
|
|
|
152
|
|
|
If set, will still include the non-bootstrapped results (sample=0 in the output). |
153
|
|
|
""", |
154
|
|
|
min=1, |
155
|
|
|
max=1000000, |
156
|
|
|
default=0, |
157
|
|
|
) |
158
|
|
|
|
159
|
|
|
|
160
|
|
|
class CalcCommands: |
|
|
|
|
161
|
|
|
@staticmethod |
162
|
|
|
def calc_analysis( |
|
|
|
|
163
|
|
|
path: Path = Ca.in_annotations_file, |
|
|
|
|
164
|
|
|
phi: Path = Aa.in_matrix_long_form, |
|
|
|
|
165
|
|
|
scores: Path = Aa.in_scores_table, |
|
|
|
|
166
|
|
|
seed: int = Aa.seed, |
|
|
|
|
167
|
|
|
samples: int = Aa.boot, |
|
|
|
|
168
|
|
|
to: Optional[Path] = Ca.out_misc_dir, |
|
|
|
|
169
|
|
|
replace: bool = Ca.replace, |
|
|
|
|
170
|
|
|
log: Optional[Path] = CommonArgs.log, |
|
|
|
|
171
|
|
|
stderr: bool = CommonArgs.stderr, |
|
|
|
|
172
|
|
|
) -> None: |
173
|
|
|
""" |
174
|
|
|
Shorthand for multiple calculations and plots. |
175
|
|
|
|
176
|
|
|
Generates n-triple statements and reified n-triples. |
177
|
|
|
Calculates correlation and enrichment using ``scores``, |
178
|
|
|
psi matrices (one per variable), and concordance between psi and tau matrices (tau). |
179
|
|
|
Plots UMAP of psi variables, enrichment bar plots, correlation violin plots, |
180
|
|
|
phi-vs-psi scatter and line plots, and phi-vs-psi (tau) violin plots. |
181
|
|
|
""" |
182
|
|
|
MANDOS_SETUP(log, stderr) |
183
|
|
|
|
184
|
|
|
@staticmethod |
185
|
|
|
def calc_enrichment( |
|
|
|
|
186
|
|
|
path: Path = Ca.in_annotations_file, |
|
|
|
|
187
|
|
|
scores: Path = Aa.in_scores_table, |
|
|
|
|
188
|
|
|
bool_alg: Optional[str] = Opt.val( |
|
|
|
|
189
|
|
|
rf""" |
190
|
|
|
Algorithm to use for scores starting with 'is_'. |
191
|
|
|
|
192
|
|
|
Allowed values: {ArgUtils.list(BoolAlg)} |
193
|
|
|
""", |
194
|
|
|
default="alpha", |
195
|
|
|
), |
196
|
|
|
real_alg: Optional[str] = Opt.val( |
|
|
|
|
197
|
|
|
rf""" |
198
|
|
|
Algorithm to use for scores starting with 'score_'. |
199
|
|
|
|
200
|
|
|
Allowed values: {ArgUtils.list(RealAlg)} |
201
|
|
|
""", |
202
|
|
|
default="weighted", |
203
|
|
|
), |
204
|
|
|
on: bool = Opt.val( |
|
|
|
|
205
|
|
|
r""" |
206
|
|
|
Determines whether the resulting rows mark single predicate/object pairs, |
207
|
|
|
or sets of pairs. |
208
|
|
|
|
209
|
|
|
**If "choose"**, decides whether to use intersection or union based on the search type. |
210
|
|
|
For example, ``chembl:mechanism`` use the intersection, |
211
|
|
|
while most others will use the union. |
212
|
|
|
|
213
|
|
|
**If "intersection"**, each compound will contribute to a single row |
214
|
|
|
for its associated set of pairs. |
215
|
|
|
For example, a compound annotated for ``increase dopamine`` and ``decrease serotonin`` |
216
|
|
|
increment the count for a single row: |
217
|
|
|
object ``["dopamine", "serotonin"]`` and predicate ``["increase", "decrease"]``. |
218
|
|
|
(Double quotes will be escaped.) |
219
|
|
|
|
220
|
|
|
**If "union"**, each compound will contribute to one row per associated pair. |
221
|
|
|
In the above example, the compound will increment the counts |
222
|
|
|
of two rows: object=``dopamine`` / predicate=``increase`` |
223
|
|
|
and ``object=serotonin`` and predicate=``decrease``. |
224
|
|
|
|
225
|
|
|
In general, this flag is useful for variables in which |
226
|
|
|
a *set of pairs* best is needed to describe a compound, |
227
|
|
|
and there are likely to be relatively few unique predicate/object pairs. |
228
|
|
|
""", |
229
|
|
|
default="choose", |
230
|
|
|
), |
231
|
|
|
boot: int = Aa.boot, |
|
|
|
|
232
|
|
|
seed: int = Aa.seed, |
|
|
|
|
233
|
|
|
to: Optional[Path] = Aa.out_enrichment, |
|
|
|
|
234
|
|
|
replace: bool = Ca.replace, |
|
|
|
|
235
|
|
|
log: Optional[Path] = CommonArgs.log, |
|
|
|
|
236
|
|
|
stderr: bool = CommonArgs.stderr, |
|
|
|
|
237
|
|
|
) -> None: |
238
|
|
|
""" |
239
|
|
|
Compare annotations to user-supplied values. |
240
|
|
|
|
241
|
|
|
Calculates correlation between provided scores and object/predicate pairs. |
242
|
|
|
For booleans, compares annotations for hits and non-hits. |
243
|
|
|
See the docs for more info. |
244
|
|
|
""" |
245
|
|
|
MANDOS_SETUP(log, stderr) |
246
|
|
|
default = f"{path}-{scores.name}-{on}{DEF_SUFFIX}" |
247
|
|
|
to = EntryUtils.adjust_filename(to, default, replace) |
248
|
|
|
hits = HitFrame.read_file(path) |
249
|
|
|
scores = ScoreDf.read_file(scores) |
250
|
|
|
calculator = EnrichmentCalculation(bool_alg, real_alg, boot, seed) |
251
|
|
|
df = calculator.calculate(hits, scores) |
|
|
|
|
252
|
|
|
df.write_file(to) |
253
|
|
|
|
254
|
|
|
@staticmethod |
255
|
|
|
def calc_psi( |
|
|
|
|
256
|
|
|
path: Path = Arg.in_file( |
|
|
|
|
257
|
|
|
rf""" |
258
|
|
|
The path to a file from ``:calc:score``. |
259
|
|
|
|
260
|
|
|
{Ca.input_formats} |
261
|
|
|
""" |
262
|
|
|
), |
263
|
|
|
algorithm: str = Opt.val( |
|
|
|
|
264
|
|
|
r""" |
265
|
|
|
The algorithm for calculating similarity between annotation sets. |
266
|
|
|
|
267
|
|
|
Currently, only "j" (J') is supported. Refer to the docs for the equation. |
268
|
|
|
""", |
269
|
|
|
default="j", |
270
|
|
|
), |
271
|
|
|
to: Path = Aa.out_matrix_long_form, |
|
|
|
|
272
|
|
|
replace: bool = Ca.replace, |
|
|
|
|
273
|
|
|
log: Optional[Path] = CommonArgs.log, |
|
|
|
|
274
|
|
|
stderr: bool = CommonArgs.stderr, |
|
|
|
|
275
|
|
|
) -> None: |
276
|
|
|
r""" |
277
|
|
|
Calculate a similarity matrix from annotations. |
278
|
|
|
|
279
|
|
|
The data are output as a dataframe (CSV by default), where rows and columns correspond |
280
|
|
|
to compounds, and the cell i,j is the overlap J' in annotations between compounds i and j. |
281
|
|
|
""" |
282
|
|
|
MANDOS_SETUP(log, stderr) |
283
|
|
|
default = path.parent / (algorithm + DEF_SUFFIX) |
284
|
|
|
to = EntryUtils.adjust_filename(to, default, replace) |
285
|
|
|
hits = HitFrame.read_file(path).to_hits() |
286
|
|
|
calculator = MatrixCalculation.create(algorithm) |
287
|
|
|
matrix = calculator.calc_all(hits) |
288
|
|
|
matrix.write_file(to) |
289
|
|
|
|
290
|
|
|
@staticmethod |
291
|
|
|
def calc_ecfp( |
|
|
|
|
292
|
|
|
path: Path = CommonArgs.in_compound_table, |
|
|
|
|
293
|
|
|
radius: int = Opt.val(r"""Radius of the ECFP fingerprint.""", default=4), |
|
|
|
|
294
|
|
|
n_bits: int = Opt.val(r"""Number of bits.""", default=2048), |
|
|
|
|
295
|
|
|
psi: bool = Opt.flag( |
|
|
|
|
296
|
|
|
r"""Use "psi" as the type in the resulting matrix instead of "phi".""" |
297
|
|
|
), |
298
|
|
|
to: Path = Aa.out_matrix_long_form, |
|
|
|
|
299
|
|
|
replace: bool = Ca.replace, |
|
|
|
|
300
|
|
|
log: Optional[Path] = CommonArgs.log, |
|
|
|
|
301
|
|
|
stderr: bool = CommonArgs.stderr, |
|
|
|
|
302
|
|
|
) -> None: |
303
|
|
|
r""" |
304
|
|
|
Compute a similarity matrix from ECFP fingerprints. |
305
|
|
|
|
306
|
|
|
Requires rdkit to be installed. |
307
|
|
|
|
308
|
|
|
This is a bit faster than computing using a search and then calculating with ``:calc:psi``. |
309
|
|
|
Values range from 0 (no overlap) to 1 (identical). |
310
|
|
|
The type will be "phi" -- in contrast to using :calc:phi. |
311
|
|
|
See ``:calc:phi`` for more info. |
312
|
|
|
This is most useful for comparing a phenotypic phi against pure structural similarity. |
313
|
|
|
""" |
314
|
|
|
MANDOS_SETUP(log, stderr) |
315
|
|
|
name = f"ecfp{radius}-n{n_bits}" |
316
|
|
|
default = path.parent / (name + DEF_SUFFIX) |
317
|
|
|
to = EntryUtils.adjust_filename(to, default, replace) |
318
|
|
|
df = InputFrame.read_file(path) |
|
|
|
|
319
|
|
|
kind = "psi" if psi else "phi" |
320
|
|
|
short = MatrixPrep.ecfp_matrix(df, radius, n_bits) |
321
|
|
|
long_form = MatrixPrep(kind, False, False, False).create({name: short}) |
322
|
|
|
long_form.write_file(to) |
323
|
|
|
|
324
|
|
|
@staticmethod |
325
|
|
|
def calc_tau( |
|
|
|
|
326
|
|
|
phi: Path = Aa.in_matrix_long_form, |
|
|
|
|
327
|
|
|
psi: Path = Aa.in_matrix_long_form, |
|
|
|
|
328
|
|
|
algorithm: str = Opt.val( |
|
|
|
|
329
|
|
|
r""" |
330
|
|
|
The algorithm for calculating concordance. |
331
|
|
|
|
332
|
|
|
Currently, only "tau" is supported. |
333
|
|
|
This calculation is a modified Kendall’s τ-a, where disconcordant ignores ties. |
334
|
|
|
See the docs for more info. |
335
|
|
|
""", |
336
|
|
|
default="tau", |
337
|
|
|
), |
338
|
|
|
seed: int = Aa.seed, |
|
|
|
|
339
|
|
|
samples: int = Aa.boot, |
|
|
|
|
340
|
|
|
to: Optional[Path] = Opt.out_file( |
|
|
|
|
341
|
|
|
rf""" |
342
|
|
|
The path to a table for output. |
343
|
|
|
|
344
|
|
|
{Ca.output_formats} |
345
|
|
|
|
346
|
|
|
[default: <input-path.parent>/<algorithm>-concordance.{DEF_SUFFIX}] |
347
|
|
|
""", |
348
|
|
|
), |
349
|
|
|
replace: bool = Ca.replace, |
|
|
|
|
350
|
|
|
log: Optional[Path] = CommonArgs.log, |
|
|
|
|
351
|
|
|
stderr: bool = CommonArgs.stderr, |
|
|
|
|
352
|
|
|
) -> None: |
353
|
|
|
r""" |
354
|
|
|
Calculate correlation between matrices. |
355
|
|
|
|
356
|
|
|
Values are calculated over bootstrap, outputting a table. |
357
|
|
|
|
358
|
|
|
Phi is typically a phenotypic matrix, and psi a matrix from Mandos. |
359
|
|
|
This command is designed to calculate the similarity between compound annotations |
360
|
|
|
(from Mandos) and some user-input compound–compound similarity matrix. |
361
|
|
|
(For example, vectors from a high-content cell screen. |
362
|
|
|
See ``:calc:correlation`` or ``:calc:enrichment`` if you have a single variable, |
363
|
|
|
such as a hit or lead-like score. |
364
|
|
|
""" |
365
|
|
|
MANDOS_SETUP(log, stderr) |
366
|
|
|
default = phi.parent / f"{psi.stem}-{algorithm}{DEF_SUFFIX}" |
367
|
|
|
to = EntryUtils.adjust_filename(to, default, replace) |
368
|
|
|
phi = SimilarityDfLongForm.read_file(phi) |
369
|
|
|
psi = SimilarityDfLongForm.read_file(psi) |
370
|
|
|
calculator = ConcordanceCalculation.create(algorithm, phi, psi, samples, seed) |
371
|
|
|
concordance = calculator.calc_all(phi, psi) |
372
|
|
|
concordance.write_file(to) |
373
|
|
|
|
374
|
|
|
@staticmethod |
375
|
|
|
def calc_projection( |
|
|
|
|
376
|
|
|
psi_matrix: Path = Aa.in_matrix_long_form, |
|
|
|
|
377
|
|
|
algorithm: str = Opt.val( |
|
|
|
|
378
|
|
|
r""" |
379
|
|
|
Projection algorithm. |
380
|
|
|
|
381
|
|
|
Currently only "umap" is supported. |
382
|
|
|
""", |
383
|
|
|
default="umap", |
384
|
|
|
), |
385
|
|
|
seed: str = Opt.val( |
|
|
|
|
386
|
|
|
r""" |
387
|
|
|
Random seed (integer or 'none'). |
388
|
|
|
|
389
|
|
|
Setting to 'none' may increase performance. |
390
|
|
|
""", |
391
|
|
|
default=0, |
392
|
|
|
), |
393
|
|
|
params: str = Opt.val( |
|
|
|
|
394
|
|
|
rf""" |
395
|
|
|
Parameters fed to the algorithm. |
396
|
|
|
|
397
|
|
|
This is a comma-separated list of key=value pairs. |
398
|
|
|
For example: ``n_neighbors=4,n_components=12,min_dist=0.8`` |
399
|
|
|
Supports all UMAP parameters except random_state and metric: |
400
|
|
|
|
401
|
|
|
{ArgUtils.definition_list(_umap_params) if UMAP else "<list is unavailable>"} |
402
|
|
|
""", |
403
|
|
|
default="", |
404
|
|
|
), |
405
|
|
|
to: Optional[Path] = Opt.val( |
|
|
|
|
406
|
|
|
rf""" |
407
|
|
|
Path to write a table of the projection coordinates. |
408
|
|
|
Will contain columns "x", "y", "inchikey", "key", and "data_source". |
409
|
|
|
|
410
|
|
|
{CommonArgs.output_formats} |
411
|
|
|
""" |
412
|
|
|
), |
413
|
|
|
replace: bool = Ca.replace, |
|
|
|
|
414
|
|
|
log: Optional[Path] = CommonArgs.log, |
|
|
|
|
415
|
|
|
stderr: bool = CommonArgs.stderr, |
|
|
|
|
416
|
|
|
) -> None: |
417
|
|
|
r""" |
418
|
|
|
Calculate compound UMAP from psi matrices. |
419
|
|
|
|
420
|
|
|
The input should probably be calculated from ``:calc:matrix``. |
421
|
|
|
Saves a table of the UMAP coordinates. |
422
|
|
|
""" |
423
|
|
|
if algorithm == "umap" and UMAP is None: |
424
|
|
|
raise ImportError(f"UMAP is not available") |
|
|
|
|
425
|
|
|
|
426
|
|
|
@staticmethod |
427
|
|
|
def calc_phi( |
|
|
|
|
428
|
|
|
matrices: List[Path] = Aa.in_matrix_short_form, |
|
|
|
|
429
|
|
|
kind: str = Opt.val( |
|
|
|
|
430
|
|
|
r""" |
431
|
|
|
Either "phi" or "psi". |
432
|
|
|
""", |
433
|
|
|
default="phi", |
434
|
|
|
hidden=True, |
435
|
|
|
), |
436
|
|
|
to: Path = Aa.out_matrix_long_form, |
|
|
|
|
437
|
|
|
replace: bool = Ca.replace, |
|
|
|
|
438
|
|
|
normalize: bool = Opt.flag( |
|
|
|
|
439
|
|
|
r"""Rescale values to between 0 and 1 by (v-min) / (max-min). (Performed after negation.)""" |
|
|
|
|
440
|
|
|
), |
441
|
|
|
log10: bool = Opt.val(r"""Rescales values by log10. (Performed after normalization.)"""), |
|
|
|
|
442
|
|
|
invert: bool = Opt.val(r"""Multiplies the values by -1. (Performed first.)"""), |
|
|
|
|
443
|
|
|
log: Optional[Path] = CommonArgs.log, |
|
|
|
|
444
|
|
|
stderr: bool = CommonArgs.stderr, |
|
|
|
|
445
|
|
|
): |
446
|
|
|
r""" |
447
|
|
|
Convert phi matrices to one long-form matrix. |
448
|
|
|
|
449
|
|
|
The keys will be derived from the filenames. |
450
|
|
|
""" |
451
|
|
|
MANDOS_SETUP(log, stderr) |
452
|
|
|
default = "." |
453
|
|
|
if to is None: |
454
|
|
|
try: |
455
|
|
|
default = next(iter({mx.parent for mx in matrices})) |
456
|
|
|
except StopIteration: |
457
|
|
|
logger.warning(f"Outputting to {default}") |
458
|
|
|
to = EntryUtils.adjust_filename(to, default, replace) |
459
|
|
|
long_form = MatrixPrep(kind, normalize, log10, invert).from_files(matrices) |
460
|
|
|
long_form.write_file(to) |
461
|
|
|
|
462
|
|
|
|
463
|
|
|
__all__ = ["CalcCommands"] |
464
|
|
|
|