modusa 0.1.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 (41) hide show
  1. modusa/.DS_Store +0 -0
  2. modusa/__init__.py +1 -0
  3. modusa/config.py +18 -0
  4. modusa/decorators.py +176 -0
  5. modusa/devtools/generate_template.py +79 -0
  6. modusa/devtools/list_authors.py +2 -0
  7. modusa/devtools/list_plugins.py +60 -0
  8. modusa/devtools/main.py +42 -0
  9. modusa/devtools/templates/engines.py +28 -0
  10. modusa/devtools/templates/generators.py +26 -0
  11. modusa/devtools/templates/plugins.py +40 -0
  12. modusa/devtools/templates/signals.py +63 -0
  13. modusa/engines/__init__.py +4 -0
  14. modusa/engines/base.py +14 -0
  15. modusa/engines/plot_1dsignal.py +130 -0
  16. modusa/engines/plot_2dmatrix.py +159 -0
  17. modusa/generators/__init__.py +3 -0
  18. modusa/generators/base.py +40 -0
  19. modusa/generators/basic_waveform.py +185 -0
  20. modusa/main.py +35 -0
  21. modusa/plugins/__init__.py +7 -0
  22. modusa/plugins/base.py +100 -0
  23. modusa/plugins/plot_1dsignal.py +59 -0
  24. modusa/plugins/plot_2dmatrix.py +76 -0
  25. modusa/plugins/plot_time_domain_signal.py +59 -0
  26. modusa/signals/__init__.py +9 -0
  27. modusa/signals/audio_signal.py +230 -0
  28. modusa/signals/base.py +294 -0
  29. modusa/signals/signal1d.py +311 -0
  30. modusa/signals/signal2d.py +226 -0
  31. modusa/signals/uniform_time_domain_signal.py +212 -0
  32. modusa/utils/.DS_Store +0 -0
  33. modusa/utils/__init__.py +1 -0
  34. modusa/utils/config.py +25 -0
  35. modusa/utils/excp.py +71 -0
  36. modusa/utils/logger.py +18 -0
  37. modusa-0.1.0.dist-info/METADATA +86 -0
  38. modusa-0.1.0.dist-info/RECORD +41 -0
  39. modusa-0.1.0.dist-info/WHEEL +4 -0
  40. modusa-0.1.0.dist-info/entry_points.txt +5 -0
  41. modusa-0.1.0.dist-info/licenses/LICENSE.md +9 -0
@@ -0,0 +1,212 @@
1
+ #!/usr/bin/env python3
2
+
3
+
4
+ from modusa import excp
5
+ from modusa.decorators import immutable_property, validate_args_type
6
+ from modusa.signals.base import ModusaSignal
7
+ from typing import Self, Any
8
+ import numpy as np
9
+ import matplotlib.pyplot as plt
10
+
11
+
12
+ class UniformTimeDomainSignal(ModusaSignal):
13
+ """
14
+
15
+ """
16
+
17
+ #--------Meta Information----------
18
+ name = "Uniform Time Domain Signal"
19
+ description = ""
20
+ author_name = "Ankit Anand"
21
+ author_email = "ankit0.anand0@gmail.com"
22
+ created_at = "2025-07-02"
23
+ #----------------------------------
24
+
25
+ @validate_args_type()
26
+ def __init__(self, y: np.ndarray, t: np.ndarray | None = None):
27
+ if y.ndim != 1:
28
+ raise excp.InputValueError(f"`y` must have 1 dimension not {y.ndim}.")
29
+ if y.shape[0] < 1:
30
+ raise excp.InputValueError(f"`y` must have atleast 1 element.")
31
+
32
+ if t is None:
33
+ t = np.arange(y.shape[0])
34
+ else:
35
+ if t.ndim != 1:
36
+ raise excp.InputValueError(f"`t` must have 1 dimension not {t.ndim}.")
37
+ if t.shape[0] != y.shape[0]:
38
+ raise excp.InputValueError(f"`t` and `y` must have same shape.")
39
+
40
+ super().__init__(data=y, data_idx=t) # Instantiating `ModusaSignal` class
41
+
42
+ self._y_unit = ""
43
+ self._t_unit = "index"
44
+
45
+ self._title = "aa"
46
+ self._y_label = ""
47
+ self._t_label = "Time"
48
+
49
+ def _with_data(self, new_data: np.ndarray, new_data_idx: np.ndarray) -> Self:
50
+ """Subclasses must override this to return a copy with new data."""
51
+ Ts = new_data_idx[1] - new_data_idx[0]
52
+ new_signal = self.__class__(y=new_data, Ts=self.Ts)
53
+ new_signal.set_units(y_unit=self.y_unit, t_unit=self.t_unit)
54
+ new_signal.set_plot_labels(title=self.title, y_label=self.y_label, t_label=self.t_label)
55
+
56
+ return new_signal
57
+
58
+ #----------------------
59
+ # From methods
60
+ #----------------------
61
+ @classmethod
62
+ @validate_args_type()
63
+ def from_array(cls, y: np.ndarray, t: np.ndarray | None = None, t_unit: str | None = None) -> Self:
64
+
65
+ signal: Self = cls(y=y, t=t)
66
+
67
+ if t_unit is not None:
68
+ signal.set_units(t_unit=t_unit)
69
+
70
+ return signal
71
+
72
+ @classmethod
73
+ @validate_args_type()
74
+ def from_array_with_Ts(cls, y: np.ndarray, Ts: float | None = None, t_unit: str | None = None) -> Self:
75
+
76
+ t = np.arange(y.shape[0]) * Ts
77
+ signal: Self = cls(y=y, t=t)
78
+
79
+ if t_unit is not None:
80
+ signal.set_units(t_unit=t_unit)
81
+
82
+ return signal
83
+
84
+ #----------------------
85
+ # Setters
86
+ #----------------------
87
+
88
+ @validate_args_type()
89
+ def set_units(
90
+ self,
91
+ y_unit: str | None = None,
92
+ t_unit: str | None = None,
93
+ ) -> Self:
94
+ if y_unit is not None:
95
+ self._y_unit = y_unit
96
+ if t_unit is not None:
97
+ self._t_unit = t_unit
98
+
99
+ return self
100
+
101
+ @validate_args_type()
102
+ def set_plot_labels(
103
+ self,
104
+ title: str | None = None,
105
+ y_label: str | None = None,
106
+ t_label: str | None = None
107
+ ) -> Self:
108
+ """"""
109
+ if title is not None:
110
+ self._title = title
111
+ if y_label is not None:
112
+ self._y_label = y_label
113
+ if t_label is not None:
114
+ self._t_label = t_label
115
+
116
+ return self
117
+
118
+
119
+
120
+ #----------------------
121
+ # Properties
122
+ #----------------------
123
+
124
+ @immutable_property("Create a new object instead.")
125
+ def y(self) -> np.ndarray:
126
+ return self.data
127
+
128
+ @immutable_property("Create a new object instead.")
129
+ def t(self) -> np.ndarray:
130
+ return self.data_idx
131
+
132
+ @immutable_property("Use `.set_t` instead.")
133
+ def y_unit(self) -> str:
134
+ return self._y_unit
135
+
136
+ @immutable_property("Use `.set_t` instead.")
137
+ def t_unit(self) -> str:
138
+ return self._t_unit
139
+
140
+ @immutable_property("Use `.labels` instead.")
141
+ def title(self) -> str:
142
+ return self._title
143
+
144
+ @immutable_property("Use `.set_t` instead.")
145
+ def y_label(self) -> str:
146
+ return self._y_label
147
+
148
+ @immutable_property("Use `.set_t` instead.")
149
+ def t_label(self) -> str:
150
+ return self._t_label
151
+
152
+ @immutable_property("Use `.resample` instead.")
153
+ def Ts(self) -> float:
154
+ """Sampling period of the signal."""
155
+ return self.t[1] - self.t[0]
156
+
157
+ @immutable_property("Use `.resample` instead.")
158
+ def sr(self) -> float:
159
+ """
160
+ Sampling rate of the signal.
161
+ """
162
+ return 1.0 / self.Ts
163
+
164
+ @immutable_property(error_msg="Use `.set_labels` instead.")
165
+ def labels(self) -> tuple[str, str, str]:
166
+ """Labels in a format appropriate for the plots."""
167
+ return (self.title, f"{self.y_label} ({self.y_unit})", f"{self.t_label} ({self.t_unit})")
168
+
169
+ #----------------------
170
+ # Plugins Access
171
+ #----------------------
172
+ @validate_args_type()
173
+ def plot(
174
+ self,
175
+ scale_y: tuple[float, float] | None = None,
176
+ scale_t: tuple[float, float] | None = None,
177
+ ax: plt.Axes | None = None,
178
+ color: str = "b",
179
+ marker: str | None = None,
180
+ linestyle: str | None = None,
181
+ stem: bool | None = None,
182
+ labels: tuple[str, str, str] | None = None,
183
+ legend_loc: str | None = None,
184
+ zoom: tuple[float, float] | None = None,
185
+ highlight: list[tuple[float, float]] | None = None,
186
+ ) -> plt.Figure:
187
+ """
188
+ Applies `modusa.plugins.PlotTimeDomainSignal` Plugin.
189
+ """
190
+
191
+ from modusa.plugins import PlotTimeDomainSignalPlugin
192
+
193
+ labels = labels or self.labels
194
+ stem = stem or False
195
+
196
+ fig: plt.Figure | None = PlotTimeDomainSignalPlugin().apply(
197
+ signal=self,
198
+ scale_y=scale_y,
199
+ scale_t=scale_t,
200
+ ax=ax,
201
+ color=color,
202
+ marker=marker,
203
+ linestyle=linestyle,
204
+ stem=stem,
205
+ labels=labels,
206
+ legend_loc=legend_loc,
207
+ zoom=zoom,
208
+ highlight=highlight
209
+ )
210
+
211
+ return fig
212
+
modusa/utils/.DS_Store ADDED
Binary file
@@ -0,0 +1 @@
1
+ #!/usr/bin/env python3
modusa/utils/config.py ADDED
@@ -0,0 +1,25 @@
1
+ #!/usr/bin/env python3
2
+
3
+ import logging
4
+
5
+ class PATHS:
6
+ from pathlib import Path
7
+ ROOT_DP: Path = Path(__file__).parents[2].resolve()
8
+ AUDIO_DP: Path = ROOT_DP / "data" / "audio"
9
+ EXAMPLE_AUDIO_FP: Path = AUDIO_DP / "Arko - Nazm Nazm.mp3"
10
+ LABELS_CSV_FP: Path = ROOT_DP / "data" / "label_data.csv"
11
+ REPORTS_DP: Path = ROOT_DP / "data" / "reports"
12
+
13
+ class DEFAULT_SETTINGS:
14
+ SR: int = 44100
15
+ LOG_LEVEL = logging.WARNING
16
+
17
+ class STFT:
18
+ N_FFT: int = 2048
19
+ WIN_SIZE: int = 2048
20
+ HOP_SIZE: int = 512
21
+ WINDOW: str = "hann"
22
+
23
+ class NOVELTY:
24
+ GAMMA: int = 10
25
+ LOCAL_AVG: int = 40
modusa/utils/excp.py ADDED
@@ -0,0 +1,71 @@
1
+ #!/usr/bin/env python3
2
+
3
+
4
+ #----------------------------------------
5
+ # Base class errors
6
+ #----------------------------------------
7
+ class MusaBaseError(Exception):
8
+ """
9
+ Ultimate base class for any kind of custom errors.
10
+ """
11
+ pass
12
+
13
+ class TypeError(MusaBaseError):
14
+ pass
15
+
16
+ class InputError(MusaBaseError):
17
+ """
18
+ Any Input type error.
19
+ """
20
+
21
+ class InputTypeError(MusaBaseError):
22
+ """
23
+ Any Input type error.
24
+ """
25
+
26
+ class InputValueError(MusaBaseError):
27
+ """
28
+ Any Input type error.
29
+ """
30
+
31
+ class ImmutableAttributeError(MusaBaseError):
32
+ """Raised when attempting to modify an immutable attribute."""
33
+ pass
34
+
35
+ class FileNotFoundError(MusaBaseError):
36
+ """Raised when file does not exist."""
37
+ pass
38
+
39
+
40
+ class PluginInputError(MusaBaseError):
41
+ pass
42
+
43
+ class PluginOutputError(MusaBaseError):
44
+ pass
45
+
46
+ class AttributeNotFoundError(MusaBaseError):
47
+ pass
48
+
49
+ class ParsingError(MusaBaseError):
50
+ """
51
+ Base class for any parsing related issues
52
+ """
53
+ pass
54
+
55
+ class ValidationError(MusaBaseError):
56
+ """
57
+ Base class for all input validation error
58
+ """
59
+ pass
60
+
61
+ class GenerationError(MusaBaseError):
62
+ """
63
+ Error when generation fails
64
+ """
65
+ pass
66
+
67
+ class FileLoadingError(MusaBaseError):
68
+ """
69
+ Error loading a file
70
+ """
71
+ pass
modusa/utils/logger.py ADDED
@@ -0,0 +1,18 @@
1
+ #!/usr/bin/env python3
2
+
3
+ import logging
4
+
5
+ def setup_logging(log_level: int | None = logging.WARNING):
6
+ logging.basicConfig(
7
+ level=log_level,
8
+ format="%(asctime)s | %(levelname)s | %(name)s | %(funcName)s():%(lineno)d\n> %(message)s\n",
9
+ datefmt='%Y-%m-%d %H:%M:%S'
10
+ )
11
+ # Silence 3rd-party spam
12
+ logging.getLogger("matplotlib").setLevel(logging.WARNING)
13
+ logging.getLogger("PIL").setLevel(logging.WARNING)
14
+ logging.getLogger("numba").setLevel(logging.WARNING)
15
+ logging.getLogger("py").setLevel(logging.ERROR)
16
+
17
+ def get_logger(name=None):
18
+ return logging.getLogger(name)
@@ -0,0 +1,86 @@
1
+ Metadata-Version: 2.1
2
+ Name: modusa
3
+ Version: 0.1.0
4
+ Summary: A modular signal analysis python library.
5
+ Author-Email: Ankit Anand <ankit0.anand0@gmail.com>
6
+ License: MIT
7
+ Requires-Python: >=3.12
8
+ Requires-Dist: jupyter>=1.1.1
9
+ Requires-Dist: pytest>=8.4.0
10
+ Requires-Dist: numpy>=2.2.6
11
+ Requires-Dist: librosa>=0.11.0
12
+ Requires-Dist: matplotlib>=3.10.3
13
+ Requires-Dist: pandas>=2.3.0
14
+ Requires-Dist: pydantic>=2.11.5
15
+ Requires-Dist: sqlalchemy>=2.0.41
16
+ Requires-Dist: tqdm>=4.67.1
17
+ Requires-Dist: sphinx==8.1.2
18
+ Requires-Dist: sphinx-autodoc-typehints==2.1.0
19
+ Requires-Dist: sphinx-copybutton>=0.5.2
20
+ Requires-Dist: furo>=2024.8.6
21
+ Requires-Dist: questionary>=2.1.0
22
+ Requires-Dist: rich>=14.0.0
23
+ Requires-Dist: snakeviz>=2.2.2
24
+ Requires-Dist: line-profiler>=4.2.0
25
+ Requires-Dist: nbsphinx==0.9.7
26
+ Requires-Dist: ghp-import>=2.1.0
27
+ Description-Content-Type: text/markdown
28
+
29
+ # modusa
30
+
31
+ **modusa**: **Mod**ular **U**nified **S**ignal **A**rchitecture* is a flexible, extensible Python framework for building, transforming, and analyzing different signal representations. It is a domain-agnostic core architecture for modern signal processing workflows.
32
+
33
+ ---
34
+
35
+ ## ๐Ÿ”ง Features
36
+
37
+ - โš™๏ธ **modusa Signals**
38
+ - ๐Ÿงฉ **modusa Plugins**
39
+ - ๐Ÿ“Š **modusa Genetators**
40
+ - โ™ป๏ธ **modusa Engine**
41
+
42
+ ---
43
+
44
+ ## ๐Ÿš€ Installation
45
+
46
+ > modusa is under active development. You can install the latest version via:
47
+
48
+ ```bash
49
+ git clone https://github.com/meluron/modusa.git
50
+ cd modusa
51
+ pdm install
52
+ ```
53
+
54
+ ---
55
+
56
+ ## ๐Ÿงช Tests
57
+
58
+ ```bash
59
+ pytest tests/
60
+ ```
61
+
62
+ ---
63
+
64
+ ## ๐ŸงŠ Status
65
+
66
+ modusa is in **early alpha**. Expect rapid iteration, breaking changes, and big ideas.
67
+ If you like the direction, consider โญ starring the repo and opening issues or ideas.
68
+
69
+ ---
70
+
71
+ ## ๐Ÿง  About
72
+
73
+ **modusa** is developed and maintained by [meluron](https://www.github.com/meluron),
74
+
75
+ ---
76
+
77
+ ## ๐Ÿ“œ License
78
+
79
+ MIT License. See `LICENSE` for details.
80
+
81
+ ---
82
+
83
+ ## ๐Ÿ™Œ Contributions
84
+
85
+ Pull requests, ideas, and discussions are welcome!
86
+ No matter which domain you are in, if you work with any signal, we'd love your input.
@@ -0,0 +1,41 @@
1
+ modusa-0.1.0.dist-info/METADATA,sha256=gJkbRke4vQ91d1kGiOAEOZxTABmPRHUsK8DFF979Abc,2026
2
+ modusa-0.1.0.dist-info/WHEEL,sha256=9P2ygRxDrTJz3gsagc0Z96ukrxjr-LFBGOgv3AuKlCA,90
3
+ modusa-0.1.0.dist-info/entry_points.txt,sha256=fmKpleVXj6CdaBVL14WoEy6xx7JQCs85jvzwTi3lePM,73
4
+ modusa-0.1.0.dist-info/licenses/LICENSE.md,sha256=JTaXAjx5awk76VArKCx5dUW8vmLEWsL_ZlR7-umaHbA,1078
5
+ modusa/.DS_Store,sha256=XmYb59Z5I9QAhS9vvksVzCbsHT2q9D4A3oZtrgkDWsA,8196
6
+ modusa/__init__.py,sha256=bfQKVSpAXlKmKvMRIU6WSRQW2qoJsaZsdNJ8E69ghn0,37
7
+ modusa/config.py,sha256=bTqK4t00FZqERVITrxW_q284aDDJAa9aMSfFknfR-oU,280
8
+ modusa/decorators.py,sha256=r2r411sXHjl29zFVwoCvcQ5Pb8war-drSuDinHfPYq4,5905
9
+ modusa/devtools/generate_template.py,sha256=X_9VC87LBrd9x4_GNkisJ6S9bimp8AdNmv7esdd4Ad0,2567
10
+ modusa/devtools/list_authors.py,sha256=FWBQKOLznVthvMYMASrx2Gw5lqKHEOccQpBisDZ53Dw,24
11
+ modusa/devtools/list_plugins.py,sha256=g-R5hoaCzHfClY_Sfa788IQ9CAKzQGTfyqmthXDZQqw,1729
12
+ modusa/devtools/main.py,sha256=L9KpdX4ob1CW7KNCULgVes7LIfHUn25Wo5Jp5mMgX5g,1704
13
+ modusa/devtools/templates/engines.py,sha256=ut3-jyRI5TmwJlni3nu4HQ7t0do_VtQuTS1sdkCePxI,512
14
+ modusa/devtools/templates/generators.py,sha256=qXvsxJ6tTj6LLlUYpXnKsDfXT2dz3wDrBzv5ZhPO1do,457
15
+ modusa/devtools/templates/plugins.py,sha256=n451iIlTOuQtpHywI8c_teyGMSMd3QCLYDUyLmg0v8Y,839
16
+ modusa/devtools/templates/signals.py,sha256=Sv0D5Irhvgnn7Q0kWKF9ryTgPmNie9kvUxOnjZCgUTQ,1210
17
+ modusa/engines/__init__.py,sha256=cKVTJjAwhlLF2x5tpvYAmxIR9B6CFOGeYsVKKJoUm2I,115
18
+ modusa/engines/base.py,sha256=gHeGTbvcPhTxwUwt16OLpepMkbG40rv19vs8tN8xqgA,297
19
+ modusa/engines/plot_1dsignal.py,sha256=0qaRuMDP5ZLnxrE53v1D-0aHu2hcpH-dMfPRRKAUVhY,3589
20
+ modusa/engines/plot_2dmatrix.py,sha256=q_k08170ras9lBjI37SYArrDkQjlKd2wicVc_iykvsU,4393
21
+ modusa/generators/__init__.py,sha256=0tr-StwA50XArEfz7jDASyQhd7Au-d4RuNhzooAYuhQ,74
22
+ modusa/generators/base.py,sha256=F0xUu97A6LJ3AIZ3VcxRw-oMMpjK13zotHprM0_WJa8,1185
23
+ modusa/generators/basic_waveform.py,sha256=FtglesHsLGflIJ2ROO1mYoDTgouXoDE_896qB9J1_2Y,4540
24
+ modusa/main.py,sha256=X3uq5nfHt89_EsnyRHNMkX-j5dxSuaTMF3EPu6XPr48,665
25
+ modusa/plugins/__init__.py,sha256=bGPEyqRaNR2uhrRbX0n_FWK4IqjlZ42akUNIbe_WUe8,211
26
+ modusa/plugins/base.py,sha256=Bh_1Bja7fOymFsCgwhXDbV6ys3D8muNrPwrfDrG_G_A,2382
27
+ modusa/plugins/plot_1dsignal.py,sha256=_Annn_4QgVTAlWanY2nctw0MIQyoEhR8u8gx8WAMd5c,1885
28
+ modusa/plugins/plot_2dmatrix.py,sha256=FXmQvUhod66Q4-jWIKb6IrP8aVmC67V7urxMSV4LJSg,2135
29
+ modusa/plugins/plot_time_domain_signal.py,sha256=fiyFQZ4VS_uvaVOm8LBryqxuWd57sf4vvKB25YqriPU,1804
30
+ modusa/signals/__init__.py,sha256=QjJUuEjbJR9UHbPPYXJVZ7oZP1YnDy1DPRl1HWqEP50,220
31
+ modusa/signals/audio_signal.py,sha256=8QaXt-LdTc_yVDLXXExlWCzzqgR-MxgwPEk30CXuanQ,5576
32
+ modusa/signals/base.py,sha256=fXPYPsaTcvZ323hkzjigKR1EZfNw6ZTxOUonbuzACnA,8362
33
+ modusa/signals/signal1d.py,sha256=sjUuRO4BEhhfJFdA3rJfIBYQxSwR_9u0Ym1_RZQQvRI,7711
34
+ modusa/signals/signal2d.py,sha256=7GvmsYEgavoMTSyacbQHva9eyc2aSII3aqOcbKzOG7A,5677
35
+ modusa/signals/uniform_time_domain_signal.py,sha256=lws6n3eYOg5Bwgb-iwZwUgvN6kbxKE4n15rCqOODguA,5250
36
+ modusa/utils/.DS_Store,sha256=nLXMwF7QJNuglLI_Gk74F7vl5Dyus2Wd74Mgowijmdo,6148
37
+ modusa/utils/__init__.py,sha256=1oLL20yLB1GL9IbFiZD8OReDqiCpFr-yetIR6x1cNkI,23
38
+ modusa/utils/config.py,sha256=cuGbqbovx5WDQq5rw3hIKcv3CnE5NttjacSOWnP1yu4,576
39
+ modusa/utils/excp.py,sha256=3HkCyyvwE035hAq5-0srb-82AYSI-_8vszKU6Fe3O3k,1189
40
+ modusa/utils/logger.py,sha256=K0rsnObeNKCxlNeSnVnJeRhgfmob6riB2uyU7h3dDmA,571
41
+ modusa-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: pdm-backend (2.4.5)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,5 @@
1
+ [console_scripts]
2
+ modusa-dev = modusa.devtools.main:main
3
+
4
+ [gui_scripts]
5
+
@@ -0,0 +1,9 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 [Ankit Anand @meluron]
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6
+
7
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8
+
9
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.