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.
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 − boneon 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, seeLICENSE. - Weights (
weights/*.ts): non-commercial research and educational use only, under the terms inweights/LICENSE-WEIGHTS.txt(CC BY-NC-SA 4.0 terms apply). The repository'slicensetag 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
