| |
|
|
| |
| |
|
|
| from abc import ABC, abstractmethod |
| from dataclasses import dataclass |
| from io import StringIO |
| from typing import List |
|
|
| import esm |
| import torch |
| from biotite.structure import AtomArray |
| import numpy as np |
| from openfold.np.residue_constants import atom_order |
| from torch.utils._pytree import tree_map |
|
|
| from language.utilities import pdb_file_to_atomarray |
|
|
|
|
| @dataclass |
| class FoldingResult: |
| atoms: AtomArray |
| ptm: float |
| plddt: float |
|
|
|
|
| class FoldingCallback(ABC): |
| "Interface for running ESMFold and other folding methods." |
|
|
| def __init__(self) -> None: |
| pass |
|
|
| @abstractmethod |
| def load(self, device: str) -> None: |
| pass |
|
|
| @abstractmethod |
| def fold(self, sequence: str, residue_indices: List[int]) -> FoldingResult: |
| pass |
|
|
|
|
| class EsmFoldv1(FoldingCallback): |
| "Runs ESMFold v1.0." |
|
|
| def __init__(self) -> None: |
| super().__init__() |
|
|
| self.model = None |
|
|
| def load(self, device: str) -> None: |
| self.model = esm.pretrained.esmfold_v1().eval() |
| self.model = self.model.to(device) |
|
|
| def fold(self, sequence: str, residue_indices: List[int]) -> FoldingResult: |
| assert self.model is not None, "Must call load() before fold()." |
|
|
| |
| |
| residue_indices = np.array(residue_indices) - 1 |
|
|
| raw_output = self.model.infer( |
| sequence, residx=torch.Tensor(residue_indices).long().reshape(1, -1), |
| ) |
| raw_output = tree_map(lambda x: x.to("cpu"), raw_output) |
|
|
| pdb_string = esm.esmfold.v1.misc.output_to_pdb(raw_output)[0] |
| atoms: AtomArray = pdb_file_to_atomarray(StringIO(pdb_string)) |
|
|
| plddt = raw_output["plddt"] |
| plddt = plddt[0, ...].numpy() |
| plddt = plddt.transpose() |
| plddt = plddt[atom_order["CA"], :] |
| plddt = float(plddt.mean()) / 100.0 |
|
|
| ptm = float(raw_output["ptm"]) |
|
|
| return FoldingResult(atoms=atoms, ptm=ptm, plddt=plddt) |
|
|