Skip to content

Commit

Permalink
Merge ad202df into ff3bc02
Browse files Browse the repository at this point in the history
  • Loading branch information
willgraf committed Mar 12, 2021
2 parents ff3bc02 + ad202df commit 036472c
Show file tree
Hide file tree
Showing 6 changed files with 167 additions and 23 deletions.
1 change: 1 addition & 0 deletions server/controllers/predict.controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ async function predict(req, res) {
'created_at', now,
'updated_at', now,
'identity_upload', config.hostname,
'channels', data.channels || '',
]);
await redis.lpush(queueName, redisKey);
return res.status(httpStatus.OK).send({ hash: redisKey });
Expand Down
1 change: 1 addition & 0 deletions src/App/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ const NotFound = lazy(() => import('../NotFound/NotFound'));

// If the mode is NOT production, then notify that we are in dev mode.
if (process.env.NODE_ENV !== 'production') {
// eslint-disable-next-line no-console
console.log('Looks like we are in development mode!');
}

Expand Down
84 changes: 84 additions & 0 deletions src/Predict/ChannelForm.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import React, { useState } from 'react';
import { PropTypes } from 'prop-types';
import { makeStyles } from '@material-ui/core/styles';
import FormControl from '@material-ui/core/FormControl';
// import FormControlLabel from '@material-ui/core/FormControlLabel';
import FormGroup from '@material-ui/core/FormGroup';
import InputLabel from '@material-ui/core/InputLabel';
import Select from '@material-ui/core/Select';
import MenuItem from '@material-ui/core/MenuItem';

const useStyles = makeStyles(theme => ({
leftPad: {
paddingLeft: theme.spacing(1),
},
floatLeft: {
float: 'left',
},
capitalize: {
textTransform: 'capitalize',
}
}));

function ChannelDropdown(props) {

const { label, value, channels, onChange } = props;
const [isOpen, setIsOpen] = useState(false);

const classes = useStyles();

return (
<FormControl fullWidth>
<InputLabel margin='dense' htmlFor={`${label}-input`}>{`${label} channel`}</InputLabel>
<Select
labelId={`${label}-input`}
open={isOpen}
onClose={() => setIsOpen(false)}
onOpen={() => setIsOpen(true)}
onChange={onChange}
value={value}
autoWidth={true}
className={classes.leftPad, classes.capitalize}
>
{channels.map((c, i) => (
<MenuItem value={c} key={i} className={classes.capitalize}>
{c}
</MenuItem>
))}
</Select>
</FormControl>
);
}

ChannelDropdown.propTypes = {
label: PropTypes.string,
value: PropTypes.string,
channels: PropTypes.array,
onChange: PropTypes.func,
};


export default function ChannelForm(props) {

const { targetChannels, channels, onChange } = props;

return (
<FormGroup>
{targetChannels && Object.keys(targetChannels).map((t, i) => (
<ChannelDropdown
label={`${t}`}
key={i}
value={targetChannels[t]}
channels={channels}
onChange={e => onChange(e.target.value, t)}
/>
))}
</FormGroup>
);
}

ChannelForm.propTypes = {
channels: PropTypes.array,
targetChannels: PropTypes.object,
onChange: PropTypes.func,
};
4 changes: 2 additions & 2 deletions src/Predict/ModelDropdown.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@ export default function ModelDropdown(props) {
value={value}
style={{textTransform: 'capitalize'}}
>
{allJobTypes.map(job => (
<MenuItem value={job} style={{textTransform: 'capitalize'}} key={allJobTypes.indexOf(job)}>
{allJobTypes.map((job, i) => (
<MenuItem value={job} style={{textTransform: 'capitalize'}} key={i}>
{job}
</MenuItem>
))}
Expand Down
92 changes: 71 additions & 21 deletions src/Predict/Predict.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useState } from 'react';
import React, { useState, useEffect } from 'react';
import { makeStyles } from '@material-ui/core/styles';
import Button from '@material-ui/core/Button';
import Container from '@material-ui/core/Container';
Expand All @@ -12,6 +12,7 @@ import FileUpload from './FileUpload';
import JobCard from './JobCard';
import ModelDropdown from './ModelDropdown';
import ScaleForm from './ScaleForm';
import ChannelForm from './ChannelForm';
import jobData from './jobData';

const useStyles = makeStyles(theme => ({
Expand Down Expand Up @@ -47,10 +48,44 @@ export default function Predict() {
const [progress, setProgress] = useState(0);
const [selectedJobType, setSelectedJobType] = useState('');
const [isAutoRescaleEnabled, setIsAutoRescaleEnabled] = useState(true);
const [displayRescaleForm, setDisplayRescaleForm] = useState(false);
const [scale, setScale] = useState(1);

/**
* Select a channel for each target
*/
const [targetChannels, setTargetChannels] = useState({});
const channels = ['gray', 'red', 'green', 'blue'];
const channelValues = {
gray: 0,
red: 1,
green: 2,
blue: 3,
};

const updateTargetChannels = (value, target) => {
setTargetChannels({ ...targetChannels, [target]: value });
};

const classes = useStyles();

useEffect(() => {
if (selectedJobType) {
setDisplayRescaleForm(jobData[selectedJobType].scaleEnabled);
const jobTargets = jobData[selectedJobType].requiredChannels;
if (jobTargets.length == 1) {
// default single-target jobs to grayscale.
setTargetChannels({ [jobTargets[0]]: channels[0] });
} else if (jobTargets.length <= 3) {
// default multi targets to RGB.
setTargetChannels(jobTargets.reduce((result, item, index) => {
result[item] = channels[index + 1];
return result;
}, {}));
}
}
}, [selectedJobType]);

const showErrorMessage = (errText) => {
setErrorText(errText);
setShowError(true);
Expand Down Expand Up @@ -124,8 +159,9 @@ export default function Predict() {
imageName: fileName,
uploadedName: uploadedFileName,
imageUrl: imageUrl,
jobType : selectedJobType,
dataRescale: isAutoRescaleEnabled ? '' : scale
jobType: selectedJobType,
dataRescale: isAutoRescaleEnabled ? (displayRescaleForm ? '' : '1') : scale,
channels: (jobData[selectedJobType].requiredChannels).map(c => channelValues[targetChannels[c]]).join(','),
}
}).then((response) => {
checkJobStatus(response.data.hash, 3000);
Expand Down Expand Up @@ -155,37 +191,44 @@ export default function Predict() {
<form autoComplete="off">
<Grid container direction="row" justify="center" spacing={6}>

{/* Job info display on left column */}
<Grid item xs={12} sm={6}>
{ selectedJobType.length > 0 &&
<JobCard {...jobData[selectedJobType]} />
}
</Grid>

{/* Job configuration for user on right column */}
<Grid item xs={12} sm={6}>

{/* Job Options section */}
<Grid container direction="row" justify="center">
<Grid container>
<Paper className={classes.paper}>
<Grid item lg>
<Typography>
Prediction Type
</Typography>
<ModelDropdown
value={selectedJobType}
onChange={setSelectedJobType}
onError={showErrorMessage}
/>
<Grid container>
<Grid item md={6}>
<Typography>
Prediction Type
</Typography>
<ModelDropdown
value={selectedJobType}
onChange={setSelectedJobType}
onError={showErrorMessage}
/>
</Grid>
<Grid item md={6}>
{/* <Typography align="right">
Input Channels
</Typography> */}
<ChannelForm
channels={channels}
targetChannels={targetChannels}
onChange={updateTargetChannels}
/>
</Grid>
</Grid>
<Grid item lg>

{ displayRescaleForm && <Grid item lg>
<ScaleForm
checked={isAutoRescaleEnabled}
scale={scale}
onCheckboxChange={e => setIsAutoRescaleEnabled(Boolean(e.target.checked))}
onScaleChange={e => setScale(Number(e.target.value))}
/>
</Grid>
}
</Paper>
</Grid>

Expand All @@ -206,6 +249,13 @@ export default function Predict() {

</Grid>

{/* Job info display on left column */}
<Grid item xs={12} sm={6}>
{ selectedJobType.length > 0 &&
<JobCard {...jobData[selectedJobType]} />
}
</Grid>

</Grid>

{/* Display error to user */}
Expand Down
8 changes: 8 additions & 0 deletions src/Predict/jobData.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,26 @@ const jobCards = {
model: 'The nuclear model performs nuclear segmentation of cell culture data.',
inputs: 'A nuclear channel image.',
thumbnail: 'thumbnails/HeLa_nuclear_color.png',
scaleEnabled: false,
requiredChannels: ['nuclei'],
},
multiplex: {
file: 'tiff_stack_examples/vectra_breast_cancer.tif',
name: 'Mesmer',
model: 'Mesmer performs whole-cell segmentation of multiplex tissue data.',
inputs: 'A two-channel TIFF where the first channel is a nuclear marker and the second channel is a membrane marker.',
thumbnail: 'thumbnails/breast_vectra.png',
scaleEnabled: false,
requiredChannels: ['nuclei', 'cytoplasm'],
},
tracking: {
file: 'tiff_stack_examples/3T3_nuc_example_256.tif',
name: 'Cell Tracking',
model: 'The cell tracking model segments and tracks objects over time and creates a lineage file for division information.',
inputs: 'A single-channel image stack (3D TIFF).',
thumbnail: 'thumbnails/3T3_nuc_example_256.png',
scaleEnabled: true,
requiredChannels: ['nuclei'],
},
// TODO: this is a stop gap to support both multiplex and mesmer names
mesmer: {
Expand All @@ -28,6 +34,8 @@ const jobCards = {
model: 'Mesmer performs whole-cell segmentation of multiplex tissue data.',
inputs: 'A two-channel TIFF where the first channel is a nuclear marker and the second channel is a membrane marker.',
thumbnail: 'thumbnails/breast_vectra.png',
scaleEnabled: false,
requiredChannels: ['nuclei', 'cytoplasm'],
},
};

Expand Down

0 comments on commit 036472c

Please sign in to comment.