Metadata-Version: 2.5
Name: lung-nodule-mlx
Version: 0.2.6
Summary: Detect lung nodule candidates in 3D chest CT with MONAI RetinaNet and native MLX
Project-URL: Homepage, https://medmlx.org
Project-URL: Documentation, https://github.com/MedMLX/lung-nodule-mlx/blob/v0.2.6/README.md
Project-URL: Repository, https://github.com/MedMLX/lung-nodule-mlx
Project-URL: Issues, https://github.com/MedMLX/lung-nodule-mlx/issues
Project-URL: Weights, https://huggingface.co/MedMLX/retinanet-lung-nodule-mlx
Project-URL: MedMLX, https://github.com/MedMLX/medmlx
Author: Joseph Sandoval, Alfredo Sandoval Jr.
License-Expression: Apache-2.0 AND BSD-3-Clause
License-File: LICENSE
License-File: THIRD_PARTY_NOTICES.md
Keywords: apple-silicon,ct,detection,lung-nodule,medical-imaging,metal,mlx,monai,retinanet
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: Science/Research
Classifier: Operating System :: MacOS :: MacOS X
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
Classifier: Topic :: Scientific/Engineering :: Image Processing
Classifier: Topic :: Scientific/Engineering :: Medical Science Apps.
Requires-Python: <3.14,>=3.13
Requires-Dist: medmlx-core==0.2.6
Requires-Dist: mlx==0.32.4; platform_system == 'Darwin' and platform_machine == 'arm64'
Requires-Dist: nibabel<6,>=5.3
Requires-Dist: numpy<3,>=2.0
Provides-Extra: dev
Requires-Dist: hypothesis<7,>=6.140; extra == 'dev'
Requires-Dist: monai==1.6.1; extra == 'dev'
Requires-Dist: pyright==1.1.414; extra == 'dev'
Requires-Dist: pytest<10,>=8.4; extra == 'dev'
Requires-Dist: ruff<1,>=0.13; extra == 'dev'
Requires-Dist: torch<2.14,>=2.13; extra == 'dev'
Requires-Dist: torchvision<0.29,>=0.28; extra == 'dev'
Description-Content-Type: text/markdown

<div align="center">

# lung-nodule-mlx

Detect lung nodule candidates in 3D chest CT with MONAI RetinaNet and native MLX.

[![PyPI](https://img.shields.io/pypi/v/lung-nodule-mlx)](https://pypi.org/project/lung-nodule-mlx/)
[![Python](https://img.shields.io/pypi/pyversions/lung-nodule-mlx)](https://pypi.org/project/lung-nodule-mlx/)
[![License](https://img.shields.io/badge/License-Apache_2.0-blue)](https://github.com/MedMLX/lung-nodule-mlx/blob/v0.2.6/LICENSE)
[![Apple Silicon](https://img.shields.io/badge/Apple_Silicon-MLX-black?logo=apple)](https://github.com/ml-explore/mlx)
[![Hugging Face](https://img.shields.io/badge/%F0%9F%A4%97_Hugging_Face-weights-yellow)](https://huggingface.co/MedMLX/retinanet-lung-nodule-mlx)
[![MedMLX](https://img.shields.io/badge/MedMLX-runtime-A51C30)](https://github.com/MedMLX/medmlx)

[Model weights](https://huggingface.co/MedMLX/retinanet-lung-nodule-mlx) · [MedMLX](https://github.com/MedMLX/medmlx)

<img src="https://raw.githubusercontent.com/MedMLX/lung-nodule-mlx/v0.2.6/docs/showcase.png" alt="Lung nodule candidates detected in chest CT" width="100%">

</div>

| Input | Output |
| :--- | :--- |
| 3D chest CT · NIfTI | Candidate boxes and scores in RAS mm |

## Use with MedMLX

The [MedMLX MCP server](https://github.com/MedMLX/medmlx/blob/main/docs/mcp.md) is the lowest-friction way to use this model. It installs this runtime, downloads its weights on first use, and lets an AI agent describe, check and run `lung-nodule` for you.

```sh
uv tool install --python 3.13 "medmlx[vision,mcp]"
MEDMLX="$(uv tool dir --bin --color never)/medmlx"
mkdir -p "$HOME/medmlx-work"
claude mcp add --scope user medmlx -- "$MEDMLX" serve --root "$HOME/medmlx-work"
```

For Codex, use `codex mcp add medmlx -- "$MEDMLX" serve --root "$HOME/medmlx-work"`. Other MCP clients take the same command and arguments. The same install provides the `medmlx` CLI, for example `medmlx describe lung-nodule`.

> **For AI agents:** prefer the MedMLX MCP tools (`list_models`, `describe_model`, `model_readiness`, `run_model`, `get_job`) over writing Python against this package.

## Install

```bash
uv venv --python 3.13
source .venv/bin/activate
uv pip install "lung-nodule-mlx>=0.2.6"
```

## Run

```python
from lung_nodule_mlx.medmlx import run

result = run(image_path="ct.nii.gz", output_dir="out", resample_raw_input=True)
print(result["outputs"]["detections"])
```

Omitting `weights_path` downloads `MedMLX/retinanet-lung-nodule-mlx` into the shared
MedMLX cache on first use; interrupted downloads resume and later runs work offline.
With MedMLX installed, `medmlx setup lung-nodule` downloads it ahead of time. To use
your own copy, pass a folder containing `network.safetensors` as `weights_path`.
`details.weights_revision` records the downloaded commit, or `null` for a folder without
a MedMLX download record.

### Inputs and outputs

| Argument | Meaning |
| --- | --- |
| `image_path` | One scalar 3D chest CT in NIfTI, in Hounsfield units, with `mm`, `meter` or `micron` spatial units |
| `output_dir` | Directory for `detections.json` and `bundle_detections.json`; created if missing |
| `weights_path` | Optional folder containing `network.safetensors`, or the file itself |
| `overwrite` | `False` by default; existing outputs are never replaced |
| `resample_raw_input` | `False` expects the bundle's 0.703125 × 0.703125 × 1.25 mm grid; `True` resamples first |

Keep `resample_raw_input=True` for ordinary CT. The default follows the bundle's
pre-resampled mode: it reorients to RAS and scales intensities, but does not check or
correct voxel spacing. Spatial units are converted to millimetres; unknown units are
rejected. DICOM series are not read.

`detections.json` holds `medmlx.detections.v1` candidate boxes (`cccwhd`, class 0
`nodule`) in RAS millimetres with their scores; `bundle_detections.json` holds the
MONAI bundle rows in patient LPS. Boxes map through the affine with MONAI's
opposite-corner convention, so on oblique affines they differ from a box enclosing all
eight corners. `run` returns `{"outputs": {...}, "details": {...}}` with the detection
count, shapes, prepared affine, MLX device and memory use. Invalid inputs raise
`InvalidInputError`; missing or mismatched weights raise `AssetNotReadyError`.

### Python API

```python
from pathlib import Path

from medmlx_core.runtime import import_mlx
from lung_nodule_mlx import RetinaNet, boxes_to_world, detections, predict_maps
from lung_nodule_mlx.medmlx import prepare_image

image, affine = prepare_image(Path("ct.nii.gz"), resample_raw_input=True)
mx = import_mlx()
network = RetinaNet(mx.load("network.safetensors"), mx=mx)
maps, padded_shape = predict_maps(image, network)
boxes, scores, labels = detections(maps, padded_shape, image.shape[2:])
world_ras = boxes_to_world(boxes, affine)
```

Arrays are float32 NCDHW in MONAI's `(i, j, k)` order, and `detections` returns voxel
boxes. Box selection uses stable NumPy top-k and needs no compiler; tied scores keep
their original order, so overlapping boxes can differ from MONAI runs when scores tie.
Inference needs Apple Silicon with Metal and has no CPU fallback.

## License

[Apache-2.0](https://github.com/MedMLX/lung-nodule-mlx/blob/v0.2.6/LICENSE). See [third-party notices](https://github.com/MedMLX/lung-nodule-mlx/blob/v0.2.6/THIRD_PARTY_NOTICES.md). Model weights retain their publisher terms.

For research and education. Outputs require human review.
