Skip to content

Commit

Permalink
Merge pull request #374 from moorhen-coot/dev
Browse files Browse the repository at this point in the history
Add PAE clustering to Slice-n-Dice UI
  • Loading branch information
FilomenoSanchez authored Mar 22, 2024
2 parents f267431 + 96d739c commit d12e526
Showing 1 changed file with 91 additions and 22 deletions.
113 changes: 91 additions & 22 deletions baby-gru/src/components/modal/MoorhenSliceNDiceModal.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { useDispatch, useSelector } from "react-redux"
import { MoorhenDraggableModalBase } from "./MoorhenDraggableModalBase"
import { moorhen } from "../../types/moorhen"
import { convertRemToPx, convertViewtoPx, findConsecutiveRanges, hslToHex } from "../../utils/MoorhenUtils"
import { Button, Card, Col, Dropdown, Form, FormSelect, Row, Spinner, SplitButton, Stack } from "react-bootstrap"
import { convertRemToPx, convertViewtoPx, findConsecutiveRanges, hslToHex, readTextFile } from "../../utils/MoorhenUtils"
import { Button, Card, Col, Dropdown, Form, FormSelect, OverlayTrigger, Row, Spinner, SplitButton, Stack } from "react-bootstrap"
import { Backdrop, IconButton, Slider, Tooltip } from "@mui/material"
import { useCallback, useEffect, useMemo, useRef, useState } from "react"
import { MoorhenMoleculeSelect } from "../select/MoorhenMoleculeSelect"
import { addMolecule, hideMolecule, showMolecule } from "../../store/moleculesSlice"
import { CenterFocusWeakOutlined, DownloadOutlined } from "@mui/icons-material"
import { CenterFocusWeakOutlined, DownloadOutlined, InfoOutlined, WarningOutlined } from "@mui/icons-material"
import { MoorhenMolecule } from "../../utils/MoorhenMolecule"
import { MoorhenColourRule } from "../../utils/MoorhenColourRule"

Expand Down Expand Up @@ -96,19 +96,19 @@ const MoorhenSliceNDiceCard = (props: {
}}
defaultValue={1}
min={1}
max={maxFragmentSize}
max={20}
style={{color: themeColor}}
/>
<Tooltip title="View">
<IconButton style={{marginRight:'0.5rem', color: themeColor}} onClick={() => props.fragmentMolecule.centreOn('/*/*/*/*', true, true)}>
<CenterFocusWeakOutlined/>
</IconButton>
</Tooltip>
<Tooltip title="Download">
</Tooltip>
<Tooltip title="Download">
<IconButton style={{marginRight:'0.5rem', color: themeColor}} onClick={handleDownload}>
<DownloadOutlined/>
</IconButton>
</Tooltip>
</Tooltip>
</Col>
</Row>
</Card.Body>
Expand All @@ -121,6 +121,8 @@ export const MoorhenSliceNDiceModal = (props: {
commandCentre: React.RefObject<moorhen.CommandCentre>;
}) => {

const paeFileContents = useRef<null | string>(null)
const paeFileUploadFormRef = useRef<null | HTMLInputElement>(null)
const clusteringTypeSelectRef = useRef<null | HTMLSelectElement>(null)
const moleculeSelectRef = useRef<null | HTMLSelectElement>(null)
const nClustersRef = useRef<number>(2)
Expand All @@ -137,6 +139,7 @@ export const MoorhenSliceNDiceModal = (props: {
const width = useSelector((state: moorhen.State) => state.sceneSettings.width)
const height = useSelector((state: moorhen.State) => state.sceneSettings.height)

const [paeFileIsUploaded, setPaeFileIsUploaded] = useState<boolean>(false)
const [thresholdType, setThresholdType] = useState<string>('bfactor')
const [moleculeBfactors, setMoleculeBfactors] = useState<{ cid: string; bFactor: number; normalised_bFactor: number; }[]>(null)
const [moleculeMinBfactor, setMoleculeMinBfactor] = useState<number>(null)
Expand All @@ -146,6 +149,7 @@ export const MoorhenSliceNDiceModal = (props: {
const [selectedMolNo, setSelectedMolNo] = useState<number>(null)
const [clusteringType, setClusteringType] = useState<string>('birch')
const [busy, setBusy] = useState<boolean>(false)
const [showError, setShowError] = useState<boolean>(false)
const [slicingResults, setSlicingResults] = useState<moorhen.Molecule[]>(null)

useEffect(() => {
Expand Down Expand Up @@ -223,7 +227,7 @@ export const MoorhenSliceNDiceModal = (props: {
}, [slicingResults, moleculeBfactors])

const doSlice = useCallback(async () => {
if (!moleculeSelectRef.current.value) {
if (!moleculeSelectRef.current.value || (clusteringTypeSelectRef.current.value === 'pae' && !paeFileContents.current)) {
return
}

Expand All @@ -235,21 +239,27 @@ export const MoorhenSliceNDiceModal = (props: {
setBusy(true)

let deleteSelectedMoleculeOnExit = false
if (selectedMoleculeCopyRef.current?.excludedSelections?.length > 0) {
if (selectedMoleculeCopyRef.current?.excludedSelections?.length > 0 && clusteringTypeSelectRef.current.value !== 'pae') {
deleteSelectedMoleculeOnExit = true
selectedMolecule = await selectedMolecule.copyFragmentUsingCid('//', false)
selectedMolecule.excludedSelections = selectedMoleculeCopyRef.current.excludedSelections
selectedMolecule.excludedCids = selectedMoleculeCopyRef.current.excludedCids
deleteHiddenResidues(selectedMolecule)
}

if (slicingResults?.length > 0) {
await Promise.all(
slicingResults.map(sliceMolecule => sliceMolecule.delete())
)
}

let commandArgs: (string | number)[]
const pae_file = ""
switch (clusteringTypeSelectRef.current.value) {
case "kmeans":
case "agglomerative":
case "birch":
commandArgs = [ selectedMolecule.molNo, nClustersRef.current, clusteringTypeSelectRef.current.value, pae_file ]
case "pae":
commandArgs = [ selectedMolecule.molNo, nClustersRef.current, clusteringTypeSelectRef.current.value, paeFileContents.current ? paeFileContents.current : ""]
break
default:
console.warn(`Unkown clustering algorithm ${clusteringTypeSelectRef.current}`)
Expand All @@ -258,21 +268,25 @@ export const MoorhenSliceNDiceModal = (props: {

if (!commandArgs) {
setBusy(false)
setSlicingResults(null)
return
}

if (slicingResults?.length > 0) {
await Promise.all(
slicingResults.map(sliceMolecule => sliceMolecule.delete())
)
}

const result = await props.commandCentre.current.cootCommand({
command: 'slicendice_slice',
commandArgs: commandArgs,
returnType: 'vector_pair_string_int'
}, false)

if (result.data.result.status === 'Exception') {
console.warn(result.data.consoleMessage)
setBusy(false)
setSlicingResults(null)
setTimeout(() => setShowError(true), 500)
setTimeout(() => setShowError(false), 3000)
return
}

selectedMoleculeCopyRef.current?.hide?.('CRs', '/*/*/*/*')

const slices = [...new Set(result.data.result.result.filter(item => item.slice !== -1).map(item => item.slice))]
Expand All @@ -289,6 +303,24 @@ export const MoorhenSliceNDiceModal = (props: {
newColourRule.setParentMolecule(newMolecule)
newMolecule.defaultColourRules = [ newColourRule ]
newMolecule.setAtomsDirty(true)
if (clusteringTypeSelectRef.current.value === 'pae') {
await newMolecule.updateAtoms()
const bFactors = newMolecule.getResidueBFactors()
let cidsToDelete: string[]
if (thresholdTypeRef.current === 'bfactor') {
cidsToDelete = bFactors.filter(residue => residue.bFactor > bFactorThresholdRef.current).map(residue => residue.cid)
} else {
cidsToDelete = bFactors.filter(residue => residue.bFactor < bFactorThresholdRef.current).map(residue => residue.cid)
}
if (cidsToDelete?.length > 0) {
const result = await newMolecule.deleteCid(cidsToDelete.join('||'), false)
if (result.second < 1) {
await newMolecule.delete()
return
}
newMolecule.setAtomsDirty(true)
}
}
await newMolecule.fetchIfDirtyAndDraw('CRs')
return newMolecule
}))
Expand All @@ -297,7 +329,7 @@ export const MoorhenSliceNDiceModal = (props: {
await selectedMolecule.delete()
}

setSlicingResults(newMolecules.sort( (a, b) => { return parseInt(a.name.replace('Slice #', '')) - parseInt(b.name.replace('Slice #', '')) }))
setSlicingResults(newMolecules.filter(molecule => molecule !== undefined).sort( (a, b) => { return parseInt(a.name.replace('Slice #', '')) - parseInt(b.name.replace('Slice #', '')) }))
setBusy(false)
}, [molecules, slicingResults, isDark])

Expand Down Expand Up @@ -360,23 +392,41 @@ export const MoorhenSliceNDiceModal = (props: {
}
}, [slicingResults])

const handlePaeFileUpload = async (e: React.ChangeEvent<HTMLInputElement>) => {
if (e.target.files?.length > 0) {
const fileContents = await readTextFile(e.target.files[0]) as string
if (fileContents.length > 0) {
paeFileContents.current = fileContents
setPaeFileIsUploaded(true)
} else {
paeFileContents.current = null
setPaeFileIsUploaded(false)
}
}
}

const bodyContent = <Stack direction="vertical" gap={1}>
<Stack direction="horizontal" gap={1} style={{display: 'flex', width: '100%'}}>
<Form.Group style={{ margin: '0.5rem', width: '100%' }}>
<Form.Label>Clustering algorithm...</Form.Label>
<FormSelect size="sm" ref={clusteringTypeSelectRef} defaultValue={'birch'} onChange={(evt) => {
setClusteringType(evt.target.value)
if (evt.target.value === 'pae') {
paeFileContents.current = null
setPaeFileIsUploaded(false)
}
clusteringTypeSelectRef.current.value = evt.target.value
setClusteringType(evt.target.value)
}}>
<option value={'birch'} key={'birch'}>Birch</option>
<option value={'kmeans'} key={'kmeans'}>K-Means</option>
<option value={'agglomerative'} key={'agglomerative'}>Agglomerative</option>
<option value={'pae'} key={'pae'}>PAE</option>
</FormSelect>
</Form.Group>
<MoorhenMoleculeSelect {...props} width="100%" molecules={molecules} allowAny={false} ref={moleculeSelectRef} onChange={(evt) => setSelectedMolNo(parseInt(evt.target.value))}/>
</Stack>
<Stack direction="horizontal" gap={1} style={{display: 'flex', width: '100%'}}>
{ ['kmeans', 'agglomerative', 'birch'].includes(clusteringType) &&
{ ['kmeans', 'agglomerative', 'birch', 'pae'].includes(clusteringType) &&
<div style={{ paddingLeft: '2rem', paddingRight: '2rem', paddingTop: '0.1rem', paddingBottom: '0.1rem', width: '100%'}}>
<span>Number of slices</span>
<Slider
Expand Down Expand Up @@ -471,6 +521,15 @@ export const MoorhenSliceNDiceModal = (props: {
/>
</div>
</Stack>
{clusteringType === 'pae' &&
<Form.Group style={{ margin: '0.5rem', padding: '0rem' }} controlId="uploadPAE">
<Form.Label>Upload PAE file</Form.Label>
<Tooltip title='Predicted Aligned Error (PAE) .json file' placement="top">
<InfoOutlined style={{marginLeft: '0.1rem', marginBottom: '0.2rem', width: '15px', height: '15px'}}/>
</Tooltip>
<Form.Control ref={paeFileUploadFormRef} type="file" multiple={false} accept=".json" onChange={(e: React.ChangeEvent<HTMLInputElement>) => {handlePaeFileUpload(e)}} />
</Form.Group>
}
<hr></hr>
<Row>
{slicingResults?.length > 0 ? <span>Found {slicingResults.length} possible slice(s)</span> : null}
Expand All @@ -491,7 +550,7 @@ export const MoorhenSliceNDiceModal = (props: {
</SplitButton>
</Stack>
<Stack gap={2} direction='horizontal' style={{ alignItems: 'center', alignContent: 'center', justifyContent: 'center' }}>
<Button variant='primary' onClick={doSlice}>
<Button variant='primary' onClick={doSlice} disabled={clusteringType === 'pae' && !paeFileIsUploaded}>
Slice
</Button>
<SplitButton id='download-slice-n-dice' variant="info" title="Save & Exit" onClick={() => handleClose(true)}>
Expand All @@ -501,9 +560,19 @@ export const MoorhenSliceNDiceModal = (props: {
</Stack>
</Stack>

const spinnerContent = <Backdrop sx={{ color: '#fff', zIndex: (theme) => theme.zIndex.drawer + 1 }} open={busy}>
const spinnerContent = <Backdrop sx={{ display: 'flex', flexDirection: busy ? 'row' : 'column', color: '#fff', zIndex: (theme) => theme.zIndex.drawer + 1 }} open={busy || showError}>
{busy ?
<>
<Spinner animation="border" style={{ marginRight: '0.5rem' }}/>
<span>Slicing...</span>
</>
: showError ?
<>
<WarningOutlined style={{width: '35px', height: '35px'}}/>
<span>Something went wrong...</span>
</>
: null
}
</Backdrop>


Expand Down

0 comments on commit d12e526

Please sign in to comment.