MNutils 1.2.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. mnutils/GEExam.py +495 -0
  2. mnutils/GESeries.py +1578 -0
  3. mnutils/__init__.py +54 -0
  4. mnutils/_scripts.py +177 -0
  5. mnutils/fitting/AMARES.py +831 -0
  6. mnutils/fitting/__init__.py +5 -0
  7. mnutils/matlab/__init__.py +13 -0
  8. mnutils/matlab/setup_matlab.py +116 -0
  9. mnutils/plotting/__init__.py +38 -0
  10. mnutils/plotting/_widgets/__init__.py +5 -0
  11. mnutils/plotting/_widgets/_html.py +34 -0
  12. mnutils/plotting/_widgets/_shared/__init__.py +50 -0
  13. mnutils/plotting/_widgets/_shared/dom.js +124 -0
  14. mnutils/plotting/_widgets/_shared/theme.css +140 -0
  15. mnutils/plotting/_widgets/image_grid.css +202 -0
  16. mnutils/plotting/_widgets/image_grid.js +212 -0
  17. mnutils/plotting/_widgets/image_grid.py +86 -0
  18. mnutils/plotting/_widgets/mrsi_inspector.css +376 -0
  19. mnutils/plotting/_widgets/mrsi_inspector.js +681 -0
  20. mnutils/plotting/_widgets/mrsi_inspector.py +70 -0
  21. mnutils/plotting/_widgets/slice_viewer.css +38 -0
  22. mnutils/plotting/_widgets/slice_viewer.js +41 -0
  23. mnutils/plotting/_widgets/slice_viewer.py +35 -0
  24. mnutils/plotting/images.py +1399 -0
  25. mnutils/plotting/saving.py +138 -0
  26. mnutils/plotting/spectra.py +357 -0
  27. mnutils/py.typed +0 -0
  28. mnutils/testing/__init__.py +3 -0
  29. mnutils/testing/_spectra.py +338 -0
  30. mnutils/testing/synthetic.py +373 -0
  31. mnutils/utils/__init__.py +25 -0
  32. mnutils/utils/data_loaders.py +218 -0
  33. mnutils/utils/file_helpers.py +708 -0
  34. mnutils/utils/images.py +202 -0
  35. mnutils/utils/nifti.py +379 -0
  36. mnutils/utils/spectra.py +149 -0
  37. mnutils-1.2.0.dist-info/METADATA +161 -0
  38. mnutils-1.2.0.dist-info/RECORD +41 -0
  39. mnutils-1.2.0.dist-info/WHEEL +4 -0
  40. mnutils-1.2.0.dist-info/entry_points.txt +7 -0
  41. mnutils-1.2.0.dist-info/licenses/LICENSE +28 -0
@@ -0,0 +1,149 @@
1
+ import numpy as np
2
+ import numpy.typing as npt
3
+ from loguru import logger
4
+
5
+
6
+ def calculate_hz_axis(
7
+ spectral_width: float | None = None,
8
+ npts: int | None = None,
9
+ header: dict | None = None,
10
+ ) -> npt.NDArray[np.float64]:
11
+ """Calculate the frequency (Hz) axis for a spectrum.
12
+
13
+ Parameters
14
+ ----------
15
+ spectral_width : float, optional
16
+ Spectral width in Hz. Read from `header` if not given.
17
+ npts : int, optional
18
+ Number of spectral points. Read from `header` if not given.
19
+ header : dict, optional
20
+ Raw header dict to fall back on for `spectral_width`/`npts`.
21
+
22
+ Returns
23
+ -------
24
+ npt.NDArray[np.float64]
25
+ Frequency axis in Hz, centered on zero.
26
+ """
27
+ _sw = spectral_width
28
+ _npts = npts
29
+
30
+ if header is not None:
31
+ rdb_hdr = header.get("rdb_hdr", {})
32
+ if isinstance(rdb_hdr, dict):
33
+ if _sw is None:
34
+ _sw = rdb_hdr.get("spectral_width", None)
35
+ if _npts is None:
36
+ _npts = rdb_hdr.get("user1", None)
37
+
38
+ if _sw is None:
39
+ raise ValueError(
40
+ "Spectral width information is required to calculate frequency axis. "
41
+ "Please provide spectral width or ensure it is present in the header."
42
+ )
43
+ if _npts is None:
44
+ raise ValueError(
45
+ "Number of points information is required to calculate frequency axis. "
46
+ "Please provide number of points or ensure it is present in the header."
47
+ )
48
+
49
+ return np.linspace(-_sw / 2, _sw / 2, int(_npts))
50
+
51
+
52
+ def calculate_ppm_axis(
53
+ spectral_width: float | None = None,
54
+ frequency: float | None = None,
55
+ carrier_ppm: float | None = None,
56
+ nucleus: int | str | None = None,
57
+ npts: int | None = None,
58
+ header: dict | None = None,
59
+ ) -> npt.NDArray[np.float64]:
60
+ """Calculate the chemical shift (ppm) axis for a spectrum.
61
+
62
+ Parameters
63
+ ----------
64
+ spectral_width : float, optional
65
+ Spectral width in Hz. Read from `header` if not given.
66
+ frequency : float, optional
67
+ Spectrometer frequency in MHz. Read from `header` if not given.
68
+ carrier_ppm : float, optional
69
+ Carrier frequency offset in ppm. Derived from `nucleus` if not given.
70
+ nucleus : int or str, optional
71
+ Nucleus identifier (e.g. `"1H"` or `1`) used to derive `carrier_ppm`
72
+ when it is not provided directly.
73
+ npts : int, optional
74
+ Number of spectral points. Read from `header` if not given.
75
+ header : dict, optional
76
+ Raw header dict to fall back on for the other parameters.
77
+
78
+ Returns
79
+ -------
80
+ npt.NDArray[np.float64]
81
+ Chemical shift axis in ppm.
82
+ """
83
+ hz_axis = calculate_hz_axis(
84
+ spectral_width=spectral_width,
85
+ npts=npts,
86
+ header=header,
87
+ )
88
+
89
+ _freq = None
90
+ _carrier_ppm = None
91
+ _nuc = None
92
+
93
+ if frequency is not None:
94
+ _freq = frequency
95
+
96
+ if header is not None:
97
+ rdb_hdr = header.get("rdb_hdr", {})
98
+ if isinstance(rdb_hdr, dict):
99
+ if _freq is None:
100
+ _freq = rdb_hdr.get("ps_mps_freq", None)
101
+ if _freq is not None:
102
+ _freq = np.double(_freq / 1e7) # Convert to MHz
103
+ image_hdr = header.get("image", {})
104
+ if isinstance(image_hdr, dict):
105
+ _nuc = image_hdr.get("specnuc", None)
106
+
107
+ if _freq is None:
108
+ raise ValueError(
109
+ "Frequency information is required to calculate ppm axis. "
110
+ "Please provide frequency or ensure it is present in the header."
111
+ )
112
+
113
+ if nucleus is not None:
114
+ # Convert nucleus to integer if provided as string (e.g., '1H' -> 1) by
115
+ # extracting 1 to 3 digits from the string
116
+ if isinstance(nucleus, str):
117
+ import re
118
+
119
+ match = re.search(r"\d{1,3}", nucleus)
120
+ if match:
121
+ _nuc = int(match.group(0))
122
+ else:
123
+ logger.warning(
124
+ f"Could not parse nucleus from string '{nucleus}'. Defaulting to 1H. "
125
+ "This will offset ppm by 4.68!"
126
+ )
127
+ _nuc = 1
128
+ else:
129
+ _nuc = nucleus
130
+
131
+ if _nuc is not None and carrier_ppm is not None:
132
+ logger.warning(
133
+ "Both nucleus and carrier_ppm provided. Carrier ppm will be used for ppm "
134
+ "axis calculation."
135
+ )
136
+ _carrier_ppm = carrier_ppm
137
+ elif carrier_ppm is not None:
138
+ _carrier_ppm = carrier_ppm
139
+ elif _nuc is not None:
140
+ _carrier_ppm = 4.68 if _nuc in [1, 2] else 0.0
141
+ else:
142
+ logger.warning(
143
+ "Neither nucleus nor carrier_ppm provided. Defaulting to 1H with carrier ppm of "
144
+ "4.68. This will offset ppm by 4.68!"
145
+ )
146
+ _carrier_ppm = 4.68
147
+
148
+ ppm = (hz_axis / _freq) + _carrier_ppm
149
+ return ppm.astype(np.float64)
@@ -0,0 +1,161 @@
1
+ Metadata-Version: 2.4
2
+ Name: MNutils
3
+ Version: 1.2.0
4
+ Summary: Utility functions for multinuclear MR data processing
5
+ Keywords: mri,mrs,xarray,spectroscopy,nmr
6
+ Author: Sebastian Bauer
7
+ Author-email: Sebastian Bauer <28663646+bastigw@users.noreply.github.com>
8
+ License-Expression: BSD-3-Clause
9
+ License-File: LICENSE
10
+ Classifier: Programming Language :: Python :: 3.12
11
+ Classifier: Programming Language :: Python :: 3.13
12
+ Classifier: Programming Language :: Python :: 3.14
13
+ Classifier: Intended Audience :: Science/Research
14
+ Classifier: Topic :: Scientific/Engineering :: Medical Science Apps.
15
+ Classifier: License :: OSI Approved :: BSD License
16
+ Classifier: Operating System :: OS Independent
17
+ Classifier: Framework :: Jupyter
18
+ Requires-Dist: dcm2niiw>=0.1.5
19
+ Requires-Dist: ipython>=8.0
20
+ Requires-Dist: loguru>=0.7.3
21
+ Requires-Dist: mat73
22
+ Requires-Dist: matplotlib>=3.10.7
23
+ Requires-Dist: nibabel>=5.3.2
24
+ Requires-Dist: nmrglue>=0.11
25
+ Requires-Dist: pillow>=12.3.0
26
+ Requires-Dist: scipy>=1.15.3
27
+ Requires-Dist: seaborn>=0.13.2
28
+ Requires-Dist: xmris[fitting]
29
+ Requires-Dist: matlabengine==25.1.* ; extra == '2025a'
30
+ Requires-Dist: hd-bet>=2.0.1 ; extra == 'bet'
31
+ Requires-Dist: torch>=2.10.0 ; extra == 'bet'
32
+ Requires-Python: >=3.12
33
+ Project-URL: Homepage, https://github.com/bastigw/mnutils
34
+ Project-URL: Documentation, https://bastigw.github.io/mnutils/
35
+ Project-URL: Repository, https://github.com/bastigw/mnutils
36
+ Project-URL: Issues, https://github.com/bastigw/mnutils/issues
37
+ Provides-Extra: 2025a
38
+ Provides-Extra: bet
39
+ Description-Content-Type: text/markdown
40
+
41
+ <p align="center">
42
+ <picture>
43
+ <source media="(prefers-color-scheme: dark)"
44
+ srcset="docs/assets/logo/mnutils-banner-dark.svg">
45
+ <img src="docs/assets/logo/mnutils-banner-light.svg" alt="MNUtils" width="420">
46
+ </picture>
47
+ </p>
48
+
49
+ [![Tests](https://github.com/bastigw/mnutils/actions/workflows/ci-fast.yml/badge.svg)](https://github.com/bastigw/mnutils/actions/workflows/ci-fast.yml)
50
+ [![Docs](https://github.com/bastigw/mnutils/actions/workflows/deploy.yml/badge.svg)](https://bastigw.github.io/mnutils/)
51
+ [![License: BSD-3-Clause](https://img.shields.io/badge/license-BSD--3--Clause-blue.svg)](LICENSE)
52
+
53
+ MNUtils is a collection of utility functions for working with multi-nuclear MR data from our GE
54
+ scanners.
55
+
56
+ ## Features
57
+
58
+ - **Data loading** — read GE raw/reconstructed data via `GEExam`/`GESeries`.
59
+ - **NIfTI/DICOM conversion** — convert scanner output to NIfTI.
60
+ - **Plotting** — spectral and slice plotting utilities.
61
+ - **Spectral fitting** — AMARES fitting delegated to [`xmris`](https://github.com/andrewendlinger/xmris)
62
+ (`xmris[fitting]`, which wraps pyAMARES).
63
+
64
+ See the [documentation](https://bastigw.github.io/mnutils/) for the full basics, data model,
65
+ plotting, fitting, NIfTI, and MATLAB-bridge guides.
66
+
67
+ ## Installation
68
+
69
+ ```bash
70
+ uv add git+https://github.com/bastigw/mnutils.git
71
+ ```
72
+
73
+ Optional extras:
74
+
75
+ ```bash
76
+ uv add "mnutils[bet] @ git+https://github.com/bastigw/mnutils.git" # brain extraction (hd-bet, torch)
77
+ uv add "mnutils[2025a] @ git+https://github.com/bastigw/mnutils.git" # MATLAB Engine pinned to MATLAB 2025a
78
+ ```
79
+
80
+ `2025a` pins `matlabengine==25.1.*`. If your local MATLAB install is a different version, install
81
+ the matching `matlabengine` release yourself instead (see [MATLAB Engine
82
+ Dependency](#matlab-engine-dependency) below) — there's no way to pin one version project-wide
83
+ across contributors' machines.
84
+
85
+ ## Usage
86
+
87
+ The main entry point is `ExamBase`: point it at a GE exam's data folder and it classifies every
88
+ series for you — no need to know in advance which folder is an anatomical scan and which is a
89
+ spectrum.
90
+
91
+ ```python
92
+ from mnutils.GEExam import ExamBase
93
+
94
+ exam = ExamBase("data/HeVo-11") # prints an overview of every series it found
95
+
96
+ exam.exam_overview.head() # same overview as a DataFrame
97
+ exam.series_dict[6] # the GESeries subclass ExamBase picked for series 6 — nothing loaded yet
98
+ ```
99
+
100
+ Load the series you actually need and analyse it — everything about it (spectrum, header fields,
101
+ scan time) lives on the returned object:
102
+
103
+ ```python
104
+ exam.load_series(6)
105
+ mrs = exam[6]
106
+
107
+ print(mrs.protocol_name, mrs.spec.dims, mrs.spec.shape)
108
+ avg = mrs.avg_spec # average over repetitions
109
+ ```
110
+
111
+ `load_series` also takes a list, and `load_all` loads everything `ExamBase` classified. If you
112
+ already know a series's type, construct it directly (e.g. `MRSSeries(DATA_FOLDER, 6)`) and skip
113
+ the exam-level overview entirely.
114
+
115
+ From there, spectral fitting is handled by [**xmris**](https://github.com/andrewendlinger/xmris)
116
+ — MNUtils deliberately doesn't reimplement fitting, it wraps xmris's AMARES implementation
117
+ (itself built on pyAMARES) so a series's spectrum can go straight into a fit:
118
+
119
+ ```bash
120
+ uv add "xmris[fitting]"
121
+ ```
122
+
123
+ See the [fitting docs](https://bastigw.github.io/mnutils/fitting/) for the full workflow, and the
124
+ [loading data](https://bastigw.github.io/mnutils/basics/loading_data/) and
125
+ [GEExam](https://bastigw.github.io/mnutils/data-model/geexam/) tutorials for more on exams and
126
+ series. Need a raw `.mat` file's path directly instead of a loaded object?
127
+ `mnutils.utils.file_helpers.get_mat_data_from_series(base_folder, series_id)` returns it.
128
+
129
+ ## Requirements
130
+
131
+ - Python 3.12+
132
+
133
+ ## MATLAB Engine Dependency
134
+
135
+ To use MNUtils, you need to install the MATLAB Engine for Python. The version of the MATLAB Engine
136
+ you require depends on your local MATLAB version. Ensure you install the correct version that
137
+ matches your MATLAB installation.
138
+
139
+ You can find the appropriate MATLAB Engine version for your setup here: [MATLAB Engine for
140
+ Python](https://pypi.org/project/matlabengine/#history).
141
+
142
+ To install the MATLAB Engine in your project, use the following command, replacing `<version>`
143
+ with the correct version for your MATLAB:
144
+
145
+ ```bash
146
+ uv add "matlabengine==<version>"
147
+ ```
148
+
149
+ ## Development
150
+
151
+ See [`CONTRIBUTING.md`](CONTRIBUTING.md) and the [contributor docs](docs/contribute/) for setup,
152
+ tests, docs, and lint workflow.
153
+
154
+ ## Contributions and Suggestions
155
+
156
+ I'm very open to suggestions and improvements! If you have ideas or want to share your own
157
+ utilities, feel free to reach out or submit a pull request.
158
+
159
+ ## License
160
+
161
+ BSD-3-Clause — see [`LICENSE`](LICENSE).
@@ -0,0 +1,41 @@
1
+ mnutils/GEExam.py,sha256=-76gvH23SiS_Tlbf8GBqtRCeaYXyvq1MFdUKNim8I8Y,20077
2
+ mnutils/GESeries.py,sha256=EXtTdSQAfK8lvRyLHwH0yFSOmtpAMMLNbAKiwV_6ato,61404
3
+ mnutils/__init__.py,sha256=zw-6KymGUR7pvGeLaFR5idgaGuPgM6B444HvitP1tnw,1302
4
+ mnutils/_scripts.py,sha256=1K1iCkVjuIoEDihLbG3O9NJ9CeYM2Tu_pI2bfvfHcMo,6716
5
+ mnutils/fitting/AMARES.py,sha256=qAPV_hadASrlBVr6Ss9wOfZJGUP0MahrC3WhqZkaPTM,30042
6
+ mnutils/fitting/__init__.py,sha256=o10Q9_phhEpwEtLNv56Muv7YjO2sZSl8vTjiIi2wdY0,114
7
+ mnutils/matlab/__init__.py,sha256=YA4s6D3pdfjgi89qLtB3uQiOCb46iob3hGCRF39K08c,273
8
+ mnutils/matlab/setup_matlab.py,sha256=Pk9UvPfY-QjCPAicKK1glH5qA4oor4QlYOavY_AiWgY,4411
9
+ mnutils/plotting/__init__.py,sha256=SZyoLuhCIWfRotzrNSWdbE5lK6qQ4CnTbZgOWOTJTXU,926
10
+ mnutils/plotting/_widgets/__init__.py,sha256=Dl4holZEoUKVK-8kK2IXZNny_QHCyqD2OzF7Dd0KC5E,217
11
+ mnutils/plotting/_widgets/_html.py,sha256=_xWvz137-Ty4_uhZUJz4H_HlgoU3zOss9NbsIQdHCC8,1522
12
+ mnutils/plotting/_widgets/_shared/__init__.py,sha256=O-_I4G_rJ8B4fF9LPMkheoWmbjiwzWs58iAuptZgf40,1673
13
+ mnutils/plotting/_widgets/_shared/dom.js,sha256=tnjXuLTqj2Susq91XhntWo1SGKQFs2XdS8cw8QCj95k,4259
14
+ mnutils/plotting/_widgets/_shared/theme.css,sha256=e6m2dmMUmvbJ9VQ9K2ZnwJk9Th6daL38O0kuLO4uvYs,3286
15
+ mnutils/plotting/_widgets/image_grid.css,sha256=Qwwrwwjf6k6y97GtEPlUWHEirigtXRBdJZXDo5Pc8Jo,7404
16
+ mnutils/plotting/_widgets/image_grid.js,sha256=aTIwG6isaMO4dGmcWj_mCTK5huDtxVmppgvA3V--yLc,7643
17
+ mnutils/plotting/_widgets/image_grid.py,sha256=3PNy00lreYe4p8iO-yyVBRSNarwKwA2SD77imDDzRqQ,3365
18
+ mnutils/plotting/_widgets/mrsi_inspector.css,sha256=pCxVPeXTRUB-qRLWsrzNLczsuOhWfYw9Fgv5XaB4pU0,12325
19
+ mnutils/plotting/_widgets/mrsi_inspector.js,sha256=phwBT6PtuEXY3qpfQOOrZNRnfwc1CCyzvodMHrs06fA,26595
20
+ mnutils/plotting/_widgets/mrsi_inspector.py,sha256=R0y6ENnicAEXoTwQqIBZwoW6b5aFl4dr9Ck0NvhMU0g,2579
21
+ mnutils/plotting/_widgets/slice_viewer.css,sha256=HwQSjbxVPCfMOtHMdcRRCNIrBJVAG0tUjyfWFPmCbq4,686
22
+ mnutils/plotting/_widgets/slice_viewer.js,sha256=9JAo0NHYFdn8PMIQIshRbbBdHtP_SpkAIsrpIAdMi4I,1072
23
+ mnutils/plotting/_widgets/slice_viewer.py,sha256=L8LB91Fwqlh2zgVJnD2GQCXY2lVFENl2YgarRMjd2ic,1039
24
+ mnutils/plotting/images.py,sha256=6Sxe-Dshk3bBpsgyCDU7UCYlS8YHtriuUPqj7r2wtwY,55585
25
+ mnutils/plotting/saving.py,sha256=wk7yrlCcWcGW4bxwUNyCn7-vEUqlxluHeS6L0yhsUfg,3936
26
+ mnutils/plotting/spectra.py,sha256=WsaQ5zsc5zgBsNucp2dAAT7VVyWTSZZeX3xA4BylAGs,11564
27
+ mnutils/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
28
+ mnutils/testing/__init__.py,sha256=riccXHSgG6yMJV3hIM0QZHCtwXrRilz_b9WHyW_xAyk,70
29
+ mnutils/testing/_spectra.py,sha256=tWn5ixg0y3iq7J8v1pOBAgHNrzC0xpF6mICz9nu_YQs,15460
30
+ mnutils/testing/synthetic.py,sha256=wktyXHzThPfdxdgcer8R7Dk2oROiojElJumTzExNeMQ,12729
31
+ mnutils/utils/__init__.py,sha256=GZMY9BwBLFdGPQyNdY7XViGP3Kr9ioZGoQCt0nEsrWw,589
32
+ mnutils/utils/data_loaders.py,sha256=F4feHhCgthhmvIJ2TQgaYF9MkY6OhBiTb57t7F-3wQY,7785
33
+ mnutils/utils/file_helpers.py,sha256=tBQRoObr2Yfn7BK96Mcp9Z_rE8dTC-NBZJmUPD_BBQY,23649
34
+ mnutils/utils/images.py,sha256=Kq2Y-_oCIGMNh5ev5msR2kGJTxiYN7HndtuL3J4Yaj4,7278
35
+ mnutils/utils/nifti.py,sha256=G3y_PospnZmfqxLjwtH4lWcBIS2Ikhyc4_9rdxwjNx4,14805
36
+ mnutils/utils/spectra.py,sha256=P7fiBwacifh_6xxYa4FTq22z4VeLQoKnGxKJ-c0qGW0,4990
37
+ mnutils-1.2.0.dist-info/licenses/LICENSE,sha256=74rhSzSkSwSeBsl16Rv-JlTy-XQvWxj5K-v80ouR_Fs,1507
38
+ mnutils-1.2.0.dist-info/WHEEL,sha256=4OL6Foqnnp3xRY5wMkjgc25_i5YJC6dKsC6LPcjqEoU,80
39
+ mnutils-1.2.0.dist-info/entry_points.txt,sha256=Mqz92Hi3-LREpPdJVQqyb_YKlt_Zcu-8N8yTIjMtxtc,264
40
+ mnutils-1.2.0.dist-info/METADATA,sha256=X-9Jue90WLtsrs5Tzft_ihz74SN2pj_ty18vW-fo444,6169
41
+ mnutils-1.2.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: uv 0.12.5
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,7 @@
1
+ [console_scripts]
2
+ docs-mnutils = mnutils._scripts:docs_all
3
+ docs-mnutils-api = mnutils._scripts:docs_api
4
+ docs-mnutils-notebooks = mnutils._scripts:docs_notebooks
5
+ test-mnutils = mnutils._scripts:run_tests
6
+ test-mnutils-gen = mnutils._scripts:generate_test_notebooks
7
+
@@ -0,0 +1,28 @@
1
+ BSD 3-Clause License
2
+
3
+ Copyright (c) 2025-2026, Sebastian Bauer
4
+
5
+ Redistribution and use in source and binary forms, with or without
6
+ modification, are permitted provided that the following conditions are met:
7
+
8
+ 1. Redistributions of source code must retain the above copyright notice, this
9
+ list of conditions and the following disclaimer.
10
+
11
+ 2. Redistributions in binary form must reproduce the above copyright notice,
12
+ this list of conditions and the following disclaimer in the documentation
13
+ and/or other materials provided with the distribution.
14
+
15
+ 3. Neither the name of the copyright holder nor the names of its
16
+ contributors may be used to endorse or promote products derived from
17
+ this software without specific prior written permission.
18
+
19
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
23
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
25
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
26
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
27
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.