Skip to content

Commit

Permalink
Show active/passive residues in 3D + remove non-surface residues + re…
Browse files Browse the repository at this point in the history
…generate restraints schema

Also sort display residue ids
  • Loading branch information
sverhoeven committed Apr 4, 2024
1 parent 1421602 commit 0173584
Show file tree
Hide file tree
Showing 7 changed files with 331 additions and 22 deletions.
2 changes: 1 addition & 1 deletion app/entry.client.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
*/

import { RemixBrowser } from "@remix-run/react";
import { startTransition, StrictMode } from "react";
import { startTransition } from "react";
import { hydrateRoot } from "react-dom/client";

startTransition(() => {
Expand Down
200 changes: 200 additions & 0 deletions app/haddock3-restraints-client/schema.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,57 @@ export interface paths {
patch?: never;
trace?: never;
};
"/restrain_bodies": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
get?: never;
put?: never;
/** Restrain Bodies */
post: operations["restrain_bodies_restrain_bodies_post"];
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/calc_accessibility": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
get?: never;
put?: never;
/** Calculate Accessibility */
post: operations["calculate_accessibility_calc_accessibility_post"];
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/validate_tbl": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
get?: never;
put?: never;
/** Validate Tbl */
post: operations["validate_tbl_validate_tbl_post"];
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
}
export type webhooks = Record<string, never>;
export interface components {
Expand Down Expand Up @@ -78,6 +129,20 @@ export interface components {
*/
segid2: string;
};
/** CalcAccessibilityRequest */
CalcAccessibilityRequest: {
/**
* Structure
* @description The structure file as base64 encoded string.
*/
structure: string;
/**
* Cutoff
* @description Relative cutoff for sidechain accessibility.
* @default 0.4
*/
cutoff: number;
};
/** HTTPValidationError */
HTTPValidationError: {
/** Detail */
Expand Down Expand Up @@ -108,6 +173,40 @@ export interface components {
*/
surface: number[];
};
/** RestrainBodiesRequest */
RestrainBodiesRequest: {
/**
* Structure
* @description The structure file as base64 encoded string.
*/
structure: string;
/**
* Exclude
* @description Chains to exclude from the calculation.
* @default []
*/
exclude: string[];
};
/** ValidateTblRequest */
ValidateTblRequest: {
/**
* Tbl
* @description The TBL file as base64 encoded string.
*/
tbl: string;
/**
* Pcs
* @description Flag to indicate if the TBL file is in PCS mode.
* @default false
*/
pcs: boolean;
/**
* Quick
* @description Check global formatting before going line by line (opening/closing parenthesis and quotation marks.
* @default false
*/
quick: boolean;
};
/** ValidationError */
ValidationError: {
/** Location */
Expand Down Expand Up @@ -192,4 +291,105 @@ export interface operations {
};
};
};
restrain_bodies_restrain_bodies_post: {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
requestBody: {
content: {
"application/json": components["schemas"]["RestrainBodiesRequest"];
};
};
responses: {
/** @description Successful Response */
200: {
headers: {
[name: string]: unknown;
};
content: {
"text/plain": string;
};
};
/** @description Validation Error */
422: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["HTTPValidationError"];
};
};
};
};
calculate_accessibility_calc_accessibility_post: {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
requestBody: {
content: {
"application/json": components["schemas"]["CalcAccessibilityRequest"];
};
};
responses: {
/** @description Successful Response */
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": {
[key: string]: number[] | undefined;
};
};
};
/** @description Validation Error */
422: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["HTTPValidationError"];
};
};
};
};
validate_tbl_validate_tbl_post: {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
requestBody: {
content: {
"application/json": components["schemas"]["ValidateTblRequest"];
};
};
responses: {
/** @description Successful Response */
200: {
headers: {
[name: string]: unknown;
};
content: {
"text/plain": string;
};
};
/** @description Validation Error */
422: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["HTTPValidationError"];
};
};
};
};
}
2 changes: 1 addition & 1 deletion app/scenarios/MolViewerDialog.client.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { useState } from "react";
import { Structure } from "ngl";

import { Button } from "~/components/ui/button";
import { NGLComponent, NGLStage, Viewer } from "~/scenarios/Viewer.client";
import { NGLComponent, NGLStage } from "~/scenarios/Viewer.client";
import { Dialog, DialogContent, DialogTrigger } from "~/components/ui/dialog";

export function MolViewerDialog({ structure }: { structure?: Structure }) {
Expand Down
91 changes: 81 additions & 10 deletions app/scenarios/MoleculeSubForm.client.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,15 @@ import { FormItem } from "~/scenarios/FormItem";
import { PDBFileInput } from "~/scenarios/PDBFileInput.client";
import { ChainSelect } from "~/scenarios/ChainSelect";
import { ResiduesSelect } from "~/scenarios/ResiduesSelect";
import { Molecule, chainsFromStructure } from "./molecule.client";
import {
Chains,
Molecule,
Residue,
chainsFromStructure,
} from "./molecule.client";
import { Viewer } from "./Viewer.client";
import { client } from "~/haddock3-restraints-client/client";
import { Checkbox } from "~/components/ui/checkbox";

export type ResidueSelection = { chain: string; resno: number[] };
export type ActPassSelection = {
Expand Down Expand Up @@ -41,6 +47,40 @@ export async function passiveFromActive(
return data;
}

async function calculateAccessibility(file: File, chains: Chains) {
const structure = await file.text();
const body = {
structure: btoa(structure),
cutoff: 0.4,
};
const { data, error } = await client.POST("/calc_accessibility", {
body,
});
if (error) {
console.error(error);
return;
}
Object.entries(data).forEach(([chain, surfaceResidues]) => {
if (!surfaceResidues) {
return;
}
chains[chain].forEach((residue: Residue) => {
residue.surface = surfaceResidues.includes(residue.resno);
});
});
}

function filterBuriedResidues(
chain: string,
residues: number[],
chains: Chains
) {
return residues.filter((resno) => {
const residue = chains[chain].find((r) => r.resno === resno);
return residue && residue.surface;
});
}

export function MoleculeSubForm({
name,
legend,
Expand All @@ -55,9 +95,11 @@ export function MoleculeSubForm({
onActPassChange: (actpass: ActPassSelection) => void;
}) {
const [molecule, setMolecule] = useState<Molecule | undefined>();
const [showPassive, setShowPassive] = useState(false);

function handleStructureLoad(structure: Structure, file: File) {
async function handleStructureLoad(structure: Structure, file: File) {
const chains = chainsFromStructure(structure);
await calculateAccessibility(file, chains);
setMolecule({ structure, chains, file });
}

Expand All @@ -72,23 +114,39 @@ export function MoleculeSubForm({
function handleActiveResiduesChange(activeResidues: number[]) {
passiveFromActive(molecule!.file, {
chain: actpass.active.chain,
resno: activeResidues,
resno: filterBuriedResidues(
actpass.active.chain,
activeResidues,
molecule!.chains
),
})
.then((passiveResidues) => {
const filteredPassiveResidues = filterBuriedResidues(
actpass.passive.chain,
passiveResidues,
molecule!.chains
);
onActPassChange({
active: { chain: actpass.active.chain, resno: activeResidues },
passive: { chain: actpass.passive.chain, resno: passiveResidues },
passive: {
chain: actpass.passive.chain,
resno: filteredPassiveResidues,
},
});
})
.catch((error) => {
onActPassChange({
active: { chain: actpass.active.chain, resno: activeResidues },
passive: { chain: actpass.passive.chain, resno: [] },
});
console.error(error);
// TODO show error to user
});
}

return (
<fieldset className="border border-solid border-primary p-3">
<legend className="">{legend}</legend>
<legend>{legend}</legend>
<FormItem name={name} label="Structure">
<PDBFileInput
name={name}
Expand All @@ -103,6 +161,7 @@ export function MoleculeSubForm({
structure={molecule?.structure}
chain={actpass.active.chain}
active={actpass.active.resno}
passive={showPassive ? actpass.passive.resno : []}
/>
) : (
<p>Load a structure first</p>
Expand All @@ -121,11 +180,23 @@ export function MoleculeSubForm({
</FormItem>
<FormItem name={`${name}-active-residues`} label="Active residues">
{actpass.active.chain ? (
<ResiduesSelect
options={molecule?.chains[actpass.active.chain] || []}
selected={actpass.active.resno}
onChange={handleActiveResiduesChange}
/>
<>
<ResiduesSelect
options={molecule?.chains[actpass.active.chain] || []}
selected={actpass.active.resno}
onChange={handleActiveResiduesChange}
/>
<div className="flex items-center space-x-2">
<Checkbox
id="showpassive"
defaultChecked={showPassive}
onCheckedChange={() => setShowPassive(!showPassive)}
/>
<label htmlFor="showpassive" className="">
Show passive restraints
</label>
</div>
</>
) : (
<p>Select a chain first</p>
)}
Expand Down
Loading

0 comments on commit 0173584

Please sign in to comment.