Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add nuclei inferral UI #5669

Merged
merged 17 commits into from
Aug 24, 2021
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.unreleased.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ For upgrade instructions, please check the [migration guide](MIGRATIONS.released
- Fixed a bug where non-existing resolutions could be selected for wk-worker-based meshfile computations [#5631](https://github.com/scalableminds/webknossos/pull/5631)
- Added new mesh-related functions to the front-end API: getAvailableMeshFiles, getActiveMeshFile, setActiveMeshFile, loadPrecomputedMesh, computeMeshOnDemand, setMeshVisibility, removeMesh. [#5634](https://github.com/scalableminds/webknossos/pull/5634)
- Added a route to call new webknossos-worker job for nuclei inferral. [#5626](https://github.com/scalableminds/webknossos/pull/5626)
- Added UI to infer nuclei for webknossos instances, which support jobs (e.g., webknossos.org). [#5669](https://github.com/scalableminds/webknossos/pull/5669)
- Added the possibility to restrict the volume resolutions when creating explorative annotations. Use this to annotate larger structures without creating tons of high-res data. [#5645](https://github.com/scalableminds/webknossos/pull/5645)
- Added tooltips for all elements of the Settings tab in the left sidebar. [#5673](https://github.com/scalableminds/webknossos/pull/5673)

Expand Down
10 changes: 10 additions & 0 deletions frontend/javascripts/admin/admin_rest_api.js
Original file line number Diff line number Diff line change
Expand Up @@ -915,6 +915,16 @@ export function startComputeMeshFileJob(
);
}

export function startNucleiInferralJob(
organizationName: string,
datasetName: string,
layerName: string,
): Promise<APIJob> {
return Request.receiveJSON(
`/api/jobs/run/inferNuclei/${organizationName}/${datasetName}?layerName=${layerName}`,
);
}

export function getDatasetDatasource(
dataset: APIMaybeUnimportedDataset,
): Promise<APIDataSourceWithMessages> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
* @flow
*/
import type { Dispatch } from "redux";
import { Tooltip, Button } from "antd";
import { EditOutlined, InfoCircleOutlined } from "@ant-design/icons";
import { Tooltip, Button, Dropdown, Menu } from "antd";
import { EditOutlined, InfoCircleOutlined, StarOutlined } from "@ant-design/icons";
import { connect } from "react-redux";
import Markdown from "react-remarkable";
import React from "react";
Expand All @@ -26,7 +26,9 @@ import {
import ButtonComponent from "oxalis/view/components/button_component";
import EditableTextLabel from "oxalis/view/components/editable_text_label";
import Model from "oxalis/model";
import features from "features";
import Store, { type OxalisState, type Task, type Tracing } from "oxalis/store";
import NucleiInferralModal from "oxalis/view/right-border-tabs/nuclei_inferral_modal";

type StateProps = {|
tracing: Tracing,
Expand All @@ -42,6 +44,10 @@ type DispatchProps = {|

type Props = {| ...StateProps, ...DispatchProps |};

type State = {
showNucleiInferralModal: boolean,
};

const shortcuts = [
{
key: "1",
Expand Down Expand Up @@ -122,7 +128,11 @@ export function convertPixelsToNm(
return lengthInPixel * zoomValue * getBaseVoxel(dataset.dataSource.scale);
}

class DatasetInfoTabView extends React.PureComponent<Props> {
class DatasetInfoTabView extends React.PureComponent<Props, State> {
state = {
showNucleiInferralModal: false,
};

setAnnotationName = (newName: string) => {
this.props.setAnnotationName(newName);
};
Expand Down Expand Up @@ -185,6 +195,48 @@ class DatasetInfoTabView extends React.PureComponent<Props> {
) : null;
}

getProcessingJobsMenu = () => {
if (!this.props.dataset.jobsEnabled) {
philippotto marked this conversation as resolved.
Show resolved Hide resolved
return (
<tr>
<td style={{ paddingRight: 4, paddingTop: 10, verticalAlign: "top" }}>
<StarOutlined className="info-tab-icon" width={24} height={24} />
MichaelBuessemeyer marked this conversation as resolved.
Show resolved Hide resolved
</td>
<td>
<Tooltip title="Dataset Processing features are only available for datasets hosted natively and not on other datastores.">
<Button disabled type="link" style={{ padding: 0 }}>
Process Dataset
</Button>
</Tooltip>
</td>
</tr>
);
}
const overlay = (
<Menu>
<Menu.Item onClick={() => this.setState({ showNucleiInferralModal: true })}>
<Tooltip title="Start a job that automatically detects nuclei for this dataset.">
Start Nuclei Inferral
</Tooltip>
</Menu.Item>
</Menu>
);
MichaelBuessemeyer marked this conversation as resolved.
Show resolved Hide resolved
return (
<tr>
<td style={{ paddingRight: 4, paddingTop: 10, verticalAlign: "top" }}>
<StarOutlined className="info-tab-icon" style={{ fontSize: 18 }} />
MichaelBuessemeyer marked this conversation as resolved.
Show resolved Hide resolved
</td>
<Dropdown overlay={overlay}>
<td>
<Button type="link" style={{ padding: 0 }}>
Process Dataset
</Button>
</td>
</Dropdown>
</tr>
);
};

getDatasetName(isDatasetViewMode: boolean) {
const {
name: datasetName,
Expand Down Expand Up @@ -370,7 +422,7 @@ class DatasetInfoTabView extends React.PureComponent<Props> {
}

render() {
const { dataset, activeResolution } = this.props;
const { dataset, activeResolution, activeUser } = this.props;
const isDatasetViewMode =
Store.getState().temporaryConfiguration.controlMode === ControlModeEnum.VIEW;

Expand Down Expand Up @@ -450,8 +502,20 @@ class DatasetInfoTabView extends React.PureComponent<Props> {
</tr>
</Tooltip>
{resolutionInfo}

{features().jobsEnabled &&
activeUser != null &&
(activeUser.isDatasetManager || activeUser.isAdmin)
? this.getProcessingJobsMenu()
: null}
</tbody>
</table>
{this.state.showNucleiInferralModal ? (
<NucleiInferralModal
dataset={dataset}
handleClose={() => this.setState({ showNucleiInferralModal: false })}
/>
) : null}
</div>

<div className="info-tab-block">{this.getTracingStatistics()}</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
// @flow
import React, { useEffect, useState } from "react";
import { type APIDataset } from "types/api_flow_types";
import { Modal, Select, Button } from "antd";
import { startNucleiInferralJob } from "admin/admin_rest_api";
import { getColorLayers } from "oxalis/model/accessors/dataset_accessor";
import Toast from "libs/toast";
import { Unicode } from "oxalis/constants";

const { ThinSpace } = Unicode;

type Props = {
dataset: APIDataset,
handleClose: () => void,
};

export default function NucleiInferralModal(props: Props) {
const { dataset, handleClose } = props;
const [selectedColorLayerName, setSelectedColorLayerName] = useState(null);
const colorLayers = getColorLayers(dataset);
MichaelBuessemeyer marked this conversation as resolved.
Show resolved Hide resolved
useEffect(() => {
if (colorLayers.length === 1) {
setSelectedColorLayerName(colorLayers[0].name);
}
});
if (colorLayers.length < 1) {
return null;
}

const onChange = selectedLayerName => {
setSelectedColorLayerName(selectedLayerName);
};

const startJob = async () => {
if (selectedColorLayerName == null) {
return;
}
try {
await startNucleiInferralJob(
dataset.owningOrganization,
dataset.name,
selectedColorLayerName,
);
Toast.info(
<>
MichaelBuessemeyer marked this conversation as resolved.
Show resolved Hide resolved
The nuclei inferral job has been started. You can look in the{" "}
<a target="_blank" href="/jobs" rel="noopener noreferrer">
Processing Jobs
</a>{" "}
view under Administration for details on the progress of this job.
</>,
);
handleClose();
} catch (error) {
console.error(error);
Toast.error(
"The nuclei inferral job could not be started. Please contact an administrator or look in the console for more details.",
);
handleClose();
}
};

return (
<Modal title="Start Nuclei Inferral" onCancel={handleClose} visible width={700} footer={null}>
<p>
Start a job that automatically detects nuclei for this dataset. This job creates a copy of
this dataset once it has finished. The new dataset will contain the detected nuclei as a
segmentation layer.{" "}
</p>
<p>
<b>
Note that this feature is still experimental. Nuclei detection currently works best with
EM data and a resolution of approximately 200{ThinSpace}nm per voxel. The inferral process
will automatically use the magnification that matches that resolution best.
</b>
</p>
<br />
<div style={{ textAlign: "center" }}>
<img
src="/assets/images/nuclei_inferral_example.jpg"
alt="Nuclei inferral example"
style={{ width: 400, height: "auto", borderRadius: 3 }}
/>
</div>
<br />
{colorLayers.length > 1 ? (
<React.Fragment>
<p>
The detection approach uses a single color layer to predict the nuclei. Please select
the layer that should be used for detection.
</p>
<div style={{ textAlign: "center" }}>
<Select
showSearch
style={{ width: 200 }}
placeholder="Select a color layer"
optionFilterProp="children"
onChange={onChange}
filterOption={(input, option) =>
option.children.toLowerCase().indexOf(input.toLowerCase()) >= 0
}
>
{colorLayers.map(colorLayer => (
<Select.Option key={colorLayer.name} value={colorLayer.name}>
{colorLayer.name}
</Select.Option>
))}
</Select>
</div>
<br />
</React.Fragment>
) : null}
<div style={{ textAlign: "center" }}>
<Button type="primary" disabled={selectedColorLayerName == null} onClick={startJob}>
Start Nuclei Inferral
</Button>
</div>
</Modal>
);
}
Binary file added public/images/nuclei_inferral_example.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.