- fixed SpliceAI visualizations to clarify positions of predicted splicing changes. See
for details.
- - the Gencode gene track is now optional in the visualizations
+
June 3, 2024
+ - show the input variant's
VEP consequence in the results table
+ - the Gencode gene track is now optional in the visualizations
+ - updated everything to
Gencode v46
+
March 7, 2024
- added warning for insertion variants with delta scores ≥ 0.2 and position = 0bp saying that they may be difficult to interpret due to
issue #67. Thanks to @SophieCandille for the issue report and example variant:
2:47790924 C>CAGTTG
- moved server to Google Cloud Run to better support higher usage
@@ -722,37 +737,6 @@
*/
}
-
- /*
- const generateVariantAnnotationTable = async (normalizedVariant, genomeVersion) => {
- const matchedRegExp = normalizedVariant.match(VARIANT_RE)
- if (!matchedRegExp) {
- console.error(`Unable to parse normalized variant: ${normalizedVariant}`)
- return {}
- }
- // was able to parse the user input using a simple reg-exp
- const chrom = matchedRegExp[2].toUpperCase()
- const pos = parseInt(matchedRegExp[3].replace(/,/g, ""))
- const ref = matchedRegExp[4]
- const alt = matchedRegExp[5]
-
- const myVariantApiUrl = `https://myvariant.info/v1/variant/chr${chrom}:g.${pos}${ref}>${alt}${genomeVersion == '38' ? '?assembly=hg38' : ''}`
-
- let myVariantApiResponse
- try {
- myVariantApiResponse = await makeRequest(myVariantApiUrl)
- } catch(e) {
- console.error(e)
- throw Error(`MyVariant API call failed: ${e}`)
- }
-
- const myVariantApiResponseJson = await myVariantApiResponse.json()
- console.log(`MyVariant API response:`, myVariantApiResponseJson)
-
- return myVariantApiResponseJson
- }
- */
-
const makeRequest = (url) => {
const method = "GET"
return new Promise(async (resolve, reject) => {
@@ -803,6 +787,10 @@
}
}
+ const getGnomadDataVersion = (genomeVersion) => {
+ return genomeVersion === "38" ? "gnomad_r4" : "gnomad_r2_1"
+ }
+
const generateSplicingResultsTable = async (normalizedVariant, variantConsequence, tool, variant, genomeVersion, maxDistance, mask, showRefAltScoreColumns) => {
/* Generate the results table to show either the SpliceAI or Pangolin scores */
@@ -868,7 +856,7 @@
const isPangolin = tool.toLowerCase() == "pangolin"
const subRowCount = isPangolin ? 2 : 4
const strand = scores['t_strand'] == "-" ? "minus" : "plus"
- const gnomadVersion = genomeVersion === "38" ? "gnomad_r3" : "gnomad_r2_1"
+ const gnomadVersion = getGnomadDataVersion(genomeVersion)
const isMainTranscript = scores['t_priority'] != "N" && (scores['t_priority'] != "C" || transcriptCategories["MS"] == undefined)
const refSeqLink = scores['t_refseq_ids']? `/
${scores['t_refseq_ids'][0]}` : ""
@@ -986,37 +974,104 @@
const alt = variantTokens[3]
const otherPredictorScores = []
- await lookupTables[genomeVersion].getLines(chrom, pos-1, pos, line => {
- //console.log("Got line:", line)
- const fields = line.split('\t')
- const lineChrom = fields[0]
- const linePos = parseInt(fields[1])
- const lineRef = fields[2]
- const lineAlt = fields[3]
- //console.log("`Got scores`:", fields)
-
- if (linePos != pos || lineRef != ref || lineAlt != alt) {
- return
- }
- const percentile = parseFloat(fields[4])
- const genePercentilethreshold = parseFloat(fields[5])
- //console.log("PrimateAI-3D", percentile, genePercentilethreshold)
- if (!isNaN(percentile)) {
- otherPredictorScores.push({
- 'name': 'PrimateAI-3D',
- 'percentile': percentile,
- 'genePercentileThreshold': genePercentilethreshold,
- })
- }
- const promoterAiScore = parseFloat(fields[6])
- //console.log("PromoterAI", promoterAiScore)
- if (!isNaN(promoterAiScore)) {
- otherPredictorScores.push({
- 'name': 'PromoterAI',
- 'score': promoterAiScore,
- })
+
+ const queryLookupTable = async () => {
+ await lookupTables[genomeVersion].getLines(chrom, pos-1, pos, line => {
+ //console.log("Got line:", line)
+ const fields = line.split('\t')
+ const lineChrom = fields[0]
+ const linePos = parseInt(fields[1])
+ const lineRef = fields[2]
+ const lineAlt = fields[3]
+ //console.log("`Got scores`:", fields)
+
+ if (linePos != pos || lineRef != ref || lineAlt != alt) {
+ return
+ }
+ const percentile = parseFloat(fields[4])
+ const genePercentilethreshold = parseFloat(fields[5])
+ //console.log("PrimateAI-3D", percentile, genePercentilethreshold)
+ if (!isNaN(percentile)) {
+ otherPredictorScores.push({
+ 'name': 'primateai3d',
+ 'percentile': percentile,
+ 'genePercentileThreshold': genePercentilethreshold,
+ })
+ }
+ const promoterAiScore = parseFloat(fields[6])
+ //console.log("PromoterAI", promoterAiScore)
+ if (!isNaN(promoterAiScore)) {
+ otherPredictorScores.push({
+ 'name': 'promoterai',
+ 'score': promoterAiScore,
+ })
+ }
+ },)
+ }
+ /*
+ joint {
+ ac
+ an
+ homozygote_count
+ hemizygote_count
+ af
+ ac_hom
+ ac_hemi
+ */
+
+ const queryGnomAD = async () => {
+ console.log("Querying gnomAD")
+ query = `{
+ variant(variantId: "${chrom.replace("chr", "")}-${pos}-${ref}-${alt}", dataset: ${getGnomadDataVersion(genomeVersion)}) {
+ in_silico_predictors {
+ id,
+ value
+ },
+ }
+ }`
+
+ const response = await fetch("https://gnomad.broadinstitute.org/api", {
+ "method": "POST",
+ "headers": {
+ "Content-Type": "application/json",
+ "Accept": "application/json",
+ },
+ "body": JSON.stringify({ query }),
+ })
+
+ if (response.ok) {
+ const responseJson = await response.json()
+ console.log("gnomAD response:", responseJson)
+ if (responseJson && responseJson.data && responseJson.data.variant) {
+ /*
+ if (responseJson.data.variant.joint && responseJson.data.variant.joint.ac && responseJson.data.variant.joint.an) {
+ otherPredictorScores.push({
+ 'name': 'gnomAD',
+ 'score': `AC: ${responseJson.data.variant.joint.ac}, AN: ${responseJson.data.variant.joint.an}`,
+ })
+ }
+ */
+
+ if (responseJson.data.variant.in_silico_predictors) {
+ responseJson.data.variant.in_silico_predictors.forEach((predictor) => {
+ if (predictor.id.startsWith("spliceai") || predictor.id.startsWith("pangolin")) {
+ return
+ }
+ otherPredictorScores.push({
+ 'name': predictor.id,
+ 'score': parseFloat(predictor.value).toFixed(3),
+ })
+ })
+ }
+ }
}
- },)
+ }
+
+
+ console.log("Querying lookup table and gnomAD")
+ await Promise.all([queryLookupTable(), queryGnomAD()]) //.map(p => p.catch(e => ({'error': e.message})))
+
+ console.log("Other predictors scores:", otherPredictorScores)
const tableRows = []
const noDifferenceDueToNormalization = variant.toLowerCase().trim().replace(/^chr/, "").replace(/[>: _-]+/g, "-") == normalizedVariant.toLowerCase().trim().replace(/^chr/, "").replace(/[>: _-]+/g, "-")
@@ -1028,12 +1083,28 @@
`
const helpTextForPredictorScores = {
- "PrimateAI-3D": `
PrimateAI-3D gene-specific thresholds are provided by the authors. Values above the threshold are annotated as likely deleterious.`,
- "PromoterAI": `PromoterAI scores range from -1 to 1 with 0 meaning no activity, negative values are under-expression and positive values are over-expression. A threshold of +/-0.1 is used for high sensitivity, and +/-0.5 for high precision.`,
+ "primateai3d": `
PrimateAI-3D gene-specific thresholds are provided by the authors. Values above the threshold are annotated as likely deleterious.`,
+ "promoterai": `PromoterAI scores range from -1 to 1 with 0 meaning no activity, negative values are under-expression and positive values are over-expression. A threshold of +/-0.1 is used for high sensitivity, and +/-0.5 for high precision.`,
+ "cadd": "CADD scores",
+ "phylop": "PhyloP scores",
+ "revel": "REVEL scores",
+ "gnomAD": "gnomAD allele counts and allele numbers",
+ }
+
+ const nameMapForPredictorScores = {
+ "primateai3d": "PrimateAI-3D",
+ "promoterai": "PromoterAI",
+ "cadd": "CADD",
+ "revel": "REVEL",
+ "revel_max": "REVEL",
+ "sift_max": "SIFT",
+ "phylop": "PhyloP",
+ "gnomad": "gnomAD",
+ "polyphen_max": "PolyPhen",
}
const colorMapForPredictorScores = {
- "PrimateAI-3D": (record) => {
+ "primateai3d": (record) => {
if (record.percentile >= record.genePercentileThreshold) { // + 0.1) {
return "#fccfb8"
//} else if (record.percentile >= record.genePercentileThreshold - 0.1) {
@@ -1042,8 +1113,28 @@
return "#ffffff"
}
},
- "PromoterAI": (record) => {
- const absScore = Math.abs(record.score)
+ "promoterai": (record) => {
+ const absScore = Math.abs(parseFloat(record.score))
+ if (absScore >= 0.5) {
+ return "#fccfb8"
+ } else if (absScore >= 0.1) {
+ return "#fff19d"
+ } else {
+ return "#ffffff"
+ }
+ },
+ "cadd": (record) => {
+ const absScore = Math.abs(parseFloat(record.score))
+ if (absScore >= 20) {
+ return "#fccfb8"
+ } else if (absScore >= 10) {
+ return "#fff19d"
+ } else {
+ return "#ffffff"
+ }
+ },
+ "revel": (record) => {
+ const absScore = Math.abs(parseFloat(record.score))
if (absScore >= 0.5) {
return "#fccfb8"
} else if (absScore >= 0.1) {
@@ -1052,47 +1143,67 @@
return "#ffffff"
}
},
+ "phylop": (record) => {
+ const absScore = Math.abs(parseFloat(record.score))
+ if (absScore >= 2) {
+ return "#fccfb8"
+ } else if (absScore >= 1) {
+ return "#fff19d"
+ } else {
+ return "#ffffff"
+ }
+ },
+
+ "default": (record) => {
+ return "#ffffff"
+ }
}
$(`#other-predictors-header`).nextAll().remove()
let firstColumn = `
-
+ |
${variant}
${normalizedVariantDiv}
| `
+
if (otherPredictorScores.length == 0) {
tableRows.push(`
${firstColumn}
PrimateAI-3D |
only available for missense variants |
`)
} else {
+ otherPredictorScores.sort((a, b) => a.name > b.name ? 1 : -1)
for (const otherPredictor of otherPredictorScores) {
let row
- if (otherPredictor.name == "PromoterAI") {
+ const bgColor = (colorMapForPredictorScores[otherPredictor.name] || colorMapForPredictorScores["default"])(otherPredictor)
+ const predictorName = nameMapForPredictorScores[otherPredictor.name] || otherPredictor.name
+ const helpIcon = helpTextForPredictorScores[otherPredictor.name] ? `
` : ""
+ if (otherPredictor.name == "primateai3d") {
row = `
${firstColumn}
- ${otherPredictor.name}
+ ${predictorName}
|
-
- ${otherPredictor.score.toFixed(3)}
-
+ |
+ ${otherPredictor.percentile.toFixed(2)} (gene-specific threshold: ${otherPredictor.genePercentileThreshold.toFixed(2)})
+ ${helpIcon}
|
`
- } else if (otherPredictor.name == "PrimateAI-3D") {
+ } else {
row = `
${firstColumn}
- ${otherPredictor.name}
+ ${predictorName}
|
-
- ${otherPredictor.percentile.toFixed(2)} (gene-specific threshold: ${otherPredictor.genePercentileThreshold.toFixed(2)})
-
+ |
+ ${otherPredictor.score}
+ ${helpIcon}
|
`
+
}
tableRows.push(row)
- firstColumn = "
| "
+ firstColumn = ""
}
}
$(`#other-predictors-header`).after(tableRows.join(""))