Skip to content

Commit 9c8c72e

Browse files
dweindldilpath
andauthored
Add command line interface for plotting (#98)
CLI for `petab.visualize.plot_problem`. See `petab_visualize -h` for options. Co-authored-by: Dilan Pathirana <[email protected]>
1 parent 7c5305a commit 9c8c72e

File tree

4 files changed

+163
-60
lines changed

4 files changed

+163
-60
lines changed

petab/visualize/cli.py

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
"""Command-line interface for visualization."""
2+
import argparse
3+
from pathlib import Path
4+
5+
import matplotlib.pyplot as plt
6+
7+
from .plot_data_and_simulation import plot_problem
8+
from .. import Problem, get_simulation_df, get_visualization_df
9+
10+
11+
def _parse_cli_args():
12+
"""Parse command-line arguments."""
13+
14+
parser = argparse.ArgumentParser(
15+
description='Create PEtab visualizations.')
16+
17+
parser.add_argument('-y', '--yaml', dest='yaml_file_name', required=True,
18+
help='PEtab problem YAML filename')
19+
parser.add_argument('-s', '--simulations', dest='simulation_file_name',
20+
required=False,
21+
help='PEtab simulation filename')
22+
parser.add_argument('-o', '--output-directory', dest='output_directory',
23+
required=True, help='Output directory')
24+
parser.add_argument('-v', '--visualizations', required=False,
25+
dest='visualization_file_name',
26+
help='PEtab visualization specification filename')
27+
parser.add_argument('--style', required=False,
28+
dest='style_file_name',
29+
help='Matplotlib style file')
30+
return parser.parse_args()
31+
32+
33+
def _petab_visualize_main():
34+
"""Entrypoint for visualization command-line interface."""
35+
args = _parse_cli_args()
36+
37+
petab_problem = Problem.from_yaml(args.yaml_file_name)
38+
simulations_df = None
39+
if args.simulation_file_name:
40+
simulations_df = get_simulation_df(args.simulation_file_name)
41+
42+
if args.visualization_file_name:
43+
petab_problem.visualization_df = get_visualization_df(
44+
args.visualization_file_name)
45+
46+
if args.style_file_name:
47+
plt.style.use(args.style_file_name)
48+
49+
# Avoid errors when plotting without X server
50+
plt.switch_backend('agg')
51+
52+
Path(args.output_directory).mkdir(exist_ok=True, parents=True)
53+
54+
plot_problem(
55+
petab_problem=petab_problem,
56+
simulations_df=simulations_df,
57+
subplot_dir=args.output_directory,
58+
)

setup.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ def absolute_links(txt):
4141
ENTRY_POINTS = {
4242
'console_scripts': [
4343
'petablint = petab.petablint:main',
44+
'petab_visualize = petab.visualize.cli:_petab_visualize_main',
4445
]
4546
}
4647

tests/test_visualization.py

Lines changed: 100 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
1+
import subprocess
12
import warnings
23
from os import path
34
from tempfile import TemporaryDirectory
5+
from pathlib import Path
6+
import matplotlib.pyplot as plt
47
import pytest
58
from petab.C import *
69
from petab.visualize import (plot_data_and_simulation,
710
plot_measurements_by_observable,
811
save_vis_spec)
9-
import matplotlib.pyplot as plt
1012

1113

1214
@pytest.fixture
@@ -79,10 +81,12 @@ def simulation_file_Isensee():
7981
return "doc/example/example_Isensee/Isensee_simulationData.tsv"
8082

8183

82-
def test_visualization_with_vis_and_sim(data_file_Isensee,
83-
condition_file_Isensee,
84-
vis_spec_file_Isensee,
85-
simulation_file_Isensee):
84+
def test_visualization_with_vis_and_sim(
85+
data_file_Isensee,
86+
condition_file_Isensee,
87+
vis_spec_file_Isensee,
88+
simulation_file_Isensee
89+
):
8690
plot_data_and_simulation(data_file_Isensee,
8791
condition_file_Isensee,
8892
vis_spec_file_Isensee,
@@ -91,17 +95,21 @@ def test_visualization_with_vis_and_sim(data_file_Isensee,
9195

9296
@pytest.mark.skip(reason="vis_spec_file_Isensee can't be used without "
9397
"simulation_df anymore")
94-
def test_visualization_with_vis(data_file_Isensee,
95-
condition_file_Isensee,
96-
vis_spec_file_Isensee):
98+
def test_visualization_with_vis(
99+
data_file_Isensee,
100+
condition_file_Isensee,
101+
vis_spec_file_Isensee
102+
):
97103
plot_data_and_simulation(data_file_Isensee,
98104
condition_file_Isensee,
99105
vis_spec_file_Isensee)
100106

101107

102-
def test_visualization_small_visu_file_w_datasetid(data_file_Fujita,
103-
condition_file_Fujita,
104-
visu_file_Fujita_small):
108+
def test_visualization_small_visu_file_w_datasetid(
109+
data_file_Fujita,
110+
condition_file_Fujita,
111+
visu_file_Fujita_small
112+
):
105113
"""
106114
Test: visualization spezification file only with few columns in
107115
particular datasetId
@@ -112,9 +120,11 @@ def test_visualization_small_visu_file_w_datasetid(data_file_Fujita,
112120
visu_file_Fujita_small)
113121

114122

115-
def test_visualization_small_visu_file_wo_datasetid(data_file_Fujita,
116-
condition_file_Fujita,
117-
visu_file_Fujita_wo_dsid):
123+
def test_visualization_small_visu_file_wo_datasetid(
124+
data_file_Fujita,
125+
condition_file_Fujita,
126+
visu_file_Fujita_wo_dsid
127+
):
118128
"""
119129
Test: visualization spezification file only with few columns in
120130
particular no datasetId column
@@ -125,9 +135,11 @@ def test_visualization_small_visu_file_wo_datasetid(data_file_Fujita,
125135
visu_file_Fujita_wo_dsid)
126136

127137

128-
def test_visualization_minimal_visu_file(data_file_Fujita,
129-
condition_file_Fujita,
130-
visu_file_Fujita_minimal):
138+
def test_visualization_minimal_visu_file(
139+
data_file_Fujita,
140+
condition_file_Fujita,
141+
visu_file_Fujita_minimal
142+
):
131143
"""
132144
Test: visualization spezification file only with mandatory column plotId
133145
(optional columns are optional)
@@ -137,9 +149,11 @@ def test_visualization_minimal_visu_file(data_file_Fujita,
137149
visu_file_Fujita_minimal)
138150

139151

140-
def test_visualization_empty_visu_file(data_file_Fujita,
141-
condition_file_Fujita,
142-
visu_file_Fujita_empty):
152+
def test_visualization_empty_visu_file(
153+
data_file_Fujita,
154+
condition_file_Fujita,
155+
visu_file_Fujita_empty
156+
):
143157
"""
144158
Test: Empty visualization spezification file should default to routine
145159
for no file at all
@@ -149,9 +163,11 @@ def test_visualization_empty_visu_file(data_file_Fujita,
149163
visu_file_Fujita_empty)
150164

151165

152-
def test_visualization_minimal_data_file(data_file_Fujita_minimal,
153-
condition_file_Fujita,
154-
visu_file_Fujita_small):
166+
def test_visualization_minimal_data_file(
167+
data_file_Fujita_minimal,
168+
condition_file_Fujita,
169+
visu_file_Fujita_small
170+
):
155171
"""
156172
Test visualization, with the case: data file only with mandatory columns
157173
(optional columns are optional)
@@ -161,9 +177,11 @@ def test_visualization_minimal_data_file(data_file_Fujita_minimal,
161177
visu_file_Fujita_small)
162178

163179

164-
def test_visualization_with_dataset_list(data_file_Isensee,
165-
condition_file_Isensee,
166-
simulation_file_Isensee):
180+
def test_visualization_with_dataset_list(
181+
data_file_Isensee,
182+
condition_file_Isensee,
183+
simulation_file_Isensee
184+
):
167185
datasets = [['JI09_150302_Drg345_343_CycNuc__4_ABnOH_and_ctrl',
168186
'JI09_150302_Drg345_343_CycNuc__4_ABnOH_and_Fsk'],
169187
['JI09_160201_Drg453-452_CycNuc__ctrl',
@@ -179,9 +197,11 @@ def test_visualization_with_dataset_list(data_file_Isensee,
179197
dataset_id_list=datasets)
180198

181199

182-
def test_visualization_without_datasets(data_file_Fujita,
183-
condition_file_Fujita,
184-
simu_file_Fujita):
200+
def test_visualization_without_datasets(
201+
data_file_Fujita,
202+
condition_file_Fujita,
203+
simu_file_Fujita
204+
):
185205
sim_cond_num_list = [[0, 1, 2], [0, 2, 3], [0, 3, 4], [0, 4, 5]]
186206
sim_cond_id_list = [['model1_data1'], ['model1_data2', 'model1_data3'],
187207
['model1_data4', 'model1_data5'], ['model1_data6']]
@@ -217,16 +237,20 @@ def test_visualization_without_datasets(data_file_Fujita,
217237
plotted_noise=PROVIDED)
218238

219239

220-
def test_visualization_omit_empty_datasets(data_file_Fujita_nanData,
221-
condition_file_Fujita):
240+
def test_visualization_omit_empty_datasets(
241+
data_file_Fujita_nanData,
242+
condition_file_Fujita
243+
):
222244
observable_num_list = [[0, 1]]
223245
plot_data_and_simulation(data_file_Fujita_nanData, condition_file_Fujita,
224246
observable_num_list=observable_num_list)
225247

226248

227-
def test_visualization_raises(data_file_Fujita,
228-
condition_file_Fujita,
229-
data_file_Fujita_wrongNoise):
249+
def test_visualization_raises(
250+
data_file_Fujita,
251+
condition_file_Fujita,
252+
data_file_Fujita_wrongNoise
253+
):
230254
sim_cond_num_list = [[0, 1, 2], [0, 2, 3], [0, 3, 4], [0, 4, 5]]
231255
sim_cond_id_list = [['model1_data1'], ['model1_data2', 'model1_data3'],
232256
['model1_data4', 'model1_data5'], ['model1_data6']]
@@ -240,10 +264,10 @@ def test_visualization_raises(data_file_Fujita,
240264
sim_cond_num_list=sim_cond_num_list,
241265
sim_cond_id_list=sim_cond_id_list)
242266
except NotImplementedError as ErrMsg:
243-
assert(ErrMsg.args[0] == 'Either specify a list of simulation '
244-
'condition IDs or a list of simulation '
245-
'condition numbers, but not both. '
246-
'Stopping.')
267+
assert (ErrMsg.args[0] == 'Either specify a list of simulation '
268+
'condition IDs or a list of simulation '
269+
'condition numbers, but not both. '
270+
'Stopping.')
247271
error_counter += 1
248272
assert (error_counter == 1)
249273

@@ -253,9 +277,10 @@ def test_visualization_raises(data_file_Fujita,
253277
observable_num_list=observable_num_list,
254278
observable_id_list=observable_id_list)
255279
except NotImplementedError as ErrMsg:
256-
assert(ErrMsg.args[0] == 'Either specify a list of observable IDs or '
257-
'a list of observable numbers, but not both. '
258-
'Stopping.')
280+
assert (ErrMsg.args[0] ==
281+
'Either specify a list of observable IDs or '
282+
'a list of observable numbers, but not both. '
283+
'Stopping.')
259284
error_counter += 1
260285
assert (error_counter == 2)
261286

@@ -266,21 +291,23 @@ def test_visualization_raises(data_file_Fujita,
266291
sim_cond_num_list=observable_num_list,
267292
observable_num_list=observable_num_list)
268293
except NotImplementedError as ErrMsg:
269-
assert(ErrMsg.args[0] == 'Plotting without visualization specification'
270-
' file and datasetId can be performed via '
271-
'grouping by simulation conditions OR '
272-
'observables, but not both. Stopping.')
294+
assert (ErrMsg.args[
295+
0] == 'Plotting without visualization specification'
296+
' file and datasetId can be performed via '
297+
'grouping by simulation conditions OR '
298+
'observables, but not both. Stopping.')
273299
error_counter += 1
274300
assert (error_counter == 3)
275301
try:
276302
plot_data_and_simulation(data_file_Fujita, condition_file_Fujita,
277303
sim_cond_id_list=observable_id_list,
278304
observable_id_list=observable_id_list)
279305
except NotImplementedError as ErrMsg:
280-
assert(ErrMsg.args[0] == 'Plotting without visualization specification'
281-
' file and datasetId can be performed via '
282-
'grouping by simulation conditions OR '
283-
'observables, but not both. Stopping.')
306+
assert (ErrMsg.args[
307+
0] == 'Plotting without visualization specification'
308+
' file and datasetId can be performed via '
309+
'grouping by simulation conditions OR '
310+
'observables, but not both. Stopping.')
284311
error_counter += 1
285312
assert (error_counter == 4)
286313

@@ -290,8 +317,8 @@ def test_visualization_raises(data_file_Fujita,
290317
condition_file_Fujita,
291318
plotted_noise='provided')
292319
except NotImplementedError as ErrMsg:
293-
assert(ErrMsg.args[0] == "No numerical noise values provided in the "
294-
"measurement table. Stopping.")
320+
assert (ErrMsg.args[0] == "No numerical noise values provided in the "
321+
"measurement table. Stopping.")
295322
error_counter += 1
296323

297324
assert (error_counter == 5)
@@ -354,8 +381,10 @@ def test_simple_visualization(data_file_Fujita, condition_file_Fujita):
354381
plotted_noise=PROVIDED)
355382

356383

357-
def test_save_plots_to_file(data_file_Isensee, condition_file_Isensee,
358-
vis_spec_file_Isensee, simulation_file_Isensee):
384+
def test_save_plots_to_file(
385+
data_file_Isensee, condition_file_Isensee,
386+
vis_spec_file_Isensee, simulation_file_Isensee
387+
):
359388
with TemporaryDirectory() as temp_dir:
360389
plot_data_and_simulation(
361390
data_file_Isensee,
@@ -365,9 +394,10 @@ def test_save_plots_to_file(data_file_Isensee, condition_file_Isensee,
365394
subplot_file_path=temp_dir)
366395

367396

368-
def test_save_visu_file(data_file_Isensee,
369-
condition_file_Isensee):
370-
397+
def test_save_visu_file(
398+
data_file_Isensee,
399+
condition_file_Isensee
400+
):
371401
with TemporaryDirectory() as temp_dir:
372402
save_vis_spec(data_file_Isensee,
373403
condition_file_Isensee,
@@ -383,3 +413,17 @@ def test_save_visu_file(data_file_Isensee,
383413
condition_file_Isensee,
384414
dataset_id_list=datasets,
385415
output_file_path=path.join(temp_dir, "visuSpec1.tsv"))
416+
417+
418+
def test_cli():
419+
example_dir = Path(__file__).parent.parent / "doc" / "example"
420+
fujita_dir = example_dir / "example_Fujita"
421+
422+
with TemporaryDirectory() as temp_dir:
423+
args = [
424+
"petab_visualize",
425+
"-y", str(fujita_dir / "Fujita.yaml"),
426+
"-s", str(fujita_dir / "Fujita_simulatedData.tsv"),
427+
"-o", temp_dir
428+
]
429+
subprocess.run(args, check=True)

tests/test_visualization_new_structure.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -259,9 +259,9 @@ def test_save_visu_file(data_file_Isensee,
259259

260260
with TemporaryDirectory() as temp_dir:
261261

262-
vis_spec_parcer = VisSpecParser(condition_file_Isensee,
262+
vis_spec_parser = VisSpecParser(condition_file_Isensee,
263263
data_file_Isensee)
264-
figure, _ = vis_spec_parcer.parse_from_id_list()
264+
figure, _ = vis_spec_parser.parse_from_id_list()
265265

266266
figure.save_to_tsv(path.join(temp_dir, "visuSpec.tsv"))
267267

@@ -271,8 +271,8 @@ def test_save_visu_file(data_file_Isensee,
271271
'JI09_160201_Drg453-452_CycNuc__Fsk',
272272
'JI09_160201_Drg453-452_CycNuc__Sp8_Br_cAMPS_AM']]
273273

274-
vis_spec_parcer = VisSpecParser(condition_file_Isensee,
274+
vis_spec_parser = VisSpecParser(condition_file_Isensee,
275275
data_file_Isensee)
276-
figure, _ = vis_spec_parcer.parse_from_id_list(datasets,
276+
figure, _ = vis_spec_parser.parse_from_id_list(datasets,
277277
group_by='dataset')
278278
figure.save_to_tsv(path.join(temp_dir, "visuSpec1.tsv"))

0 commit comments

Comments
 (0)