Skip to content

Commit

Permalink
Merge pull request #49 from MannLabs/FEAT-multi-fasta-gui
Browse files Browse the repository at this point in the history
FEAT multi fasta gui
  • Loading branch information
GeorgWa authored Nov 6, 2023
2 parents 4d0b3b3 + 14d2f90 commit 63688b6
Show file tree
Hide file tree
Showing 13 changed files with 154 additions and 57 deletions.
31 changes: 16 additions & 15 deletions alphadia/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ def gui():
help="Extract DIA precursors from a list of raw files using a spectral library."
)
@click.argument(
"output-location",
"output-directory",
type=click.Path(exists=True, file_okay=False, dir_okay=True),
required=False,
)
Expand Down Expand Up @@ -119,33 +119,34 @@ def extract(**kwargs):
with open(kwargs['config'], 'r') as f:
config_update = yaml.safe_load(f)

output_location = None
if kwargs['output_location'] is not None:
output_location = kwargs['output_location']
output_directory = None
if kwargs['output_directory'] is not None:
output_directory = kwargs['output_directory']

if "output" in config_update:
output_location = config_update['output']
if "output_directory" in config_update:
output_directory = config_update['output_directory']

if output_location is None:
logging.error("No output location specified.")
if output_directory is None:
logging.error("No output directory specified.")
return

reporting.init_logging(kwargs['output_location'])
reporting.init_logging(kwargs['output_directory'])
logger = logging.getLogger()

# assert input files have been specified
files = None
files = []
if kwargs['file'] is not None:
files = list(kwargs['file'])

if kwargs['directory'] is not None:
files += [os.path.join(kwargs['directory'], f) for f in os.listdir(kwargs['directory'])]

if "files" in config_update:
files += config_update['files'] if type(config_update['files']) is list else [config_update['files']]
if "raw_file_list" in config_update:
files += config_update['raw_file_list'] if type(config_update['raw_file_list']) is list else [config_update['raw_file_list']]

print(config_update)
if (files is None) or (len(files) == 0):
logging.error("No files specified.")
logging.error("No raw files specified.")
return

# assert library has been specified
Expand All @@ -164,7 +165,7 @@ def extract(**kwargs):
for f in files:
logger.progress(f" {f}")
logger.progress(f"Using library {library}.")
logger.progress(f"Saving output to {output_location}.")
logger.progress(f"Saving output to {output_directory}.")

try:

Expand All @@ -178,7 +179,7 @@ def extract(**kwargs):
#config_update = eval(kwargs['config_update']) if kwargs['config_update'] else None

plan = Plan(
output_location,
output_directory,
files,
library,
config_update = config_update
Expand Down
7 changes: 7 additions & 0 deletions alphadia/libtransform.py
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,7 @@ class AnnotateFasta(ProcessingStep):
def __init__(self,
fasta_path_list: typing.List[str],
drop_unannotated: bool = True,
drop_decoy: bool = True,
) -> None:
"""Annotate the precursor dataframe with protein information from a FASTA file.
Expects a `SpecLibBase` object as input and will return a `SpecLibBase` object.
Expand All @@ -183,6 +184,7 @@ def __init__(self,
super().__init__()
self.fasta_path_list = fasta_path_list
self.drop_unannotated = drop_unannotated
self.drop_decoy = drop_decoy

def validate(self, input: SpecLibBase) -> bool:
"""Validate the input object. It is expected that the input is a `SpecLibBase` object and that all FASTA files exist."""
Expand All @@ -201,6 +203,11 @@ def forward(self, input: SpecLibBase) -> SpecLibBase:
protein_df = fasta.load_fasta_list_as_protein_df(
self.fasta_path_list
)

if self.drop_decoy and 'decoy' in input.precursor_df.columns:
logger.info(f'Dropping decoys from input library before annotation')
input._precursor_df = input._precursor_df[input._precursor_df['decoy'] == 0]

input._precursor_df = fasta.annotate_precursor_df(input.precursor_df, protein_df)

if self.drop_unannotated and 'cardinality' in input._precursor_df.columns:
Expand Down
4 changes: 2 additions & 2 deletions alphadia/planning.py
Original file line number Diff line number Diff line change
Expand Up @@ -141,8 +141,8 @@ def spectral_library(

def load_library(self, spec_lib_path):

if 'fasta_files' in self.config:
fasta_files = self.config['fasta_files']
if 'fasta_list' in self.config:
fasta_files = self.config['fasta_list']
else:
fasta_files = []

Expand Down
2 changes: 1 addition & 1 deletion gui/src/main/electron.js
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ async function handleGetUtilisation (event) {

function handleStartWorkflow(workflow) {

const workflowFolder = workflow.output.path
const workflowFolder = workflow.output_directory.path
const config = workflowToConfig(workflow)

// check if workflow folder exists
Expand Down
11 changes: 8 additions & 3 deletions gui/src/main/modules/cmd.js
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,6 @@ const CondaEnvironment = class {
}).then(() => {
return this.checkCondaVersion().then((info) => {


// executing conda run -n envName in an alreadt activated conda environment causes an error
// this is a workaround
// check if info exist and active_prefix is not null
Expand All @@ -90,6 +89,9 @@ const CondaEnvironment = class {
this.checkPythonVersion(),
this.checkAlphadiaVersion(),
])
}).catch((error) => {
console.log("Conda not found", "Conda could not be found on your system. Please make sure conda is installed and added to your PATH." + error)
dialog.showErrorBox("Conda not found", "Conda could not be found on your system. Please make sure conda is installed and added to your PATH." + error)
})
}).then(() => {
this.ready = [this.exists.conda, this.exists.python, this.exists.alphadia].every(Boolean);
Expand All @@ -102,8 +104,11 @@ const CondaEnvironment = class {

discoverCondaPATH(){
return new Promise((resolve, reject) => {

const paths = [this.profile.config.conda.path, ...condaPATH(os.userInfo().username, os.platform())]

// 1st choice: conda is already in PATH
// 2nd choice: conda path from profile setting is used
// 3rd choice: default conda paths are tested
const paths = ["", this.profile.config.conda.path, ...condaPATH(os.userInfo().username, os.platform())]
Promise.all(paths.map((path) => {
return testCommand("conda", path)
})).then((codes) => {
Expand Down
24 changes: 12 additions & 12 deletions gui/src/main/modules/workflows.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,10 @@ function validateWorkflow(workflow) {
if (!workflow.hasOwnProperty('library')) {
throw new Error('Workflow does not have a library field.')
}
if (!workflow.hasOwnProperty('fasta')) {
if (!workflow.hasOwnProperty('fasta_list')) {
throw new Error('Workflow does not have a fasta field.')
}
if (!workflow.hasOwnProperty('files')) {
if (!workflow.hasOwnProperty('raw_file_list')) {
throw new Error('Workflow does not have a files field.')
}
if (!workflow.hasOwnProperty('config')) {
Expand All @@ -52,18 +52,18 @@ function validateWorkflow(workflow) {
}

// make sure fasta has the following fields: [active, path]
if (!workflow.fasta.hasOwnProperty('active')) {
if (!workflow.fasta_list.hasOwnProperty('active')) {
throw new Error('Workflow fasta does not have an active field.')
}
if (!workflow.fasta.hasOwnProperty('path')) {
if (!workflow.fasta_list.hasOwnProperty('path')) {
throw new Error('Workflow fasta does not have a path field.')
}

// make sure files has the following fields: [active, path]
if (!workflow.files.hasOwnProperty('active')) {
if (!workflow.raw_file_list.hasOwnProperty('active')) {
throw new Error('Workflow files does not have an active field.')
}
if (!workflow.files.hasOwnProperty('path')) {
if (!workflow.raw_file_list.hasOwnProperty('path')) {
throw new Error('Workflow files does not have a path field.')
}

Expand All @@ -82,16 +82,16 @@ function workflowToConfig(workflow) {
output["library"] = workflow.library.path
}

if (workflow.fasta.path != "") {
output["fasta"] = workflow.fasta.path
if (workflow.fasta_list.path != "") {
output["fasta_list"] = workflow.fasta_list.path
}

if (workflow.files.path != "") {
output["files"] = workflow.files.path
if (workflow.raw_file_list.path != "") {
output["raw_file_list"] = workflow.raw_file_list.path
}

if (workflow.output.path != "") {
output["output"] = workflow.output.path
if (workflow.output_directory.path != "") {
output["output_directory"] = workflow.output_directory.path
}

workflow.config.forEach((config) => {
Expand Down
80 changes: 80 additions & 0 deletions gui/src/renderer/components/MultiSelect.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import { Grid, Stack, Typography, Tooltip, Button } from "@mui/material";
import HelpOutlineIcon from "@mui/icons-material/HelpOutline";

const MultiSelect = ({
label = "File",
active = true,
type = "file",
path = [],
tooltipText = "",
onChange = () => {},
...props
}) => {

// set color based on activity status
const color = active ? "primary" : "divider";
// get file name from path
// eslint-disable-next-line no-useless-escape
const fileNames = path.map((path) => path.replace(/^.*[\\\/]/, ''));

// handle select button click

let handleSelect = () => {}

if (type === "folder") {
handleSelect = () => {
window.electronAPI.getMultipleFolders().then((folder) => {
onChange(folder);
}).catch((err) => {
console.log(err);
})
}
} else {
handleSelect = () => {
window.electronAPI.getMultipleFiles().then((file) => {
onChange(file);
}).catch((err) => {
console.log(err);
})
}
}

return (
<Grid container spacing={2} sx={{color, minHeight:"100px"}} wrap="nowrap">
<Grid item xs={3}>
<Stack direction="row" alignItems="center" gap={1}>
<Typography component="span">{label}</Typography>
<Tooltip title = {active && tooltipText} sx= {{color}}>
<HelpOutlineIcon fontSize="small" />
</Tooltip>
</Stack>
</Grid>
<Grid item xs={6} zeroMinWidth sx={{overflow: 'hidden', textOverflow: 'ellipsis'}}>
<Typography component="span" noWrap >
{
// iterate fileNames and display with line break
fileNames.map((fileName) => {
return (
<Typography component="span" noWrap key={fileName}>
{fileName}
<br/>
</Typography>
)
})
}
</Typography>

</Grid>
<Grid item xs={3} position={'relative'}>
<Button
variant="outlined"
sx={{float: 'right', ml:1, minWidth: "115px"}}
disabled={!active}
onClick={handleSelect}>
Select Files
</Button>
</Grid>
</Grid>
)}

export default MultiSelect;
4 changes: 2 additions & 2 deletions gui/src/renderer/components/RunButton.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@ const validateMethod = (method) => {
if (method.library.required && (method.library.path === "")) {
return {valid: false, message: "Please select a spectral library."}
}
if (method.fasta.required && (method.fasta.path === "")) {
if (method.fasta_list.required && (method.fasta_list.path === "")) {
return {valid: false, message: "Please select a fasta file."}
}
if (method.files.required && (method.files.path.length === 0)) {
if (method.raw_file_list.required && (method.raw_file_list.path.length === 0)) {
return {valid: false, message: "Please select at least one Bruker .d folder."}
}
return {valid: true, message: ""}
Expand Down
1 change: 1 addition & 0 deletions gui/src/renderer/components/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,4 @@ export {default as SingleSelect} from './SingleSelect'
export {default as StyledLink} from './StyledLink'
export {default as UtilMonitor} from './UtilMonitor'
export {default as WorkflowMenu} from './WorkflowMenu'
export {default as MultiSelect} from './MultiSelect'
15 changes: 8 additions & 7 deletions gui/src/renderer/logic/context.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,17 @@ const initialMethod = {
active: false,
path: ""
},
fasta: {
fasta_list: {
active: false,
path: ""
path: [
]
},
files: {
raw_file_list: {
active: false,
path: [
]
},
output: {
output_directory: {
active: true,
path: ""
},
Expand All @@ -31,11 +32,11 @@ export function methodReducer(method, action) {
return {...method, library: {...method.library, path: action.path}}

case 'updateFasta':
return {...method, fasta: {...method.fasta, path: action.path}}
return {...method, fasta_list: {...method.fasta_list, path: action.path}}

case 'updateFiles':
console.log(action)
return {...method, files: {...method.files, path: action.path}}
return {...method, raw_file_list: {...method.raw_file_list, path: action.path}}

case 'updateParameter':
console.log(action)
Expand Down Expand Up @@ -63,7 +64,7 @@ export function methodReducer(method, action) {

case "updateOutput":
console.log(action)
return {...method, output: {...method.output, path: action.path}}
return {...method, output_directory: {...method.output_directory, path: action.path}}
default:
throw new Error(`Unhandled action type: ${action.type}`);
}
Expand Down
16 changes: 8 additions & 8 deletions gui/src/renderer/pages/Files.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import styled from '@emotion/styled'
import { useMethod, useMethodDispatch } from '../logic/context'

import { Box } from '@mui/material'
import { SingleSelect, InputFileSelect, FileViewer } from '../components'
import { SingleSelect, MultiSelect, InputFileSelect, FileViewer } from '../components'

const FullWidthBox = styled(Box)(({ theme }) => ({
width: '100%'
Expand Down Expand Up @@ -31,23 +31,23 @@ const Files = () => {
/>
</FullWidthBox>
<FullWidthBox>
<SingleSelect
label="Fasta File"
active={method.fasta.active}
path={method.fasta.path}
<MultiSelect
label="Fasta File(s)"
active={method.fasta_list.active}
path={method.fasta_list.path}
tooltipText="Select the fasta file which you would like to use."
onChange={(path) => {dispatch({type: 'updateFasta', path: path})}}
/>
</FullWidthBox>
<FullWidthBox>
<InputFileSelect
active={method.files.active}
path={method.files.path}
active={method.raw_file_list.active}
path={method.raw_file_list.path}
onChange={(path) => {dispatch({type: 'updateFiles', path: path})}}
/>
</FullWidthBox>
<FullWidthBox sx={{flexGrow: 1}}>
<FileViewer path={method.files.path}/>
<FileViewer path={method.raw_file_list.path}/>
</FullWidthBox>
</Box>
)
Expand Down
Loading

0 comments on commit 63688b6

Please sign in to comment.