From 1dad6a198aa00a894d2a4f2ef1245712375165ff Mon Sep 17 00:00:00 2001 From: xnought Date: Thu, 18 Apr 2024 16:06:37 -0700 Subject: [PATCH] feat: color by pLDDT --- backend/src/api/protein.py | 19 +++++++ frontend/src/lib/Molstar.svelte | 31 ++++++++++- frontend/src/lib/venomeMolstarUtils.ts | 74 ++++++++++++++++++++++++++ frontend/src/routes/Protein.svelte | 65 +++++++++++++++++++--- 4 files changed, 179 insertions(+), 10 deletions(-) diff --git a/backend/src/api/protein.py b/backend/src/api/protein.py index 87dd20dc..4e55a169 100644 --- a/backend/src/api/protein.py +++ b/backend/src/api/protein.py @@ -105,11 +105,30 @@ def str_as_file_stream(input_str: str, filename_as: str) -> StreamingResponse: ) +def get_residue_bfactors(pdb: PDB): + chains = {} + for chain in pdb.structure.get_chains(): + chains[chain.get_id()] = [] + for r in chain.get_residues(): + for a in r.get_atoms(): + chains[chain.get_id()].append(a.bfactor) + break + return chains + + """ ENDPOINTS TODO: add the other protein types here instead of in api_types.py """ +@router.get("/protein/pLDDT/{protein_name:str}", response_model=dict[str, list[float]]) +def get_pLDDT_given_protein(protein_name: str): + if protein_name_found(protein_name): + pdb = parse_protein_pdb(protein_name, encoding="file") + return get_residue_bfactors(pdb) + return {} + + class UploadPNGBody(CamelModel): protein_name: str base64_encoding: str diff --git a/frontend/src/lib/Molstar.svelte b/frontend/src/lib/Molstar.svelte index 53beb6e2..30d89086 100644 --- a/frontend/src/lib/Molstar.svelte +++ b/frontend/src/lib/Molstar.svelte @@ -1,7 +1,12 @@ diff --git a/frontend/src/lib/venomeMolstarUtils.ts b/frontend/src/lib/venomeMolstarUtils.ts index d3342928..0ffad3e6 100644 --- a/frontend/src/lib/venomeMolstarUtils.ts +++ b/frontend/src/lib/venomeMolstarUtils.ts @@ -1,6 +1,13 @@ import { BACKEND_URL } from "./backend"; import { PDBeMolstarPlugin } from "../../venome-molstar/lib"; import type { InitParams } from "../../venome-molstar/lib/spec"; +import type { QueryParam } from "../../venome-molstar/lib/helpers"; +import * as d3 from "d3"; + +export type ResidueColor = { r: number; g: number; b: number }; +export type ChainId = string; +export type ChainColors = { [chainId: ChainId]: ResidueColor[] }; +export type ChainpLDDT = { [chainId: ChainId]: number[] }; export async function screenshotMolstar(initParams: Partial) { const { div, molstar } = await renderHeadless(initParams); @@ -74,3 +81,70 @@ export const defaultInitParams = (name: string): Partial => ({ "controlInfo", ], }); + +export function colorResidues({ + colors = [], + entity_id = undefined, + struct_asym_id = "A", +}: { + colors?: { r: number; g: number; b: number }[]; + entity_id?: string; + struct_asym_id?: string; +} = {}): QueryParam[] { + let selections: QueryParam[] = []; + for (let i = 0; i < colors.length; i++) { + const color = colors[i]; + const residueIndex = i + 1; + const residueColoring = { + entity_id, + struct_asym_id, + color, + start_residue_number: residueIndex, + end_residue_number: residueIndex, + }; + selections.push(residueColoring); + } + return selections; +} + +const alphafoldColorscheme = [ + "rgb(1,83,214)", // > 90 + "rgb(100,203,243)", // > 70 + "rgb(255,219,18)", // > 50 + "rgb(255,125,69)", // < 50 +]; +export function pLDDTToAlphaFoldResidueColors(pLDDT: number[]): ResidueColor[] { + const colors = pLDDT.map((d) => { + if (d > 90) { + return alphafoldColorscheme[0]; + } else if (d > 70) { + return alphafoldColorscheme[1]; + } else if (d > 50) { + return alphafoldColorscheme[2]; + } else { + return alphafoldColorscheme[3]; + } + }); + return colors.map((c) => { + const rgb = d3.color(c)!.rgb()!; + return { r: rgb.r, g: rgb.g, b: rgb.b }; + }); +} + +export function pLDDTToResidueColors(pLDDT: number[]): ResidueColor[] { + const colors = pLDDT.map((d) => { + if (d > 90) { + return alphafoldColorscheme[0]; + } else if (d > 70) { + return alphafoldColorscheme[1]; + } else if (d > 50) { + return alphafoldColorscheme[2]; + } else { + return alphafoldColorscheme[3]; + } + }); + return colors.map((c) => { + const rgb = d3.color(c)!.rgb()!; + return { r: rgb.r, g: rgb.g, b: rgb.b }; + }); +} diff --git a/frontend/src/routes/Protein.svelte b/frontend/src/routes/Protein.svelte index 44280c35..c9532419 100644 --- a/frontend/src/routes/Protein.svelte +++ b/frontend/src/routes/Protein.svelte @@ -11,18 +11,31 @@ } from "../lib/format"; import { navigate } from "svelte-routing"; import References from "../lib/References.svelte"; - import { ChevronDownSolid, PenOutline } from "flowbite-svelte-icons"; + import { + ChevronDownSolid, + PaletteOutline, + PenOutline, + RefreshOutline, + UndoOutline, + UndoSolid, + } from "flowbite-svelte-icons"; import EntryCard from "../lib/EntryCard.svelte"; import SimilarProteins from "../lib/SimilarProteins.svelte"; import DelayedSpinner from "../lib/DelayedSpinner.svelte"; import { user } from "../lib/stores/user"; import { AccordionItem, Accordion } from "flowbite-svelte"; + import { + pLDDTToAlphaFoldResidueColors, + pLDDTToResidueColors, + } from "../lib/venomeMolstarUtils"; + import type { ChainColors, ChainpLDDT } from "../lib/venomeMolstarUtils"; const fileDownloadDropdown = ["pdb", "fasta"]; export let urlId: string; let entry: ProteinEntry | null = null; let error = false; + let chainColors: ChainColors = {}; // when this component mounts, request protein wikipedia entry from backend onMount(async () => { @@ -123,13 +136,11 @@
- -
+
Organism
{entry.speciesName} @@ -145,6 +156,44 @@ >
+ +
+ {#if Object.keys(chainColors).length > 0} + + {/if} + +