Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added TM-Align Data to compare page #258

Merged
merged 9 commits into from
May 3, 2024
62 changes: 62 additions & 0 deletions backend/src/api/protein.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
from fastapi import APIRouter
from fastapi.responses import FileResponse, StreamingResponse
from fastapi.requests import Request
import re

router = APIRouter()

Expand Down Expand Up @@ -397,3 +398,64 @@ def align_proteins(proteinA: str, proteinB: str):
except Exception as e:
log.error(e)
raise HTTPException(status_code=500, detail=str(e))


class TMAlignInfo(CamelModel):
aligned_length: str | None
rmsd: str | None
seq_id: str | None
chain1_tm_score: str | None
chain2_tm_score: str | None
alignment_string: str


# Returns the alignment string info from TM Align's console log.
@router.get(
"/protein/tmalign/{proteinA:str}/{proteinB:str}", response_model=TMAlignInfo
)
def get_tm_info(proteinA: str, proteinB: str):
if not protein_name_found(proteinA) or not protein_name_found(proteinB):
raise HTTPException(
status_code=404, detail="One of the proteins provided is not found in DB"
)
try:
filepath_pdbA = stored_pdb_file_name(proteinA)
filepath_pdbB = stored_pdb_file_name(proteinB)
tmalign_output = tm_align_return(filepath_pdbA, filepath_pdbB, 1)

log.warn("TM Align Output follows:")
# Split TMAlign data into an array format
tmalign_output_list = tmalign_output.splitlines()
log.warn(tmalign_output)

# Grab aligned length, RMSD, and Seq ID
tmalign_tri = tmalign_output_list[12].split(", ")
# Note: \d+?.\d* means "match 1 or more numbers, 0 or 1 decimal points, and 0 or more numbers" in regex
aligned_length = re.search("\d+?.\d*", tmalign_tri[0]).group()
rmsd = re.search("\d+.?\d*", tmalign_tri[1]).group()
seq_id = re.search("\d+.?\d*", tmalign_tri[2]).group()

# Grabs both TM scores
# NOTE: This is ONLY grabbing the TM-Score from the file. It's leaving out the LN and d0 stats.
chain1_normalized_tm_score = re.search(
"\d+.?\d*", tmalign_output_list[13]
).group()
chain2_normalized_tm_score = re.search(
"\d+.?\d*", tmalign_output_list[14]
).group()

# Grabs TM Alignment String
tmalign_string = "\n".join(tmalign_output_list[18:21])
log.warn(tmalign_string)

return TMAlignInfo(
aligned_length=aligned_length,
rmsd=rmsd,
seq_id=seq_id,
chain1_tm_score=chain1_normalized_tm_score,
chain2_tm_score=chain2_normalized_tm_score,
alignment_string=tmalign_string,
)
except Exception as e:
log.error(e)
raise HTTPException(status_code=500, detail=str(e))
10 changes: 7 additions & 3 deletions backend/src/tmalign.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ def tm_align(
return desired_file


def tm_align_return(pdbA: str, pdbB: str) -> str:
def tm_align_return(pdbA: str, pdbB: str, consoleOutput=False) -> str:
"""
Description:
Returns two overlaid, aligned, and colored PDB structures in a single PDB file.
Expand All @@ -121,10 +121,14 @@ def tm_align_return(pdbA: str, pdbB: str) -> str:
with UniqueTempDir(base_path=TMALIGN_LOCATION) as temp_dir_path:
try:
output_location = os.path.join(temp_dir_path, "output")
cmd = f"{TMALIGN_EXECUTABLE} {pdbA} {pdbB} -o {output_location}"
cmd = f"{TMALIGN_EXECUTABLE} {pdbA} {pdbB} -o {output_location} > {output_location}.txt"
bash_cmd(cmd)

tmalign_pdb_path = f"{output_location}_all_atm"
tmalign_pdb_path = (
f"{output_location}_all_atm"
if not consoleOutput
else f"{output_location}.txt"
)

with open(tmalign_pdb_path, "r") as tmalign_pdb_file:
tmalign_pdb_file_str = tmalign_pdb_file.read()
Expand Down
1 change: 1 addition & 0 deletions frontend/src/lib/openapi/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export type { RangeFilter } from './models/RangeFilter';
export type { SearchProteinsBody } from './models/SearchProteinsBody';
export type { SearchProteinsResults } from './models/SearchProteinsResults';
export type { SimilarProtein } from './models/SimilarProtein';
export type { TMAlignInfo } from './models/TMAlignInfo';
export type { UploadArticleImageComponent } from './models/UploadArticleImageComponent';
export type { UploadArticleProteinComponent } from './models/UploadArticleProteinComponent';
export type { UploadArticleTextComponent } from './models/UploadArticleTextComponent';
Expand Down
13 changes: 13 additions & 0 deletions frontend/src/lib/openapi/models/TMAlignInfo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/* generated using openapi-typescript-codegen -- do not edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export type TMAlignInfo = {
alignedLength: (string | null);
rmsd: (string | null);
seqId: (string | null);
chain1TmScore: (string | null);
chain2TmScore: (string | null);
alignmentString: string;
};

24 changes: 24 additions & 0 deletions frontend/src/lib/openapi/services/DefaultService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import type { RangeFilter } from '../models/RangeFilter';
import type { SearchProteinsBody } from '../models/SearchProteinsBody';
import type { SearchProteinsResults } from '../models/SearchProteinsResults';
import type { SimilarProtein } from '../models/SimilarProtein';
import type { TMAlignInfo } from '../models/TMAlignInfo';
import type { UploadArticleImageComponent } from '../models/UploadArticleImageComponent';
import type { UploadArticleProteinComponent } from '../models/UploadArticleProteinComponent';
import type { UploadArticleTextComponent } from '../models/UploadArticleTextComponent';
Expand Down Expand Up @@ -329,6 +330,29 @@ export class DefaultService {
},
});
}
/**
* Get Tm Info
* @param proteinA
* @param proteinB
* @returns TMAlignInfo Successful Response
* @throws ApiError
*/
public static getTmInfo(
proteinA: string,
proteinB: string,
): CancelablePromise<TMAlignInfo> {
return __request(OpenAPI, {
method: 'GET',
url: '/protein/tmalign/{proteinA}/{proteinB}',
path: {
'proteinA': proteinA,
'proteinB': proteinB,
},
errors: {
422: `Validation Error`,
},
});
}
/**
* Get Article
* get_article
Expand Down
110 changes: 86 additions & 24 deletions frontend/src/routes/Align.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@
import TMAlignEntry from "../lib/ProteinLinkCard.svelte";

import { onMount } from "svelte";
import { Backend, BACKEND_URL, type SimilarProtein, type ProteinEntry } from "../lib/backend";
import { Backend, BACKEND_URL, type SimilarProtein, type ProteinEntry, type TMAlignInfo } from "../lib/backend";
import Molstar from "../lib/Molstar.svelte";
import DelayedSpinner from "../lib/DelayedSpinner.svelte";
import { DownloadOutline } from "flowbite-svelte-icons";
import { Button } from "flowbite-svelte";
import * as d3 from "d3";
import { undoFormatProteinName } from "../lib/format";
import AlignBlock from "../lib/AlignBlock.svelte";
import { AccordionItem, Accordion } from "flowbite-svelte";

export let proteinA: string;
export let proteinB: string;
Expand All @@ -19,6 +20,8 @@
let foldseekData: SimilarProtein;
let foldseekError = false;
let error = false;
let tmData: TMAlignInfo;
let tmDataError = false;

const dark2green = d3.schemeDark2[0];
const dark2orange = d3.schemeDark2[1];
Expand Down Expand Up @@ -46,8 +49,16 @@
);
foldseekError = true;
}


try {
tmData = await Backend.getTmInfo(proteinA, proteinB)
} catch (e) {
console.error(e);
console.error("NEED TO DOWMLOAD T M ALIGN IN THE SERVER. SEE THE SERVER ERROR MESSAGE.")
tmDataError = true;
}


// if we could not find the entry, the id is garbo
if (entryA == null || entryB == null) error = true;
console.log(entryA, entryB);
Expand All @@ -72,33 +83,79 @@
</div>
<TMAlignEntry entry={entryA} color={dark2green} />
<TMAlignEntry entry={entryB} color={dark2orange} />
<h1>
Foldseek Data
</h1>
{#if foldseekData === undefined && !foldseekError}
<Accordion>
<AccordionItem>
<span slot="header" style="font-size: 18px;"
>TM-Align Data <span
style="font-weight: 300; font-size: 15px;"
></span
></span
>
{#if tmData === undefined && ! tmDataError}
<DelayedSpinner
text="Loading TM-Align"
textRight
msDelay={0}
/>
{:else if tmData !== undefined}
<div>
<b>Aligned Length:</b> {tmData.alignedLength}
</div>
<div>
<b>RMSD:</b> {tmData.rmsd}
</div>
<div>
<b>Seq_ID:</b> {tmData.seqId}
</div>
<div>
<b>TM Score (Chain 1-Normalized):</b> {tmData.chain1TmScore}
</div>
<div>
<b>TM Score (Chain 2-Normalized):</b> {tmData.chain2TmScore}
</div>
<div>
<b>Alignment String</b>
<div class="sidescroll">
{tmData.alignmentString}
</div>
</div>
{/if}
</AccordionItem>
</Accordion>
<Accordion>
<AccordionItem>
<span slot="header" style="font-size: 18px;"
>Foldseek Data <span
style="font-weight: 300; font-size: 15px;"
></span
></span
>
{#if foldseekData === undefined && !foldseekError}
<DelayedSpinner
text="Loading Foldseek..."
textRight
msDelay={0}
/>
{:else if foldseekData !== undefined}
<div>
<b>Prob. Match:</b> {foldseekData.prob}
</div>
<div>
<b>E-Value:</b> {foldseekData.evalue}
</div>
<div>
<b>Region of Similarity</b>
<AlignBlock
width={260}
height={20}
ogLength={entryA.length}
qstart={foldseekData.qstart}
qend={foldseekData.qend}
/>
</div>
{/if}
{:else if foldseekData !== undefined}
<div>
<b>Prob. Match:</b> {foldseekData.prob}
</div>
<div>
<b>E-Value:</b> {foldseekData.evalue}
</div>
<div>
<b>Region of Similarity</b>
<AlignBlock
width={260}
height={20}
ogLength={entryA.length}
qstart={foldseekData.qstart}
qend={foldseekData.qend}
/>
</div>
{/if}
</AccordionItem>
</Accordion>
<div style="width: 300px;" class="mt-3">
<Button href="{BACKEND_URL}/protein/pdb/{combined}"
>Download Aligned PDB File<DownloadOutline
Expand Down Expand Up @@ -143,4 +200,9 @@
overflow: hidden;
text-overflow: ellipsis;
}
.sidescroll {
font-family: monospace;
white-space: pre;
overflow-x: scroll;
}
</style>
Loading