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

Lock mapping once the volume annotation was changed by the user #7549

Merged
merged 49 commits into from
Mar 11, 2024
Merged
Show file tree
Hide file tree
Changes from 33 commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
5177f39
make segmentation output layer name for neuron detection configurable
MichaelBuessemeyer Dec 4, 2023
f142aaf
Merge branch 'master' of github.com:scalableminds/webknossos
MichaelBuessemeyer Jan 12, 2024
0df0c87
pin mapping once user did some volume annotation
MichaelBuessemeyer Jan 12, 2024
9cc5b45
some minor clean up
MichaelBuessemeyer Jan 12, 2024
8febc53
use and enforce pin mapping action in backend
fm3 Jan 15, 2024
7d42133
undo temporary changes
MichaelBuessemeyer Jan 17, 2024
b503c70
reload available agglomerate mappings names if not fetched yet
MichaelBuessemeyer Jan 17, 2024
73c7247
Merge branch 'master' of github.com:scalableminds/webknossos into pin…
MichaelBuessemeyer Jan 17, 2024
da7ef21
add changelog entry
MichaelBuessemeyer Jan 17, 2024
b933ff1
fix tests
MichaelBuessemeyer Jan 17, 2024
c334ec6
add test for pin mapping functionality
MichaelBuessemeyer Jan 18, 2024
41e5eb8
clean up code
MichaelBuessemeyer Jan 18, 2024
16acac3
fix assertion
fm3 Jan 22, 2024
cc0a6db
Merge branch 'master' into pin-mapping
MichaelBuessemeyer Jan 22, 2024
d57ac5f
Merge branch 'master' of github.com:scalableminds/webknossos into pin…
MichaelBuessemeyer Jan 23, 2024
57fac0c
disable proofreading tool in case a mapping is pinned and not editable
MichaelBuessemeyer Jan 23, 2024
0292f11
pin mapping upon interpolation & floodfill
MichaelBuessemeyer Jan 23, 2024
8692bf6
Merge branch 'master' of github.com:scalableminds/webknossos into pin…
MichaelBuessemeyer Jan 23, 2024
d4ab683
update proofread tool disable message
MichaelBuessemeyer Jan 23, 2024
b8fccc6
fix tests
MichaelBuessemeyer Jan 23, 2024
378d0f8
Ask user for confirmation before pinning mapping
MichaelBuessemeyer Feb 12, 2024
0db891a
pin mapping / no mapping upon first volume annotation action
MichaelBuessemeyer Feb 12, 2024
617e391
disable merger mode once a mapping is pinned
MichaelBuessemeyer Feb 12, 2024
ee67207
Merge branch 'master' of github.com:scalableminds/webknossos into pin…
MichaelBuessemeyer Feb 12, 2024
57c3ecf
don't do quick select if user aborts the pinning of the active mapping
MichaelBuessemeyer Feb 12, 2024
17182dd
fix tests
MichaelBuessemeyer Feb 12, 2024
ae4981c
improve user informing message
MichaelBuessemeyer Feb 13, 2024
75c396b
remove unused import
MichaelBuessemeyer Feb 13, 2024
92ae8dd
apply feedback
MichaelBuessemeyer Feb 13, 2024
9053a82
apply pr feedback
MichaelBuessemeyer Feb 13, 2024
cc396d4
Merge branch 'master' of github.com:scalableminds/webknossos into pin…
MichaelBuessemeyer Feb 19, 2024
ccadeb2
properly catch user abort via modal
MichaelBuessemeyer Feb 19, 2024
06a8d63
Let server saved annotation take precedence over the URL configuratio…
MichaelBuessemeyer Feb 19, 2024
cf7dcbe
pin "no active mapping" in case a JSON mapping is active & fix linting
MichaelBuessemeyer Feb 19, 2024
e1ce3d8
Merge branch 'master' of github.com:scalableminds/webknossos into pin…
MichaelBuessemeyer Feb 20, 2024
994cdce
disable volume annotations tools while json mapping is active and ren…
MichaelBuessemeyer Feb 20, 2024
625323b
small code clean up
MichaelBuessemeyer Feb 20, 2024
6325e1c
rename pinned to locked also in backend + proto
fm3 Feb 20, 2024
5ae9144
apply feedback & further replace word "pinned" with "locked"
MichaelBuessemeyer Feb 26, 2024
f6ce1be
Merge branch 'pin-mapping' of github.com:scalableminds/webknossos int…
MichaelBuessemeyer Feb 26, 2024
8180e04
further replace "pin" with "locked"
MichaelBuessemeyer Feb 26, 2024
f8485c5
fix bucket fill ignoring use decision whether to lock the mapping
MichaelBuessemeyer Feb 26, 2024
cada9ba
Merge branch 'master' into pin-mapping
MichaelBuessemeyer Feb 26, 2024
bd7daab
Merge branch 'master' into pin-mapping
MichaelBuessemeyer Feb 27, 2024
6c79877
Merge branch 'master' into pin-mapping
philippotto Mar 5, 2024
df310b5
format
philippotto Mar 5, 2024
7c71170
Merge branch 'master' of github.com:scalableminds/webknossos into pin…
philippotto Mar 11, 2024
ebed38c
Merge branch 'master' into pin-mapping
MichaelBuessemeyer Mar 11, 2024
0fad70e
Merge branch 'master' into pin-mapping
philippotto Mar 11, 2024
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 @@ -19,6 +19,7 @@ For upgrade instructions, please check the [migration guide](MIGRATIONS.released
### Changed
- Datasets stored in WKW format are no longer loaded with memory mapping, reducing memory demands. [#7528](https://github.com/scalableminds/webknossos/pull/7528)
- Content Security Policy (CSP) settings are now relaxed by default. To keep stricter CSP rules, add them to your specific `application.conf`. [#7589](https://github.com/scalableminds/webknossos/pull/7589)
- HDF5 agglomerate mappings are now pinned to a volume annotation layer once the user performs a volume annotation action while a mapping is active. [#7549](https://github.com/scalableminds/webknossos/pull/7549)
- WEBKNOSSOS now uses Java 21. [#7599](https://github.com/scalableminds/webknossos/pull/7599)
- Email verification is disabled by default. To enable it, set `webKnossos.user.emailVerification.activated` to `true` in your `application.conf`. [#7620](https://github.com/scalableminds/webknossos/pull/7620) [#7621](https://github.com/scalableminds/webknossos/pull/7621)
- Added more documentation for N5 and Neuroglancer precomputed web upload. [#7622](https://github.com/scalableminds/webknossos/pull/7622)
Expand Down
4 changes: 4 additions & 0 deletions frontend/javascripts/messages.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,10 @@ instead. Only enable this option if you understand its effect. All layers will n
'All trees are currently hidden. You can disable this by toggling the "Skeleton" layer in the layer settings in the left sidebar.',
"tracing.invalid_json_url_hash":
"Cannot parse JSON URL hash. More information was printed to the browser's console.",
"tracing.pin_mapping_info":
"The active volume annotation layer has an active mapping. By mutating the layer, the mapping will be permanently enabled. This can only be undone by restoring an older version of this annotation. Are you sure you want to continue?",
"tracing.pin_mapping_confirmed": (mappingName: string) =>
`The mapping ${mappingName} is now permanently enabled for this annotation and can no longer be changed or disabled.`,
"layouting.missing_custom_layout_info":
"The annotation views are separated into four classes. Each of them has their own layouts. If you can't find your layout please open the annotation in the correct view mode or just add it here manually.",
"datastore.unknown_type": "Unknown datastore type:",
Expand Down
81 changes: 60 additions & 21 deletions frontend/javascripts/oxalis/model/accessors/tool_accessor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,13 +112,61 @@ function _getDisabledInfoWhenVolumeIsDisabled(
};
}

function _getDisabledInfoForProofreadTool(
hasSkeleton: boolean,
agglomerateState: AgglomerateState,
isProofReadingToolAllowed: boolean,
isUneditableMappingPinned: boolean,
activeOrganization: APIOrganization | null,
activeUser: APIUser | null | undefined,
) {
// The explanations are prioritized according to effort the user has to put into
// activating proofreading.
// 1) If a non editable mapping is pinned to the annotation, proofreading actions are
// not allowed for this annotation.
// 2) If no agglomerate mapping is available (or activated), the user should know
// about this requirement and be able to set it up (this can be the most difficult
// step).
// 3) If a mapping is available, the pricing plan is potentially warned upon.
// 4) In the end, a potentially missing skeleton is warned upon (quite rare, because
// most annotations have a skeleton).
const isDisabled =
!hasSkeleton ||
!agglomerateState.value ||
!isProofReadingToolAllowed ||
isUneditableMappingPinned;
let explanation = "Proofreading actions are not supported after modifying the segmentation.";
if (!isUneditableMappingPinned) {
if (!agglomerateState.value) {
explanation = agglomerateState.reason;
} else if (!isProofReadingToolAllowed) {
explanation = getFeatureNotAvailableInPlanMessage(
PricingPlanEnum.Power,
activeOrganization,
activeUser,
);
} else {
explanation = disabledSkeletonExplanation;
}
} else {
explanation =
"A mapping that does not support proofreading actions is pinned to this annotation. Most likely, the annotation layer was modified earlier (e.g. by brushing).";
}
return {
isDisabled,
explanation,
};
}

const getDisabledInfoWhenVolumeIsDisabled = memoizeOne(_getDisabledInfoWhenVolumeIsDisabled);
const getDisabledInfoForProofreadTool = memoizeOne(_getDisabledInfoForProofreadTool);

function _getDisabledInfoFromArgs(
hasSkeleton: boolean,
isZoomStepTooHighForBrushing: boolean,
isZoomStepTooHighForTracing: boolean,
isZoomStepTooHighForFilling: boolean,
isUneditableMappingPinned: boolean,
agglomerateState: AgglomerateState,
genericDisabledExplanation: string,
activeOrganization: APIOrganization | null,
Expand Down Expand Up @@ -170,27 +218,14 @@ function _getDisabledInfoFromArgs(
isDisabled: isZoomStepTooHighForFilling,
explanation: zoomInToUseToolMessage,
},
[AnnotationToolEnum.PROOFREAD]: {
isDisabled: !hasSkeleton || !agglomerateState.value || !isProofReadingToolAllowed,
explanation:
// The explanations are prioritized according to effort the user has to put into
// activating proofreading.
// 1) If no agglomerate mapping is available (or activated), the user should know
// about this requirement and be able to set it up (this can be the most difficult
// step).
// 2) If a mapping is available, the pricing plan is potentially warned upon.
// 3) In the end, a potentially missing skeleton is warned upon (quite rare, because
// most annotations have a skeleton).
MichaelBuessemeyer marked this conversation as resolved.
Show resolved Hide resolved
agglomerateState.value
? isProofReadingToolAllowed
? disabledSkeletonExplanation
: getFeatureNotAvailableInPlanMessage(
PricingPlanEnum.Power,
activeOrganization,
activeUser,
)
: agglomerateState.reason,
},
[AnnotationToolEnum.PROOFREAD]: getDisabledInfoForProofreadTool(
hasSkeleton,
agglomerateState,
isProofReadingToolAllowed,
isUneditableMappingPinned,
activeOrganization,
activeUser,
),
[AnnotationToolEnum.LINE_MEASUREMENT]: {
isDisabled: false,
explanation: genericDisabledExplanation,
Expand Down Expand Up @@ -242,6 +277,9 @@ export function getDisabledInfoForTools(state: OxalisState): Record<
isEditableMappingActive,
isSegmentationTracingTransformed,
);
const isUneditableMappingPinned =
(segmentationTracingLayer?.mappingIsPinned && !segmentationTracingLayer?.mappingIsEditable) ??
false;

const isVolumeDisabled =
!hasVolume ||
Expand All @@ -268,6 +306,7 @@ export function getDisabledInfoForTools(state: OxalisState): Record<
isVolumeAnnotationDisallowedForZoom(AnnotationToolEnum.BRUSH, state),
isVolumeAnnotationDisallowedForZoom(AnnotationToolEnum.TRACE, state),
isVolumeAnnotationDisallowedForZoom(AnnotationToolEnum.FILL_CELL, state),
isUneditableMappingPinned,
agglomerateState,
genericDisabledExplanation,
state.activeOrganization,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -525,35 +525,56 @@ export function getMappingInfoForVolumeTracing(
return getMappingInfo(state.temporaryConfiguration.activeMappingByLayer, tracingId);
}

export function hasEditableMapping(
function getVolumeTracingForLayerName(
state: OxalisState,
layerName?: string | null | undefined,
): boolean {
): VolumeTracing | null | undefined {
if (layerName != null) {
// This needs to be checked before calling getRequestedOrDefaultSegmentationTracingLayer,
// as the function will throw an error if layerName is given but a corresponding tracing layer
// does not exist.
const layer = getSegmentationLayerByName(state.dataset, layerName);
const tracing = getTracingForSegmentationLayer(state, layer);

if (tracing == null) return false;
if (tracing == null) return null;
}

const volumeTracing = getRequestedOrDefaultSegmentationTracingLayer(state, layerName);

return volumeTracing;
}

export function hasEditableMapping(
state: OxalisState,
layerName?: string | null | undefined,
): boolean {
const volumeTracing = getVolumeTracingForLayerName(state, layerName);

if (volumeTracing == null) return false;

return !!volumeTracing.mappingIsEditable;
}

export function isMappingPinned(
state: OxalisState,
layerName?: string | null | undefined,
): boolean {
const volumeTracing = getVolumeTracingForLayerName(state, layerName);

if (volumeTracing == null) return false;

return !!volumeTracing.mappingIsPinned;
}

export function isMappingActivationAllowed(
state: OxalisState,
mappingName: string | null | undefined,
layerName?: string | null | undefined,
): boolean {
const isEditableMappingActive = hasEditableMapping(state, layerName);
const isActiveMappingPinned = isMappingPinned(state, layerName);

if (!isEditableMappingActive) return true;
if (!isEditableMappingActive && !isActiveMappingPinned) return true;

const volumeTracing = getRequestedOrDefaultSegmentationTracingLayer(state, layerName);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ export type RemoveSegmentAction = ReturnType<typeof removeSegmentAction>;
export type DeleteSegmentDataAction = ReturnType<typeof deleteSegmentDataAction>;
export type SetSegmentGroupsAction = ReturnType<typeof setSegmentGroupsAction>;
export type SetMappingIsEditableAction = ReturnType<typeof setMappingIsEditableAction>;
export type SetMappingIsPinnedAction = ReturnType<typeof setMappingIsPinnedAction>;

export type ComputeQuickSelectForRectAction = ReturnType<typeof computeQuickSelectForRectAction>;
export type MaybePrefetchEmbeddingAction = ReturnType<typeof maybePrefetchEmbeddingAction>;
Expand Down Expand Up @@ -89,6 +90,7 @@ export type VolumeTracingAction =
| SetLargestSegmentIdAction
| SetSelectedSegmentsOrGroupAction
| SetMappingIsEditableAction
| SetMappingIsPinnedAction
| InitializeEditableMappingAction
| ComputeQuickSelectForRectAction
| MaybePrefetchEmbeddingAction
Expand All @@ -110,6 +112,8 @@ export const VolumeTracingSaveRelevantActions = [
"SET_MAPPING",
"SET_MAPPING_ENABLED",
"BATCH_UPDATE_GROUPS_AND_SEGMENTS",
"SET_MAPPING_IS_EDITABLE",
"SET_MAPPING_IS_PINNED",
];

export const VolumeTracingUndoRelevantActions = ["START_EDITING", "COPY_SEGMENTATION_LAYER"];
Expand Down Expand Up @@ -356,6 +360,11 @@ export const setMappingIsEditableAction = () =>
type: "SET_MAPPING_IS_EDITABLE",
} as const);

export const setMappingIsPinnedAction = () =>
({
type: "SET_MAPPING_IS_PINNED",
MichaelBuessemeyer marked this conversation as resolved.
Show resolved Hide resolved
} as const);

export const computeQuickSelectForRectAction = (
startPosition: Vector3,
endPosition: Vector3,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,7 @@ export function serverVolumeToClientVolumeTracing(tracing: ServerVolumeTracing):
userBoundingBoxes,
mappingName: tracing.mappingName,
mappingIsEditable: tracing.mappingIsEditable,
mappingIsPinned: tracing.mappingIsPinned,
hasSegmentIndex: tracing.hasSegmentIndex || false,
additionalAxes: convertServerAdditionalAxesToFrontEnd(tracing.additionalAxes),
};
Expand Down Expand Up @@ -391,18 +392,27 @@ function VolumeTracingReducer(

case "SET_MAPPING_NAME": {
// Editable mappings cannot be disabled or switched for now
if (volumeTracing.mappingIsEditable) return state;
if (volumeTracing.mappingIsEditable || volumeTracing.mappingIsPinned) return state;

const { mappingName, mappingType } = action;
return setMappingNameReducer(state, volumeTracing, mappingName, mappingType);
}

case "SET_MAPPING_IS_EDITABLE": {
// Editable mappings cannot be disabled or switched for now
if (volumeTracing.mappingIsEditable) return state;
// Editable mappings cannot be disabled or switched for now.
if (volumeTracing.mappingIsEditable || volumeTracing.mappingIsPinned) return state;

// An editable mapping is always pinned.
return updateVolumeTracing(state, volumeTracing.tracingId, {
mappingIsEditable: true,
mappingIsPinned: true,
});
}
case "SET_MAPPING_IS_PINNED": {
if (volumeTracing.mappingIsPinned) return state;

return updateVolumeTracing(state, volumeTracing.tracingId, {
mappingIsPinned: true,
});
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -153,8 +153,10 @@ export function setMappingNameReducer(
mappingType: MappingType,
isMappingEnabled: boolean = true,
) {
// Editable mappings cannot be disabled or switched for now
if (volumeTracing.mappingIsEditable) return state;
// Editable mappings or pinned mappings cannot be disabled or switched for now
if (volumeTracing.mappingIsEditable || volumeTracing.mappingIsPinned) {
return state;
}
// Only HDF5 mappings are persisted in volume annotations for now
if (mappingType !== "HDF5" || !isMappingEnabled) {
mappingName = null;
Expand Down
16 changes: 16 additions & 0 deletions frontend/javascripts/oxalis/model/sagas/quick_select_saga.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ import performQuickSelectML, {
} from "./quick_select_ml_saga";
import { AnnotationToolEnum } from "oxalis/constants";
import getSceneController from "oxalis/controller/scene_controller_provider";
import { getActiveSegmentationTracing } from "../accessors/volumetracing_accessor";
import { VolumeTracing } from "oxalis/store";
import { ensureMaybeActiveMappingIsPinned } from "./saga_helpers";

function* shouldUseHeuristic() {
const useHeuristic = yield* select((state) => state.userConfiguration.quickSelect.useHeuristic);
Expand All @@ -29,6 +32,19 @@ export default function* listenToQuickSelect(): Saga<void> {
"COMPUTE_QUICK_SELECT_FOR_RECT",
function* guard(action: ComputeQuickSelectForRectAction) {
try {
// As changes to the volume layer will be applied, the potentially existing mapping should be pinned to ensure a consistent state.
const volumeTracing: VolumeTracing | null | undefined = yield* select(
getActiveSegmentationTracing,
);
if (volumeTracing) {
const { isMappingPinnedIfNeeded } = yield* call(
MichaelBuessemeyer marked this conversation as resolved.
Show resolved Hide resolved
ensureMaybeActiveMappingIsPinned,
volumeTracing,
);
if (!isMappingPinnedIfNeeded) {
return;
}
}
yield* put(setBusyBlockingInfoAction(true, "Selecting segment"));

yield* put(setQuickSelectStateAction("active"));
Expand Down
Loading