oml-plot-tools 0.9.1__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 (35) hide show
  1. oml_plot_tools/__init__.py +24 -0
  2. oml_plot_tools/common.py +172 -0
  3. oml_plot_tools/consum.py +187 -0
  4. oml_plot_tools/main.py +49 -0
  5. oml_plot_tools/radio.py +184 -0
  6. oml_plot_tools/tests/__init__.py +22 -0
  7. oml_plot_tools/tests/common.py +126 -0
  8. oml_plot_tools/tests/common_test.py +133 -0
  9. oml_plot_tools/tests/consum_test.py +201 -0
  10. oml_plot_tools/tests/examples/consumption.oml +4179 -0
  11. oml_plot_tools/tests/examples/consumption_all.png +0 -0
  12. oml_plot_tools/tests/examples/consumption_clock.png +0 -0
  13. oml_plot_tools/tests/examples/consumption_current.png +0 -0
  14. oml_plot_tools/tests/examples/consumption_only_one.oml +10 -0
  15. oml_plot_tools/tests/examples/grenoble-iotlab.png +0 -0
  16. oml_plot_tools/tests/examples/radio.oml +2009 -0
  17. oml_plot_tools/tests/examples/radio_clock.png +0 -0
  18. oml_plot_tools/tests/examples/radio_separated_last.png +0 -0
  19. oml_plot_tools/tests/examples/radio_single.png +0 -0
  20. oml_plot_tools/tests/examples/robot.oml +1447 -0
  21. oml_plot_tools/tests/examples/trajectory_all.png +0 -0
  22. oml_plot_tools/tests/examples/trajectory_angle.png +0 -0
  23. oml_plot_tools/tests/examples/trajectory_circuit.png +0 -0
  24. oml_plot_tools/tests/examples/trajectory_clock.png +0 -0
  25. oml_plot_tools/tests/examples/trajectory_only.png +0 -0
  26. oml_plot_tools/tests/main_parser_test.py +51 -0
  27. oml_plot_tools/tests/radio_test.py +173 -0
  28. oml_plot_tools/tests/traj_test.py +223 -0
  29. oml_plot_tools/traj.py +352 -0
  30. oml_plot_tools-0.9.1.dist-info/METADATA +35 -0
  31. oml_plot_tools-0.9.1.dist-info/RECORD +35 -0
  32. oml_plot_tools-0.9.1.dist-info/WHEEL +4 -0
  33. oml_plot_tools-0.9.1.dist-info/entry_points.txt +5 -0
  34. oml_plot_tools-0.9.1.dist-info/licenses/AUTHORS +1 -0
  35. oml_plot_tools-0.9.1.dist-info/licenses/COPYING +518 -0
@@ -0,0 +1,126 @@
1
+ # -*- coding:utf-8 -*-
2
+
3
+ # This file is a part of IoT-LAB oml-plot-tools
4
+ # Copyright (C) 2015 INRIA (Contact: admin@iot-lab.info)
5
+ # Contributor(s) : see AUTHORS file
6
+ #
7
+ # This software is governed by the CeCILL license under French law
8
+ # and abiding by the rules of distribution of free software. You can use,
9
+ # modify and/ or redistribute the software under the terms of the CeCILL
10
+ # license as circulated by CEA, CNRS and INRIA at the following URL
11
+ # http://www.cecill.info.
12
+ #
13
+ # As a counterpart to the access to the source code and rights to copy,
14
+ # modify and redistribute granted by the license, users are provided only
15
+ # with a limited warranty and the software's author, the holder of the
16
+ # economic rights, and the successive licensors have only limited
17
+ # liability.
18
+ #
19
+ # The fact that you are presently reading this means that you have had
20
+ # knowledge of the CeCILL license and that you accept its terms.
21
+
22
+
23
+ """Common tests functions."""
24
+
25
+ import math
26
+ import os
27
+ import runpy
28
+ import sys
29
+ from io import StringIO
30
+ from unittest import mock
31
+
32
+ import matplotlib.pyplot as plt
33
+ from PIL import Image
34
+
35
+ TEST_DIR = os.path.dirname(__file__)
36
+ IS_PYTHON_3_10 = (3, 10) == tuple(map(int, sys.version.split()[0].split(".")))[:2]
37
+
38
+
39
+ def fixture_path(*args):
40
+ """Test file path."""
41
+ return os.path.join(TEST_DIR, *args)
42
+
43
+
44
+ def help_main_and_doc(module, help_opt="--help"):
45
+ """Check that help message is module docstring.
46
+
47
+ Force COLUMNS to 80 as argparse wraps its usage line on the terminal
48
+ width, while the docstrings are written for the default 80 columns.
49
+ """
50
+ if not hasattr(module, "main"):
51
+ return None, None # pragma: no cover
52
+
53
+ help_args = [module.__name__, help_opt]
54
+ out_msg = StringIO()
55
+
56
+ with mock.patch.dict(os.environ, {"COLUMNS": "80"}):
57
+ with mock.patch("sys.argv", help_args):
58
+ with mock.patch("sys.stderr", out_msg):
59
+ with mock.patch("sys.stdout", out_msg):
60
+ try:
61
+ runpy.run_module(
62
+ module.__name__, run_name="__main__", alter_sys=True
63
+ )
64
+ except SystemExit:
65
+ pass
66
+
67
+ help_msg = out_msg.getvalue().strip()
68
+ help_doc = module.__doc__.strip()
69
+ print(help_msg)
70
+ print(help_doc)
71
+
72
+ return help_msg, help_doc
73
+
74
+
75
+ def utest_help_as_doc(testcase_instance, module, *args, **kwargs):
76
+ """Run help_as_doc as unittest test."""
77
+ help_msg, help_doc = help_main_and_doc(module, *args, **kwargs)
78
+ testcase_instance.assertEqual(help_msg, help_doc)
79
+
80
+
81
+ def compare_images(ref_img, tmp_img):
82
+ """Compare two images using PIL.
83
+
84
+ :returns: root-mean-square of the two images
85
+ """
86
+ ref_h = Image.open(ref_img).histogram()
87
+ tmp_h = Image.open(tmp_img).histogram()
88
+ rms = math.sqrt(sum((a - b) ** 2 for a, b in zip(ref_h, tmp_h)) / len(ref_h))
89
+ return rms
90
+
91
+
92
+ def utest_plot_and_compare(testcase, ref_img, threshold=0.0):
93
+ """Plot image and compare files.
94
+
95
+ Keep file if they differ.
96
+ 'savefig' is not determinist between hosts so allow some differences with
97
+ threshold.
98
+ """
99
+ tmp_img = os.path.join("/tmp", os.path.basename(ref_img))
100
+
101
+ plt.tight_layout()
102
+ plt.savefig(tmp_img)
103
+
104
+ rms = compare_images(ref_img, tmp_img)
105
+
106
+ msg = f"{ref_img} != {tmp_img}: Root-mean-square == {rms:f} > {threshold:f}"
107
+ testcase.assertTrue(rms <= threshold, msg=msg)
108
+
109
+ os.remove(tmp_img)
110
+
111
+
112
+ def assert_called_with_nparray(mock_function, np_array, *args, **kwargs):
113
+ """Assert mock function has been called with given np_array and arguments.
114
+
115
+ Using default mock assert_called_with failed.
116
+ Also using numpy comparison functions did not return expected value so use
117
+ 'repr'
118
+ """
119
+ call_args, call_kwargs = mock_function.call_args
120
+
121
+ call_array = call_args[0]
122
+ call_args = call_args[1:]
123
+
124
+ assert repr(call_array) == repr(np_array)
125
+ assert repr(call_args) == repr(args)
126
+ assert call_kwargs == kwargs
@@ -0,0 +1,133 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ # This file is a part of IoT-LAB oml-plot-tools
4
+ # Copyright (C) 2015 INRIA (Contact: admin@iot-lab.info)
5
+ # Contributor(s) : see AUTHORS file
6
+ #
7
+ # This software is governed by the CeCILL license under French law
8
+ # and abiding by the rules of distribution of free software. You can use,
9
+ # modify and/ or redistribute the software under the terms of the CeCILL
10
+ # license as circulated by CEA, CNRS and INRIA at the following URL
11
+ # http://www.cecill.info.
12
+ #
13
+ # As a counterpart to the access to the source code and rights to copy,
14
+ # modify and redistribute granted by the license, users are provided only
15
+ # with a limited warranty and the software's author, the holder of the
16
+ # economic rights, and the successive licensors have only limited
17
+ # liability.
18
+ #
19
+ # The fact that you are presently reading this means that you have had
20
+ # knowledge of the CeCILL license and that you accept its terms.
21
+
22
+ """Tests for common.py."""
23
+
24
+ import unittest
25
+ from io import StringIO
26
+
27
+ import numpy
28
+
29
+ from oml_plot_tools import common, consum
30
+
31
+ MEASURE_FMT = "{t} {type} {num} {t_s} {t_us} {measures}\n"
32
+ HEADER = "HEADER\n" * common.OML_HEADER_LEN
33
+ CONSO_T = common.OML_TYPES["consumption"]
34
+
35
+
36
+ class TestCommon(unittest.TestCase):
37
+ """Tests for common OML loading utilities."""
38
+
39
+ def test_oml_load(self):
40
+ """Test loading valid OML data with two measurement entries."""
41
+ meas = "1. 2. 3."
42
+ content = HEADER
43
+ content += MEASURE_FMT.format(
44
+ t=0.1234, type=CONSO_T, num=1, t_s=12345, t_us=678900, measures=meas
45
+ )
46
+ content += MEASURE_FMT.format(
47
+ t=1.1234, type=CONSO_T, num=2, t_s=12346, t_us=678900, measures=meas
48
+ )
49
+
50
+ ret = common.oml_load(
51
+ StringIO(content), "consumption", consum.MEASURES_D.values()
52
+ )
53
+
54
+ expected = [
55
+ (12345.6789, "consumption", 1, 12345, 678900, 1.0, 2.0, 3.0),
56
+ (12346.6789, "consumption", 2, 12346, 678900, 1.0, 2.0, 3.0),
57
+ ]
58
+ self.assertEqual(expected, ret.tolist())
59
+ self.assertTrue(isinstance(ret, numpy.ndarray))
60
+
61
+ def test_oml_load_only_one(self):
62
+ """Test loading valid OML data with a single measurement entry."""
63
+ meas = "1. 2. 3."
64
+ content = HEADER
65
+ content += MEASURE_FMT.format(
66
+ t=0.1234, type=CONSO_T, num=1, t_s=12345, t_us=678900, measures=meas
67
+ )
68
+
69
+ ret = common.oml_load(
70
+ StringIO(content), "consumption", consum.MEASURES_D.values()
71
+ )
72
+
73
+ expected = [(12345.6789, "consumption", 1, 12345, 678900, 1.0, 2.0, 3.0)]
74
+ self.assertEqual(expected, ret.tolist())
75
+ self.assertTrue(isinstance(ret, numpy.ndarray))
76
+
77
+ def test_oml_invalid(self):
78
+ """Test that loading invalid or malformed OML data raises ValueError."""
79
+ # invalid data
80
+ content = HEADER + "invalid_content"
81
+ self.assertRaises(
82
+ ValueError,
83
+ common.oml_load,
84
+ StringIO(content),
85
+ "consumption",
86
+ consum.MEASURES_D.values(),
87
+ )
88
+
89
+ # Unknown file path
90
+ self.assertRaises(
91
+ ValueError,
92
+ common.oml_load,
93
+ "/invalid/file/path",
94
+ "consumption",
95
+ consum.MEASURES_D.values(),
96
+ )
97
+
98
+ # skip header fail
99
+ self.assertRaises(
100
+ ValueError,
101
+ common.oml_load,
102
+ StringIO("1 2 3"),
103
+ "consumption",
104
+ consum.MEASURES_D.values(),
105
+ )
106
+
107
+ # invalid oml 'type' file
108
+ meas = "1. 2. 3."
109
+ content = HEADER
110
+ content += MEASURE_FMT.format(
111
+ t=0.1234, type=(CONSO_T + 1), num=1, t_s=12345, t_us=678900, measures=meas
112
+ )
113
+ self.assertRaises(
114
+ ValueError,
115
+ common.oml_load,
116
+ StringIO(content),
117
+ "consumption",
118
+ consum.MEASURES_D.values(),
119
+ )
120
+
121
+ def test_array_empty(self):
122
+ """Test the array_empty utility function."""
123
+ self.assertTrue(common.array_empty(None))
124
+ self.assertTrue(common.array_empty([]))
125
+ self.assertFalse(common.array_empty([1, 2, 3]))
126
+
127
+ def test_measures_dict(self):
128
+ """Test the measures_dict utility function."""
129
+ d = common.measures_dict(("power", float, "Power (W)"))
130
+ self.assertIn("power", d)
131
+ self.assertEqual(d["power"].name, "power")
132
+ self.assertEqual(d["power"].type, float)
133
+ self.assertEqual(d["power"].label, "Power (W)")
@@ -0,0 +1,201 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ # This file is a part of IoT-LAB oml-plot-tools
4
+ # Copyright (C) 2015 INRIA (Contact: admin@iot-lab.info)
5
+ # Contributor(s) : see AUTHORS file
6
+ #
7
+ # This software is governed by the CeCILL license under French law
8
+ # and abiding by the rules of distribution of free software. You can use,
9
+ # modify and/ or redistribute the software under the terms of the CeCILL
10
+ # license as circulated by CEA, CNRS and INRIA at the following URL
11
+ # http://www.cecill.info.
12
+ #
13
+ # As a counterpart to the access to the source code and rights to copy,
14
+ # modify and redistribute granted by the license, users are provided only
15
+ # with a limited warranty and the software's author, the holder of the
16
+ # economic rights, and the successive licensors have only limited
17
+ # liability.
18
+ #
19
+ # The fact that you are presently reading this means that you have had
20
+ # knowledge of the CeCILL license and that you accept its terms.
21
+
22
+ """Tests for consum.py."""
23
+
24
+ import unittest
25
+ from unittest import mock
26
+
27
+ from .. import common, consum
28
+ from .common import (
29
+ IS_PYTHON_3_10,
30
+ assert_called_with_nparray,
31
+ fixture_path,
32
+ utest_help_as_doc,
33
+ utest_plot_and_compare,
34
+ )
35
+
36
+
37
+ class TestConsumptionOmlPlot(unittest.TestCase):
38
+ """Tests for consumption OML plot functions."""
39
+
40
+ def setUp(self):
41
+ """Set up consumption OML data for plot tests."""
42
+ conso_file = fixture_path("examples", "consumption.oml")
43
+ self.data = consum.oml_load(conso_file)
44
+ self.title = "Node"
45
+
46
+ def test_plot_all(self):
47
+ """Test plotting all consumption measures."""
48
+ ref_img = fixture_path("examples", "consumption_all.png")
49
+ consum.oml_plot(self.data, self.title, consum.MEASURES_D.values())
50
+ utest_plot_and_compare(self, ref_img, 150)
51
+
52
+ def test_plot_current(self):
53
+ """Test plotting current consumption measure only."""
54
+ ref_img = fixture_path("examples", "consumption_current.png")
55
+ consum.oml_plot(self.data, self.title, [consum.MEASURES_D["current"]])
56
+ utest_plot_and_compare(self, ref_img, 100)
57
+
58
+ def test_plot_clock(self):
59
+ """Test plotting OML clock data for consumption."""
60
+ ref_img = fixture_path("examples", "consumption_clock.png")
61
+ consum.common.oml_plot_clock(self.data)
62
+ utest_plot_and_compare(self, ref_img, 50)
63
+
64
+
65
+ class TestConsumptionPlot(unittest.TestCase):
66
+ """Tests for consumption main CLI entry point."""
67
+
68
+ def setUp(self):
69
+ """Set up mocks and test arguments for consumption main tests."""
70
+ meas_file = fixture_path("examples", "consumption.oml")
71
+ self.data = consum.oml_load(meas_file)[0:1]
72
+
73
+ self.title = "TITLE_TESTS"
74
+ self.args = [
75
+ "plot_oml_consum",
76
+ "-i",
77
+ meas_file,
78
+ "--begin",
79
+ "0",
80
+ "--end",
81
+ "1",
82
+ "-l",
83
+ self.title,
84
+ ]
85
+
86
+ self.oml_plot = mock.patch("oml_plot_tools.consum.oml_plot").start()
87
+ self.oml_plot_clock = mock.patch(
88
+ "oml_plot_tools.consum.common.oml_plot_clock"
89
+ ).start()
90
+
91
+ def tearDown(self):
92
+ """Stop all active patches after each test."""
93
+ mock.patch.stopall()
94
+
95
+ def consum_main(self, *args):
96
+ """Call consumption main with given additional args."""
97
+ with mock.patch("sys.argv", list(self.args) + list(args)):
98
+ consum.main()
99
+
100
+ def test_plot_selection(self):
101
+ """Test individual measure selection options -p, -v, -c."""
102
+ self.consum_main("-p")
103
+ assert_called_with_nparray(
104
+ self.oml_plot,
105
+ self.data,
106
+ self.title,
107
+ [common.MeasureTuple("power", float, "Power (W)")],
108
+ )
109
+
110
+ self.consum_main("-v")
111
+ assert_called_with_nparray(
112
+ self.oml_plot,
113
+ self.data,
114
+ self.title,
115
+ [common.MeasureTuple("voltage", float, "Voltage (V)")],
116
+ )
117
+
118
+ self.consum_main("-c")
119
+ assert_called_with_nparray(
120
+ self.oml_plot,
121
+ self.data,
122
+ self.title,
123
+ [common.MeasureTuple("current", float, "Current (A)")],
124
+ )
125
+
126
+ self.oml_plot.reset_mock()
127
+ self.consum_main("-c", "-v", "-p", "-p")
128
+ self.assertEqual(self.oml_plot.call_count, 3)
129
+
130
+ def test_plot_all(self):
131
+ """Test -a option calls oml_plot with all measures."""
132
+ self.consum_main("-a")
133
+ assert_called_with_nparray(
134
+ self.oml_plot, self.data, self.title, consum.MEASURES_D.values()
135
+ )
136
+
137
+ def test_plot_default_all(self):
138
+ """Test that no measure option defaults to plotting all measures."""
139
+ self.consum_main()
140
+ assert_called_with_nparray(
141
+ self.oml_plot, self.data, self.title, consum.MEASURES_D.values()
142
+ )
143
+
144
+ def test_plot_time(self):
145
+ """Test -t option calls oml_plot_clock with consumption data."""
146
+ self.consum_main("-t")
147
+ assert_called_with_nparray(self.oml_plot_clock, self.data)
148
+
149
+
150
+ class TestConsumptionPlotDirect(unittest.TestCase):
151
+ """Tests for direct calls to consumption OML loading and plotting."""
152
+
153
+ def setUp(self):
154
+ """Set up test OML data for direct loading and plotting tests."""
155
+ meas_file = fixture_path("examples", "consumption.oml")
156
+ self.data = consum.oml_load(meas_file)
157
+ self.title = "Node"
158
+
159
+ def test_consumption_plot_all(self):
160
+ """Test direct call to consumption_plot with all measures."""
161
+ with mock.patch("oml_plot_tools.consum.oml_plot") as mock_plot:
162
+ with mock.patch("oml_plot_tools.consum.common.plot_show"):
163
+ consum.consumption_plot(self.data, self.title, ["all"])
164
+ self.assertTrue(mock_plot.called)
165
+
166
+ def test_consumption_plot_power(self):
167
+ """Test direct call to consumption_plot with power measure."""
168
+ with mock.patch("oml_plot_tools.consum.oml_plot") as mock_plot:
169
+ with mock.patch("oml_plot_tools.consum.common.plot_show"):
170
+ consum.consumption_plot(self.data, self.title, ["power"])
171
+ self.assertTrue(mock_plot.called)
172
+
173
+ def test_consumption_plot_voltage(self):
174
+ """Test direct call to consumption_plot with voltage measure."""
175
+ with mock.patch("oml_plot_tools.consum.oml_plot") as mock_plot:
176
+ with mock.patch("oml_plot_tools.consum.common.plot_show"):
177
+ consum.consumption_plot(self.data, self.title, ["voltage"])
178
+ self.assertTrue(mock_plot.called)
179
+
180
+ def test_consumption_plot_current(self):
181
+ """Test direct call to consumption_plot with current measure."""
182
+ with mock.patch("oml_plot_tools.consum.oml_plot") as mock_plot:
183
+ with mock.patch("oml_plot_tools.consum.common.plot_show"):
184
+ consum.consumption_plot(self.data, self.title, ["current"])
185
+ self.assertTrue(mock_plot.called)
186
+
187
+ def test_consumption_plot_time(self):
188
+ """Test direct call to consumption_plot with time measure."""
189
+ with mock.patch("oml_plot_tools.consum.common.oml_plot_clock") as mock_clock:
190
+ with mock.patch("oml_plot_tools.consum.common.plot_show"):
191
+ consum.consumption_plot(self.data, self.title, ["time"])
192
+ self.assertTrue(mock_clock.called)
193
+
194
+
195
+ class TestDoc(unittest.TestCase):
196
+ """Tests that module help matches its docstring."""
197
+
198
+ @unittest.skipIf(IS_PYTHON_3_10, "Python 3.10 not supported")
199
+ def test_doc(self):
200
+ """Test that the module help output matches its docstring."""
201
+ utest_help_as_doc(self, consum)