Bone and lung-component suppression for chest radiographs

Inference code and TorchScript weights for the suppression models of

Anatomy-Decomposed Chest Computed Tomography (CT) Projections as Scalable Supervision for Bone Suppression in Chest Radiographs (under review at Medical Image Analysis; authors and citation to be added on acceptance).

Released by Qure.ai. Version 1.0.0 (2026-09-15). For reproducible use, pin a repository revision (revision= in hf_hub_download, or a commit hash when cloning). Questions: the Community tab of this repository, or mrunmay.angaitkar@qure.ai.

Both models are single-pass networks whose only paired supervision was synthetic: per-structure projections (bone / non-lung soft tissue / lung component) rendered from chest CT. No dual-energy images and no paired real radiographs were used in training. Each model predicts one component and the complement is recovered by subtraction:

model input predicts complement
weights/bone_suppression.ts full radiograph bone image soft tissue = input − bone
weights/lung_component_suppression.ts soft-tissue residual (input − bone, float, not re-normalised) lung component (vessels and other intrapulmonary structure) non-lung soft tissue = soft − lung

Applied in sequence they decompose a real radiograph into bone, lung-component and non-lung soft-tissue images.

decomposition of a real chest radiograph

A JSRT radiograph (left) decomposed by the two models, applied in sequence. The bone and lung-component panels are min–max rescaled for display only; in the output files they are stored on the normalised input's [0, 1] scale and therefore look dark (in this example the bone image peaks at about 68/255; other images differ).

Quick start

git lfs install
git clone https://huggingface.co/qureaiorg/bone-suppression
cd bone-suppression

pip install -r requirements.txt          # torch >= 2.1, numpy, opencv-python
python suppress.py --input examples --output examples/outputs          # bone suppression
python suppress.py --input examples --output examples/outputs --lung   # + lung-component suppression

Cloning brings the weights with it (git-lfs, ~400 MB each). Without cloning, fetch the script and its requirements first; suppress.py then downloads the weights from this repository on first use into the Hugging Face cache:

pip install huggingface_hub
python -c "from huggingface_hub import hf_hub_download as d; [d('qureaiorg/bone-suppression', f, local_dir='.') for f in ('suppress.py', 'requirements.txt')]"
pip install -r requirements.txt
python suppress.py --input chest_xray.png --output out/

--input is a file or a directory (PNG/JPG/TIFF/BMP, 8- or 16-bit, read with cv2.IMREAD_UNCHANGED). DICOM files are read with pydicom if installed: the stored pixel_array is used and MONOCHROME1 images are inverted; no rescale, windowing or VOI-LUT processing is applied, so DICOMs that need display processing should be converted to an 8/16-bit image first. For every input X the script writes 8-bit PNGs at the input's resolution:

X_bone.png                  predicted bone image
X_soft_tissue.png           bone-suppressed radiograph  (input − bone)
X_lung_component.png        (with --lung) predicted lung component
X_nonlung_soft_tissue.png   (with --lung) lung-component-suppressed soft tissue (soft − lung)

Add --device cpu to run without a GPU (about a minute per image), --keep1024 to write outputs on the model's 1024×1024 grid. The outputs in examples/outputs/ were produced by the second command above.

Two ways to run, one preprocessing contract

The models expect a frontal (PA or AP) chest radiograph with bone bright, as displayed clinically, resized to 1024×1024 (area interpolation) and min–max normalised to [0, 1] per image. That [0, 1] range is the model's required input convention.

  • With suppress.py: supply the image as it is. The script does the resizing and normalisation (and inverts MONOCHROME1 DICOMs); do not pre-normalise.
  • Loading a trace directly: you must apply the same preprocessing yourself, as in the example below. Feeding an inverted image (bone dark) gives meaningless output in either route.
  • Lung model input: the lung model is applied to the floating-point residual soft = input − bone on the normalised input's scale, exactly as computed and without a second min–max normalisation. That is the trained inference pipeline; re-normalising the residual before the lung model changes its behaviour.
import cv2, numpy as np, torch
from huggingface_hub import hf_hub_download

p = hf_hub_download("qureaiorg/bone-suppression", "weights/bone_suppression.ts")
bone_model = torch.jit.load(p).eval()

img = cv2.imread("chest_xray.png", cv2.IMREAD_UNCHANGED).astype(np.float32)   # keeps 16-bit depth
if img.ndim == 3:
    img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
img = cv2.resize(img, (1024, 1024), interpolation=cv2.INTER_AREA)
img = (img - img.min()) / (img.max() - img.min() + 1e-10)     # min-max to [0,1]: required here

with torch.no_grad():
    bone, _ = bone_model(torch.from_numpy(img)[None, None])    # returns (bone, aux); use index 0
bone = bone[0, 0].numpy()
soft = img - bone                                              # bone-suppressed radiograph (float)

About the outputs and the additive relation

The bone and lung-component predictions live on the normalised input's [0, 1] scale, and the complements are computed by subtraction in floating point (soft = input − bone), so on that scale bone + soft equals the normalised input. The saved PNGs are 8-bit encodings of these normalised component intensities, written after clipping to [0, 1] and, unless --keep1024 is given, resizing back to the input's resolution; clipping, resizing and 8-bit quantisation can each break the exact identity between the files, and the relation is to the normalised input used for inference, not to the original file's intensities. Contrast-stretch the component PNGs for viewing.

Files

suppress.py          main inference script
requirements.txt
download_weights.py  optional: pull the traces into weights/ without cloning
config.json          input/preprocessing spec, for programmatic use
weights/             the two TorchScript traces (~400 MB each), git-lfs
examples/            two JSRT radiographs (1024×1024 PNG) and their outputs

suppress.py uses a local copy of the weights if there is one and otherwise downloads them from this repository; --bone_weights / --lung_weights override with an explicit path.

Example images

examples/JPCLN030.png (nodule case) and examples/JPCNN001.png (no nodule) are from the JSRT database (Shiraishi et al., AJR 2000), downsampled from 2048² to 1024², and are included only to demonstrate the models. Please obtain the database from http://db.jsrt.or.jp/eng.php under its own terms for any other use, and cite the original publication.

Limitations

  • Trained on synthetic CT-derived projections; evaluated on adult frontal chest radiographs from public datasets (TBX11K, Node21, VinDr-CXR, JSRT). Not evaluated on paediatric, lateral, or portable/supine images.
  • The lung-component output is a model-derived estimate of vessels and other intrapulmonary structure and can include intrapulmonary abnormalities. It has no standalone downstream validation in the paper (it was assessed only through the realism and anatomy of images built from it); treat it as experimental.
  • Research software; not a medical device and not for clinical decision-making.

License

  • Code (suppress.py, download_weights.py, config.json): Apache License 2.0, see LICENSE.
  • Weights (weights/*.ts): non-commercial research and educational use only, under the terms in weights/LICENSE-WEIGHTS.txt (CC BY-NC-SA 4.0 terms apply). The repository's license tag above refers to the weights, which are the substantive artefact of this release.
  • Example images: JSRT database terms (see above); not covered by either license here.

Citation

To be added on acceptance (CITATION.cff will be updated).

Downloads last month
69
Inference Providers NEW
This model isn't deployed by any Inference Provider. 🙋 Ask for provider support