lcmodel-wrapper 0.2.0__tar.gz → 0.3.0__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.
- lcmodel_wrapper-0.3.0/NOTICE +24 -0
- {lcmodel_wrapper-0.2.0 → lcmodel_wrapper-0.3.0}/PKG-INFO +36 -7
- {lcmodel_wrapper-0.2.0 → lcmodel_wrapper-0.3.0}/README.md +34 -6
- {lcmodel_wrapper-0.2.0 → lcmodel_wrapper-0.3.0}/lcmodel_wrapper/__init__.py +9 -2
- lcmodel_wrapper-0.3.0/lcmodel_wrapper/_version.py +4 -0
- {lcmodel_wrapper-0.2.0 → lcmodel_wrapper-0.3.0}/lcmodel_wrapper/basis.py +2 -2
- lcmodel_wrapper-0.3.0/lcmodel_wrapper/binaries.py +749 -0
- lcmodel_wrapper-0.3.0/lcmodel_wrapper/container.py +163 -0
- {lcmodel_wrapper-0.2.0 → lcmodel_wrapper-0.3.0}/lcmodel_wrapper/control.py +3 -3
- {lcmodel_wrapper-0.2.0 → lcmodel_wrapper-0.3.0}/lcmodel_wrapper/convert.py +11 -11
- {lcmodel_wrapper-0.2.0 → lcmodel_wrapper-0.3.0}/lcmodel_wrapper/coord.py +4 -4
- {lcmodel_wrapper-0.2.0 → lcmodel_wrapper-0.3.0}/lcmodel_wrapper/core.py +189 -36
- {lcmodel_wrapper-0.2.0 → lcmodel_wrapper-0.3.0}/lcmodel_wrapper/io.py +45 -3
- {lcmodel_wrapper-0.2.0 → lcmodel_wrapper-0.3.0}/lcmodel_wrapper.egg-info/PKG-INFO +36 -7
- {lcmodel_wrapper-0.2.0 → lcmodel_wrapper-0.3.0}/lcmodel_wrapper.egg-info/SOURCES.txt +5 -0
- {lcmodel_wrapper-0.2.0 → lcmodel_wrapper-0.3.0}/lcmodel_wrapper.egg-info/requires.txt +1 -0
- {lcmodel_wrapper-0.2.0 → lcmodel_wrapper-0.3.0}/pyproject.toml +8 -4
- lcmodel_wrapper-0.3.0/tests/test_binaries.py +470 -0
- lcmodel_wrapper-0.3.0/tests/test_lcm.py +92 -0
- lcmodel_wrapper-0.2.0/NOTICE +0 -34
- lcmodel_wrapper-0.2.0/lcmodel_wrapper/binaries.py +0 -245
- lcmodel_wrapper-0.2.0/tests/test_lcm.py +0 -98
- {lcmodel_wrapper-0.2.0 → lcmodel_wrapper-0.3.0}/LICENSE +0 -0
- {lcmodel_wrapper-0.2.0 → lcmodel_wrapper-0.3.0}/LICENSE.lcmodel +0 -0
- {lcmodel_wrapper-0.2.0 → lcmodel_wrapper-0.3.0}/MANIFEST.in +0 -0
- {lcmodel_wrapper-0.2.0 → lcmodel_wrapper-0.3.0}/lcmodel_wrapper.egg-info/dependency_links.txt +0 -0
- {lcmodel_wrapper-0.2.0 → lcmodel_wrapper-0.3.0}/lcmodel_wrapper.egg-info/top_level.txt +0 -0
- {lcmodel_wrapper-0.2.0 → lcmodel_wrapper-0.3.0}/setup.cfg +0 -0
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
PyLCModel (lcmodel_wrapper)
|
|
2
|
+
Copyright (c) 2024 Julian P. Merkofer and contributors
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (see LICENSE).
|
|
5
|
+
|
|
6
|
+
------------------------------------------------------------------------------
|
|
7
|
+
LCModel
|
|
8
|
+
------------------------------------------------------------------------------
|
|
9
|
+
|
|
10
|
+
LCModel is NOT part of this project's copyright. It is a separate program by
|
|
11
|
+
Dr. Stephen Provencher, distributed under the BSD 3-Clause License
|
|
12
|
+
(Copyright (c) 1992-2021, Stephen Provencher); see LICENSE.lcmodel for the
|
|
13
|
+
full text. Home page: https://s-provencher.com/lcmodel.shtml
|
|
14
|
+
|
|
15
|
+
This wrapper bundles no LCModel code or binary. At run time it may obtain one
|
|
16
|
+
from these sources, and the BSD 3-Clause license applies to every one of them:
|
|
17
|
+
|
|
18
|
+
- the community binary collection at https://github.com/schorschinho/LCModel
|
|
19
|
+
(maintained by Georg Oeltzschner and contributors),
|
|
20
|
+
- binaries and the container image ghcr.io/julianmer/lcmodel built by this
|
|
21
|
+
repository's CI (.github/workflows/lcmodel-binaries.yml) from the pinned
|
|
22
|
+
upstream source, unmodified, using the compilation flags documented there;
|
|
23
|
+
LICENSE.lcmodel and this NOTICE are published alongside them,
|
|
24
|
+
- a local build from the same source via gfortran.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: lcmodel_wrapper
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: Lightweight Python wrapper for LCModel MRS fitting
|
|
5
5
|
Author-email: Julian Merkofer <j.p.merkofer@tue.nl>
|
|
6
6
|
License: Apache-2.0
|
|
@@ -16,6 +16,7 @@ Requires-Dist: scipy>=1.10
|
|
|
16
16
|
Requires-Dist: nibabel>=4.0
|
|
17
17
|
Requires-Dist: nifti-mrs>=1.0
|
|
18
18
|
Provides-Extra: test
|
|
19
|
+
Requires-Dist: pytest>=7.0; extra == "test"
|
|
19
20
|
Requires-Dist: pandas>=1.4; extra == "test"
|
|
20
21
|
Requires-Dist: openpyxl>=3.0; extra == "test"
|
|
21
22
|
Dynamic: license-file
|
|
@@ -36,7 +37,7 @@ Dynamic: license-file
|
|
|
36
37
|
|
|
37
38
|
## Features
|
|
38
39
|
|
|
39
|
-
- **Zero-setup binaries** — the LCModel executable is resolved automatically (download, build from source, or your own path); nothing is bundled in the wheel.
|
|
40
|
+
- **Zero-setup binaries** — the LCModel executable is resolved automatically (download, container image, build from source, or your own path); nothing is bundled in the wheel.
|
|
40
41
|
- **Flexible input** — NumPy arrays, NIfTI-MRS, jMRUI text, and LCModel `.RAW`, in time or frequency domain.
|
|
41
42
|
- **Automated control files** — generated to match your data, or templated from an existing one.
|
|
42
43
|
- **Basis conversion (experimental)** — jMRUI, FSL-MRS, LCModel `.RAW`, and Osprey/FID-A basis sets to `.basis`.
|
|
@@ -50,7 +51,6 @@ Dynamic: license-file
|
|
|
50
51
|
```bash
|
|
51
52
|
pip install lcmodel-wrapper
|
|
52
53
|
```
|
|
53
|
-
Enter your API token:
|
|
54
54
|
|
|
55
55
|
### From Source
|
|
56
56
|
```bash
|
|
@@ -58,6 +58,9 @@ git clone https://github.com/julianmer/PyLCModel.git
|
|
|
58
58
|
cd PyLCModel
|
|
59
59
|
pip install -e .
|
|
60
60
|
```
|
|
61
|
+
Add `--recursive` to the clone (or run `git submodule update --init`) to also fetch the
|
|
62
|
+
[ISMRM 2016 fitting challenge](https://www.ismrm.org/workshops/Spectroscopy16/mrs_fitting_challenge/)
|
|
63
|
+
example data used by the tests.
|
|
61
64
|
|
|
62
65
|
---
|
|
63
66
|
|
|
@@ -66,11 +69,35 @@ pip install -e .
|
|
|
66
69
|
The LCModel program is **not** part of this package and is **not** shipped in the wheel. On first use, the binary is resolved in this order:
|
|
67
70
|
|
|
68
71
|
1. an explicit `path2exec="/path/to/lcmodel"` you pass to `PyLCModel`,
|
|
69
|
-
2. a previously cached download/build (under `~/.cache/lcmodel_wrapper
|
|
72
|
+
2. a previously cached download/build (under `~/.cache/lcmodel_wrapper/<os>-<arch>/`, or `%LOCALAPPDATA%` on Windows; override the root with `LCMODEL_CACHE_DIR`),
|
|
70
73
|
3. a download of the matching binary for your OS/architecture from [schorschinho/LCModel](https://github.com/schorschinho/LCModel),
|
|
71
|
-
4. a
|
|
74
|
+
4. a download of the binary built by this repository's CI and attached to the [GitHub release](https://github.com/julianmer/PyLCModel/releases) matching the installed package version (Linux x86_64/aarch64 fully static, macOS arm64/x86_64 with libgfortran linked statically; each verified against its published SHA-256),
|
|
75
|
+
5. **a container** — if `docker` (or `podman`) is installed and running, the image `ghcr.io/julianmer/lcmodel` is pulled and a small launcher script is cached that runs LCModel from it (Linux, macOS, and Windows),
|
|
76
|
+
6. a build from the LCModel Fortran source via `gfortran` (source fetched on demand).
|
|
72
77
|
|
|
73
|
-
|
|
78
|
+
Every candidate is **run once before it is accepted** — LCModel is asked to identify itself, and anything that cannot execute or does not answer is moved to `<cache>/quarantine/` so the next source gets a turn. This is what stops a wrong-architecture download from being cached and served forever. Set `LCMODEL_SKIP_VERIFY=1` to bypass the check, or `LCMODEL_VERIFY_TIMEOUT` to change its 60 s bound.
|
|
79
|
+
|
|
80
|
+
The cache is keyed by architecture, so a home directory shared across a mixed-architecture cluster does not have nodes fighting over one file.
|
|
81
|
+
|
|
82
|
+
### Running from a container
|
|
83
|
+
|
|
84
|
+
The container is the one option that behaves identically everywhere: inside it LCModel is always the same statically linked Linux binary, so nothing depends on your macOS version, Homebrew, or which Apple-silicon generation you have (upstream's macOS builds are tied to the machine they were compiled on, which is why an M1 build does not run on an M4). Docker Desktop, OrbStack, Colima, or rootless podman all work.
|
|
85
|
+
|
|
86
|
+
On Linux and macOS the launcher bind-mounts your **working directory** and your **home directory** at the same paths inside the container, so the absolute paths in the control file need no translation. On Windows it mounts the **drives** holding those two at `/host/<LETTER>` and the wrapper rewrites the file paths in the control file to match (`C:\Users\me\x.basis` → `/host/C/Users/me/x.basis`); UNC paths are not supported. The one constraint: the basis set and any absolute `save_path` must live under the working or home directory (on Windows: on one of their drives); `PyLCModel` raises a clear error otherwise.
|
|
87
|
+
|
|
88
|
+
```python
|
|
89
|
+
lcmodel = PyLCModel(path2basis="~/basis/press_3t.basis") # container used automatically if needed
|
|
90
|
+
lcmodel = PyLCModel(path2basis="...", allow_docker=False) # never use a container
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Environment knobs: `LCMODEL_NO_DOCKER=1` disables the rung, `LCMODEL_DOCKER_IMAGE` overrides the image (e.g. a locally built one), `LCMODEL_PULL_TIMEOUT` bounds the pull (default 900 s), and `LCMODEL_RELEASE_TAG` selects which release (and matching image tag) steps 4 and 5 use instead of the default `v<package version>`. Delete `<cache>/lcmodel-container` to make the resolver try the native sources again.
|
|
94
|
+
|
|
95
|
+
You can also use the image directly, without Python:
|
|
96
|
+
```bash
|
|
97
|
+
docker run --rm -i -v "$PWD:$PWD" -w "$PWD" ghcr.io/julianmer/lcmodel:latest < control.file
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
No LCModel code or binary is bundled — keeping both the repository and the PyPI wheel small. (The only git submodule in this repository is the optional example data under `example_data/`.)
|
|
74
101
|
|
|
75
102
|
---
|
|
76
103
|
|
|
@@ -130,9 +157,11 @@ This wrapper (the Python code) is released under the **Apache License 2.0** (see
|
|
|
130
157
|
|
|
131
158
|
## Acknowledgements
|
|
132
159
|
|
|
160
|
+
- LCModel source code: created and made [available](https://s-provencher.com/lcmodel.shtml) free of charge (Stephen Provencher).
|
|
133
161
|
- LCModel binaries: [schorschinho/LCModel](https://github.com/schorschinho/LCModel) (Georg Oeltzschner and contributors)
|
|
134
162
|
- Basis conversion reference: [MRS Basis Set Conversion Toolbox](https://github.com/igweckay/MRS-Basis-Set-Conversion-Toolbox) (Kay Igwe)
|
|
135
|
-
- NIfTI-MRS: [spec2nii](https://github.com/wtclarke/spec2nii), [NIfTI-MRS
|
|
163
|
+
- NIfTI-MRS: [spec2nii](https://github.com/wtclarke/spec2nii), [NIfTI-MRS Python tools](https://github.com/wtclarke/nifti_mrs_tools) (Will Clarke)
|
|
164
|
+
- Example data: [ISMRM 2016 MRS Fitting Challenge](https://www.ismrm.org/workshops/Spectroscopy16/mrs_fitting_challenge/) (Małgorzata Marjańska, Dinesh Deelchand, Roland Kreis), mirrored via [wtclarke/mrs_fitting_challenge](https://github.com/wtclarke/mrs_fitting_challenge)
|
|
136
165
|
|
|
137
166
|
---
|
|
138
167
|
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
|
|
15
15
|
## Features
|
|
16
16
|
|
|
17
|
-
- **Zero-setup binaries** — the LCModel executable is resolved automatically (download, build from source, or your own path); nothing is bundled in the wheel.
|
|
17
|
+
- **Zero-setup binaries** — the LCModel executable is resolved automatically (download, container image, build from source, or your own path); nothing is bundled in the wheel.
|
|
18
18
|
- **Flexible input** — NumPy arrays, NIfTI-MRS, jMRUI text, and LCModel `.RAW`, in time or frequency domain.
|
|
19
19
|
- **Automated control files** — generated to match your data, or templated from an existing one.
|
|
20
20
|
- **Basis conversion (experimental)** — jMRUI, FSL-MRS, LCModel `.RAW`, and Osprey/FID-A basis sets to `.basis`.
|
|
@@ -28,7 +28,6 @@
|
|
|
28
28
|
```bash
|
|
29
29
|
pip install lcmodel-wrapper
|
|
30
30
|
```
|
|
31
|
-
Enter your API token:
|
|
32
31
|
|
|
33
32
|
### From Source
|
|
34
33
|
```bash
|
|
@@ -36,6 +35,9 @@ git clone https://github.com/julianmer/PyLCModel.git
|
|
|
36
35
|
cd PyLCModel
|
|
37
36
|
pip install -e .
|
|
38
37
|
```
|
|
38
|
+
Add `--recursive` to the clone (or run `git submodule update --init`) to also fetch the
|
|
39
|
+
[ISMRM 2016 fitting challenge](https://www.ismrm.org/workshops/Spectroscopy16/mrs_fitting_challenge/)
|
|
40
|
+
example data used by the tests.
|
|
39
41
|
|
|
40
42
|
---
|
|
41
43
|
|
|
@@ -44,11 +46,35 @@ pip install -e .
|
|
|
44
46
|
The LCModel program is **not** part of this package and is **not** shipped in the wheel. On first use, the binary is resolved in this order:
|
|
45
47
|
|
|
46
48
|
1. an explicit `path2exec="/path/to/lcmodel"` you pass to `PyLCModel`,
|
|
47
|
-
2. a previously cached download/build (under `~/.cache/lcmodel_wrapper
|
|
49
|
+
2. a previously cached download/build (under `~/.cache/lcmodel_wrapper/<os>-<arch>/`, or `%LOCALAPPDATA%` on Windows; override the root with `LCMODEL_CACHE_DIR`),
|
|
48
50
|
3. a download of the matching binary for your OS/architecture from [schorschinho/LCModel](https://github.com/schorschinho/LCModel),
|
|
49
|
-
4. a
|
|
51
|
+
4. a download of the binary built by this repository's CI and attached to the [GitHub release](https://github.com/julianmer/PyLCModel/releases) matching the installed package version (Linux x86_64/aarch64 fully static, macOS arm64/x86_64 with libgfortran linked statically; each verified against its published SHA-256),
|
|
52
|
+
5. **a container** — if `docker` (or `podman`) is installed and running, the image `ghcr.io/julianmer/lcmodel` is pulled and a small launcher script is cached that runs LCModel from it (Linux, macOS, and Windows),
|
|
53
|
+
6. a build from the LCModel Fortran source via `gfortran` (source fetched on demand).
|
|
50
54
|
|
|
51
|
-
|
|
55
|
+
Every candidate is **run once before it is accepted** — LCModel is asked to identify itself, and anything that cannot execute or does not answer is moved to `<cache>/quarantine/` so the next source gets a turn. This is what stops a wrong-architecture download from being cached and served forever. Set `LCMODEL_SKIP_VERIFY=1` to bypass the check, or `LCMODEL_VERIFY_TIMEOUT` to change its 60 s bound.
|
|
56
|
+
|
|
57
|
+
The cache is keyed by architecture, so a home directory shared across a mixed-architecture cluster does not have nodes fighting over one file.
|
|
58
|
+
|
|
59
|
+
### Running from a container
|
|
60
|
+
|
|
61
|
+
The container is the one option that behaves identically everywhere: inside it LCModel is always the same statically linked Linux binary, so nothing depends on your macOS version, Homebrew, or which Apple-silicon generation you have (upstream's macOS builds are tied to the machine they were compiled on, which is why an M1 build does not run on an M4). Docker Desktop, OrbStack, Colima, or rootless podman all work.
|
|
62
|
+
|
|
63
|
+
On Linux and macOS the launcher bind-mounts your **working directory** and your **home directory** at the same paths inside the container, so the absolute paths in the control file need no translation. On Windows it mounts the **drives** holding those two at `/host/<LETTER>` and the wrapper rewrites the file paths in the control file to match (`C:\Users\me\x.basis` → `/host/C/Users/me/x.basis`); UNC paths are not supported. The one constraint: the basis set and any absolute `save_path` must live under the working or home directory (on Windows: on one of their drives); `PyLCModel` raises a clear error otherwise.
|
|
64
|
+
|
|
65
|
+
```python
|
|
66
|
+
lcmodel = PyLCModel(path2basis="~/basis/press_3t.basis") # container used automatically if needed
|
|
67
|
+
lcmodel = PyLCModel(path2basis="...", allow_docker=False) # never use a container
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Environment knobs: `LCMODEL_NO_DOCKER=1` disables the rung, `LCMODEL_DOCKER_IMAGE` overrides the image (e.g. a locally built one), `LCMODEL_PULL_TIMEOUT` bounds the pull (default 900 s), and `LCMODEL_RELEASE_TAG` selects which release (and matching image tag) steps 4 and 5 use instead of the default `v<package version>`. Delete `<cache>/lcmodel-container` to make the resolver try the native sources again.
|
|
71
|
+
|
|
72
|
+
You can also use the image directly, without Python:
|
|
73
|
+
```bash
|
|
74
|
+
docker run --rm -i -v "$PWD:$PWD" -w "$PWD" ghcr.io/julianmer/lcmodel:latest < control.file
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
No LCModel code or binary is bundled — keeping both the repository and the PyPI wheel small. (The only git submodule in this repository is the optional example data under `example_data/`.)
|
|
52
78
|
|
|
53
79
|
---
|
|
54
80
|
|
|
@@ -108,9 +134,11 @@ This wrapper (the Python code) is released under the **Apache License 2.0** (see
|
|
|
108
134
|
|
|
109
135
|
## Acknowledgements
|
|
110
136
|
|
|
137
|
+
- LCModel source code: created and made [available](https://s-provencher.com/lcmodel.shtml) free of charge (Stephen Provencher).
|
|
111
138
|
- LCModel binaries: [schorschinho/LCModel](https://github.com/schorschinho/LCModel) (Georg Oeltzschner and contributors)
|
|
112
139
|
- Basis conversion reference: [MRS Basis Set Conversion Toolbox](https://github.com/igweckay/MRS-Basis-Set-Conversion-Toolbox) (Kay Igwe)
|
|
113
|
-
- NIfTI-MRS: [spec2nii](https://github.com/wtclarke/spec2nii), [NIfTI-MRS
|
|
140
|
+
- NIfTI-MRS: [spec2nii](https://github.com/wtclarke/spec2nii), [NIfTI-MRS Python tools](https://github.com/wtclarke/nifti_mrs_tools) (Will Clarke)
|
|
141
|
+
- Example data: [ISMRM 2016 MRS Fitting Challenge](https://www.ismrm.org/workshops/Spectroscopy16/mrs_fitting_challenge/) (Małgorzata Marjańska, Dinesh Deelchand, Roland Kreis), mirrored via [wtclarke/mrs_fitting_challenge](https://github.com/wtclarke/mrs_fitting_challenge)
|
|
114
142
|
|
|
115
143
|
---
|
|
116
144
|
|
|
@@ -4,18 +4,25 @@ LCModel itself is a separate program by Dr. Stephen Provencher, distributed unde
|
|
|
4
4
|
BSD 3-Clause License (see LICENSE.lcmodel). This package only wraps it.
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
|
-
from .
|
|
7
|
+
from ._version import __version__
|
|
8
|
+
from .core import PyLCModel, LCModelError
|
|
8
9
|
from .basis import read_basis, LCModelBasis
|
|
10
|
+
from .binaries import verify_executable
|
|
9
11
|
from .convert import ensure_basis, convert_to_basis, detect_format
|
|
10
|
-
from . import
|
|
12
|
+
from .io import from_nifti_mrs, load_signals
|
|
13
|
+
from . import binaries, container, io, control, coord, convert
|
|
11
14
|
|
|
12
15
|
__all__ = [
|
|
13
16
|
"PyLCModel",
|
|
17
|
+
"LCModelError",
|
|
18
|
+
"verify_executable",
|
|
14
19
|
"read_basis",
|
|
15
20
|
"LCModelBasis",
|
|
16
21
|
"ensure_basis",
|
|
17
22
|
"convert_to_basis",
|
|
18
23
|
"detect_format",
|
|
24
|
+
"from_nifti_mrs",
|
|
25
|
+
"load_signals",
|
|
19
26
|
"binaries",
|
|
20
27
|
"io",
|
|
21
28
|
"control",
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
# Single source of truth for the package version. pyproject.toml reads it at build time,
|
|
2
|
+
# and binaries.py derives the GitHub release / container image tag ("v<version>") from
|
|
3
|
+
# it, so every wheel pairs with the LCModel artifacts built alongside it.
|
|
4
|
+
__version__ = "0.3.0"
|
|
@@ -6,9 +6,9 @@
|
|
|
6
6
|
# #
|
|
7
7
|
# Created: 26/06/26 #
|
|
8
8
|
# #
|
|
9
|
-
# Purpose: Lightweight parser for LCModel ".basis" files. Replaces the previous dependency on
|
|
9
|
+
# Purpose: Lightweight parser for LCModel ".basis" files. Replaces the previous dependency on #
|
|
10
10
|
# fsl_mrs for reading basis metadata. It extracts only what the wrapper needs to build #
|
|
11
|
-
# control files and to align fitted concentrations: the field strength (HZPPPM), dwell
|
|
11
|
+
# control files and to align fitted concentrations: the field strength (HZPPPM), dwell #
|
|
12
12
|
# time (BADELT), number of points (NDATAB) and the ordered metabolite names (METABO). #
|
|
13
13
|
# #
|
|
14
14
|
####################################################################################################
|