Skip to content

Commit 7893397

Browse files
committed
Made multimers processed by Foldmason be parsed and visualized properly.
note: cif format is not yet supported.
1 parent c9c66b1 commit 7893397

7 files changed

Lines changed: 410 additions & 103 deletions

File tree

frontend/FoldMasonSearch.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@
4848
:value="q.name"
4949
@click:close="removeQuery(index)"
5050
>
51-
{{ q.name }}
51+
{{ q.name.split('-_-_-_')[0] }}
5252
</v-chip>
5353
</div>
5454
</div>

frontend/MSA.vue

Lines changed: 66 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -407,8 +407,70 @@ export default {
407407
}, 400),
408408
},
409409
beforeMount() {
410+
const parseSuffix = (suffix) => {
411+
if (!suffix) return []
412+
413+
return suffix.split("-").map((s) => {
414+
const out = {};
415+
const info = s.split("_");
416+
417+
if (info.length != 3) return out;
418+
419+
out.chain = info[0];
420+
out.end = Number(info[1]);
421+
out.offset = Number(info[2]);
422+
return out;
423+
});
424+
}
425+
426+
const chainToOffset = (arr) => {
427+
if (!arr || arr.length == 0) {
428+
return {'A' : 0}
429+
}
430+
431+
const out = {}
432+
for (let obj of arr) {
433+
out[obj.chain] = obj.offset
434+
}
435+
return out
436+
}
437+
438+
const indexToChainAndOrigResn = (aa, arr) => {
439+
const length = aa.replaceAll('-', '').length
440+
const chains = Array(length + 1).fill('A')
441+
const resns = Array.from({length: length + 1}, (_, i) => i)
442+
443+
if (!arr || arr.length == 0) {
444+
return {chains: chains, resns: resns}
445+
}
446+
447+
let index = 0
448+
449+
for (let i = 1; i < length + 1; i++) {
450+
chains[i] = arr[index].chain
451+
resns[i] = i - arr[index].offset
452+
453+
if (i == arr[index].end) {
454+
index++
455+
}
456+
}
457+
return {chains: chains, resns: resns}
458+
}
459+
410460
this.handleUpdateMatchRatio();
461+
411462
for (let entry of this.entries) {
463+
if (/-_-_-_/.test(entry.name)) {
464+
entry.suffix = entry.name.split("-_-_-_")[1];
465+
}
466+
const parsed = parseSuffix(entry.suffix)
467+
468+
entry.offsets = chainToOffset(parsed)
469+
470+
const obj = indexToChainAndOrigResn(entry.aa, parsed)
471+
entry.chains = obj.chains
472+
entry.resns = obj.resns
473+
412474
entry.name = tryFixName(entry.name)
413475
}
414476
},
@@ -455,7 +517,10 @@ export default {
455517
const copy = {
456518
name: entry.name,
457519
aa: "",
458-
ss: ""
520+
ss: "",
521+
offsets: entry.offsets,
522+
chains: entry.chains,
523+
suffix: entry.suffix,
459524
}
460525
for (let i = 0; i < this.mask.length; i++) {
461526
if (this.mask[i] === 1) {

frontend/MSAView.vue

Lines changed: 76 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -3,70 +3,70 @@
33
<div class="msa-wrapper" ref="msaWrapper">
44
<div v-for="([start, end], i) in blockRanges" class="msa-block-wrapper">
55
<div class="msa-block" @mousemove="onBlockMouseMove($event, start, end)" @mouseleave="onBlockMouseLeave" @click="onBlockClick($event, start, end)">
6-
<span class="conservation-label">Conservation</span>
7-
<svg
8-
class="conservation-track"
9-
:viewBox="`0 0 ${end - start} ${conservationHeight}`"
10-
:style="{
11-
gridColumn: 2,
12-
gridRow: 1,
13-
width: 'calc((1ch + 4px) * ' + String(end - start) + ')',
14-
height: conservationHeight + 'px',
15-
transform: 'translateX(-2px)'
16-
}"
17-
preserveAspectRatio="none"
18-
>
19-
<path :d="conservationPath(getConservationRange(start, end))" />
20-
<g :transform="`scale(${1 / conservationScaleX}, 1)`">
21-
<text
22-
v-for="(idx) in getConservationMaxCols(start, end)"
23-
:key="`cons-max-${i}-${idx}`"
24-
class="conservation-max"
25-
:x="(idx + 0.5) * conservationScaleX"
26-
y="0"
27-
text-anchor="middle"
28-
dominant-baseline="hanging"
29-
>*</text>
30-
</g>
31-
</svg>
32-
<!-- <SequenceLogo
33-
:sequences="getEntryRanges(start, end, makeGradients=false)"
34-
:alphabet="alphabet"
35-
:lineLen="lineLen"
36-
/> -->
37-
<template v-for="({ name, aa, ss, seqStart, css }, j) in getEntryRanges(start, end)">
38-
<span class="header" :title="name" :style="headerStyle(j)" @click="handleClickHeader($event, j)">{{
39-
name }}</span>
40-
<div class="sequence-wrapper" :style="sequenceStyle(j)">
41-
<span class="sequence"
42-
:style="[css, { 'color': sequenceColor, 'font-weight': fontWeight }]">{{
43-
alphabet == 'aa' ? aa : ss }}</span>
44-
</div>
45-
<div class="column-wrapper" v-if="j == 0" :style="overlayStyle(aa.length)">
46-
<div
47-
v-for="idx in getBlockHighlights(start, end)"
48-
:key="`sel-${i}-${idx}`"
49-
class="column-marker column-active"
50-
:style="columnMarkerStyle(idx, end - start)"
51-
></div>
52-
<div
53-
v-if="getHoverLocalIndex(start, end) !== null"
54-
class="column-marker column-hover"
55-
:style="columnMarkerStyle(getHoverLocalIndex(start, end), end - start)"
56-
></div>
57-
<div
58-
v-if="getPreviewLocalIndex(start, end) !== null"
59-
class="column-marker column-preview"
60-
:style="columnMarkerStyle(getPreviewLocalIndex(start, end), end - start)"
61-
></div>
62-
<div
63-
v-if="hoverInfo && hoverInfo.start === start && hoverInfo.rowIndex !== null"
64-
class="residue-hover"
65-
:style="residueMarkerStyle(hoverInfo, end - start)"
66-
></div>
67-
</div>
68-
<span class="count" :style="countStyle(j)">{{ countSequence(aa, seqStart).toString() }}</span>
69-
</template>
6+
<span class="conservation-label">Conservation</span>
7+
<svg
8+
class="conservation-track"
9+
:viewBox="`0 0 ${end - start} ${conservationHeight}`"
10+
:style="{
11+
gridColumn: 2,
12+
gridRow: 1,
13+
width: 'calc((1ch + 4px) * ' + String(end - start) + ')',
14+
height: conservationHeight + 'px',
15+
transform: 'translateX(-2px)'
16+
}"
17+
preserveAspectRatio="none"
18+
>
19+
<path :d="conservationPath(getConservationRange(start, end))" />
20+
<g :transform="`scale(${1 / conservationScaleX}, 1)`">
21+
<text
22+
v-for="(idx) in getConservationMaxCols(start, end)"
23+
:key="`cons-max-${i}-${idx}`"
24+
class="conservation-max"
25+
:x="(idx + 0.5) * conservationScaleX"
26+
y="0"
27+
text-anchor="middle"
28+
dominant-baseline="hanging"
29+
>*</text>
30+
</g>
31+
</svg>
32+
<!-- <SequenceLogo
33+
:sequences="getEntryRanges(start, end, makeGradients=false)"
34+
:alphabet="alphabet"
35+
:lineLen="lineLen"
36+
/> -->
37+
<template v-for="({ name, aa, ss, seqStart, css }, j) in getEntryRanges(start, end)">
38+
<span class="header" :title="name" :style="headerStyle(j)" @click="handleClickHeader($event, j)">{{
39+
name }}</span>
40+
<div class="sequence-wrapper" :style="sequenceStyle(j)">
41+
<span class="sequence"
42+
:style="[css, { 'color': sequenceColor, 'font-weight': fontWeight }]">{{
43+
alphabet == 'aa' ? aa : ss }}</span>
44+
</div>
45+
<div class="column-wrapper" v-if="j == 0" :style="overlayStyle(aa.length)">
46+
<div
47+
v-for="idx in getBlockHighlights(start, end)"
48+
:key="`sel-${i}-${idx}`"
49+
class="column-marker column-active"
50+
:style="columnMarkerStyle(idx, end - start)"
51+
></div>
52+
<div
53+
v-if="getHoverLocalIndex(start, end) !== null"
54+
class="column-marker column-hover"
55+
:style="columnMarkerStyle(getHoverLocalIndex(start, end), end - start)"
56+
></div>
57+
<div
58+
v-if="getPreviewLocalIndex(start, end) !== null"
59+
class="column-marker column-preview"
60+
:style="columnMarkerStyle(getPreviewLocalIndex(start, end), end - start)"
61+
></div>
62+
<div
63+
v-if="hoverInfo && hoverInfo.start === start && hoverInfo.rowIndex !== null"
64+
class="residue-hover"
65+
:style="residueMarkerStyle(hoverInfo, end - start)"
66+
></div>
67+
</div>
68+
<span class="count" :style="countStyle(j)">{{ countSequence(aa, seqStart).toString() }}</span>
69+
</template>
7070
</div>
7171
</div>
7272
</div>
@@ -476,9 +476,18 @@ export default {
476476
handleUpdateEntries() {
477477
this.actualResno.length = 0
478478
this.entries.forEach((e, i) => {
479+
const isMultimer = !e.suffix ? false : true
479480
let acc = 0
480481
const nums = [...e.aa].map((c, j) => {
481-
if (c !== '-') return c + String(++acc)
482+
if (c !== '-') {
483+
if (isMultimer) {
484+
const chain = e.chains[++acc]
485+
const resn = acc - e.offsets[chain]
486+
return chain + ':' + c + String(resn)
487+
} else {
488+
return c + String(++acc)
489+
}
490+
}
482491
else return ""
483492
})
484493
this.actualResno.push(nums)
@@ -990,11 +999,11 @@ export default {
990999
content: "";
9911000
position: absolute;
9921001
left: 50%;
993-
top: -12px;
1002+
bottom: -12px;
9941003
transform: translateX(-50%);
9951004
border-left: 4px solid transparent;
9961005
border-right: 4px solid transparent;
997-
border-top: 6px solid rgba(0, 0, 0, 0.7);
1006+
border-bottom: 6px solid rgba(0, 0, 0, 0.7);
9981007
}
9991008
.theme--dark .column-active {
10001009
background-color: rgba(255, 255, 255, 0.4);

frontend/ResultView.vue

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,8 @@ import AllAtomPredictMixin from './AllAtomPredictMixin.vue';
163163
import NavigationButton from './NavigationButton.vue';
164164
165165
import { mockPDB, mergePdbs, concatenatePdbs,
166-
getChainName, getAccession, getAbsOffsetTop} from './Utilities';
166+
getChainName, getAccession, getAbsOffsetTop,
167+
encodeMultimer} from './Utilities';
167168
168169
import { debounce } from './lib/debounce';
169170
import ResultFoldseekDB from './ResultFoldseekDB.vue';
@@ -547,8 +548,10 @@ export default {
547548
let out = ""
548549
if (arr.length > 1) {
549550
// out = mergePdbs(arr)
550-
out = concatenatePdbs(arr) // For now, just concatenate chains into one single chain
551+
const result = encodeMultimer(arr)
552+
out = result.pdb
551553
name += chainset
554+
name += result.suffix
552555
} else {
553556
out = arr[0].pdb
554557
}

frontend/SelectToSendPanelFoldMason.vue

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@
6363
</template>
6464

6565
<script>
66-
import { getAccession, mockPDB, getResidueIndices } from './Utilities.js';
66+
import { getAccession, mockPDB, getResidueIndices, decodeMultimer, storeChains, revertChainInfo } from './Utilities.js';
6767
import { StorageWrapper} from './lib/HistoryMixin.js';
6868
import { BlobDatabase } from './lib/BlobDatabase.js';
6969
import { pulchra } from 'pulchra-wasm';
@@ -196,11 +196,23 @@ export default {
196196
},
197197
async getMockPdb(entry) {
198198
const mock = mockPDB(entry.ca, entry.aa.replace(/-/g, ''), 'A');
199-
return await pulchra(mock);
199+
if (!entry.suffix) {
200+
return await pulchra(mock);
201+
} else {
202+
const decoded = decodeMultimer(mock, entry.suffix)
203+
return decoded
204+
}
200205
},
201206
async getFullPdb(entry, signal) {
202207
const mock = mockPDB(entry.ca, entry.aa.replace(/-/g, ''), 'A');
203-
return await this.predictGivenPdb(mock, signal)
208+
if (!entry.suffix) {
209+
return await this.predictGivenPdb(mock, signal)
210+
} else {
211+
const decoded = decodeMultimer(mock, entry.suffix)
212+
const chains = storeChains(decoded)
213+
const pdb = await this.predictGivenPdb(decoded, signal)
214+
return revertChainInfo(pdb, chains)
215+
}
204216
},
205217
},
206218
computed: {
@@ -218,15 +230,15 @@ export default {
218230
}
219231
return str
220232
} else {
221-
return `<span title="${this.resnoStr.join(", ")}">`
233+
return `<span title="${this.motifStr}">`
222234
+ String(this.selectedCounts)
223235
+ (this.selectedCounts > 1 ? ' residues</span>' : ' residue</span>')
224236
}
225237
} else if (this.$vuetify.breakpoint.xlOnly) {
226238
let str = this.entries[this.targetIndex].name
227239
if (this.selectedCounts > 0) {
228240
str = '<strong>' + str + "</strong>&nbsp;"
229-
return str + `/&nbsp;<span title="${this.resnoStr.join(", ")}">`
241+
return str + `/&nbsp;<span title="${this.motifStr}">`
230242
+ String(this.selectedCounts) + (this.selectedCounts > 1 ? ' residues</span>' : ' residue</span>')
231243
} else {
232244
str = '<strong>' + str + "</strong>"
@@ -241,7 +253,7 @@ export default {
241253
} else {
242254
str = '<strong>' + str + "</strong>&nbsp;"
243255
}
244-
return str + `/&nbsp;<span title="${this.resnoStr.join(", ")}">`
256+
return str + `/&nbsp;<span title="${this.motifStr}">`
245257
+ String(this.selectedCounts) + (this.selectedCounts > 1 ? ' residues</span>' : ' residue</span>')
246258
} else {
247259
if (str.length >= 14) {
@@ -280,7 +292,12 @@ export default {
280292
return ""
281293
} else {
282294
return this.resnoStr
283-
.map((i) => 'A'+ String(i))
295+
.map((i) => {
296+
let entry = this.entries[this.targetIndex]
297+
let chain = entry.chains[i]
298+
let resno = i - entry.offsets[chain]
299+
return chain + String(resno)
300+
})
284301
.join(", ")
285302
}
286303
},

0 commit comments

Comments
 (0)