Downloads · 30 days
0
BCN001/JWST-transient-finder
JWST-transient-finder is a image classification model from BCN001. Use it when you need a label for an image. It is set up for pytorch. The card lists the license as other.
A 4-model DenseNet169 ensemble that classifies 64×64 pixel cutouts from JWST NIRCam F150W difference imaging as transient or non-transient. Trained as part of a self-learning pipeline that generates its own labeled da…
Downloads · 30 days
0
Access
Public
Updated Oct 20, 2026
Repo size
1.2 GB
Likes
0
Public
Click a slice to open those files.
.pth1.2 GB · 100%
From the Hugging Face model README
A 4-model DenseNet169 ensemble that classifies 64×64 pixel cutouts from JWST NIRCam F150W difference imaging as transient or non-transient. Trained as part of a self-learning pipeline that generates its own labeled data by injecting synthetic point sources (via the instrument PSF) into real F150W difference images. Each NIRCam filter has its own independently trained ensemble, hosted under its own subfolder in this repo.
| Filter | Folder | Version | Training data | Best val. acc. (4 models) |
|---|---|---|---|---|
| F150W | F150W/ | v1 | PSF-injected positives (SNR 3–10) + background/real-peak negatives | 99.89–100.00% |
| F115W | F115W/ | v3 | 5,336 PSF-injected positives (SNR ≥ 4) + 2,000 background + 4,072 human-labelled hard negatives | 99.30–99.56% |
F115W notes. The F115W ensemble was trained on 5 F115W difference images
(see F115W/training_info.json). The first version (v1) flagged far too many
subtraction residuals as transients on real data, so it was retrained twice
with hard negatives: cutouts that v1/v2 called transient on real F115W
difference images but that a human vetted as artifacts (each saved in all 8
rotations/flips). On a held-out F115W difference image
(Difference_F115W_61034-59942, 5,000 scored peaks), the predicted-transient
count dropped from 1,732 (v1) → 541 (v2) → 305 (v3, released). Only v3
is published here.
Single-file, resumable inference pipeline. Point it at a folder of FITS
difference files — it reads each file's FILTER header, downloads the
matching ensemble from this repo's <FILTER>/ subfolder (strictly that
filter — no falling back to another filter's weights), finds candidate
peaks, and scores them.
pip install torch torchvision timm astropy numpy pandas scipy pillow tqdm huggingface_hub
fits_files/
new_diff_file_1.fits
new_diff_file_2.fits
python inference_pipeline.py
Each run only processes files not yet processed (tracked via a .done
marker per file), so it's safe to re-run after dropping new files into
fits_files/. Use --force to reprocess everything.
Outputs go to output/<fits_stem>/, one folder per input FITS file:
predictions.csv — every scored cutout (peak location, sigma, per-model
and ensemble mean/std probability, prediction, confidence)positives/*.png — cutouts predicted as transientDownloaded model weights are cached under models/<FILTER>/. Useful flags:
--sigma-threshold, --confidence-threshold, --repo-id, --input-dir,
--output-dir (--help for the full list).
test_pipeline.py orchestrates find_peaks_above_k_sigma_test.py +
testing_script.py against a local test_directory/ → ML_results/,
using local DenseNet169_Ensemble_Model{1-4}_best.pth files instead of
fetching from Hugging Face. Kept for reference/offline use;
inference_pipeline.py above is the actively maintained entry point.
(6, 12, 32, 32)), trained from scratch (no pretraining)ZScaleInterval) cutouts from FITS difference images<FILTER>/DenseNet169_Ensemble_Model{1-4}_best.pth (currently F150W/, F115W/)The pipeline was originally designed to also train a 2-model DeiT (vision transformer) ensemble alongside the DenseNet models. Only the DenseNet169 ensemble was trained for this release (DeiT model count was 0 in the training run), so this card and repo cover the DenseNet ensemble only.
Given a candidate source cutout from a JWST NIRCam difference image (science minus reference epoch), predict whether it is a genuine astrophysical transient (e.g. supernova, variable star) versus a subtraction artifact, cosmic ray, or other spurious peak. Intended as a triage/ranking step ahead of human vetting, not a fully autonomous discovery pipeline.
Out of scope: classifying transient type, non-JWST instruments/pixel scales without re-validation, or images not normalized with ZScale the same way as training.
Training data is self-generated from a small set of real JWST NIRCam F150W difference images (no external labeled catalog):
psf_injection_script.py).find_non_peaks_64.py) and real detected peaks that are not injected
sources (find_peaks_above_k_sigma_training.py), so the model also learns
to reject real (non-transient) astrophysical sources.splitfolders, seed 42)
→ 14,565 train / 1,820 val / 1,823 test cutouts, roughly balanced between
positives and negatives.Difference images used for training were JWST NIRCam F150W filter data. The most recent inference/testing run on new science data was also on F150W difference images; an earlier run additionally evaluated the model on F200W data as a cross-filter check (see Limitations — not a training filter).
BCELoss)Best validation accuracy per ensemble member (10-epoch training run):
| Model | Best Val. Accuracy |
|---|---|
| DenseNet169 Model 1 | 100.00% |
| DenseNet169 Model 2 | 99.95% |
| DenseNet169 Model 3 | 99.89% |
| DenseNet169 Model 4 | 99.95% |
| Ensemble average | 99.95% |
These figures are on the held-out validation split of the self-generated (PSF-injection) dataset, which is easier than real-world discovery — see Limitations.
import torch
from pathlib import Path
# see testing_script.py in this repo for the DenseNet definition
# (densenet169()) and the ZScale preprocessing used to turn a FITS
# cutout into a normalized 64x64x3 tensor.
models = []
for path in sorted(Path(".").glob("DenseNet169_Ensemble_Model*_best.pth")):
model = densenet169(num_classes=1)
checkpoint = torch.load(path, map_location="cpu")
model.load_state_dict(checkpoint["model_state_dict"])
model.eval()
models.append(model)
with torch.no_grad():
probs = torch.stack([m(image_tensor) for m in models])
mean_prob = probs.mean(0)
std_prob = probs.std(0)
Produced by the self-learning-pipeline-transient-detection project. See
the parent repository's training pipeline (train_pipeline.py,
training_script.py) for full training code and data-generation details.