lcmodel-wrapper 0.2.0__tar.gz → 0.3.1__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.
Files changed (30) hide show
  1. lcmodel_wrapper-0.3.1/NOTICE +24 -0
  2. {lcmodel_wrapper-0.2.0 → lcmodel_wrapper-0.3.1}/PKG-INFO +21 -10
  3. {lcmodel_wrapper-0.2.0 → lcmodel_wrapper-0.3.1}/README.md +19 -9
  4. {lcmodel_wrapper-0.2.0 → lcmodel_wrapper-0.3.1}/lcmodel_wrapper/__init__.py +10 -2
  5. lcmodel_wrapper-0.3.1/lcmodel_wrapper/_version.py +4 -0
  6. {lcmodel_wrapper-0.2.0 → lcmodel_wrapper-0.3.1}/lcmodel_wrapper/basis.py +5 -5
  7. lcmodel_wrapper-0.3.1/lcmodel_wrapper/binaries.py +736 -0
  8. lcmodel_wrapper-0.3.1/lcmodel_wrapper/container.py +163 -0
  9. {lcmodel_wrapper-0.2.0 → lcmodel_wrapper-0.3.1}/lcmodel_wrapper/control.py +40 -55
  10. {lcmodel_wrapper-0.2.0 → lcmodel_wrapper-0.3.1}/lcmodel_wrapper/convert.py +18 -25
  11. lcmodel_wrapper-0.3.1/lcmodel_wrapper/coord.py +115 -0
  12. lcmodel_wrapper-0.3.1/lcmodel_wrapper/core.py +379 -0
  13. {lcmodel_wrapper-0.2.0 → lcmodel_wrapper-0.3.1}/lcmodel_wrapper/io.py +96 -87
  14. {lcmodel_wrapper-0.2.0 → lcmodel_wrapper-0.3.1}/lcmodel_wrapper.egg-info/PKG-INFO +21 -10
  15. {lcmodel_wrapper-0.2.0 → lcmodel_wrapper-0.3.1}/lcmodel_wrapper.egg-info/SOURCES.txt +5 -0
  16. {lcmodel_wrapper-0.2.0 → lcmodel_wrapper-0.3.1}/lcmodel_wrapper.egg-info/requires.txt +1 -0
  17. {lcmodel_wrapper-0.2.0 → lcmodel_wrapper-0.3.1}/pyproject.toml +8 -4
  18. lcmodel_wrapper-0.3.1/tests/test_binaries.py +470 -0
  19. lcmodel_wrapper-0.3.1/tests/test_lcm.py +92 -0
  20. lcmodel_wrapper-0.2.0/NOTICE +0 -34
  21. lcmodel_wrapper-0.2.0/lcmodel_wrapper/binaries.py +0 -245
  22. lcmodel_wrapper-0.2.0/lcmodel_wrapper/coord.py +0 -136
  23. lcmodel_wrapper-0.2.0/lcmodel_wrapper/core.py +0 -271
  24. lcmodel_wrapper-0.2.0/tests/test_lcm.py +0 -98
  25. {lcmodel_wrapper-0.2.0 → lcmodel_wrapper-0.3.1}/LICENSE +0 -0
  26. {lcmodel_wrapper-0.2.0 → lcmodel_wrapper-0.3.1}/LICENSE.lcmodel +0 -0
  27. {lcmodel_wrapper-0.2.0 → lcmodel_wrapper-0.3.1}/MANIFEST.in +0 -0
  28. {lcmodel_wrapper-0.2.0 → lcmodel_wrapper-0.3.1}/lcmodel_wrapper.egg-info/dependency_links.txt +0 -0
  29. {lcmodel_wrapper-0.2.0 → lcmodel_wrapper-0.3.1}/lcmodel_wrapper.egg-info/top_level.txt +0 -0
  30. {lcmodel_wrapper-0.2.0 → lcmodel_wrapper-0.3.1}/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.2.0
3
+ Version: 0.3.1
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,19 +58,28 @@ 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
 
64
67
  ## How the LCModel binary is handled
65
68
 
66
- 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:
69
+ LCModel is **not** shipped in the wheel. On first use it is found in this order, and the first one that works is cached:
67
70
 
68
- 1. an explicit `path2exec="/path/to/lcmodel"` you pass to `PyLCModel`,
69
- 2. a previously cached download/build (under `~/.cache/lcmodel_wrapper`, or `%LOCALAPPDATA%` on Windows; override with `LCMODEL_CACHE_DIR`),
70
- 3. a download of the matching binary for your OS/architecture from [schorschinho/LCModel](https://github.com/schorschinho/LCModel),
71
- 4. a build from the LCModel Fortran source via `gfortran` (source fetched on demand).
71
+ 1. `path2exec` you pass to `PyLCModel`,
72
+ 2. the community binary for your OS/architecture from [schorschinho/LCModel](https://github.com/schorschinho/LCModel),
73
+ 3. the binary built by this repository's CI for the installed version ([releases](https://github.com/julianmer/PyLCModel/releases); Linux x86_64/aarch64 and macOS arm64/x86_64, all statically linked),
74
+ 4. the container image `ghcr.io/julianmer/lcmodel`, if Docker or podman is running,
75
+ 5. a build from source with `gfortran`.
72
76
 
73
- There is no git submodule and no bundled `lcmodel/` folder — keeping both the repository and the PyPI wheel small.
77
+ Each candidate is run once before it is accepted, so a binary that cannot run on your machine is skipped rather than cached. Useful switches: `allow_download`, `allow_docker`, `allow_build` on `PyLCModel`, and the `LCMODEL_EXEC` / `LCMODEL_CACHE_DIR` environment variables.
78
+
79
+ With the container, LCModel sees your working directory and your home directory; keep the basis set and any `save_path` under one of them. The image also works on its own:
80
+ ```bash
81
+ docker run --rm -i -v "$PWD:$PWD" -w "$PWD" ghcr.io/julianmer/lcmodel < control.file
82
+ ```
74
83
 
75
84
  ---
76
85
 
@@ -130,9 +139,11 @@ This wrapper (the Python code) is released under the **Apache License 2.0** (see
130
139
 
131
140
  ## Acknowledgements
132
141
 
142
+ - LCModel source code: created and made [available](https://s-provencher.com/lcmodel.shtml) free of charge (Stephen Provencher).
133
143
  - LCModel binaries: [schorschinho/LCModel](https://github.com/schorschinho/LCModel) (Georg Oeltzschner and contributors)
134
144
  - 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 Pyhton tools](https://github.com/wtclarke/nifti_mrs_tools) (Will Clarke)
145
+ - NIfTI-MRS: [spec2nii](https://github.com/wtclarke/spec2nii), [NIfTI-MRS Python tools](https://github.com/wtclarke/nifti_mrs_tools) (Will Clarke)
146
+ - 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
147
 
137
148
  ---
138
149
 
@@ -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,19 +35,28 @@ 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
 
42
44
  ## How the LCModel binary is handled
43
45
 
44
- 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:
46
+ LCModel is **not** shipped in the wheel. On first use it is found in this order, and the first one that works is cached:
45
47
 
46
- 1. an explicit `path2exec="/path/to/lcmodel"` you pass to `PyLCModel`,
47
- 2. a previously cached download/build (under `~/.cache/lcmodel_wrapper`, or `%LOCALAPPDATA%` on Windows; override with `LCMODEL_CACHE_DIR`),
48
- 3. a download of the matching binary for your OS/architecture from [schorschinho/LCModel](https://github.com/schorschinho/LCModel),
49
- 4. a build from the LCModel Fortran source via `gfortran` (source fetched on demand).
48
+ 1. `path2exec` you pass to `PyLCModel`,
49
+ 2. the community binary for your OS/architecture from [schorschinho/LCModel](https://github.com/schorschinho/LCModel),
50
+ 3. the binary built by this repository's CI for the installed version ([releases](https://github.com/julianmer/PyLCModel/releases); Linux x86_64/aarch64 and macOS arm64/x86_64, all statically linked),
51
+ 4. the container image `ghcr.io/julianmer/lcmodel`, if Docker or podman is running,
52
+ 5. a build from source with `gfortran`.
50
53
 
51
- There is no git submodule and no bundled `lcmodel/` folder — keeping both the repository and the PyPI wheel small.
54
+ Each candidate is run once before it is accepted, so a binary that cannot run on your machine is skipped rather than cached. Useful switches: `allow_download`, `allow_docker`, `allow_build` on `PyLCModel`, and the `LCMODEL_EXEC` / `LCMODEL_CACHE_DIR` environment variables.
55
+
56
+ With the container, LCModel sees your working directory and your home directory; keep the basis set and any `save_path` under one of them. The image also works on its own:
57
+ ```bash
58
+ docker run --rm -i -v "$PWD:$PWD" -w "$PWD" ghcr.io/julianmer/lcmodel < control.file
59
+ ```
52
60
 
53
61
  ---
54
62
 
@@ -108,9 +116,11 @@ This wrapper (the Python code) is released under the **Apache License 2.0** (see
108
116
 
109
117
  ## Acknowledgements
110
118
 
119
+ - LCModel source code: created and made [available](https://s-provencher.com/lcmodel.shtml) free of charge (Stephen Provencher).
111
120
  - LCModel binaries: [schorschinho/LCModel](https://github.com/schorschinho/LCModel) (Georg Oeltzschner and contributors)
112
121
  - 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 Pyhton tools](https://github.com/wtclarke/nifti_mrs_tools) (Will Clarke)
122
+ - NIfTI-MRS: [spec2nii](https://github.com/wtclarke/spec2nii), [NIfTI-MRS Python tools](https://github.com/wtclarke/nifti_mrs_tools) (Will Clarke)
123
+ - 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
124
 
115
125
  ---
116
126
 
@@ -4,19 +4,27 @@ 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 .core import PyLCModel
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 binaries, io, control, coord, convert
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",
27
+ "container",
20
28
  "io",
21
29
  "control",
22
30
  "coord",
@@ -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.1"
@@ -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
  ####################################################################################################
@@ -77,14 +77,14 @@ def read_basis(path: str) -> LCModelBasis:
77
77
  with open(path, "r", errors="ignore") as fh:
78
78
  header_lines = []
79
79
  for line in fh:
80
- header_lines.append(line)
80
+ if len(header_lines) < 200: # scalars live near the top
81
+ header_lines.append(line)
81
82
  # "METABO" lines are sparse; keep scanning the whole file for them.
82
83
  # Match exactly "METABO =" (not "METABO_CONTAM" / "METABO_SINGLET").
83
84
  m = re.match(r"\s*METABO\s*=\s*'(.*?)'", line)
84
85
  if m:
85
86
  basis.names.append(m.group(1).strip())
86
-
87
- header = "".join(header_lines[:200]) # scalars live near the top
87
+ header = "".join(header_lines)
88
88
 
89
89
  basis.central_freq = _find_scalar(header, "HZPPPM")
90
90
  basis.dwell = _find_scalar(header, "BADELT")