crispr-kinetics-hpv16
Model weights for the paper "Deep Learning of CRISPR Fluorescence Kinetics for Ultrafast, Quantitative Viral Detection".
The model takes 15 fluorescence frames of one RPA-CRISPR/Cas12a reaction well and predicts the HPV16 concentration class (NC, 10 aM, 100 aM, 1 fM) and a log10 concentration.
It is an ensemble of two separately trained networks. Both apply the same CNN design to every frame (each network has its own weights); one then reads the frame features with an LSTM and the other with a 2-layer Transformer. The final output is the average of the two, each averaged over 4 flips.
Files
lstm/,transformer/: weights of the two branches, asmodel.safetensorsand as the original PyTorch checkpointbest_model.ptbaseline_mean.npy: mean image of the NC wells in the training set, subtracted from every framemodeling.py: model definitionspredict.py: preprocessing and inferenceconfig.json: model settingscomparison/architectures/: the temporal architectures compared in the paper (see below)
Usage
pip install torch numpy pillow safetensors huggingface_hub
import sys
from huggingface_hub import snapshot_download
path = snapshot_download("shiyuxie/crispr-kinetics-hpv16")
sys.path.insert(0, path)
from predict import Ensemble
ens = Ensemble(path)
out = ens.predict_folder("ROI_time_series_xxx", roi=5)
print(out["label"], out["concentration_fM"], out["probs"])
Or from the command line:
python predict.py --folder ROI_time_series_xxx --roi 5
Input is frames A015 to A029 (150-290 s) of a well, 140x140 RGB. The files are named like A015 - 20250324_231500_roi_5.bmp. You can also pass 15 image paths directly to ens.predict().
10 fM samples were merged into the 1 fM class for training, so the model does not separate concentrations above 1 fM.
Results
The data were split by experimental run with seed 42: 60 runs for training, 16 for validation and 15 for testing (720 / 192 / 180 wells).
| Accuracy | MAE (log10) | |
|---|---|---|
| Validation | 96.9% (186/192) | |
| Test | 93.3% (168/180) | 0.18 |
| Test, LSTM branch only | 91.7% | |
| Test, Transformer branch only | 88.9% |
Test confusion matrix (rows are true class):
| NC | 10 aM | 100 aM | 1 fM | |
|---|---|---|---|---|
| NC | 36 | 0 | 0 | 0 |
| 10 aM | 2 | 33 | 1 | 0 |
| 100 aM | 0 | 0 | 36 | 0 |
| 1 fM | 0 | 0 | 9 | 63 |
Comparison models
The temporal architectures compared in Fig. 2e and 2f all use the same CNN design and input as the main model and differ only in the temporal part. They were trained and tested on the same split as the main model. Two of them are the branches of the released ensemble; the other three are in comparison/architectures/, each with model.safetensors and a config.json.
| Temporal model | Folder | Parameters | Test accuracy |
|---|---|---|---|
| LSTM | lstm/ |
55,878 | 91.7% |
| Transformer, 2 layers, d = 48 | transformer/ |
81,750 | 88.9% |
| LSTM, mean + max pooling | comparison/architectures/lstm_meanmax |
54,629 | 87.2% |
| Transformer, 2 layers, d = 96 | comparison/architectures/transformer_d96 |
203,718 | 85.6% |
| Transformer, 3 layers, d = 64 | comparison/architectures/transformer_3l_d64 |
147,174 | 90.6% |
Fig. 2e and 2f report the mean over repeated training runs, so the values there can differ slightly from the single models above.
from modeling import load_model
m = load_model(f"{path}/comparison/architectures/transformer_3l_d64")
The loaded model takes the same preprocessed input as the main model (predict.preprocess).
Other comparison methods (Fig. 2b and 2c)
| Method | Folder | Test accuracy |
|---|---|---|
| Endpoint | comparison/curve_methods/endpoint |
71.1% |
| KS test | comparison/curve_methods/ks_test |
62.8% |
| Curve features + logistic regression | comparison/curve_methods/curve_features_lr |
79.4% |
| Slope | comparison/curve_methods/slope |
80.0% |
| CNN-GRU | comparison/cnn_gru |
85.0% |
| Single-frame CNN, 60 s | comparison/single_frame_60s |
33.3% |
| Single-frame CNN, 150 s | comparison/single_frame_150s |
56.7% |
| Single-frame CNN, 220 s | comparison/single_frame_220s |
66.7% |
| Single-frame CNN, 290 s | comparison/single_frame_290s |
68.9% |
These methods were evaluated on a different random split of the same 91 runs than the main model. comparison/test_runs.json lists the test runs of both splits. For the CNN models the paper reports the mean over repeated training runs, so its values can differ slightly from the single models here. The 1D-CNN, LSTM and GRU models on the scalar curve were not kept and are not included.
The curve methods work on the mean intensity of frames A015โA029:
import curve_methods as cm
curve = cm.curve_from_frames(frame_paths)
cm.CLASS_NAMES[cm.predict("slope", curve, cm.load("slope", path))]
CNN-GRU uses its own baseline_mean.npy in predict.preprocess. The single-frame models take one raw RGB frame:
import numpy as np, torch
from PIL import Image
m = load_model(f"{path}/comparison/single_frame_290s")
x = torch.from_numpy(np.asarray(Image.open(frame_A029).convert("RGB"), dtype=np.float32)).permute(2, 0, 1)[None]
reg, logits = m(x)
Limitations
This model is for research use only. It was trained on data from one imaging setup and one assay protocol. Results on other instruments or protocols may not hold without retraining.
Citation
Citation details will be added once the paper is published.
- Downloads last month
- 6