josand commited on
Commit
643bf83
·
verified ·
1 Parent(s): 4cde096

Update v0.2.1 inference API and bundle metadata

Browse files

Retain the exact converted tensors and tokenizer, preserve private archive and upstream component terms, and remove the superseded loader patch.

Files changed (3) hide show
  1. README.md +39 -101
  2. conversion.json +0 -12
  3. safetensors-support.patch +0 -130
README.md CHANGED
@@ -17,134 +17,72 @@ pipeline_tag: image-to-image
17
 
18
  # LesionDiffusion for MLX
19
 
20
- Native MLX LesionDiffusion synthesis on Apple Silicon using LMNet, LINet, and VQ components. The recorded paper configuration inserts a liver tumor into a prepared CT crop; current conditioned inference is documented separately below.
21
 
22
- **Repository contents:** converted FP32 safetensors weights, runtime JSON metadata where required, and retained publisher notices. This repository is private; original checkpoint and component terms remain applicable.
23
 
24
- Requires an Apple Silicon Mac with Metal and Python 3.12/3.13. Model inference uses MLX; PyTorch is used for one-time checkpoint conversion and optional upstream comparisons. Intended for research and education; clinical diagnostic performance has not been established.
25
 
26
- Native MLX package: [lesiondiffusion-mlx](https://github.com/MedMLX/lesiondiffusion-mlx).
27
- Evaluated revision: `9b7a15eef808a38ab56e82325c22f209f69b9b78`.
28
-
29
- This card describes the one configuration evaluated in the MedMLX paper. It is research software; the single-case comparison does not estimate clinical accuracy.
30
-
31
- ## Source and checkpoint
32
-
33
- Original publisher: HengruiTianSJTU.
34
- Original implementation: [https://github.com/HengruiTianSJTU/LesionDiffusion](https://github.com/HengruiTianSJTU/LesionDiffusion).
35
- Source revision or version: `880736e2b8a4d9becdb6594da7f8af6db19d04d1`.
36
- Original checkpoint: Component locations are in the native run record and the upstream package documentation.
37
-
38
- Conversion: lesiondiffusion_mlx.write_conversion (README recipe), using the upstream component checkpoints and an explicitly supplied BiomedCLIP tokenizer.
39
- Conversion changes tensor layout or storage as specified by the native package; it does not retrain the model.
40
-
41
- ## Download the safetensors bundle
42
-
43
- Install [uv](https://docs.astral.sh/uv/getting-started/installation/) and Git first. Your Hugging Face account needs access to this private MedMLX entry, and your Git credentials need access to the companion private MedMLX source repository. In a new working directory, create and activate a Python environment, install the Hub CLI, and download the bundle:
44
 
45
  ```bash
46
- uv venv --python 3.12
47
  source .venv/bin/activate
48
- uv pip install huggingface_hub
 
49
  hf auth login
50
- hf download MedMLX/lesiondiffusion-mlx --local-dir lesiondiffusion
51
  ```
52
 
53
- The current source checkout needs the included safetensors loader patch. The patch changes serialization support only; the existing local NPZ input contract is retained. Install the pinned source with that patch:
54
 
55
- ```bash
56
- git clone https://github.com/MedMLX/lesiondiffusion-mlx.git lesiondiffusion-mlx-source
57
- git -C lesiondiffusion-mlx-source checkout c8cac6e616895618254069bcf10ddf292409bc62
58
- git -C lesiondiffusion-mlx-source apply ../lesiondiffusion/safetensors-support.patch
59
- uv pip install ./lesiondiffusion-mlx-source
60
- ```
61
-
62
- The recorded usage example below uses this bundle. The original conversion recipe is retained for reproducibility.
63
 
64
- ## Reproduce the upstream conversion (optional)
65
-
66
- Install the native package with conversion support:
67
-
68
- ```bash
69
- uv pip install "lesiondiffusion-mlx[conversion] @ git+https://github.com/MedMLX/lesiondiffusion-mlx.git"
70
- ```
71
-
72
- Obtain the LMNet, LINet, and VQ checkpoints through the [official upstream project](https://github.com/HengruiTianSJTU/LesionDiffusion), together with its BiomedCLIP `tokenizer.json`. Component terms remain separate. Convert the explicitly selected local assets:
73
-
74
- ```python
75
- from pathlib import Path
76
- from lesiondiffusion_mlx import write_conversion
77
-
78
- write_conversion(
79
- checkpoints={
80
- "lmnet": Path("LMNet.ckpt"),
81
- "linet": Path("LINet.ckpt"),
82
- "vq": Path("VQ.ckpt"),
83
- },
84
- tokenizer_path=Path("tokenizer.json"),
85
- output_dir=Path("lesiondiffusion"),
86
- )
87
- ```
88
-
89
- The [package README](https://github.com/MedMLX/lesiondiffusion-mlx#input-and-output) defines the CT and organ-mask preparation contract. The example below explicitly selects the paper's `source` inference mode. The package currently defaults to `conditioned`; its source-parity result must not be applied to that mode.
90
-
91
- ## Recorded example
92
-
93
- AMOS22 (validation) / amos_0008; AMOS22 (validation) / amos_0008 label 6 (liver)
94
 
95
  ```python
96
  from lesiondiffusion_mlx.medmlx import run
97
 
98
- # Replace file and directory names with your locally obtained inputs and weights.
99
- kwargs = {
100
- "image_path": "amos_0008_ct_1mm_ras_160x160x128.nii.gz",
101
- "organ_mask_path": "amos_0008_liver_1mm_ras_160x160x128.nii.gz",
102
- "output_dir": "out",
103
- "weights_path": "lesiondiffusion",
104
- "organ": "liver",
105
- "lesion_type": "tumor",
106
- "bbox_size_mm": [
107
- 30,
108
- 30,
109
- 20
110
- ],
111
- "placement": "inside_parenchyma",
112
- "inference_mode": "source",
113
- "diffusion_steps": 200,
114
- "overwrite": False,
115
- "seed": 23
116
- }
117
- result = run(**kwargs)
118
- print(result["outputs"])
119
  ```
120
 
121
- Native result: **success**. Original-code comparison: **pass**, `cosine_similarity` = `0.9999697715207947`.
122
-
123
- Full controlled 200-step paired reference completed October 12; image cosine 0.99996977 and lesion Dice 1.0. Density and outside-lesion reconstruction limitations remain.
124
 
125
- The complete [native run](https://github.com/MedMLX/Med-MLX/blob/main/results/runs/lesiondiffusion.json) and [original-code comparison](https://github.com/MedMLX/Med-MLX/blob/main/results/reference/lesiondiffusion.json) retain commands, geometry, precision, and output-specific checks. Use the [replay harness](https://github.com/MedMLX/Med-MLX/blob/main/benchmarks/README.md) to substitute local paths. The package README documents installation and conversion.
126
 
127
- The current package defaults to `inference_mode="conditioned"`, which enables LINet text context and native FP16 convolution. The example above explicitly selects `source` to retain the paper's evaluated mode. The [October 12 follow-up](https://github.com/MedMLX/Med-MLX/blob/main/results/lesiondiffusion-conditioned-20261012.json) records a 452.6 s 200-step run with −20.6 HU lesion-minus-nearby-liver contrast and a 128.4 s 50-step draw with −19.0 HU contrast on the same case. This additional visibility check does not extend the paper's source-parity result to conditioned mode or establish clinical validity.
128
 
129
- ## Distribution terms
130
 
131
- Upstream code: **MIT**. Weights: **Unresolved; retrieve directly from the original publisher**.
132
- Distribution: **private converted-weight archive**. Public redistribution review remains separate; the original component terms are unchanged.
133
 
134
- Full controlled 200-step paired reference completed October 12; image cosine 0.99996977 and lesion Dice 1.0. Density and outside-lesion reconstruction limitations remain. The complete component terms have not been cleared for public redistribution.
135
 
136
- Required notices:
137
 
138
- - Retain the applicable license, copyright and attribution notices; identify the upstream checkpoint, authors, and MLX conversion changes.
139
 
140
- Review sources:
141
 
142
- - https://github.com/HengruiTianSJTU/LesionDiffusion
143
 
144
- ## Packaged weights
145
 
146
- Exactly one safetensors file contains the converted FP32 weights. JSON files are required runtime metadata or tokenizer assets. This upload does not rerun or extend the recorded model qualification.
147
 
148
- - `model.safetensors` (1,665,997,956 bytes)
149
 
150
- Component names prefix tensor keys in `model.safetensors`; the included loader patch selects each component without changing its values or precision.
 
17
 
18
  # LesionDiffusion for MLX
19
 
20
+ Converted LMNet, LINet, text-encoder and VQ weights for native MLX lesion synthesis on Apple Silicon. This entry remains **private**; original checkpoint and component terms remain applicable. Intended for research and education, not clinical use.
21
 
22
+ Use the public [lesiondiffusion-mlx v0.2.1 package](https://github.com/MedMLX/lesiondiffusion-mlx/releases/tag/v0.2.1), source revision `41f7400dd6f34b1b5844732ad2360a1301cdb9fa`, on an Apple Silicon Mac with Metal and Python 3.13 or 3.14. Inference does not require PyTorch. The package reads this consolidated safetensors bundle directly; no source patch is needed.
23
 
24
+ ## Install and download
25
 
26
+ Install [uv](https://docs.astral.sh/uv/getting-started/installation/) and Git, then create a Python environment and install the pinned public package. Install the Hub CLI as a standalone tool; your Hugging Face account needs access to this private model entry.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
27
 
28
  ```bash
29
+ uv venv --python 3.13
30
  source .venv/bin/activate
31
+ uv pip install "lesiondiffusion-mlx @ git+https://github.com/MedMLX/lesiondiffusion-mlx.git@41f7400dd6f34b1b5844732ad2360a1301cdb9fa"
32
+ uv tool install huggingface-hub
33
  hf auth login
34
+ hf download MedMLX/lesiondiffusion-mlx --local-dir lesiondiffusion-weights
35
  ```
36
 
37
+ The download contains `model.safetensors`, `conversion.json`, `tokenizer.json`, and retained notices. Weights are stored in FP32 with `<stage>.` prefixes: `lmnet`, `lmnet_text`, `linet`, `linet_text`, and `vq`. The package applies its declared inference precision profile when loading each stage.
38
 
39
+ ## Public API
 
 
 
 
 
 
 
40
 
41
+ Provide a CT NIfTI and an aligned binary organ mask (0 background, 1 organ). For a liver example:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
42
 
43
  ```python
44
  from lesiondiffusion_mlx.medmlx import run
45
 
46
+ result = run(
47
+ image_path="ct_1mm_ras.nii.gz",
48
+ organ_mask_path="liver_mask.nii.gz",
49
+ weights_path="lesiondiffusion-weights",
50
+ output_dir="out",
51
+ organ="liver",
52
+ lesion_type="tumor",
53
+ bbox_size_mm=[30, 30, 20],
54
+ placement="inside_parenchyma",
55
+ inference_mode="conditioned",
56
+ diffusion_steps=200,
57
+ seed=23,
58
+ )
59
+ print(result["outputs"]["synthetic_image"])
 
 
 
 
 
 
 
60
  ```
61
 
62
+ The default `conditioned` mode sends phenotype text to LINet and uses native FP16 convolution. It changes generated outputs relative to `inference_mode="source"`, which follows the pinned upstream inference behavior, including disabled LINet image-text conditioning and source-ordered convolution arithmetic. Both modes default to 200 steps and seed 23. Source-mode agreement does not establish conditioned-mode equivalence or generated-image quality.
 
 
63
 
64
+ The [v0.2.1 input/output contract](https://github.com/MedMLX/lesiondiffusion-mlx/blob/v0.2.1/README.md#input-and-output) defines supported geometry, organ labels, placement and optional phenotype controls. Axis-aligned inputs are standardized to an even-dimensioned RAS 1 mm grid; oblique or sheared transforms are refused. Inputs must support the documented 152 × 152 × 64 and 128³ crops. The CT and mask must resolve to the same standardized grid.
65
 
66
+ A successful run returns output paths under `result["outputs"]`: `synthetic_image`, `target_mask` and `organ_mask`. The files are `synthetic_ct.nii.gz`, `lesion_mask.nii.gz` and `organ_mask.nii.gz` on the standardized grid. Existing outputs require explicit `overwrite=True` to replace them. In the default full reconstruction, voxels outside the lesion can also change, and CT intensities are clipped to [-1000, 1000] HU. See the package contract for `output_region="box"`. Synthetic output is not ground truth.
67
 
68
+ ## Qualification scope
69
 
70
+ The v0.2.1 release acceptance on October 24, 2026 used the installed wheel with public `medmlx-core==0.2.3`, MLX 0.32.4 and no PyTorch in the inference environment. A complete default conditioned 200-step, seed-23 public-API run finished in 372.0 seconds on one fixed AMOS22 `amos_0008` CT/liver crop (label 6; 160 × 160 × 128, RAS, 1 mm). The generated CT was finite, output masks had the required label types, and output geometry and coded forms matched the prepared input. This is an execution and output-contract check, not an assessment of appearance, lesion fidelity or clinical effectiveness.
 
71
 
72
+ All 139 current owner tests passed with zero skips, including the independent pinned-source numerical/contract tests. Their reduced-width and source-mode scope is described in the [technical notes](https://github.com/MedMLX/lesiondiffusion-mlx/blob/v0.2.1/docs/technical.md). The full conditioned run above is not a paired full-checkpoint source comparison.
73
 
74
+ Before this card update, every tensor in this Hub bundle was compared on CPU against the fresh canonical conversion used for that release acceptance: all 1,141 tensor keys, shapes, dtypes and values across five stages matched exactly, as did the tokenizer bytes. Both `source` and `conditioned` asset admission passed through the installed v0.2.1 public package. The Hub serialization was not given a separate full inference rerun; its tensor-equivalence check does not extend the model qualification.
75
 
76
+ ## Source and conversion
77
 
78
+ Original publisher: HengruiTianSJTU. Source: [LesionDiffusion](https://github.com/HengruiTianSJTU/LesionDiffusion), pinned revision `880736e2b8a4d9becdb6594da7f8af6db19d04d1`.
79
 
80
+ The bundle was converted from the released `LMNet.ckpt`, `LINet.ckpt` and `VQ.ckpt` components, with an explicitly supplied BiomedCLIP tokenizer. Conversion uses EMA weights and changes tensor layout/storage without retraining. `conversion.json` records the source revision, layout and precision profile. To repeat conversion from assets obtained from the original publisher, use the [v0.2.1 conversion recipe](https://github.com/MedMLX/lesiondiffusion-mlx/blob/v0.2.1/README.md#quick-start) and its optional `conversion` extra. Do not use this card as a grant to obtain or redistribute upstream assets.
81
 
82
+ ## Distribution terms and notices
83
 
84
+ Upstream LesionDiffusion code: **MIT**. Native package code: **Apache-2.0**, with component-specific notices in the [source distribution](https://github.com/MedMLX/lesiondiffusion-mlx/blob/v0.2.1/THIRD_PARTY_NOTICES.md). **Checkpoint redistribution terms remain unresolved; retrieve original checkpoints directly from the original publisher.**
85
 
86
+ This entry is a **private converted-weight archive**. Public redistribution review remains separate; the original checkpoint and component terms are unchanged. This update grants no new or broader license and does not clear the model for public redistribution.
87
 
88
+ Retain the applicable license, copyright and attribution notices, identify the upstream checkpoint and authors, and identify the MLX conversion changes. The existing `NOTICE` and `SOURCE_LICENSE` files are retained without alteration. Review the [original publisher's project](https://github.com/HengruiTianSJTU/LesionDiffusion) and its component terms before use or redistribution.
conversion.json CHANGED
@@ -10,18 +10,6 @@
10
  "vq": "float16"
11
  },
12
  "ema": true,
13
- "source_files": {
14
- "LMNet.ckpt": {},
15
- "LINet.ckpt": {},
16
- "VQ.ckpt": {}
17
- },
18
- "converted_files": {
19
- "lmnet.npz": {},
20
- "linet.npz": {},
21
- "vq.npz": {},
22
- "lmnet_text.npz": {},
23
- "linet_text.npz": {}
24
- },
25
  "tensor_file": "model.safetensors",
26
  "tensor_prefixes": {
27
  "linet": "linet.",
 
10
  "vq": "float16"
11
  },
12
  "ema": true,
 
 
 
 
 
 
 
 
 
 
 
 
13
  "tensor_file": "model.safetensors",
14
  "tensor_prefixes": {
15
  "linet": "linet.",
safetensors-support.patch DELETED
@@ -1,130 +0,0 @@
1
- diff --git a/pyproject.toml b/pyproject.toml
2
- index bbaab24..e3e0534 100644
3
- --- a/pyproject.toml
4
- +++ b/pyproject.toml
5
- @@ -20,6 +20,7 @@ dependencies = [
6
- "medmlx-core @ git+https://github.com/MedMLX/medmlx-core.git@0a83421597991ad04c76a532780273c13854cbac",
7
- "nibabel>=5.3,<6",
8
- "numpy>=2.0,<3",
9
- + "safetensors>=0.6,<1",
10
- "scipy>=1.15,<2",
11
- "tokenizers>=0.21,<1",
12
- ]
13
- diff --git a/src/lesiondiffusion_mlx/medmlx.py b/src/lesiondiffusion_mlx/medmlx.py
14
- index 6dfc21a..e8ec266 100644
15
- --- a/src/lesiondiffusion_mlx/medmlx.py
16
- +++ b/src/lesiondiffusion_mlx/medmlx.py
17
- @@ -69,7 +69,7 @@ WARNING: Final = (
18
- )
19
- WEIGHTS_HINT: Final = (
20
- "Convert explicit upstream checkpoints with scripts/convert_weights.py; "
21
- - "weights_path must contain the NPZ stages, tokenizer.json and conversion.json."
22
- + "weights_path must contain safetensors or NPZ stages, tokenizer.json and conversion.json."
23
- )
24
-
25
- # Canonical labels match upstream organ_type.json and its inference vocabulary.
26
- @@ -776,6 +776,12 @@ def source_organ_label(organ: str) -> tuple[int, str]:
27
- # --- converted weights ----------------------------------------------------------------
28
-
29
-
30
- +def _stage_file(directory: Path, name: str) -> Path:
31
- + """Use the consolidated release or an existing local NPZ conversion."""
32
- + path = directory / "model.safetensors"
33
- + return path if path.is_file() else directory / (name + ".npz")
34
- +
35
- +
36
- def require_weights(path: _PathArg, *, inference_mode: str = DEFAULT_INFERENCE_MODE) -> Path:
37
- """Require a self-contained, current conversion."""
38
-
39
- @@ -787,17 +793,29 @@ def require_weights(path: _PathArg, *, inference_mode: str = DEFAULT_INFERENCE_M
40
- hint=WEIGHTS_HINT,
41
- )
42
- inference_files = SOURCE_INFERENCE_FILES if inference_mode == "source" else CONVERTED_FILES
43
- - missing = [
44
- - directory / name
45
- - for name in (*inference_files, TOKENIZER_FILE, CONVERSION_FILE)
46
- - if not (directory / name).is_file()
47
- - ]
48
- + files = [_stage_file(directory, Path(name).stem) for name in inference_files]
49
- + files.extend((directory / TOKENIZER_FILE, directory / CONVERSION_FILE))
50
- + missing = [file for file in files if not file.is_file()]
51
- if missing:
52
- raise AssetNotReadyError(
53
- f"Native MLX LesionDiffusion weights are not ready: missing {missing[0]}",
54
- reason=f"Missing native LesionDiffusion asset: {missing[0]}",
55
- hint=WEIGHTS_HINT,
56
- )
57
- + packed = directory / "model.safetensors"
58
- + if packed.is_file():
59
- + from safetensors import safe_open
60
- +
61
- + with safe_open(packed, framework="np") as tensors:
62
- + names = tensors.keys()
63
- + present = {key.split(".", 1)[0] for key in names}
64
- + for name in inference_files:
65
- + if Path(name).stem not in present:
66
- + raise AssetNotReadyError(
67
- + f"Native MLX LesionDiffusion weights are missing stage {Path(name).stem}",
68
- + reason=f"Missing stage in {packed.name}",
69
- + hint=WEIGHTS_HINT,
70
- + )
71
- try:
72
- load_conversion(directory)
73
- except (ValueError, OSError) as exc:
74
- @@ -843,8 +861,15 @@ def load_conversion(directory: Path) -> dict[str, object]:
75
- def load_stage(directory: Path, name: str, mx: Mlx, *, dtype: Dtype) -> dict[str, Array]:
76
- """Load converted arrays and copy them to the MLX device."""
77
- load_conversion(directory)
78
- - path = directory / (name + ".npz")
79
- + path = _stage_file(directory, name)
80
- loaded = mx.load(str(path))
81
- + if path.name == "model.safetensors":
82
- + prefix = name + "."
83
- + return {
84
- + key.removeprefix(prefix): value.astype(dtype)
85
- + for key, value in loaded.items()
86
- + if key.startswith(prefix)
87
- + }
88
- return {key: value.astype(dtype) for key, value in loaded.items()}
89
-
90
-
91
- diff --git a/uv.lock b/uv.lock
92
- index 708012c..63dc443 100644
93
- --- a/uv.lock
94
- +++ b/uv.lock
95
- @@ -200,6 +200,7 @@ dependencies = [
96
- { name = "medmlx-core" },
97
- { name = "nibabel" },
98
- { name = "numpy" },
99
- + { name = "safetensors" },
100
- { name = "scipy" },
101
- { name = "tokenizers" },
102
- ]
103
- @@ -237,6 +238,7 @@ requires-dist = [
104
- { name = "pyright", extras = ["nodejs"], marker = "extra == 'dev'", specifier = "==1.1.414" },
105
- { name = "pytest", marker = "extra == 'dev'", specifier = ">=8.4,<10" },
106
- { name = "ruff", marker = "extra == 'dev'", specifier = ">=0.13,<1" },
107
- + { name = "safetensors", specifier = ">=0.6,<1" },
108
- { name = "scipy", specifier = ">=1.15,<2" },
109
- { name = "scipy-stubs", marker = "extra == 'dev'", specifier = ">=1.18.1.1,<1.19" },
110
- { name = "tokenizers", specifier = ">=0.21,<1" },
111
- diff --git a/typings/safetensors/__init__.pyi b/typings/safetensors/__init__.pyi
112
- new file mode 100644
113
- index 0000000..fcfb957
114
- --- /dev/null
115
- +++ b/typings/safetensors/__init__.pyi
116
- @@ -0,0 +1,14 @@
117
- +from os import PathLike
118
- +from types import TracebackType
119
- +from typing import Self
120
- +
121
- +class safe_open:
122
- + def __init__(self, filename: str | PathLike[str], framework: str) -> None: ...
123
- + def __enter__(self) -> Self: ...
124
- + def __exit__(
125
- + self,
126
- + exc_type: type[BaseException] | None,
127
- + exc_value: BaseException | None,
128
- + traceback: TracebackType | None,
129
- + ) -> None: ...
130
- + def keys(self) -> list[str]: ...