musclemap-mlx 0.1.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.
@@ -0,0 +1,38 @@
1
+ # MedMLX standard
2
+ __pycache__/
3
+ *.py[cod]
4
+ *.egg-info/
5
+ .venv
6
+ build/
7
+ dist/
8
+ .pytest_cache/
9
+ .ruff_cache/
10
+ .mypy_cache/
11
+ .hypothesis/
12
+ .coverage
13
+ htmlcov/
14
+ .DS_Store
15
+ # Never commit secrets (Hugging Face tokens)
16
+ .env
17
+ # Never commit data or weights
18
+ /data/
19
+ /work/
20
+ /weights/
21
+ /models/
22
+ /checkpoints/
23
+ /outputs/
24
+ *.nii
25
+ *.nii.gz
26
+ *.dcm
27
+ *.nrrd
28
+ *.mha
29
+ *.mhd
30
+ *.safetensors
31
+ *.pt
32
+ *.pth
33
+ *.ckpt
34
+ *.bin
35
+ *.h5
36
+ *.npy
37
+ *.npz
38
+ !tests/fixtures/**/*.npz
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2023 MuscleMap
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,155 @@
1
+ Metadata-Version: 2.5
2
+ Name: musclemap-mlx
3
+ Version: 0.1.0
4
+ Summary: Segment 113 whole-body muscle and bone structures with MuscleMap v1.4 and native MLX
5
+ Project-URL: Homepage, https://medmlx.org
6
+ Project-URL: Repository, https://github.com/MedMLX/musclemap-mlx
7
+ Project-URL: Issues, https://github.com/MedMLX/musclemap-mlx/issues
8
+ Project-URL: Weights, https://huggingface.co/MedMLX/musclemap-mlx
9
+ Project-URL: Upstream, https://github.com/MuscleMap/MuscleMap
10
+ Project-URL: MedMLX, https://github.com/MedMLX/medmlx
11
+ Author: Joseph Sandoval, Alfredo Sandoval Jr.
12
+ License-Expression: MIT
13
+ License-File: LICENSE
14
+ License-File: THIRD_PARTY_NOTICES.md
15
+ License-File: UPSTREAM_LICENSE
16
+ Keywords: apple-silicon,ct,medical-imaging,metal,mlx,mri,musclemap,segmentation,whole-body
17
+ Classifier: Development Status :: 3 - Alpha
18
+ Classifier: Intended Audience :: Developers
19
+ Classifier: Intended Audience :: Science/Research
20
+ Classifier: Operating System :: MacOS :: MacOS X
21
+ Classifier: Programming Language :: Python :: 3
22
+ Classifier: Programming Language :: Python :: 3 :: Only
23
+ Classifier: Programming Language :: Python :: 3.13
24
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
25
+ Classifier: Topic :: Scientific/Engineering :: Image Processing
26
+ Classifier: Topic :: Scientific/Engineering :: Medical Science Apps.
27
+ Classifier: Typing :: Typed
28
+ Requires-Python: <3.14,>=3.13
29
+ Requires-Dist: medmlx-core<0.3,>=0.2.7
30
+ Requires-Dist: mlx==0.32.4; platform_system == 'Darwin' and platform_machine == 'arm64'
31
+ Requires-Dist: nibabel<6,>=5.3
32
+ Requires-Dist: numpy<3,>=2.0
33
+ Requires-Dist: scipy<2,>=1.14
34
+ Description-Content-Type: text/markdown
35
+
36
+ # musclemap-mlx
37
+
38
+ Segment 113 muscle and bone structures in MRI or CT NIfTI images with
39
+ MuscleMap v1.4, running natively through MLX and Metal on Apple Silicon.
40
+ Weights download automatically on first use.
41
+
42
+ ## Install and segment
43
+
44
+ Requires an Apple Silicon Mac, Metal and Python 3.13. With
45
+ [uv](https://docs.astral.sh/uv/):
46
+
47
+ ```sh
48
+ uv venv --python 3.13
49
+ source .venv/bin/activate
50
+ uv pip install musclemap-mlx
51
+
52
+ # Real MRI example from TotalSegmentator's official test data.
53
+ curl -L https://raw.githubusercontent.com/wasserth/TotalSegmentator/d89e47f73d24f5fba732a715aeb6fdc0ae34526b/tests/reference_files/example_mr_sm.nii.gz -o mri.nii.gz
54
+ musclemap-mlx segment --input mri.nii.gz --output-dir out
55
+ ```
56
+
57
+ The command writes `out/mask.nii.gz` and prints JSON containing the output path,
58
+ label table, device, memory use and chunking settings. To use the corresponding
59
+ CT example, replace `example_mr_sm.nii.gz` with `example_ct_sm.nii.gz`.
60
+
61
+ ### Python
62
+
63
+ ```python
64
+ from musclemap_mlx.medmlx import run
65
+
66
+ result = run(image_path="mri.nii.gz", output_dir="out-python")
67
+ print(result["outputs"]["mask"])
68
+ ```
69
+
70
+ ### MedMLX
71
+
72
+ ```sh
73
+ uv pip install 'medmlx[segment]>=0.9.1,<0.10'
74
+ medmlx describe musclemap
75
+ medmlx setup musclemap
76
+ medmlx run musclemap --arg image_path=mri.nii.gz output_dir=out-medmlx
77
+ ```
78
+
79
+ Or use its Python interface:
80
+
81
+ ```python
82
+ import medmlx
83
+
84
+ medmlx.setup_model("musclemap")
85
+ job = medmlx.run("musclemap", arguments={
86
+ "image_path": "mri.nii.gz",
87
+ "output_dir": "out-medmlx-python",
88
+ })
89
+ print(job["result"])
90
+ ```
91
+
92
+ The MCP server exposes the same model through `list_models`, `describe_model`,
93
+ `model_readiness`, `prepare_model` and `run_model`; poll `get_job` for setup and
94
+ segmentation results. Start it with `medmlx serve --root .`.
95
+
96
+ ## Inputs and outputs
97
+
98
+ Input is one finite 3D `.nii` or `.nii.gz` MRI or CT volume. Millimetre spatial
99
+ units are supported; an unspecified unit is interpreted as millimetres, matching
100
+ upstream. Other declared spatial units and 4D images are rejected.
101
+
102
+ The output is an `int16` mask with background 0 and the original 113 anatomical
103
+ IDs, including 1101 for left levator scapulae and 8222 for right plantaris. Its
104
+ shape, affine, qform, sform and spatial-unit metadata follow the input. The
105
+ largest 6-connected component of each label is retained after chunk assembly.
106
+ The JSON result lists every anatomical ID and name.
107
+
108
+ | Option | Meaning | Default |
109
+ | --- | --- | --- |
110
+ | `--input` | 3D NIfTI image | Required |
111
+ | `--output-dir` | Destination for `mask.nii.gz` | Required |
112
+ | `--model-dir` | Local folder containing `model.safetensors` | Automatic download/cache |
113
+ | `--chunk-size` | Source storage-axis slices per independent pass | Eight |
114
+ | `--overlap` | Gaussian window overlap, integer 0–99 percent | 90 |
115
+ | `--overwrite` | Replace an existing output | Off |
116
+
117
+ Each chunk is independently normalized. For integer source images, chunking
118
+ also reproduces upstream's source-header NIfTI round trip before normalization.
119
+ Changing chunk size can change the mask. A chunk size at least the source depth
120
+ uses one whole-volume pass. Inference uses MLX on Metal; there is no CPU or
121
+ PyTorch network fallback.
122
+
123
+ ## Weights and offline use
124
+
125
+ The approximately 105 MB float32 bundle is hosted at
126
+ [MedMLX/musclemap-mlx](https://huggingface.co/MedMLX/musclemap-mlx/tree/a798b3073e3b2c449987c13aa193626aaacc3092),
127
+ pinned to revision `a798b3073e3b2c449987c13aa193626aaacc3092`. It includes the
128
+ publisher configuration, original label metadata and MIT notices.
129
+
130
+ ```sh
131
+ # Pre-download to the shared cache; subsequent runs can work offline.
132
+ musclemap-mlx download
133
+
134
+ # Export a local bundle, then supply it explicitly.
135
+ musclemap-mlx download --model-dir musclemap-weights
136
+ musclemap-mlx segment --input mri.nii.gz --model-dir musclemap-weights --output-dir out-local
137
+ ```
138
+
139
+ MedMLX and this package share the `MEDMLX_ASSET_CACHE` cache setting. Python
140
+ callers supply a local bundle with `weights_path="musclemap-weights"`.
141
+
142
+ ## Qualification and license
143
+
144
+ The native implementation was compared with the pinned MuscleMap v1.4 reference
145
+ on real CT and MRI examples. Reproduction commands, acceptance criteria and
146
+ measurements are in
147
+ [docs/technical.md](https://github.com/MedMLX/musclemap-mlx/blob/dev/docs/technical.md).
148
+ These two compact examples establish implementation parity; anatomical accuracy
149
+ against manual segmentations and coverage of all 113 structures were not measured.
150
+
151
+ Code and publisher weights are MIT licensed. See
152
+ [UPSTREAM_LICENSE](https://github.com/MedMLX/musclemap-mlx/blob/dev/UPSTREAM_LICENSE)
153
+ and [THIRD_PARTY_NOTICES.md](https://github.com/MedMLX/musclemap-mlx/blob/dev/THIRD_PARTY_NOTICES.md).
154
+ The original checkpoint and configuration are published in
155
+ [Zenodo record 21929873](https://zenodo.org/records/21929873).
@@ -0,0 +1,120 @@
1
+ # musclemap-mlx
2
+
3
+ Segment 113 muscle and bone structures in MRI or CT NIfTI images with
4
+ MuscleMap v1.4, running natively through MLX and Metal on Apple Silicon.
5
+ Weights download automatically on first use.
6
+
7
+ ## Install and segment
8
+
9
+ Requires an Apple Silicon Mac, Metal and Python 3.13. With
10
+ [uv](https://docs.astral.sh/uv/):
11
+
12
+ ```sh
13
+ uv venv --python 3.13
14
+ source .venv/bin/activate
15
+ uv pip install musclemap-mlx
16
+
17
+ # Real MRI example from TotalSegmentator's official test data.
18
+ curl -L https://raw.githubusercontent.com/wasserth/TotalSegmentator/d89e47f73d24f5fba732a715aeb6fdc0ae34526b/tests/reference_files/example_mr_sm.nii.gz -o mri.nii.gz
19
+ musclemap-mlx segment --input mri.nii.gz --output-dir out
20
+ ```
21
+
22
+ The command writes `out/mask.nii.gz` and prints JSON containing the output path,
23
+ label table, device, memory use and chunking settings. To use the corresponding
24
+ CT example, replace `example_mr_sm.nii.gz` with `example_ct_sm.nii.gz`.
25
+
26
+ ### Python
27
+
28
+ ```python
29
+ from musclemap_mlx.medmlx import run
30
+
31
+ result = run(image_path="mri.nii.gz", output_dir="out-python")
32
+ print(result["outputs"]["mask"])
33
+ ```
34
+
35
+ ### MedMLX
36
+
37
+ ```sh
38
+ uv pip install 'medmlx[segment]>=0.9.1,<0.10'
39
+ medmlx describe musclemap
40
+ medmlx setup musclemap
41
+ medmlx run musclemap --arg image_path=mri.nii.gz output_dir=out-medmlx
42
+ ```
43
+
44
+ Or use its Python interface:
45
+
46
+ ```python
47
+ import medmlx
48
+
49
+ medmlx.setup_model("musclemap")
50
+ job = medmlx.run("musclemap", arguments={
51
+ "image_path": "mri.nii.gz",
52
+ "output_dir": "out-medmlx-python",
53
+ })
54
+ print(job["result"])
55
+ ```
56
+
57
+ The MCP server exposes the same model through `list_models`, `describe_model`,
58
+ `model_readiness`, `prepare_model` and `run_model`; poll `get_job` for setup and
59
+ segmentation results. Start it with `medmlx serve --root .`.
60
+
61
+ ## Inputs and outputs
62
+
63
+ Input is one finite 3D `.nii` or `.nii.gz` MRI or CT volume. Millimetre spatial
64
+ units are supported; an unspecified unit is interpreted as millimetres, matching
65
+ upstream. Other declared spatial units and 4D images are rejected.
66
+
67
+ The output is an `int16` mask with background 0 and the original 113 anatomical
68
+ IDs, including 1101 for left levator scapulae and 8222 for right plantaris. Its
69
+ shape, affine, qform, sform and spatial-unit metadata follow the input. The
70
+ largest 6-connected component of each label is retained after chunk assembly.
71
+ The JSON result lists every anatomical ID and name.
72
+
73
+ | Option | Meaning | Default |
74
+ | --- | --- | --- |
75
+ | `--input` | 3D NIfTI image | Required |
76
+ | `--output-dir` | Destination for `mask.nii.gz` | Required |
77
+ | `--model-dir` | Local folder containing `model.safetensors` | Automatic download/cache |
78
+ | `--chunk-size` | Source storage-axis slices per independent pass | Eight |
79
+ | `--overlap` | Gaussian window overlap, integer 0–99 percent | 90 |
80
+ | `--overwrite` | Replace an existing output | Off |
81
+
82
+ Each chunk is independently normalized. For integer source images, chunking
83
+ also reproduces upstream's source-header NIfTI round trip before normalization.
84
+ Changing chunk size can change the mask. A chunk size at least the source depth
85
+ uses one whole-volume pass. Inference uses MLX on Metal; there is no CPU or
86
+ PyTorch network fallback.
87
+
88
+ ## Weights and offline use
89
+
90
+ The approximately 105 MB float32 bundle is hosted at
91
+ [MedMLX/musclemap-mlx](https://huggingface.co/MedMLX/musclemap-mlx/tree/a798b3073e3b2c449987c13aa193626aaacc3092),
92
+ pinned to revision `a798b3073e3b2c449987c13aa193626aaacc3092`. It includes the
93
+ publisher configuration, original label metadata and MIT notices.
94
+
95
+ ```sh
96
+ # Pre-download to the shared cache; subsequent runs can work offline.
97
+ musclemap-mlx download
98
+
99
+ # Export a local bundle, then supply it explicitly.
100
+ musclemap-mlx download --model-dir musclemap-weights
101
+ musclemap-mlx segment --input mri.nii.gz --model-dir musclemap-weights --output-dir out-local
102
+ ```
103
+
104
+ MedMLX and this package share the `MEDMLX_ASSET_CACHE` cache setting. Python
105
+ callers supply a local bundle with `weights_path="musclemap-weights"`.
106
+
107
+ ## Qualification and license
108
+
109
+ The native implementation was compared with the pinned MuscleMap v1.4 reference
110
+ on real CT and MRI examples. Reproduction commands, acceptance criteria and
111
+ measurements are in
112
+ [docs/technical.md](https://github.com/MedMLX/musclemap-mlx/blob/dev/docs/technical.md).
113
+ These two compact examples establish implementation parity; anatomical accuracy
114
+ against manual segmentations and coverage of all 113 structures were not measured.
115
+
116
+ Code and publisher weights are MIT licensed. See
117
+ [UPSTREAM_LICENSE](https://github.com/MedMLX/musclemap-mlx/blob/dev/UPSTREAM_LICENSE)
118
+ and [THIRD_PARTY_NOTICES.md](https://github.com/MedMLX/musclemap-mlx/blob/dev/THIRD_PARTY_NOTICES.md).
119
+ The original checkpoint and configuration are published in
120
+ [Zenodo record 21929873](https://zenodo.org/records/21929873).
@@ -0,0 +1,35 @@
1
+ # Third-party notices
2
+
3
+ ## MuscleMap
4
+
5
+ - Source: https://github.com/MuscleMap/MuscleMap
6
+ - Revision: `35a2138a4a3a63be713bae5584632ffc6433b960`
7
+ - Copyright: Copyright (c) 2023 MuscleMap
8
+ - License: MIT; original text retained in [UPSTREAM_LICENSE](UPSTREAM_LICENSE).
9
+ The package [LICENSE](LICENSE) retains the same MIT terms.
10
+ - Adapted: native MLX implementation of the published 2D MONAI UNet and its
11
+ inference preprocessing/postprocessing. The original anatomical label IDs,
12
+ configuration semantics and largest-connected-component helper are preserved.
13
+ Upstream source remains unchanged and read-only in the development-only
14
+ `reference/` submodule.
15
+
16
+ ## Dependencies, weights and data
17
+
18
+ Dependencies declared in [pyproject.toml](pyproject.toml) are installed
19
+ separately and keep their own licenses. MLX is the runtime backend. MONAI 1.3.2
20
+ and PyTorch 2.4.1 are used only in the development reference environment; the
21
+ converter uses PyTorch's restricted `weights_only=True` deserializer and the
22
+ runtime never imports Torch.
23
+
24
+ Zenodo record [21929873](https://zenodo.org/records/21929873) supplies the
25
+ contrast-agnostic wholebody v1.4 checkpoint, original JSON configuration and
26
+ publisher `LICENSE` under MIT. The license grant matches
27
+ [UPSTREAM_LICENSE](UPSTREAM_LICENSE). Converted weights retain all original
28
+ float32 parameters and are hosted at
29
+ [MedMLX/musclemap-mlx](https://huggingface.co/MedMLX/musclemap-mlx/tree/a798b3073e3b2c449987c13aa193626aaacc3092),
30
+ with configuration, labels, license and attribution. No weights or medical images
31
+ are included in package distributions.
32
+
33
+ ## Showcase image
34
+
35
+ No showcase image or patient data is included.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2023 MuscleMap
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,116 @@
1
+ [build-system]
2
+ requires = ["hatchling>=1.27"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "musclemap-mlx"
7
+ version = "0.1.0"
8
+ description = "Segment 113 whole-body muscle and bone structures with MuscleMap v1.4 and native MLX"
9
+ readme = "README.md"
10
+ requires-python = ">=3.13,<3.14"
11
+ license = "MIT"
12
+ license-files = ["LICENSE", "UPSTREAM_LICENSE", "THIRD_PARTY_NOTICES.md"]
13
+ authors = [
14
+ { name = "Joseph Sandoval" },
15
+ { name = "Alfredo Sandoval Jr." },
16
+ ]
17
+ keywords = [
18
+ "mlx",
19
+ "apple-silicon",
20
+ "metal",
21
+ "medical-imaging",
22
+ "musclemap",
23
+ "whole-body",
24
+ "segmentation",
25
+ "mri",
26
+ "ct",
27
+ ]
28
+ classifiers = [
29
+ "Development Status :: 3 - Alpha",
30
+ "Intended Audience :: Developers",
31
+ "Intended Audience :: Science/Research",
32
+ "Operating System :: MacOS :: MacOS X",
33
+ "Programming Language :: Python :: 3",
34
+ "Programming Language :: Python :: 3 :: Only",
35
+ "Programming Language :: Python :: 3.13",
36
+ "Topic :: Scientific/Engineering :: Artificial Intelligence",
37
+ "Topic :: Scientific/Engineering :: Image Processing",
38
+ "Topic :: Scientific/Engineering :: Medical Science Apps.",
39
+ "Typing :: Typed",
40
+ ]
41
+ dependencies = [
42
+ "medmlx-core>=0.2.7,<0.3",
43
+ "mlx==0.32.4; platform_system == 'Darwin' and platform_machine == 'arm64'",
44
+ "nibabel>=5.3,<6",
45
+ "numpy>=2.0,<3",
46
+ "scipy>=1.14,<2",
47
+ ]
48
+
49
+ [project.urls]
50
+ Homepage = "https://medmlx.org"
51
+ Repository = "https://github.com/MedMLX/musclemap-mlx"
52
+ Issues = "https://github.com/MedMLX/musclemap-mlx/issues"
53
+ Weights = "https://huggingface.co/MedMLX/musclemap-mlx"
54
+ Upstream = "https://github.com/MuscleMap/MuscleMap"
55
+ MedMLX = "https://github.com/MedMLX/medmlx"
56
+ [project.scripts]
57
+ "musclemap-mlx" = "musclemap_mlx.cli:main"
58
+
59
+ [project.entry-points."medmlx.models"]
60
+ musclemap = "musclemap_mlx.medmlx:MODEL"
61
+ [dependency-groups]
62
+ dev = [
63
+ "monai==1.6.1",
64
+ "pyright==1.1.414",
65
+ "pytest>=8.4,<10",
66
+ "ruff>=0.13,<1",
67
+ "safetensors>=0.4,<1",
68
+ "scipy-stubs>=1.18.1,<1.19",
69
+ "torch==2.14.1",
70
+ ]
71
+
72
+ [tool.uv]
73
+ environments = ["sys_platform == 'darwin' and platform_machine == 'arm64'"]
74
+ required-environments = ["sys_platform == 'darwin' and platform_machine == 'arm64'"]
75
+ constraint-dependencies = ["setuptools>=84.1.0", "filelock>=4.2.1"]
76
+ [tool.hatch.build.targets.wheel]
77
+ packages = ["src/musclemap_mlx"]
78
+ [tool.hatch.build.targets.sdist]
79
+ only-include = [
80
+ "src/musclemap_mlx",
81
+ "pyproject.toml",
82
+ "README.md",
83
+ "LICENSE",
84
+ "THIRD_PARTY_NOTICES.md",
85
+ "UPSTREAM_LICENSE",
86
+ ]
87
+
88
+ [tool.ruff]
89
+ line-length = 100
90
+ target-version = "py313"
91
+ extend-exclude = ["reference"]
92
+
93
+ [tool.ruff.lint]
94
+ select = ["E", "F", "I", "UP", "B", "SIM", "RUF", "TID251", "TID252", "ANN401"]
95
+
96
+ [tool.ruff.lint.flake8-tidy-imports]
97
+ ban-relative-imports = "all"
98
+
99
+ [tool.ruff.lint.flake8-tidy-imports.banned-api]
100
+ "typing.Any" = { msg = "Use a precise type and parse untyped values at the boundary." }
101
+ "typing_extensions.Any" = { msg = "Use a precise type and parse untyped values at the boundary." }
102
+ [tool.pyright]
103
+ include = ["src", "tests"]
104
+ extraPaths = ["src"]
105
+ pythonVersion = "3.13"
106
+ typeCheckingMode = "strict"
107
+ enableTypeIgnoreComments = false
108
+ reportUnnecessaryTypeIgnoreComment = "error"
109
+
110
+ [tool.pytest.ini_options]
111
+ testpaths = ["tests"]
112
+ addopts = "--strict-config --strict-markers"
113
+ markers = [
114
+ "metal: needs the MLX Metal GPU and the shared Perfkit GPU lock",
115
+ "oracle: compares against a recorded MONAI 1.3.2 reference; opt-in",
116
+ ]
@@ -0,0 +1,11 @@
1
+ """MuscleMap v1.4 whole-body segmentation with native MLX on Apple Silicon."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from importlib.metadata import version
6
+
7
+
8
+ def __getattr__(name: str) -> str:
9
+ if name == "__version__":
10
+ return version("musclemap-mlx")
11
+ raise AttributeError(name)
@@ -0,0 +1,45 @@
1
+ """Check publisher asset completeness without deserializing checkpoint data."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ from pathlib import Path
7
+ from typing import cast
8
+
9
+ from musclemap_mlx.config import CONFIG_NAME, MODEL_ID
10
+
11
+ ASSETS = {
12
+ "contrast_agnostic_wholebody_model.pth": 104884148,
13
+ "contrast_agnostic_wholebody_model.json": 10555,
14
+ "LICENSE": 1087,
15
+ }
16
+
17
+
18
+ def verify_assets(folder: Path) -> dict[str, str]:
19
+ """Check publisher file sizes and return each asset's absolute path.
20
+
21
+ Raises:
22
+ ValueError: An asset is missing or incomplete for Zenodo record 21929873.
23
+ """
24
+ paths: dict[str, str] = {}
25
+ for name, size in ASSETS.items():
26
+ path = folder / name
27
+ if not path.is_file():
28
+ raise ValueError(f"Missing asset: {path}; obtain it from Zenodo record 21929873")
29
+ if path.stat().st_size != size:
30
+ raise ValueError(f"Unexpected size for {name}; expected {size} bytes")
31
+ paths[name] = path.absolute().as_posix()
32
+ return paths
33
+
34
+
35
+ def inspect_assets(folder: Path) -> dict[str, object]:
36
+ """Check asset completeness and return the original configuration as JSON data."""
37
+ verify_assets(folder)
38
+ config = cast(object, json.loads((folder / CONFIG_NAME).read_text(encoding="utf-8")))
39
+ return {
40
+ "status": "assets-complete",
41
+ "model_id": MODEL_ID,
42
+ "record": "https://zenodo.org/records/21929873",
43
+ "configuration": config,
44
+ "checkpoint_deserialized": False,
45
+ }