Skip to content

Commit

Permalink
Add script to run docking locally for wrong task inputs
Browse files Browse the repository at this point in the history
  • Loading branch information
luk27official committed May 20, 2024
1 parent 08ba636 commit 34c692b
Show file tree
Hide file tree
Showing 3 changed files with 103 additions and 22 deletions.
78 changes: 73 additions & 5 deletions frontend/client/tasks/server-docking-task.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { PredictionInfo } from "../prankweb-api";
import { PredictionInfo, getApiEndpoint } from "../prankweb-api";
import { PocketData, Point3D, ServerTaskInfo, ServerTaskLocalStorageData } from "../custom-types";

import { getPocketAtomCoordinates } from "../viewer/molstar-visualise";
Expand Down Expand Up @@ -107,13 +107,10 @@ export async function dockingHash(pocket: string, smiles: string, exhaustiveness

/**
* Downloads the result of the task.
* @param smiles SMILES identifier
* @param fileURL URL to download the result from
* @param pocket Pocket identifier
* @param exhaustiveness exhaustiveness value (for Autodock Vina)
* @returns void
*/
export async function downloadDockingResult(smiles: string, fileURL: string, pocket: string, exhaustiveness: string) {
export async function downloadDockingResult(fileURL: string) {
// https://stackoverflow.com/questions/50694881/how-to-download-file-in-react-js
fetch(fileURL)
.then((response) => response.blob())
Expand Down Expand Up @@ -182,3 +179,74 @@ export async function pollForDockingTask(predictionInfo: PredictionInfo) {
}
return localStorage.getItem(`${predictionInfo.id}_serverTasks`);
}

/**
* Generates a bash script that can be used to run the docking task locally.
* @param smiles SMILES identifier of the ligand
* @param pocket Pocket data
* @param plugin Mol* plugin
* @param prediction Prediction info
* @returns A bash script that can be used to run the docking task locally.
*/
export function generateBashScriptForDockingTask(smiles: string, pocket: PocketData, plugin: PluginUIContext, prediction: PredictionInfo) {
const box = computeBoundingBox(plugin, pocket);
const url = getApiEndpoint(prediction.database, prediction.id).replace(".", window.location.host) + `/public/${prediction.metadata.structureName}`;

const script =
`#!/bin/bash
# This script runs the docking task locally.
# It requires Docker to be installed on the system.
json_content='{
"receptor": "${prediction.metadata.structureName}",
"ligand": "ligand.smi",
"output": "output.pdbqt",
"center": {
"x": ${box.center.x},
"y": ${box.center.y},
"z": ${box.center.z}
},
"size": {
"x": ${box.size.x},
"y": ${box.size.y},
"z": ${box.size.z}
}
}'
echo "$json_content" > docking_parameters.json
echo "${smiles}" > ligand.smi
wget "${url}" -O ${prediction.metadata.structureName}.gz
gunzip ${prediction.metadata.structureName}.gz
docker run -it -v \$\{PWD\}/:/data ghcr.io/kiarka7/dodo:latest`;

return script;
}

/**
* Downloads the generated bash script with the current timestamp.
* @param script Bash script
* @returns void
*/
export async function downloadDockingBashScript(script: string) {
const today = new Date();
const filename = `docking-task-${today.toISOString()}.sh`;

const url = window.URL.createObjectURL(
new Blob([script]),
);
const link = document.createElement('a');
link.href = url;
link.setAttribute(
'download',
filename,
);

document.body.appendChild(link);
link.click();
link.parentNode!.removeChild(link);

return;
}
43 changes: 28 additions & 15 deletions frontend/client/viewer/components/tasks-tab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { Button, Paper, Typography } from "@mui/material";

import "./tasks-tab.css";
import { PredictionInfo } from "../../prankweb-api";
import { computeDockingTaskOnBackend } from "../../tasks/server-docking-task";
import { computeDockingTaskOnBackend, generateBashScriptForDockingTask, downloadDockingBashScript } from "../../tasks/server-docking-task";
import { PluginUIContext } from "molstar/lib/mol-plugin-ui/context";
import { computePocketVolume } from "../../tasks/client-atoms-volume";
import { TasksTable } from "./tasks-table";
Expand Down Expand Up @@ -73,28 +73,39 @@ export default function TasksTab(props: { pockets: PocketData[], predictionInfo:
type: TaskType.Server,
name: "Docking",
compute: (params, customName, pocketIndex) => {
const smiles = params[0].replaceAll(" ", "");

const handleInvalidDockingInput = (baseMessage: string) => {
if (baseMessage === "") {
setInvalidInputMessage("");
return;
}

setInvalidInputMessage(`${baseMessage} Try running the script below to run docking locally.`);
setDockingScript(generateBashScriptForDockingTask(smiles, props.pockets[pocketIndex], props.plugin, props.predictionInfo));
};

// check if exhaustiveness is a number
const exhaustiveness = params[1].replaceAll(",", ".").replaceAll(" ", "");
if (isNaN(parseFloat(exhaustiveness))) {
setInvalidInput(true);
if (isNaN(parseInt(exhaustiveness))) {
handleInvalidDockingInput("Exhaustiveness must be an integer.");
return;
}

// 1-64 is the allowed range
if (Number(exhaustiveness) < 1 || Number(exhaustiveness) > 64) {
setInvalidInput(true);
handleInvalidDockingInput("Exhaustiveness must be in the range 1-64.");
return;
}

setInvalidInput(false);
const smiles = params[0].replaceAll(" ", "");
// check if SMILES is < 300 characters, otherwise
// TODO: add an option to prepare a script that will run DODO with the ligand
if (smiles.length > 300) {
setInvalidInput(true);
handleInvalidDockingInput("SMILES must be shorter than 300 characters.");
return;
}

handleInvalidDockingInput("");

let savedTasks = localStorage.getItem(`${props.predictionInfo.id}_serverTasks`);
if (!savedTasks) savedTasks = "[]";
const tasks: ServerTaskLocalStorageData[] = JSON.parse(savedTasks);
Expand Down Expand Up @@ -127,7 +138,8 @@ export default function TasksTab(props: { pockets: PocketData[], predictionInfo:
const [name, setName] = React.useState<string>("");
const [parameters, setParameters] = React.useState<string[]>([]);
const [forceUpdate, setForceUpdate] = React.useState<number>(0);
const [invalidInput, setInvalidInput] = React.useState<boolean>(false);
const [invalidInputMessage, setInvalidInputMessage] = React.useState<string>("");
const [dockingScript, setDockingScript] = React.useState<string>("");

const handleTaskTypeChange = (event: SelectChangeEvent) => {
const newTask = tasks.find(task => task.id == Number(event.target.value))!;
Expand Down Expand Up @@ -228,16 +240,17 @@ export default function TasksTab(props: { pockets: PocketData[], predictionInfo:
)
}
{
invalidInput &&
<tr>
<td colSpan={2}>
<Typography variant="body1" style={{ color: "red" }}>Error: The task could not be created. Check the formatting.</Typography>
</td>
</tr>
invalidInputMessage === "" ? null :
<tr>
<td colSpan={2}>
<Typography variant="body1" style={{ color: "red" }}>{invalidInputMessage}</Typography>
</td>
</tr>
}
<tr>
<td>
<Button variant="contained" onClick={handleSubmitButton}>Create task</Button>
{dockingScript !== "" && <>&nbsp;<Button onClick={() => downloadDockingBashScript(dockingScript)} variant="contained" color="warning">Download the script</Button></>}
</td>
<td>

Expand Down
4 changes: 2 additions & 2 deletions frontend/client/viewer/components/tasks-table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ export function TasksTable(props: { pocket: PocketData | null, predictionInfo: P
const handleResultClick = (serverTask: ServerTaskLocalStorageData) => {
switch (serverTask.type) {
case ServerTaskType.Docking:
downloadDockingResult(serverTask.params[0], serverTask.responseData[0].url, serverTask.pocket.toString(), serverTask.params[1]);
downloadDockingResult(serverTask.responseData[0].url);
break;
default:
break;
Expand Down Expand Up @@ -237,4 +237,4 @@ export function TasksTable(props: { pocket: PocketData | null, predictionInfo: P
</TableBody>
</Table>
);
}
};

0 comments on commit 34c692b

Please sign in to comment.