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

Support multiple segmentation layers #5683

Merged
merged 73 commits into from
Sep 8, 2021
Merged
Show file tree
Hide file tree
Changes from 45 commits
Commits
Show all changes
73 commits
Select commit Hold shift + click to select a range
1dd401c
[WIP] don't crash when multiple segmentation layers exist; start intr…
philippotto Aug 12, 2021
cb08c87
[WIP] fix all flow errors
philippotto Aug 23, 2021
5d31282
[WIP]: implement and use getVisibleSegmentationLayer, getSomeSegmenta…
philippotto Aug 23, 2021
16abedc
[WIP]: implement and use getFirstSegmentationLayer which only needs a…
philippotto Aug 23, 2021
9bd7a88
remove deprecated functions
philippotto Aug 23, 2021
8cd8144
fix wrong parameter
philippotto Aug 23, 2021
1acd9cf
fix isColorLayer
philippotto Aug 23, 2021
c5fbede
adapt shader code to multiple segmentation layers
philippotto Aug 23, 2021
7947f30
fix cell hovering
philippotto Aug 23, 2021
6d4886c
support mappings for multiple segmentation layers
philippotto Aug 24, 2021
1bf928f
fix reducer for initial changes of mapping objects
philippotto Aug 24, 2021
1cafe08
fix runtime exceptions
philippotto Aug 24, 2021
715df28
make shader code and materials compatible with mappings (only one map…
philippotto Aug 24, 2021
4581384
fix some mapping issues
philippotto Aug 24, 2021
422fb67
Merge branch 'master' of github.com:scalableminds/webknossos into mul…
philippotto Aug 25, 2021
c3d36f4
Merge branch 'master' of github.com:scalableminds/webknossos into mul…
philippotto Aug 25, 2021
c738945
fix hovered segment id in status bar
philippotto Aug 25, 2021
74898de
fix multiple mapping bugs (e.g., race condition when activating a map…
philippotto Aug 25, 2021
8228b32
fix wrong attachment of mapping-related textures
philippotto Aug 25, 2021
b036111
fix callHandlerOnSubscribe behavior in listenToStoreProperty if initi…
philippotto Aug 26, 2021
648b24e
update threeJS to 110 so that we can use to avoid discarded updates;…
philippotto Aug 26, 2021
d1c7884
support volume annotation in dataset with multiple segmentation layers
philippotto Aug 26, 2021
e4b1c5a
adapt more volume code to multiple segmentation layers
philippotto Aug 26, 2021
2933c23
remove outdated comment
philippotto Aug 27, 2021
f494249
adapt UI for creation of explorative so that fallback layer can be ch…
philippotto Aug 27, 2021
7f07bfa
change back-end route from boolean withFallback to optional string fa…
philippotto Aug 27, 2021
4d9a3fe
clean up GET parameter construction
philippotto Aug 27, 2021
577b426
further clean up in CreateExplorativeModal logic
philippotto Aug 27, 2021
cc55fb8
fix creation of volume tracing when viewing dataset and only show rem…
philippotto Aug 27, 2021
c21b34c
ensure that only one segmentation layer is visible
philippotto Aug 27, 2021
3d2b6e3
remove activeSegmentationLayerIndex code, as it's not needed anymore
philippotto Aug 27, 2021
6d1d126
trigger agglomeration warning for correct segmentation layer
philippotto Aug 27, 2021
0923f22
adapt data export to multiple segmentation layers
philippotto Aug 27, 2021
0517184
remove unused isRefreshingIsosurfaces store property
philippotto Aug 27, 2021
51d1ed1
fix passing fallbackLayerName when creating explorational
philippotto Aug 30, 2021
fb3ce1e
make mesh management dependent on concrete segmentation layer
philippotto Aug 30, 2021
6d7c811
fix ad-hoc and precomputed mesh usages
philippotto Aug 30, 2021
ae0ed59
rename mesh/isosurface variables to ...ByLayer where appropriate
philippotto Aug 30, 2021
1fb21ca
get rid of getEnforcedSomeSegmentationLayer function
philippotto Aug 30, 2021
1d357ac
add better typing for mesh view and remove some dead code
philippotto Aug 30, 2021
6a84c71
change getResolutionInfoOfSomeSegmentationLayer to getResolutionInfoO…
philippotto Aug 30, 2021
fe793d4
remove some usages of getFirstSegmentationLayer
philippotto Aug 30, 2021
59e58d2
fix default 'Create Annotation' link for fallback layers
philippotto Aug 30, 2021
77a76da
fix linting
philippotto Aug 30, 2021
f0841d5
use correct segmentation layer in version view
philippotto Aug 30, 2021
811b962
remove redundant if-null check
philippotto Aug 30, 2021
bf0af45
make 3-shortcut work better with multiple segmentation layers
philippotto Aug 30, 2021
cc578a7
fix unit tests
philippotto Aug 30, 2021
6727777
improve the api_latest functions
philippotto Aug 30, 2021
5f4466e
further improvements of api_latest
philippotto Aug 30, 2021
1d62c82
format back-end code
philippotto Aug 30, 2021
797abe2
fail if non-existing fallback layer is selected, add fallbackLayerNam…
fm3 Aug 31, 2021
2473b1e
legacy api
fm3 Aug 31, 2021
af27ad9
only allow volume annotation in actual tracing layer
philippotto Aug 31, 2021
9c07917
Merge branch 'multiple-segmentations' of github.com:scalableminds/web…
philippotto Aug 31, 2021
af2fed4
Merge branch 'master' of github.com:scalableminds/webknossos into mul…
philippotto Aug 31, 2021
01d3030
resolve todo affecting features.publicDemoDatasetUrl
philippotto Aug 31, 2021
a701fe5
use currently visible segmentation layer as fallback layer when creat…
philippotto Aug 31, 2021
8bf6bb7
rename activeMapping to activeMappingByLayer
philippotto Aug 31, 2021
fa2b031
fix CI
philippotto Aug 31, 2021
1fc3df6
integrate some PR feedback
philippotto Sep 1, 2021
987ebf4
Apply suggestions from code review
philippotto Sep 1, 2021
d6b8183
rename getRequestedOrVisibleSegmentationLayer, getRequestedOrVisibleS…
philippotto Sep 1, 2021
41a3695
Merge branch 'multiple-segmentations' of github.com:scalableminds/web…
philippotto Sep 1, 2021
d1696b3
integrate further PR feedback
philippotto Sep 2, 2021
15341b5
improve docstrings in api_latest
philippotto Sep 2, 2021
eb4ebf7
Merge branch 'master' of github.com:scalableminds/webknossos into mul…
philippotto Sep 2, 2021
dc69e67
remove other segmentation layers for volume annotations without fallb…
philippotto Sep 6, 2021
3c35e4a
update changelog
philippotto Sep 6, 2021
0b1dd3b
make getVolumeTracingLayerName backwards compatible
philippotto Sep 8, 2021
de2ad94
fix merger mode for hybrids and for skeleton-only annotations
philippotto Sep 8, 2021
a45c81c
fix segmentationOpacity dictated by recommended settings in tasks whe…
philippotto Sep 8, 2021
de5c234
Merge branch 'master' into multiple-segmentations
philippotto Sep 8, 2021
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
4 changes: 2 additions & 2 deletions app/controllers/AnnotationController.scala
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import scala.concurrent.ExecutionContext
import scala.concurrent.duration._

case class CreateExplorationalParameters(typ: String,
withFallback: Option[Boolean],
fallbackLayerName: Option[String],
resolutionRestrictions: Option[ResolutionRestrictions])
object CreateExplorationalParameters {
implicit val jsonFormat: OFormat[CreateExplorationalParameters] = Json.format[CreateExplorationalParameters]
Expand Down Expand Up @@ -159,7 +159,7 @@ class AnnotationController @Inject()(
request.identity,
dataSet._id,
tracingType,
request.body.withFallback.getOrElse(true),
request.body.fallbackLayerName,
request.body.resolutionRestrictions.getOrElse(ResolutionRestrictions.empty)
) ?~> "annotation.create.failed"
_ = analyticsService.track(CreateAnnotationEvent(request.identity: User, annotation: Annotation))
Expand Down
17 changes: 9 additions & 8 deletions app/models/annotation/AnnotationService.scala
Original file line number Diff line number Diff line change
Expand Up @@ -139,17 +139,18 @@ class AnnotationService @Inject()(
dataSet: DataSet,
dataSource: DataSource,
tracingType: TracingType.Value,
withFallback: Boolean,
fallbackLayerNameOpt: Option[String],
resolutionRestrictions: ResolutionRestrictions,
organizationName: String,
oldTracingId: Option[String] = None)(implicit ctx: DBAccessContext): Fox[(Option[String], Option[String])] = {
def getFallbackLayer: Option[SegmentationLayer] =
if (withFallback) {
dataSource.dataLayers.flatMap {
// todo: probably should fail if the fallbackLayerName cannot be found
fallbackLayerNameOpt.map(fallbackLayerName =>
philippotto marked this conversation as resolved.
Show resolved Hide resolved
dataSource.dataLayers.filter(dl => dl.name == fallbackLayerName).flatMap {
case layer: SegmentationLayer => Some(layer)
case _ => None
}.headOption
} else None
).getOrElse(None)

tracingType match {
case TracingType.skeleton =>
Expand Down Expand Up @@ -199,7 +200,7 @@ class AnnotationService @Inject()(
def createExplorationalFor(user: User,
_dataSet: ObjectId,
tracingType: TracingType.Value,
withFallback: Boolean,
fallbackLayerName: Option[String],
resolutionRestrictions: ResolutionRestrictions)(implicit ctx: DBAccessContext,
m: MessagesProvider): Fox[Annotation] =
for {
Expand All @@ -210,7 +211,7 @@ class AnnotationService @Inject()(
tracingIds <- createTracingsForExplorational(dataSet,
usableDataSource,
tracingType,
withFallback,
fallbackLayerName,
resolutionRestrictions,
organization.name)
teamId <- selectSuitableTeam(user, dataSet) ?~> "annotation.create.forbidden"
Expand All @@ -235,7 +236,7 @@ class AnnotationService @Inject()(
createTracingsForExplorational(dataSet,
dataSource,
TracingType.volume,
withFallback = true,
fallbackLayerNameOpt = None, // todo
ResolutionRestrictions.empty,
organizationName).flatMap {
case (_, Some(volumeId)) => annotationDAO.updateVolumeTracingId(annotation._id, volumeId)
Expand All @@ -245,7 +246,7 @@ class AnnotationService @Inject()(
createTracingsForExplorational(dataSet,
dataSource,
TracingType.skeleton,
withFallback = false,
fallbackLayerNameOpt = None, // In skeleton case, this is irrelevant.
ResolutionRestrictions.empty,
organizationName,
annotation.volumeTracingId).flatMap {
Expand Down
4 changes: 2 additions & 2 deletions frontend/javascripts/admin/admin_rest_api.js
Original file line number Diff line number Diff line change
Expand Up @@ -675,14 +675,14 @@ export function getAnnotationInformation(
export function createExplorational(
datasetId: APIDatasetId,
typ: TracingType,
withFallback: boolean,
fallbackLayerName: ?string,
resolutionRestrictions: ?APIResolutionRestrictions,
options?: RequestOptions = {},
): Promise<APIAnnotation> {
const url = `/api/datasets/${datasetId.owningOrganization}/${datasetId.name}/createExplorational`;
return Request.sendJSONReceiveJSON(
url,
Object.assign({}, { data: { typ, withFallback, resolutionRestrictions } }, options),
Object.assign({}, { data: { typ, fallbackLayerName, resolutionRestrictions } }, options),
);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// @flow
import { Modal, Radio, Button, Checkbox, Tooltip, Slider, Spin } from "antd";
import { Modal, Radio, Button, Tooltip, Slider, Spin } from "antd";
import { InfoCircleOutlined } from "@ant-design/icons";
import React, { useState } from "react";
import type { APIDatasetId } from "types/api_flow_types";
Expand All @@ -8,10 +8,10 @@ import { useFetch } from "libs/react_helpers";
import { getDataset } from "admin/admin_rest_api";

import {
getSegmentationLayer,
doesSupportVolumeWithFallback,
getDatasetResolutionInfo,
getResolutionInfoOfSegmentationLayer,
getSegmentationLayers,
getResolutionInfo,
} from "oxalis/model/accessors/dataset_accessor";

type Props = {
Expand All @@ -22,99 +22,80 @@ type Props = {
const CreateExplorativeModal = ({ datasetId, onClose }: Props) => {
const dataset = useFetch(() => getDataset(datasetId), null, [datasetId]);
const [annotationType, setAnnotationType] = useState("hybrid");
const [userDefinedWithFallback, setUserDefinedWithFallback] = useState(true);
const [userDefinedResolutionIndices, setUserDefinedResolutionIndices] = useState([0, 10000]);
const [selectedSegmentationLayerIndex, setSelectedSegmentationLayerIndex] = useState(null);

let modalContent = <Spin />;

if (dataset !== null) {
const segmentationLayer = getSegmentationLayer(dataset);

const isFallbackSegmentationAlwaysOff =
segmentationLayer == null ||
(!doesSupportVolumeWithFallback(dataset) && annotationType !== "skeleton");
const isFallbackSegmentationAlwaysOn =
!isFallbackSegmentationAlwaysOff && annotationType === "skeleton";

const isFallbackSegmentationOptional =
!isFallbackSegmentationAlwaysOff && !isFallbackSegmentationAlwaysOn;

const isFallbackSegmentationSelected =
isFallbackSegmentationAlwaysOn ||
(userDefinedWithFallback && !isFallbackSegmentationAlwaysOff);

const isFallbackSegmentationSelectedString = isFallbackSegmentationSelected ? "true" : "false";

const fallbackCheckbox = (
<Checkbox
onChange={e => setUserDefinedWithFallback(e.target.checked)}
checked={isFallbackSegmentationSelected}
disabled={!isFallbackSegmentationOptional}
style={{ marginBottom: 16 }}
>
With Existing Segmentation{" "}
<Tooltip
title="Base your volume annotation on an existing segmentation layer of this dataset. Note that skeleton-only annotations always show the existing segmentation by default."
placement="right"
>
<InfoCircleOutlined />
</Tooltip>
</Checkbox>
);
const segmentationLayers = getSegmentationLayers(dataset);
const selectedSegmentationLayer =
annotationType !== "skeleton" &&
segmentationLayers.length > 0 &&
selectedSegmentationLayerIndex != null
? segmentationLayers[selectedSegmentationLayerIndex]
: null;

const fallbackLayerGetParameter =
selectedSegmentationLayer != null
? `&fallbackLayerName=${selectedSegmentationLayer.name}`
: "";

const datasetResolutionInfo = getDatasetResolutionInfo(dataset);
let highestResolutionIndex = datasetResolutionInfo.getHighestResolutionIndex();
let lowestResolutionIndex = datasetResolutionInfo.getClosestExistingIndex(0);

if (isFallbackSegmentationSelected && annotationType !== "skeleton") {
const datasetFallbackLayerResolutionInfo = getResolutionInfoOfSegmentationLayer(dataset);
if (selectedSegmentationLayer != null) {
const datasetFallbackLayerResolutionInfo = getResolutionInfo(
selectedSegmentationLayer.resolutions,
);
highestResolutionIndex = datasetFallbackLayerResolutionInfo.getHighestResolutionIndex();
lowestResolutionIndex = datasetFallbackLayerResolutionInfo.getClosestExistingIndex(0);
}

const highResolutionIndex = Math.min(highestResolutionIndex, userDefinedResolutionIndices[1]);
const lowResolutionIndex = Math.max(lowestResolutionIndex, userDefinedResolutionIndices[0]);

const resolutionSlider = (
<React.Fragment>
<h5 style={{ marginBottom: 0 }}>
Volume Resolutions{" "}
<Tooltip
title="Select which of the dataset resolutions the volume data should be created at. Restricting the available resolutions can greatly improve the performance when annotating large structures, such as nuclei, since the volume data does not need to be stored in all quality levels. How to read: Resolution 1 is the most detailed, 4-4-2 is downsampled by factor 4 in x and y, and by factor 2 in z."
placement="right"
const resolutionSlider =
annotationType !== "skeleton" ? (
<React.Fragment>
<h5 style={{ marginBottom: 0 }}>
Restrict Volume Resolutions{" "}
<Tooltip
title="Select which of the dataset resolutions the volume data should be created at. Restricting the available resolutions can greatly improve the performance when annotating large structures, such as nuclei, since the volume data does not need to be stored in all quality levels. How to read: Resolution 1 is the most detailed, 4-4-2 is downsampled by factor 4 in x and y, and by factor 2 in z."
placement="right"
>
<InfoCircleOutlined />
</Tooltip>
</h5>
<div
style={{
marginBottom: 16,
display: "flex",
justifyContent: "center",
alignItems: "center",
alignContent: "center",
}}
>
<InfoCircleOutlined />
</Tooltip>
</h5>
<div
style={{
marginBottom: 16,
display: "flex",
justifyContent: "center",
alignItems: "center",
alignContent: "center",
}}
>
<div style={{ width: "5em" }}>
{datasetResolutionInfo.getResolutionByIndexOrThrow(lowResolutionIndex).join("-")}
<div style={{ width: "5em" }}>
{datasetResolutionInfo.getResolutionByIndexOrThrow(lowResolutionIndex).join("-")}
</div>
<Slider
tooltipVisible={false}
onChange={value => setUserDefinedResolutionIndices(value)}
range
step={1}
min={lowestResolutionIndex}
max={highestResolutionIndex}
value={[lowResolutionIndex, highResolutionIndex]}
style={{ flexGrow: 1 }}
/>
<div style={{ width: "6.5em", textAlign: "right" }}>
{datasetResolutionInfo.getResolutionByIndexOrThrow(highResolutionIndex).join("-")}
</div>
</div>
<Slider
tooltipVisible={false}
onChange={value => setUserDefinedResolutionIndices(value)}
range
disabled={annotationType === "skeleton"}
step={1}
min={lowestResolutionIndex}
max={highestResolutionIndex}
value={[lowResolutionIndex, highResolutionIndex]}
style={{ flexGrow: 1 }}
/>
<div style={{ width: "6.5em", textAlign: "right" }}>
{datasetResolutionInfo.getResolutionByIndexOrThrow(highResolutionIndex).join("-")}
</div>
</div>
</React.Fragment>
);
</React.Fragment>
) : null;

modalContent = (
<React.Fragment>
Expand All @@ -125,17 +106,49 @@ const CreateExplorativeModal = ({ datasetId, onClose }: Props) => {
<Radio value="volume">Volume only</Radio>
</Radio.Group>
</div>
{doesSupportVolumeWithFallback(dataset) ? fallbackCheckbox : null}

{annotationType !== "skeleton" && segmentationLayers.length > 0 ? (
<div style={{ marginBottom: 16 }}>
Base Volume Annotation On{" "}
<Tooltip
title="Base your volume annotation on an existing segmentation layer of this dataset or create a new (empty) layer for the annotation."
placement="right"
>
<InfoCircleOutlined />
</Tooltip>
<Radio.Group
onChange={e => {
const index = parseInt(e.target.value);
setSelectedSegmentationLayerIndex(index !== -1 ? index : null);
}}
value={selectedSegmentationLayerIndex != null ? selectedSegmentationLayerIndex : -1}
>
<Radio key={-1} value={-1}>
Create empty layer
</Radio>
{segmentationLayers.map((segmentationLayer, index) => (
<Radio
key={segmentationLayer.name}
value={index}
disabled={!doesSupportVolumeWithFallback(dataset, segmentationLayer)}
>
{segmentationLayer.name}
</Radio>
))}
</Radio.Group>
</div>
) : null}

{lowestResolutionIndex < highestResolutionIndex ? resolutionSlider : null}
<div style={{ textAlign: "right" }}>
<Link
to={`/datasets/${dataset.owningOrganization}/${
dataset.name
}/createExplorative/${annotationType}/${isFallbackSegmentationSelectedString}?minRes=${Math.max(
}/createExplorative/${annotationType}/?minRes=${Math.max(
...datasetResolutionInfo.getResolutionByIndexOrThrow(lowResolutionIndex),
)}&maxRes=${Math.max(
...datasetResolutionInfo.getResolutionByIndexOrThrow(highResolutionIndex),
)}`}
)}${fallbackLayerGetParameter}`}
title="Create new annotation with selected properties"
>
<Button size="large" type="primary">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,12 @@ import { Unicode } from "oxalis/constants";

import type { APIMaybeUnimportedDataset, APIDatasetId } from "types/api_flow_types";
import { clearCache } from "admin/admin_rest_api";
import { doesSupportVolumeWithFallback } from "oxalis/model/accessors/dataset_accessor";
import {
getFirstSegmentationLayer,
doesSupportVolumeWithFallback,
} from "oxalis/model/accessors/dataset_accessor";
import Toast from "libs/toast";
import messages from "messages";

import CreateExplorativeModal from "dashboard/advanced_dataset/create_explorative_modal";

const disabledStyle = { pointerEvents: "none", color: "var(--ant-disabled)" };
Expand All @@ -34,7 +36,14 @@ function NewAnnotationLink({
onShowCreateExplorativeModal,
onCloseCreateExplorativeModal,
}) {
const withFallback = doesSupportVolumeWithFallback(dataset) ? "true" : "false";
const firstSegmentationLayer = getFirstSegmentationLayer(dataset);
philippotto marked this conversation as resolved.
Show resolved Hide resolved
const supportsFallback = doesSupportVolumeWithFallback(dataset, firstSegmentationLayer)
? "true"
: "false";
const fallbackLayerGetParameter =
firstSegmentationLayer != null && supportsFallback
? `?fallbackLayerName=${firstSegmentationLayer.name}`
: "";

return (
<React.Fragment>
Expand All @@ -43,7 +52,7 @@ function NewAnnotationLink({
<LinkWithDisabled
to={`/datasets/${dataset.owningOrganization}/${
dataset.name
}/createExplorative/hybrid/${withFallback}`}
}/createExplorative/hybrid${fallbackLayerGetParameter}`}
style={{ display: "inline-block" }}
title="New Annotation (Skeleton + Volume)"
disabled={isReloading}
Expand Down
1 change: 1 addition & 0 deletions frontend/javascripts/features.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export function getDemoDatasetUrl() {
throw new Error("Features not yet loaded.");
}

// todop: how to pass the fallback layer here?
philippotto marked this conversation as resolved.
Show resolved Hide resolved
// Only create a tracing on the demo instance, as the user might not be logged in there
// otherwise.
const suffix = features.isDemoInstance ? "/createExplorative/hybrid/true" : "/view";
Expand Down
7 changes: 3 additions & 4 deletions frontend/javascripts/libs/UpdatableTexture.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ UpdatableTexture.prototype.isUpdatableTexture = true;
UpdatableTexture.prototype.setRenderer = function setRenderer(renderer) {
this.renderer = renderer;
this.gl = this.renderer.getContext();
this.utils = THREE.WebGLUtils(this.gl, this.renderer.extensions);
this.utils = THREE.WebGLUtils(this.gl, this.renderer.extensions, this.renderer.capabilities);
};

UpdatableTexture.prototype.setSize = function setSize(width, height) {
Expand Down Expand Up @@ -89,11 +89,10 @@ UpdatableTexture.prototype.isInitialized = function isInitialized() {
};

UpdatableTexture.prototype.update = function update(src, x, y, width, height) {
this.setSize(width, width);
if (!this.isInitialized()) {
console.warn("Update called before texture was initialized. This update is discarded");
return;
this.renderer.initTexture(this);
philippotto marked this conversation as resolved.
Show resolved Hide resolved
}
this.setSize(width, width);

const activeTexture = this.gl.getParameter(this.gl.TEXTURE_BINDING_2D);
const textureProperties = this.renderer.properties.get(this);
Expand Down
Loading