DiaModality 0.2.6__tar.gz → 0.2.8__tar.gz
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.
- {diamodality-0.2.6/src/DiaModality.egg-info → diamodality-0.2.8}/PKG-INFO +5 -24
- {diamodality-0.2.6 → diamodality-0.2.8}/README.md +1 -0
- {diamodality-0.2.6 → diamodality-0.2.8}/pyproject.toml +3 -2
- {diamodality-0.2.6 → diamodality-0.2.8}/src/DiaModality/ModalityPlot.py +37 -37
- diamodality-0.2.8/src/DiaModality/_version.py +1 -0
- {diamodality-0.2.6 → diamodality-0.2.8/src/DiaModality.egg-info}/PKG-INFO +5 -24
- {diamodality-0.2.6 → diamodality-0.2.8}/src/DiaModality.egg-info/SOURCES.txt +2 -1
- diamodality-0.2.8/tests/test_diamodality.py +286 -0
- diamodality-0.2.6/src/DiaModality/_version.py +0 -1
- {diamodality-0.2.6 → diamodality-0.2.8}/LICENSE +0 -0
- {diamodality-0.2.6 → diamodality-0.2.8}/MANIFEST.in +0 -0
- {diamodality-0.2.6 → diamodality-0.2.8}/requirements.txt +0 -0
- {diamodality-0.2.6 → diamodality-0.2.8}/setup.cfg +0 -0
- {diamodality-0.2.6 → diamodality-0.2.8}/src/DiaModality/__init__.py +0 -0
- {diamodality-0.2.6 → diamodality-0.2.8}/src/DiaModality/__main__.py +0 -0
- {diamodality-0.2.6 → diamodality-0.2.8}/src/DiaModality.egg-info/dependency_links.txt +0 -0
- {diamodality-0.2.6 → diamodality-0.2.8}/src/DiaModality.egg-info/requires.txt +0 -0
- {diamodality-0.2.6 → diamodality-0.2.8}/src/DiaModality.egg-info/top_level.txt +0 -0
|
@@ -1,29 +1,9 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: DiaModality
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.8
|
|
4
4
|
Summary: Tool to plot modality vector diagrams
|
|
5
5
|
Author-email: konung-yaropolk <yaropolk1995@gmail.com>
|
|
6
|
-
License: MIT
|
|
7
|
-
|
|
8
|
-
Copyright (c) 2024 konung-yaropolk
|
|
9
|
-
|
|
10
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
11
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
12
|
-
in the Software without restriction, including without limitation the rights
|
|
13
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
14
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
15
|
-
furnished to do so, subject to the following conditions:
|
|
16
|
-
|
|
17
|
-
The above copyright notice and this permission notice shall be included in all
|
|
18
|
-
copies or substantial portions of the Software.
|
|
19
|
-
|
|
20
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
21
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
22
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
23
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
24
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
25
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
26
|
-
SOFTWARE.
|
|
6
|
+
License-Expression: MIT
|
|
27
7
|
Project-URL: Homepage, https://github.com/konung-yaropolk/DiaModality
|
|
28
8
|
Project-URL: Repository, https://github.com/konung-yaropolk/DiaModality.git
|
|
29
9
|
Project-URL: Issues, https://github.com/konung-yaropolk/DiaModality/issues
|
|
@@ -31,7 +11,6 @@ Keywords: Visualization,Science,Plotting,Matplotlib
|
|
|
31
11
|
Classifier: Programming Language :: Python
|
|
32
12
|
Classifier: Programming Language :: Python :: 3
|
|
33
13
|
Classifier: Programming Language :: Python :: 3.12
|
|
34
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
35
14
|
Classifier: Operating System :: OS Independent
|
|
36
15
|
Classifier: Development Status :: 4 - Beta
|
|
37
16
|
Classifier: Intended Audience :: Science/Research
|
|
@@ -43,11 +22,13 @@ License-File: LICENSE
|
|
|
43
22
|
Requires-Dist: numpy
|
|
44
23
|
Requires-Dist: matplotlib
|
|
45
24
|
Requires-Dist: scsv>=0.1.4
|
|
25
|
+
Dynamic: license-file
|
|
46
26
|
|
|
47
27
|
# DiaModality - The Modality Diagram
|
|
48
28
|
|
|
49
29
|
Simple tool to plot vector modality diagram
|
|
50
30
|
|
|
31
|
+

|
|
51
32
|
[](https://pypi.org/project/diamodality)
|
|
52
33
|
[](https://github.com/konung-yaropolk/DiaModality)
|
|
53
34
|
[](https://pypi.org/project/diamodality)
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Simple tool to plot vector modality diagram
|
|
4
4
|
|
|
5
|
+

|
|
5
6
|
[](https://pypi.org/project/diamodality)
|
|
6
7
|
[](https://github.com/konung-yaropolk/DiaModality)
|
|
7
8
|
[](https://pypi.org/project/diamodality)
|
|
@@ -5,7 +5,8 @@ build-backend = "setuptools.build_meta"
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "DiaModality"
|
|
7
7
|
dynamic = ["version", "dependencies"]
|
|
8
|
-
license =
|
|
8
|
+
license = "MIT"
|
|
9
|
+
# license = {file = "LICENSE"}
|
|
9
10
|
authors = [
|
|
10
11
|
{ name="konung-yaropolk", email="yaropolk1995@gmail.com" },
|
|
11
12
|
]
|
|
@@ -17,7 +18,7 @@ classifiers = [
|
|
|
17
18
|
"Programming Language :: Python",
|
|
18
19
|
"Programming Language :: Python :: 3",
|
|
19
20
|
"Programming Language :: Python :: 3.12",
|
|
20
|
-
"License :: OSI Approved :: MIT License",
|
|
21
|
+
# "License :: OSI Approved :: MIT License",
|
|
21
22
|
"Operating System :: OS Independent",
|
|
22
23
|
"Development Status :: 4 - Beta",
|
|
23
24
|
"Intended Audience :: Science/Research",
|
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
#!/usr/bin/env python
|
|
2
|
+
import os
|
|
3
|
+
import pathlib
|
|
4
|
+
import numpy as np
|
|
2
5
|
import matplotlib.pyplot as plt
|
|
3
6
|
# import matplotlib.colors as mcolors
|
|
4
7
|
import matplotlib.gridspec as gridspec
|
|
5
|
-
import numpy as np
|
|
6
8
|
|
|
7
9
|
# Only use for plot layout adjustment
|
|
8
10
|
DEBUG = False
|
|
9
11
|
|
|
10
12
|
|
|
11
|
-
class
|
|
13
|
+
class _Figure():
|
|
12
14
|
'''
|
|
13
15
|
Super class of all future plots
|
|
14
16
|
'''
|
|
@@ -44,22 +46,24 @@ class __Figure():
|
|
|
44
46
|
plt.suptitle(self.title)
|
|
45
47
|
|
|
46
48
|
|
|
47
|
-
class
|
|
49
|
+
class _Output():
|
|
48
50
|
'''
|
|
49
51
|
Output options mixin
|
|
50
52
|
'''
|
|
51
53
|
|
|
52
54
|
def show(self):
|
|
53
|
-
|
|
55
|
+
self.fig.show()
|
|
54
56
|
|
|
55
|
-
def save(self,
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
57
|
+
def save(self, filename: str, file_type: str = 'png', transparent: bool = False) -> None:
|
|
58
|
+
p = pathlib.Path(filename)
|
|
59
|
+
if p.suffix.lower() == f'.{file_type.lower()}':
|
|
60
|
+
out_path = p
|
|
61
|
+
else:
|
|
62
|
+
out_path = p.with_suffix(f'.{file_type}')
|
|
63
|
+
self.fig.savefig(out_path, transparent=transparent)
|
|
60
64
|
|
|
61
65
|
|
|
62
|
-
class ModalityPlot(
|
|
66
|
+
class ModalityPlot(_Figure, _Output):
|
|
63
67
|
'''
|
|
64
68
|
Input fotmat:
|
|
65
69
|
|
|
@@ -157,10 +161,11 @@ class ModalityPlot(__Figure, __Output):
|
|
|
157
161
|
Each row containing at least one value that will be represented as a point.
|
|
158
162
|
'''
|
|
159
163
|
|
|
160
|
-
|
|
164
|
+
data = np.array(input_data, dtype=object)
|
|
161
165
|
# Replace empty cells with zeros
|
|
162
|
-
output_data
|
|
163
|
-
|
|
166
|
+
output_data = np.nan_to_num(
|
|
167
|
+
np.array(data, dtype=np.float64),
|
|
168
|
+
nan=0.0)
|
|
164
169
|
|
|
165
170
|
# Replace zeros with False and other numbers with True
|
|
166
171
|
output_bin = np.array(input_bin, dtype=np.bool_)
|
|
@@ -187,32 +192,27 @@ class ModalityPlot(__Figure, __Output):
|
|
|
187
192
|
# log_input = np.log1p(input)
|
|
188
193
|
# func = lambda x: (x - np.min(log_input)) / (np.max(log_input) - np.min(log_input))
|
|
189
194
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
195
|
+
case _:
|
|
196
|
+
raise ValueError(
|
|
197
|
+
f"Unknown normalization_func {self.normalization_func!r}. "
|
|
198
|
+
"Expected 'linear' or 'sigmoid'."
|
|
199
|
+
)
|
|
193
200
|
|
|
194
|
-
|
|
201
|
+
return [func(x) for x in input]
|
|
195
202
|
|
|
203
|
+
def __vector_addition(self, data, binarization) -> np.ndarray:
|
|
204
|
+
resultants = []
|
|
196
205
|
for points, bins in zip(data, binarization):
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
resultants = np.append(
|
|
203
|
-
resultants,
|
|
204
|
-
np.sum(
|
|
205
|
-
[points[i] * np.exp(1j * self.angles[i])
|
|
206
|
-
if bins[i] or self.whole_sum
|
|
207
|
-
else 0
|
|
208
|
-
for i in range(len(points))
|
|
209
|
-
]
|
|
210
|
-
)
|
|
206
|
+
if np.any(points != 0):
|
|
207
|
+
r = sum(
|
|
208
|
+
points[i] * np.exp(1j * self.angles[i])
|
|
209
|
+
if bins[i] or self.whole_sum else 0
|
|
210
|
+
for i in range(3)
|
|
211
211
|
)
|
|
212
|
+
resultants.append(r)
|
|
212
213
|
else:
|
|
213
|
-
resultants
|
|
214
|
-
|
|
215
|
-
return resultants
|
|
214
|
+
resultants.append(0)
|
|
215
|
+
return np.array(resultants, dtype=complex)
|
|
216
216
|
|
|
217
217
|
def __find_match_modality(self, sample, list) -> int:
|
|
218
218
|
for i, item in enumerate(list):
|
|
@@ -339,9 +339,9 @@ class ModalityPlot(__Figure, __Output):
|
|
|
339
339
|
if self.debug_flag:
|
|
340
340
|
self.__debug_grid(self.fig, 20, 20)
|
|
341
341
|
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
342
|
+
self.fig.subplots_adjust(wspace=0.0, hspace=0.0)
|
|
343
|
+
self.fig.tight_layout()
|
|
344
|
+
self.fig.suptitle(self.title)
|
|
345
345
|
|
|
346
346
|
|
|
347
347
|
if __name__ == '__main__':
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.2.8"
|
|
@@ -1,29 +1,9 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: DiaModality
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.8
|
|
4
4
|
Summary: Tool to plot modality vector diagrams
|
|
5
5
|
Author-email: konung-yaropolk <yaropolk1995@gmail.com>
|
|
6
|
-
License: MIT
|
|
7
|
-
|
|
8
|
-
Copyright (c) 2024 konung-yaropolk
|
|
9
|
-
|
|
10
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
11
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
12
|
-
in the Software without restriction, including without limitation the rights
|
|
13
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
14
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
15
|
-
furnished to do so, subject to the following conditions:
|
|
16
|
-
|
|
17
|
-
The above copyright notice and this permission notice shall be included in all
|
|
18
|
-
copies or substantial portions of the Software.
|
|
19
|
-
|
|
20
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
21
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
22
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
23
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
24
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
25
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
26
|
-
SOFTWARE.
|
|
6
|
+
License-Expression: MIT
|
|
27
7
|
Project-URL: Homepage, https://github.com/konung-yaropolk/DiaModality
|
|
28
8
|
Project-URL: Repository, https://github.com/konung-yaropolk/DiaModality.git
|
|
29
9
|
Project-URL: Issues, https://github.com/konung-yaropolk/DiaModality/issues
|
|
@@ -31,7 +11,6 @@ Keywords: Visualization,Science,Plotting,Matplotlib
|
|
|
31
11
|
Classifier: Programming Language :: Python
|
|
32
12
|
Classifier: Programming Language :: Python :: 3
|
|
33
13
|
Classifier: Programming Language :: Python :: 3.12
|
|
34
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
35
14
|
Classifier: Operating System :: OS Independent
|
|
36
15
|
Classifier: Development Status :: 4 - Beta
|
|
37
16
|
Classifier: Intended Audience :: Science/Research
|
|
@@ -43,11 +22,13 @@ License-File: LICENSE
|
|
|
43
22
|
Requires-Dist: numpy
|
|
44
23
|
Requires-Dist: matplotlib
|
|
45
24
|
Requires-Dist: scsv>=0.1.4
|
|
25
|
+
Dynamic: license-file
|
|
46
26
|
|
|
47
27
|
# DiaModality - The Modality Diagram
|
|
48
28
|
|
|
49
29
|
Simple tool to plot vector modality diagram
|
|
50
30
|
|
|
31
|
+

|
|
51
32
|
[](https://pypi.org/project/diamodality)
|
|
52
33
|
[](https://github.com/konung-yaropolk/DiaModality)
|
|
53
34
|
[](https://pypi.org/project/diamodality)
|
|
@@ -11,4 +11,5 @@ src/DiaModality.egg-info/PKG-INFO
|
|
|
11
11
|
src/DiaModality.egg-info/SOURCES.txt
|
|
12
12
|
src/DiaModality.egg-info/dependency_links.txt
|
|
13
13
|
src/DiaModality.egg-info/requires.txt
|
|
14
|
-
src/DiaModality.egg-info/top_level.txt
|
|
14
|
+
src/DiaModality.egg-info/top_level.txt
|
|
15
|
+
tests/test_diamodality.py
|
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Test suite for DiaModality.ModalityPlot
|
|
3
|
+
Run with: pytest test_diamodality.py -v
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import pytest
|
|
7
|
+
import numpy as np
|
|
8
|
+
import matplotlib
|
|
9
|
+
matplotlib.use('Agg') # Non-interactive backend — essential for CI / headless runs
|
|
10
|
+
|
|
11
|
+
import sys
|
|
12
|
+
import os
|
|
13
|
+
sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'src'))
|
|
14
|
+
|
|
15
|
+
import DiaModality.ModalityPlot as mp
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
# ---------------------------------------------------------------------------
|
|
19
|
+
# Fixtures
|
|
20
|
+
# ---------------------------------------------------------------------------
|
|
21
|
+
|
|
22
|
+
@pytest.fixture
|
|
23
|
+
def simple_data():
|
|
24
|
+
"""Minimal valid 3-column dataset."""
|
|
25
|
+
return [
|
|
26
|
+
[1.0, 0.0, 0.0],
|
|
27
|
+
[0.0, 2.0, 0.0],
|
|
28
|
+
[0.0, 0.0, 3.0],
|
|
29
|
+
[1.5, 1.5, 0.0],
|
|
30
|
+
[1.0, 1.0, 1.0],
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
@pytest.fixture
|
|
34
|
+
def simple_bin():
|
|
35
|
+
"""Corresponding binarization."""
|
|
36
|
+
return [
|
|
37
|
+
[1, 0, 0],
|
|
38
|
+
[0, 1, 0],
|
|
39
|
+
[0, 0, 1],
|
|
40
|
+
[1, 1, 0],
|
|
41
|
+
[1, 1, 1],
|
|
42
|
+
]
|
|
43
|
+
|
|
44
|
+
@pytest.fixture
|
|
45
|
+
def plot(simple_data, simple_bin):
|
|
46
|
+
"""A freshly constructed ModalityPlot (closes its figure on teardown)."""
|
|
47
|
+
import matplotlib.pyplot as plt
|
|
48
|
+
p = mp.ModalityPlot(simple_data, simple_bin)
|
|
49
|
+
yield p
|
|
50
|
+
plt.close('all')
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
# ---------------------------------------------------------------------------
|
|
54
|
+
# 1. Construction & basic smoke tests
|
|
55
|
+
# ---------------------------------------------------------------------------
|
|
56
|
+
|
|
57
|
+
class TestConstruction:
|
|
58
|
+
|
|
59
|
+
def test_basic_construction(self, simple_data, simple_bin):
|
|
60
|
+
"""Should not raise with valid minimal input."""
|
|
61
|
+
import matplotlib.pyplot as plt
|
|
62
|
+
p = mp.ModalityPlot(simple_data, simple_bin)
|
|
63
|
+
plt.close('all')
|
|
64
|
+
|
|
65
|
+
def test_numpy_input_accepted(self, simple_bin):
|
|
66
|
+
"""numpy arrays should be accepted as input."""
|
|
67
|
+
import matplotlib.pyplot as plt
|
|
68
|
+
data = np.array([[1.0, 0.5, 0.0], [0.0, 1.0, 2.0]])
|
|
69
|
+
binarization = np.array([[1, 0, 0], [0, 1, 1]])
|
|
70
|
+
p = mp.ModalityPlot(data, binarization)
|
|
71
|
+
plt.close('all')
|
|
72
|
+
|
|
73
|
+
def test_custom_angles(self, simple_data, simple_bin):
|
|
74
|
+
import matplotlib.pyplot as plt
|
|
75
|
+
p = mp.ModalityPlot(simple_data, simple_bin, angles=[0, 120, 240])
|
|
76
|
+
plt.close('all')
|
|
77
|
+
|
|
78
|
+
def test_custom_modality_names(self, simple_data, simple_bin):
|
|
79
|
+
import matplotlib.pyplot as plt
|
|
80
|
+
p = mp.ModalityPlot(simple_data, simple_bin,
|
|
81
|
+
modalities=('Alpha', 'Beta', 'Gamma'))
|
|
82
|
+
plt.close('all')
|
|
83
|
+
|
|
84
|
+
def test_normalization_linear(self, simple_data, simple_bin):
|
|
85
|
+
import matplotlib.pyplot as plt
|
|
86
|
+
p = mp.ModalityPlot(simple_data, simple_bin,
|
|
87
|
+
normalization_func='linear')
|
|
88
|
+
plt.close('all')
|
|
89
|
+
|
|
90
|
+
def test_normalization_sigmoid(self, simple_data, simple_bin):
|
|
91
|
+
import matplotlib.pyplot as plt
|
|
92
|
+
p = mp.ModalityPlot(simple_data, simple_bin,
|
|
93
|
+
normalization_func='sigmoid')
|
|
94
|
+
plt.close('all')
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
# ---------------------------------------------------------------------------
|
|
98
|
+
# 2. Input validation / assertions
|
|
99
|
+
# ---------------------------------------------------------------------------
|
|
100
|
+
|
|
101
|
+
class TestInputValidation:
|
|
102
|
+
|
|
103
|
+
def test_mismatched_lengths_raises(self):
|
|
104
|
+
import matplotlib.pyplot as plt
|
|
105
|
+
data = [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0]]
|
|
106
|
+
binarization = [[1, 0, 0]] # one row short
|
|
107
|
+
with pytest.raises((AssertionError, ValueError)):
|
|
108
|
+
mp.ModalityPlot(data, binarization)
|
|
109
|
+
plt.close('all')
|
|
110
|
+
|
|
111
|
+
def test_wrong_column_count_raises(self):
|
|
112
|
+
"""Only 2 columns instead of 3 should fail."""
|
|
113
|
+
import matplotlib.pyplot as plt
|
|
114
|
+
data = [[1.0, 2.0], [3.0, 4.0]]
|
|
115
|
+
binarization = [[1, 0], [0, 1]]
|
|
116
|
+
with pytest.raises((AssertionError, ValueError, IndexError)):
|
|
117
|
+
mp.ModalityPlot(data, binarization)
|
|
118
|
+
plt.close('all')
|
|
119
|
+
|
|
120
|
+
def test_empty_data_raises(self):
|
|
121
|
+
import matplotlib.pyplot as plt
|
|
122
|
+
with pytest.raises((AssertionError, ValueError)):
|
|
123
|
+
mp.ModalityPlot([], [])
|
|
124
|
+
plt.close('all')
|
|
125
|
+
|
|
126
|
+
def test_none_values_handled(self):
|
|
127
|
+
"""None / empty cells (from CSV parsing) should be treated as 0."""
|
|
128
|
+
import matplotlib.pyplot as plt
|
|
129
|
+
data = [[1.0, None, 0.0], [0.0, 2.0, None]]
|
|
130
|
+
binarization = [[1, 0, 0], [0, 1, 0]]
|
|
131
|
+
p = mp.ModalityPlot(data, binarization) # should not raise
|
|
132
|
+
plt.close('all')
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
# ---------------------------------------------------------------------------
|
|
136
|
+
# 3. Internal helpers — private-name-mangled access
|
|
137
|
+
# ---------------------------------------------------------------------------
|
|
138
|
+
|
|
139
|
+
class TestInternals:
|
|
140
|
+
|
|
141
|
+
def test_format_input_converts_none_to_zero(self):
|
|
142
|
+
"""__format_input must replace None with 0 in the data array."""
|
|
143
|
+
# Access through name-mangling
|
|
144
|
+
p_class = mp.ModalityPlot
|
|
145
|
+
# Build a minimal instance to call the method:
|
|
146
|
+
import matplotlib.pyplot as plt
|
|
147
|
+
data = [[1.0, None, 0.5]]
|
|
148
|
+
bindata = [[1, 0, 0]]
|
|
149
|
+
obj = mp.ModalityPlot(data, bindata)
|
|
150
|
+
# After formatting, no NaN/None should remain
|
|
151
|
+
assert not np.isnan(obj.data).any(), "NaNs found after __format_input"
|
|
152
|
+
assert obj.data[0][1] == 0.0, "None was not replaced with 0.0"
|
|
153
|
+
plt.close('all')
|
|
154
|
+
|
|
155
|
+
def test_vector_addition_single_row(self):
|
|
156
|
+
"""A single-axis unit vector should land on the unit circle."""
|
|
157
|
+
import matplotlib.pyplot as plt
|
|
158
|
+
data = [[1.0, 0.0, 0.0]]
|
|
159
|
+
bindata = [[1, 0, 0]]
|
|
160
|
+
obj = mp.ModalityPlot(data, bindata, angles=[90, 210, 330],
|
|
161
|
+
whole_sum=False)
|
|
162
|
+
# resultant magnitude should be 1.0 for a pure first-axis point
|
|
163
|
+
resultants = obj._ModalityPlot__vector_addition(obj.data, obj.binarization)
|
|
164
|
+
assert len(resultants) == 1
|
|
165
|
+
# magnitude ≈ 1 (allowing float tolerance)
|
|
166
|
+
assert abs(abs(resultants[0]) - 1.0) < 1e-5, \
|
|
167
|
+
f"Expected |resultant| ≈ 1.0, got {abs(resultants[0])}"
|
|
168
|
+
plt.close('all')
|
|
169
|
+
|
|
170
|
+
def test_normalization_sigmoid_range(self):
|
|
171
|
+
"""Sigmoid output must stay within (0, 1)."""
|
|
172
|
+
import matplotlib.pyplot as plt
|
|
173
|
+
obj = mp.ModalityPlot([[1,0,0],[0,1,0]], [[1,0,0],[0,1,0]],
|
|
174
|
+
normalization_func='sigmoid')
|
|
175
|
+
values = np.linspace(-10, 10, 100)
|
|
176
|
+
normed = obj._ModalityPlot__normalization(values)
|
|
177
|
+
assert all(0 < v < 1 for v in normed), "Sigmoid output out of (0, 1) range"
|
|
178
|
+
plt.close('all')
|
|
179
|
+
|
|
180
|
+
def test_normalization_linear_range(self):
|
|
181
|
+
"""Linear normalization must output [0, 1]."""
|
|
182
|
+
import matplotlib.pyplot as plt
|
|
183
|
+
obj = mp.ModalityPlot([[1,0,0],[0,1,0]], [[1,0,0],[0,1,0]],
|
|
184
|
+
normalization_func='linear')
|
|
185
|
+
values = np.array([1.0, 2.0, 3.0, 4.0, 5.0])
|
|
186
|
+
normed = obj._ModalityPlot__normalization(values)
|
|
187
|
+
assert abs(min(normed)) < 1e-9, "Linear min should be 0"
|
|
188
|
+
assert abs(max(normed) - 1.0) < 1e-9, "Linear max should be 1"
|
|
189
|
+
plt.close('all')
|
|
190
|
+
|
|
191
|
+
def test_find_match_modality_hit(self):
|
|
192
|
+
"""Should return the correct index when a matching pattern exists."""
|
|
193
|
+
import matplotlib.pyplot as plt
|
|
194
|
+
obj = mp.ModalityPlot([[1,0,0],[0,1,0]], [[1,0,0],[0,1,0]])
|
|
195
|
+
patterns = [(True, False, False), (False, True, False)]
|
|
196
|
+
idx = obj._ModalityPlot__find_match_modality((False, True, False), patterns)
|
|
197
|
+
assert idx == 1
|
|
198
|
+
plt.close('all')
|
|
199
|
+
|
|
200
|
+
def test_find_match_modality_miss_returns_zero(self):
|
|
201
|
+
"""Should return 0 when no pattern matches."""
|
|
202
|
+
import matplotlib.pyplot as plt
|
|
203
|
+
obj = mp.ModalityPlot([[1,0,0],[0,1,0]], [[1,0,0],[0,1,0]])
|
|
204
|
+
patterns = [(True, False, False)]
|
|
205
|
+
idx = obj._ModalityPlot__find_match_modality((False, True, False), patterns)
|
|
206
|
+
assert idx == 0
|
|
207
|
+
plt.close('all')
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
# ---------------------------------------------------------------------------
|
|
211
|
+
# 4. Save / output
|
|
212
|
+
# ---------------------------------------------------------------------------
|
|
213
|
+
|
|
214
|
+
class TestOutput:
|
|
215
|
+
|
|
216
|
+
def test_save_creates_file(self, plot, tmp_path):
|
|
217
|
+
outfile = str(tmp_path / 'test_output')
|
|
218
|
+
plot.save(outfile, file_type='png')
|
|
219
|
+
assert (tmp_path / 'test_output.png').exists(), "save() did not create a file"
|
|
220
|
+
|
|
221
|
+
def test_save_svg(self, plot, tmp_path):
|
|
222
|
+
outfile = str(tmp_path / 'test_output')
|
|
223
|
+
plot.save(outfile, file_type='svg')
|
|
224
|
+
assert (tmp_path / 'test_output.svg').exists()
|
|
225
|
+
|
|
226
|
+
def test_save_transparent(self, plot, tmp_path):
|
|
227
|
+
"""transparent=True should not raise."""
|
|
228
|
+
outfile = str(tmp_path / 'test_transparent')
|
|
229
|
+
plot.save(outfile, file_type='png', transparent=True)
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
# ---------------------------------------------------------------------------
|
|
233
|
+
# 5. Edge / boundary cases
|
|
234
|
+
# ---------------------------------------------------------------------------
|
|
235
|
+
|
|
236
|
+
class TestEdgeCases:
|
|
237
|
+
|
|
238
|
+
def test_all_zero_data(self):
|
|
239
|
+
"""All-zero data should not crash (vectors are skipped)."""
|
|
240
|
+
import matplotlib.pyplot as plt
|
|
241
|
+
data = [[0.0, 0.0, 0.0]] * 5
|
|
242
|
+
binarization = [[0, 0, 0]] * 5
|
|
243
|
+
# The assertion `self.data.any()` will fire — that's expected behaviour.
|
|
244
|
+
with pytest.raises(AssertionError):
|
|
245
|
+
mp.ModalityPlot(data, binarization)
|
|
246
|
+
plt.close('all')
|
|
247
|
+
|
|
248
|
+
def test_single_row(self):
|
|
249
|
+
import matplotlib.pyplot as plt
|
|
250
|
+
data = [[1.0, 2.0, 3.0]]
|
|
251
|
+
binarization = [[1, 1, 1]]
|
|
252
|
+
p = mp.ModalityPlot(data, binarization)
|
|
253
|
+
plt.close('all')
|
|
254
|
+
|
|
255
|
+
def test_large_dataset(self):
|
|
256
|
+
"""Should handle 1000 rows without error or significant slowdown."""
|
|
257
|
+
import matplotlib.pyplot as plt
|
|
258
|
+
rng = np.random.default_rng(42)
|
|
259
|
+
data = rng.uniform(0, 5, (1000, 3)).tolist()
|
|
260
|
+
binarization = (rng.uniform(0, 1, (1000, 3)) > 0.5).astype(int).tolist()
|
|
261
|
+
p = mp.ModalityPlot(data, binarization)
|
|
262
|
+
plt.close('all')
|
|
263
|
+
|
|
264
|
+
def test_whole_sum_false(self):
|
|
265
|
+
import matplotlib.pyplot as plt
|
|
266
|
+
data = [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0]]
|
|
267
|
+
binarization = [[1, 0, 0], [0, 1, 0]]
|
|
268
|
+
p = mp.ModalityPlot(data, binarization, whole_sum=False)
|
|
269
|
+
plt.close('all')
|
|
270
|
+
|
|
271
|
+
def test_same_scale_true(self):
|
|
272
|
+
import matplotlib.pyplot as plt
|
|
273
|
+
data = [[1.0, 2.0, 0.5], [0.5, 0.5, 0.5]]
|
|
274
|
+
binarization = [[1, 1, 0], [1, 0, 1]]
|
|
275
|
+
p = mp.ModalityPlot(data, binarization, same_scale=True)
|
|
276
|
+
plt.close('all')
|
|
277
|
+
|
|
278
|
+
def test_full_center_false(self):
|
|
279
|
+
import matplotlib.pyplot as plt
|
|
280
|
+
data = [[1.0, 1.0, 1.0], [1.0, 0.0, 0.0]]
|
|
281
|
+
binarization = [[1, 1, 1], [1, 0, 0]]
|
|
282
|
+
p = mp.ModalityPlot(data, binarization, full_center=False)
|
|
283
|
+
plt.close('all')
|
|
284
|
+
|
|
285
|
+
if __name__ == '__main__':
|
|
286
|
+
pytest.main([__file__, '-v'])
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.2.6"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|