Skip to content

Commit 56057ab

Browse files
authored
Added missense support to alphafold (#793)
1 parent 4e67a92 commit 56057ab

8 files changed

+592
-39
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import { html, css, LitElement } from 'https://unpkg.com/[email protected]/index.js?module';
2+
3+
export class ConfidenceMissenseToggle extends LitElement {
4+
5+
static get styles() {
6+
7+
return css`
8+
.panel-title {
9+
display: flex;
10+
align-items: center;
11+
justify-content: space-between;
12+
background-color: var(--main-dark);
13+
color: white;
14+
font-weight: bold;
15+
padding: 4px 28px 4px 4px;
16+
}
17+
`
18+
}
19+
20+
static get properties() {
21+
return {
22+
isConfidenceSelected: { attribute: false }
23+
}
24+
}
25+
26+
createOption(id,label,isChecked)
27+
{
28+
if(isChecked)
29+
{
30+
return html`
31+
<input type="radio" name="toggleConfidenceMissense" @change=${this.onToggleChanged}
32+
id=${id} checked /><label for=${id}>${label}</label>
33+
`;
34+
}
35+
else
36+
{
37+
return html`
38+
<input type="radio" name="toggleConfidenceMissense" @change=${this.onToggleChanged}
39+
id=${id} /><label for=${id}>${label}</label>
40+
`;
41+
}
42+
}
43+
44+
render() {
45+
46+
let confidenceChecked = this.createOption('tconfidence','Model Confidence',this.isConfidenceSelected);
47+
let missenseChecked = this.createOption('tmissense','AlphaMissense Pathogenicity',!this.isConfidenceSelected);
48+
49+
return html`
50+
<div class="panel-title">
51+
<span>Toggle</span>
52+
</div>
53+
<div class="body">
54+
<div class="row">
55+
56+
${confidenceChecked}
57+
${missenseChecked}
58+
59+
</div>
60+
`};
61+
};
62+
63+
customElements.define('confidence-missense-toggle', ConfidenceMissenseToggle);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
const COLOR_MAP = {
2+
min: { r: 0, g: 0, b: 187 },
3+
center: { r: 255, g: 255, b: 255 },
4+
max: { r: 255, g: 0, b: 0 },
5+
}
6+
7+
const BASE_COLOR = { r: 100, g: 100, b: 100 };
8+
9+
export class EnsemblAlphafoldMissense
10+
{
11+
constructor() {
12+
this.sequenceColors = [];
13+
this.hasMissenseLoaded = false;
14+
this.missenseSelection = {
15+
data: [
16+
{ struct_asym_id: 'A', color: BASE_COLOR }
17+
]
18+
}
19+
}
20+
21+
getSelection() {
22+
return this.missenseSelection;
23+
}
24+
25+
hasMissense()
26+
{
27+
return this.hasMissenseLoaded;
28+
}
29+
30+
async loadMissense(annotationCSVUrl) {
31+
32+
const missenseCSVResponse = await fetch(annotationCSVUrl);
33+
34+
if (!missenseCSVResponse.ok) {
35+
this.hasMissenseLoaded = false;
36+
console.log("Unable to load missense");
37+
return;
38+
}
39+
40+
//process file
41+
this.hasMissenseLoaded = this.extractColors(await missenseCSVResponse.text());
42+
}
43+
44+
extractColors(data){
45+
const DELIMITER = ',';
46+
const MUTATION_COLUMN = 0;
47+
const SCORE_COLUMN = 1;
48+
const N_HEADER_ROWS = 1;
49+
50+
const lines = data.split('\n').filter(line => line.trim() !== '' && !line.trim().startsWith('#'));
51+
if (N_HEADER_ROWS > 0) lines.splice(0, N_HEADER_ROWS);
52+
const rows = lines.map(line => line.split(DELIMITER));
53+
54+
//const scores = { [seq_id: number]: number[] } = {};
55+
const scores = {};
56+
for (const row of rows) {
57+
const mutation = row[MUTATION_COLUMN];
58+
const score = Number(row[SCORE_COLUMN]);
59+
const match = mutation.match(/([A-Za-z]+)([0-9]+)([A-Za-z]+)/);
60+
if (!match)
61+
{
62+
console.log("Missense CSV parse error!");
63+
console(row);
64+
throw new Error(`FormatError: cannot parse "${mutation}" as a mutation (should look like Y123A)`);
65+
}
66+
const seq_id = match[2];
67+
68+
if(!scores[seq_id])
69+
{
70+
scores[seq_id] = []
71+
}
72+
scores[seq_id].push(Number(score));
73+
74+
}
75+
76+
for (const seq_id in scores) {
77+
const aggrScore = this.mean(scores[seq_id]); // The original paper also uses mean (https://www.science.org/doi/10.1126/science.adg7492)
78+
const color = this.assignColor(aggrScore);
79+
this.sequenceColors.push([Number(seq_id), color]);
80+
}
81+
82+
this.missenseSelection.data.push(
83+
...this.sequenceColors.map((rc) => ({ 'struct_asym_id': 'A', 'residue_number': rc[0], 'color': rc[1]}))
84+
) // TODO here I assume the model is always chain A, is it correct?
85+
86+
return true;
87+
}
88+
89+
mean(values) {
90+
return values.reduce((a, b) => a + b, 0) / values.length;
91+
}
92+
93+
/** Map a score within [0, 1] to a color (0 -> COLOR_MAP.min, 0.5 -> COLOR_MAP.center, 1 -> COLOR_MAP.max) */
94+
assignColor(score) {
95+
if (score <= 0.5) {
96+
return this.mixColor(COLOR_MAP.min, COLOR_MAP.center, 2 * score);
97+
}
98+
else {
99+
return this.mixColor(COLOR_MAP.center, COLOR_MAP.max, 2 * score - 1);
100+
}
101+
}
102+
103+
mixColor(color0, color1, q) {
104+
return {
105+
r: color0.r * (1 - q) + color1.r * q,
106+
g: color0.g * (1 - q) + color1.g * q,
107+
b: color0.b * (1 - q) + color1.b * q,
108+
};
109+
}
110+
111+
}
112+

widgets/htdocs/alphafold/components/ensemblAlphafoldProtein.js

+49-13
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
1-
import { html, LitElement } from 'https://unpkg.com/[email protected]/index.js?module';
1+
import { html, nothing, LitElement } from 'https://unpkg.com/[email protected]/index.js?module';
22

33
import { MolstarController } from '../controllers/molstarController.js';
44
import { ExonsController } from '../controllers/exonsController.js';
55
import { ProteinFeaturesController } from '../controllers/proteinFeaturesController.js';
66
import { VariantsController } from '../controllers/variantsController.js';
77
import { ConfidenceColorsController } from '../controllers/confidenceColorsController.js';
8+
import { MissenseController } from '../controllers/missenseController.js';
9+
import { ConfidenceMissenseColorsController} from '../controllers/confidenceMissenseController.js'
810

911
import {
1012
fetchAlphaFoldId,
@@ -15,6 +17,8 @@ import './exonsControlPanel.js';
1517
import './variantsControlPanel.js';
1618
import './defaultColorsPanel.js';
1719
import './proteinFeaturesControlPanel.js';
20+
import './missenseColorsPanel.js';
21+
import './confidenceMissenseToggle.js';
1822

1923

2024
export class EnsemblAlphafoldProtein extends LitElement {
@@ -35,6 +39,12 @@ export class EnsemblAlphafoldProtein extends LitElement {
3539
this.confidenceColorsController = new ConfidenceColorsController({
3640
host: this
3741
})
42+
this.confidenceMissenseColorsController = new ConfidenceMissenseColorsController({
43+
host: this
44+
})
45+
this.missenseController = new MissenseController({host:this});
46+
47+
3848
}
3949

4050
// prevent the component from rendering into the shadow DOM, see README.md for explanation
@@ -53,10 +63,14 @@ export class EnsemblAlphafoldProtein extends LitElement {
5363
this.exonsController.load({ rootUrl: restUrlRoot, enspId }),
5464
this.proteinFeaturesController.load({ rootUrl: restUrlRoot, enspId }),
5565
this.variantsController.load({ rootUrl: restUrlRoot, enspId })
56-
]).then(([alphafoldId]) => {
66+
]).then(this.molstarController.getModelFileAndAnnotationUrl
67+
).then((alphafoldData) => {
68+
this.missenseController.load(alphafoldData.annotation);
69+
return alphafoldData;
70+
}).then((alphafoldData)=> {
5771
// below is a promise; make sure it gets returned
5872
return this.molstarController.renderAlphafoldStructure({
59-
moleculeId: alphafoldId,
73+
modelFileUrl: alphafoldData.cif,
6074
canvasContainer: molstarContainer
6175
});
6276
}).then(() => {
@@ -66,6 +80,7 @@ export class EnsemblAlphafoldProtein extends LitElement {
6680
});
6781
this.onLoadComplete();
6882
}).catch(error => {
83+
console.log(error);
6984
this.onLoadFailed(error);
7085
});
7186
}
@@ -74,19 +89,27 @@ export class EnsemblAlphafoldProtein extends LitElement {
7489
if (!this.loadCompleted) {
7590
return;
7691
}
92+
7793
const selections = [
7894
this.exonsController.getSelectedExons(),
7995
this.proteinFeaturesController.getSelectedFeatures(),
8096
this.variantsController.getSelectedSiftVariants(),
8197
this.variantsController.getSelectedPolyphenVariants()
82-
].flat();
83-
84-
const showConfidence = !selections.length
98+
].flat()
99+
const hasNoSelection = !selections.length
100+
101+
let updateData = {
102+
selections: selections,
103+
showConfidence: hasNoSelection
104+
}
85105

86-
this.molstarController.updateSelections({
87-
selections,
88-
showConfidence
89-
});
106+
if(hasNoSelection && !this.confidenceMissenseColorsController.showConfidence)
107+
{
108+
updateData.altSelection = this.missenseController.getSelection();
109+
updateData.showConfidence = false;
110+
}
111+
112+
this.molstarController.updateSelections(updateData);
90113
}
91114

92115
onLoadComplete() {
@@ -106,7 +129,7 @@ export class EnsemblAlphafoldProtein extends LitElement {
106129

107130
render() {
108131
return html`
109-
<link rel="stylesheet" type="text/css" href="https://www.ebi.ac.uk/pdbe/pdb-component-library/css/pdbe-molstar-light-3.0.0.css">
132+
<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/npm/[email protected]/build/pdbe-molstar-light.css">
110133
<div class="container">
111134
<div class="molstar-canvas"></div>
112135
<div class="controls">
@@ -147,9 +170,22 @@ export class EnsemblAlphafoldProtein extends LitElement {
147170
.onVariantSelectionChange=${this.variantsController.onSelectionChange}
148171
></variants-control-panel>
149172
`}
150-
<default-colors-panel></default-colors-panel>
173+
174+
${this.missenseController.hasMissense() ? html`
175+
<confidence-missense-toggle
176+
.onToggleChanged=${this.confidenceMissenseColorsController.toggle}
177+
.isConfidenceSelected=${this.confidenceMissenseColorsController.getShowConfidence()}
178+
></confidence-missense-toggle>
179+
` : nothing }
180+
181+
${this.confidenceMissenseColorsController.showConfidence ? html`
182+
<default-colors-panel></default-colors-panel>` :
183+
html`
184+
<missense-colors-panel></missense-colors-panel>`
185+
}
186+
151187
`;
152188
}
153189
}
154190

155-
customElements.define('ensembl-alphafold-protein', EnsemblAlphafoldProtein);
191+
customElements.define('ensembl-alphafold-protein', EnsemblAlphafoldProtein);

0 commit comments

Comments
 (0)