3. Detect HRD
Call homologous-recombination deficiency from point mutations, indels and copy number with the HRD model.
Before you begin, you need the package installed, a MAF or VCFs with indels, the repeat-locus files, copy-number segments when you have them.
Homologous-recombination deficiency leaves marks in three places: Signature 3 in the point mutations, loss of heterozygosity, telomeric allelic imbalance and large-scale state transitions in the copy-number profile, and microhomology at the breakpoints of deletions. SigMA2's HRD model reads these marks and predicts, per tumor, whether its mutations and copy number resemble those of tumors with biallelic BRCA1/2 inactivation: HRD, HRD-low or HRP. It needs the tumor's SNVs and indels and uses copy-number segments when you give them; what a call means states what a call supports and what it does not establish. A Signature 3 call answers another question, whether the point mutations carry Signature 3, and is reported beside the HRD call, not in its place:
- Does a tumor carry a mutational signature? Any signature of the catalog: SBS3, APOBEC, SBS8, SBS18, UV, …
run(soi="APOBEC")2. Detect a signature - Is a tumor homologous-recombination deficient?
run(soi="HRD", segments=…)this guide - Is mismatch repair deficient, or POLE mutated?
predict_mmrd_ppd()4. Detect MMRD or POLE
Get the example files
The MMRD/POLE step an HRD run starts with needs the repeat-locus files (sigma2 data fetch --all, or from the public SigMA (R) repository); without them the run skips that step without a message. The deletion features need only the genome.
curl -LO https://raw.githubusercontent.com/parklab/SigMA/master/inst/extdata/examples/test_mutations_50sample.maf
curl -LO https://sigscape.org/sigma2/examples/msk_breast_50_segments.seg
Run the HRD model
import sigma2 as sg2
hrd = sg2.run(
input="test_mutations_50sample.maf", ref_genome="hg19",
tumor_type="breast", seq_type="msk", soi="HRD",
segments="msk_breast_50_segments.seg",
)
hrd.df[[
"total_snvs",
"sig3_score",
"fga",
"lst_lr",
"mmej",
"nhej",
"hrd_variant",
"hrd_score",
"hrd_call",
"MMRD_category",
]].head(3)
| Parameter | Description | Default |
|---|---|---|
| inputpath, list of paths, folder or DataFrame | The mutations: a MAF path, a VCF path, a list of VCF paths, a folder of VCFs, a CSV, or a DataFrame of SBS-96 counts. | Required |
| ref_genomestr or path | The build of the coordinates (hg19, hg38, GRCh37, GRCh38) or a FASTA path; unused for a matrix input. | 'hg19' |
| tumor_typestr | A tumor type of the vocabulary, in any case (the package lists 39 names; msi and pole among them are signature sets, not tumor types); it sets the signature set, the reference spectra and the classifier. | Required |
| seq_typestr | The platform (msk, fo, op, seqcap, tcga_mc3, wgs); "auto" chooses by SNV burden and reports the choice. | 'auto' |
| soistr or CompositeSignature | The signature of interest: a COSMIC name, a composite preset (APOBEC, SBS7, SBS10, SBS17, CLOCK), an alias (UV, POLE), or HRD. | Required |
| segmentspath or DataFrame | Copy-number segments for HRD: a path or DataFrame in a recognized format, with a sample column. | None |
total_snvs sig3_score fga lst_lr mmej nhej hrd_variant hrd_score hrd_call MMRD_category
P-0001484-T02-IM5 <NA> NaN 0.006771 0.0 NaN NaN tcn 0.001089 HRP NaN
P-0001939-T02-IM5 13 0.219434 0.277629 0.0 0.0 0.0 tcn 0.010756 HRP MMRP
P-0003375-T01-IM5 10 0.104040 0.334849 0.0 0.0 0.0 tcn 0.003901 HRP MMRP
Provide the three inputs
Three sources, each optional when given another way:
| Feature | From | Columns |
|---|---|---|
| Signature 3 | an SBS3 run on input, with the settings recorded on the HRD model (SigMA v1's SBS3 classifier on cosmic_v2_inhouse), or a sig3= frame with SBS3_mva, SBS3_ml, exp_SBS3 | sig3_score, sig3_ml, sig3_exp |
| indels | the indels of a VCF or MAF input, or indels= (a path, VCF or MAF object, or frame), or precomputed mmej and nhej columns in a matrix | mmej, nhej |
| copy number | segments= (allele-specific or log-ratio, formats), or gis= (a per-sample Series or dict), or gis, fga, lst_lr columns in a matrix | hrd_loh, tai, lst, gis; or fga, lst_lr |
The model variant follows what is present:
hrd_variant | Copy-number input | Features |
|---|---|---|
gis | allele-specific segments (ASCAT, FACETS, PURPLE, major/minor) | Signature 3, HRD-LOH, TAI, LST, mmej, nhej |
tcn | log-ratio segments (.seg, seg.mean) | Signature 3, fraction of genome altered, log-ratio LST, mmej, nhej |
nogis | none | Signature 3, mmej, nhej |
hrd0 = sg2.run(
input="test_mutations_50sample.maf",
ref_genome="hg19",
tumor_type="breast",
seq_type="msk",
soi="HRD",
verbose=1,
)
HRD: no copy-number input (segments= or gis=); scoring with the model trained without copy number.
The gis variant reads the three GIS components; a GIS sum alone (gis=) falls back to a weaker variant with a warning. On msk, segments are read on the model's IMPACT gene grid, which is hg19, and hg38 segments are refused. The refusal suggests passing a GIS sum as gis=, but the msk model's gis variant reads the three components, so a sum alone sends the run to the variant without copy number. To keep copy number, lift the segments to hg19, compute the three components on the grid with sigma2.hrd.genomic_instability(lifted, genome="hg19", grid=sg2.hrd_model(seq_type="msk").gis_grid), write hrd_loh, tai, lst and gis into the table sg2.hrd_features() returns, and score it with sg2.score_hrd() (Optional: run in two steps); on hg19 input this route gives the same scores as passing the segments.
Read the output
| Column | Meaning |
|---|---|
hrd_call | HRD, HRD-low, HRP; or MMRD / POLE for tumors the pre-step set aside |
hrd_score | the mean of the two models' probabilities |
hrd_score_pan, hrd_score_type | the pan-cancer model's and the tumor type's (or the pooled) model's probabilities; the call is HRD when both exceed 0.5, HRP when neither does, HRD-low when they disagree |
hrd_group | which tumor-type model scored it: the type's own, or pooled |
hrd_variant | gis, tcn, nogis |
HRD_mva, pass_mva, pass_mva_strict, confidence | the HRD score and calls in the standard columns: pass_mva is True for HRD and for HRD-low, pass_mva_strict for HRD only, and confidence reads low for HRD-low. Read hrd_call; counting pass_mva counts every disagreement of the two models as a positive |
SBS3_pass_mva, SBS3_pass_mva_strict, SBS3_confidence | the calls of the Signature 3 classifier the model reads, SigMA v1's; a separate soi="SBS3" run uses SigMA2's classifier for the cell, and the two can disagree |
MMRD_category | the pre-step's call |
An HRD run keeps every sample in out.df. Below the SNV floor a tumor has no Signature 3 features and no MMRD/POLE category, and the output shows its deletion counts as NA, although the model read them. It still receives a call: with segments, from its copy number and deletion counts; without segments, from its deletion counts alone, or from no feature of the tumor at all, when its HRP is the model's prior for an empty row. Report such tumors as not assessable and leave them out of an HRD prevalence; in the example, 12 of the 50 panels are below the floor and all 12 read HRP. segments= restricts nothing either: a tumor without segments is scored without copy number, with its own hrd_variant.
hrd.df["hrd_call"].value_counts()
hrd_call
HRP 41
HRD-low 4
HRD 4
MMRD 1
Cohorts. An HRD run costs about a second per tumor on one worker, most of it the MMRD/POLE pre-step. Pass mxw="max" to use every core:
sg2.run(
input="cohort.maf",
ref_genome="hg19",
tumor_type="breast",
seq_type="tcga_mc3",
soi="HRD",
segments="ascat.tsv",
mxw="max",
)
Note
HRD-lowmeans the two models disagree: one of the pan-cancer and the tumor-type model scores the tumor above 0.5 and the other does not. The result is indeterminate, and an orthogonal assay settles it; the standard columns count it as a loose positive (pass_mvaTrue,confidencelow), so readhrd_call. A Signature 3 call withHRPis a discordant case: the spectrum carries Signature 3 while the model, reading the deletions and copy number as well, does not place the tumor with the BRCA1/2-deficient ones; look at the spectrum and the copy number.MMRDandPOLEtumors keep a score, but their call is replaced: a hypermutated spectrum's Signature 3 features mean something else, so the pre-step (Detect MMRD or POLE) runs first, and its label becomeshrd_call; ignorehrd_scorefor them. The pre-step needs the indels and the repeat-locus files. Without the files it is skipped with no warning, the table has noMMRD_categorycolumn, and a hypermutated tumor gets an HRD call like any other: check for that column.
The spectrum of the panel the model scores highest, drawn by the package:
from sigma2 import plot as sp
scored = hrd.df[hrd.df["total_snvs"] >= 5]
top = scored["hrd_score"].idxmax()
row = hrd.df.loc[top]
sp.sbs(
hrd.df.loc[[top]].iloc[:, :96],
title=f"{top}: {row['hrd_call']}, hrd_score {row['hrd_score']:.2f}",
path="top_hrd.svg",
)
Check that the tumor type is scored
An HRD model is shipped for msk, fo, op, seqcap and tcga_mc3. Within each, a tumor type has its own model when it had more than ten positives in the first training step, its BRCA1/2-deficient tumors plus three neighbors of each, so an own model can rest on as few as three BRCA1/2-deficient tumors; the others share the pooled one, whose call rests on other tumor types' data. The bundled classifiers page gives, per tumor type and platform, the model and its number of BRCA1/2-deficient training tumors. hrd_group names which scored your sample; the bundled classifiers page lists own and pooled per platform.
Some tumor types the models saw in training have no Signature 3 signature set and cannot be scored; run(soi="HRD") refuses them with the reason:
| Platform | Refused |
|---|---|
msk | sarc |
fo, op, seqcap, tcga_mc3 | paraganglioma, sarc, testicular, thymoma, uveal |
Tumor types absent from the HRD training (bone_other, ewing, liposarc, lms, medullo, myeloma, oligo, osteo, panc_en, piloastro) are refused too.
m = sg2.hrd_model("msk")
sorted(m.covered_tumor_types()), sorted(m.unscorable_tumor_types())
hrd_model is called with its defaults. All parameters of hrd_model
HRD models are cross-validated and shipped without a release gate or a weak tier. Their caveats: a pooled type's call rests on other types' data; the exome-derived platforms (fo, op, seqcap, tcga_mc3) share one training cohort; without copy number the weaker nogis variant runs; the msk gene grid is hg19. Reference: what each HRD model was trained on gives the cohorts and their truth.
Choose between SBS3 and HRD
| Question | Use |
|---|---|
| Does the point-mutation spectrum carry Signature 3? | soi="SBS3" |
| Do the tumor's mutations, deletions and copy number resemble those of BRCA1/2-deficient tumors? | soi="HRD", with segments= when you have them |
| Only SNVs, as a matrix without indels | the HRD model cannot run; soi="SBS3" answers the signature question only |
| The same result as SigMA (R) | soi="SBS3" with model="legacy" (Reproduce SigMA v1) |
| Another signature: APOBEC, SBS8, SBS18, … | soi="APOBEC" and so on, Detect a signature |
Report hrd_call with a Signature 3 call beside it, and name the classifier: in an HRD run SBS3_pass_mva is SigMA v1's, the one the model reads; a soi="SBS3" run gives SigMA2's for the cell. Neither is a clinical determination on its own.
Optional: run in two steps
For a feature table you can inspect or score more than once:
feats = sg2.hrd_features(
"test_mutations_50sample.maf",
tumor_type="breast",
seq_type="msk",
segments="msk_breast_50_segments.seg",
ref_genome="hg19",
)
scores = sg2.score_hrd(feats, seq_type="msk", tumor_type="breast")
| Parameter | Description | Default |
|---|---|---|
| tumor_typestr | One of the tumor types the HRD model covers. | Required |
| seq_typestr | The platform with an HRD model: msk, fo, op, seqcap or tcga_mc3. | Required |
| segmentspath or DataFrame | Copy-number segments, allele-specific (ASCAT, FACETS, PURPLE) or log-ratio (.seg), with a sample column. | None |
| ref_genomestr | The build of the coordinates or a FASTA path. | 'hg19' |
| Parameter | Description | Default |
|---|---|---|
| seq_typestr | The platform whose HRD model scores it. | None |
| tumor_type | The tumor type, when the features table has no tumor_type column. | None |
sg2.genomic_instability(segments, genome="hg19") computes HRD-LOH, TAI and LST from allele-specific segments on their own; sg2.copy_number_instability(segments) the log-ratio features. HRD on exomes runs both on ASCAT segments.
Optional: run from the command line
sigma2 hrd score --input test_mutations_50sample.maf --tumor-type breast \
--seq-type msk --segments msk_breast_50_segments.seg -o hrd.tsv
sigma2 hrd features --input test_mutations_50sample.maf --tumor-type breast \
--seq-type msk --segments msk_breast_50_segments.seg -o features.tsv
sigma2 hrd models
Command line lists every option.
Optional: train your own HRD model
sg2.tune_hrd(features, labels, seq_type=...) trains an HRD model from an hrd_features() table of MMR-proficient tumors with biallelic BRCA1/2 labels and a per-row tumor type, for groups with their own assay and their own truth. Its validation is cross-validation only: there is no release gate and no experimental tier for HRD models. The API reference lists the options.
Reference: what each HRD model was trained on
The models follow the two-step procedure of the preprint of Gulhan et al. (2025), which describes the TCGA exome model; the panel models apply it to other cohorts, with the truth each cohort allows. The positives are tumors with biallelic BRCA1/2 inactivation, plus three nearest wild-type neighbors of each; mismatch-repair deficient and POLE tumors were left out.
| Model | Training tumors | Truth | Copy number the model learned |
|---|---|---|---|
tcga_mc3 | TCGA MC3 exomes | a BRCA1/2 mutation with loss of the other allele, two mutations, a homozygous deletion (ASCAT), or BRCA1 silencing by methylation with low expression; germline mutations from Knijnenburg et al. (2018) for the 9,125 patients it covers, somatic only for the rest | allele-specific segments from SNP arrays (ASCAT), at full resolution |
fo, op, seqcap | the same TCGA exomes, reduced to each panel's footprint | as tcga_mc3 | as tcga_mc3: SNP-array segments, not the panel's own |
msk | 28,834 MSK-IMPACT panels of the 50K cohort and 8,828 TCGA exomes reduced to MSK-IMPACT | on the panels, somatic truncating BRCA1/2 mutations with loss of the other allele (FACETS), two truncating hits, or a homozygous deletion both MSK callers report; germline status is not public, so germline carriers sit among the negatives; panels kept only if FACETS passed quality control, microsatellite-stable and with at most 60 mutations; on the TCGA rows, as tcga_mc3 | FACETS calls on the IMPACT gene grid |
Four consequences for reading a call. On msk, an HRP call is partly learned from germline carriers labeled negative, and the model's probabilities are expressed at the class balance of the TCGA rows, whose truth is complete; its training also left the BRCA1, BRCA2 and PALB2 indels out of the deletion counts, which scoring keeps. On fo, op and seqcap, copy number from the panel itself resolves fewer breakpoints than the SNP-array segments the model learned from, so its scar counts run lower than the training's; report the call without copy number (nogis) beside it. And where SigMA v1 has no Signature 3 classifier for the tumor type and platform, the model never reads a Signature 3 score: on msk this holds for every lung, skin, head and neck and cervix tumor, and most MSK-IMPACT training panels of the other types had no score either, below SigMA v1's floor. Last, an HRD run computes its Signature 3 features with SigMA2's port of SigMA v1's classifier (sg2.hrd_model("msk").sig3 names it); where the port passes a feature as missing (Check the legacy grid), those features differ from what SigMA v1 in R gives, so compare an HRD run's SBS3_* columns with a SigMA2 legacy run, not with R.