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 12 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 @@ -27,6 +27,7 @@ For upgrade instructions, please check the [migration guide](MIGRATIONS.released
- Admins and Team Managers can now also download job exports for jobs of other users, if they have the link. [#7462](https://github.com/scalableminds/webknossos/pull/7462)
- Updated some dependencies of the backend code (play 2.9, sbt 1.9, minor upgrades for others) for optimized performance. [#7366](https://github.com/scalableminds/webknossos/pull/7366)
- Processing jobs can now be distributed to multiple webknossos-workers with finer-grained configurability. Compare migration guide. [#7463](https://github.com/scalableminds/webknossos/pull/7463)
- 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)
- Removed Swagger/OpenAPI json description of the HTTP API. [#7494](https://github.com/scalableminds/webknossos/pull/7494)
- Updated antd UI library from version 4.24.8 to 4.24.15. [#7505](https://github.com/scalableminds/webknossos/pull/7505)
- Changed the default dataset search mode to also search in subfolders. [#7539](https://github.com/scalableminds/webknossos/pull/7539)
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 Down Expand Up @@ -356,6 +358,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;
// An editable mapping is always pinned.
if (volumeTracing.mappingIsEditable && volumeTracing.mappingIsPinned) return state;
MichaelBuessemeyer marked this conversation as resolved.
Show resolved Hide resolved

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
3 changes: 2 additions & 1 deletion frontend/javascripts/oxalis/model/sagas/update_actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -413,10 +413,11 @@ export function serverCreateTracing(timestamp: number) {
export function updateMappingName(
mappingName: string | null | undefined,
isEditable: boolean | undefined,
isPinned: boolean | undefined,
) {
return {
name: "updateMappingName",
value: { mappingName, isEditable },
value: { mappingName, isEditable, isPinned },
} as const;
}
export function splitAgglomerate(
Expand Down
33 changes: 31 additions & 2 deletions frontend/javascripts/oxalis/model/sagas/volumetracing_saga.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ import type {
} from "oxalis/model/actions/annotation_actions";
import { addUserBoundingBoxAction } from "oxalis/model/actions/annotation_actions";
import {
setMappingNameAction,
updateTemporarySettingAction,
updateUserSettingAction,
} from "oxalis/model/actions/settings_actions";
Expand All @@ -70,6 +71,7 @@ import type {
import {
finishAnnotationStrokeAction,
registerLabelPointAction,
setMappingIsPinnedAction,
setSelectedSegmentsOrGroupAction,
updateSegmentAction,
} from "oxalis/model/actions/volumetracing_actions";
Expand Down Expand Up @@ -214,6 +216,25 @@ export function* editVolumeLayerAsync(): Saga<any> {
}

const activeCellId = yield* select((state) => enforceActiveVolumeTracing(state).activeCellId);
const activeMappingByLayer = yield* select(
(state) => state.temporaryConfiguration.activeMappingByLayer,
);
const isSomeMappingActive = volumeTracing.tracingId in activeMappingByLayer;
if (isSomeMappingActive && !volumeTracing.mappingIsPinned) {
// A mapping that is active and is annotated on needs to be pinned to ensure a consistent state in the future.
// See https://github.com/scalableminds/webknossos/issues/5431 for more information.
const activeMapping = activeMappingByLayer[volumeTracing.tracingId];
if (activeMapping.mappingName) {
yield* put(
setMappingNameAction(
volumeTracing.tracingId,
activeMapping.mappingName,
activeMapping.mappingType,
),
);
yield* put(setMappingIsPinnedAction());
}
}

if (isDrawing && activeCellId === 0) {
yield* call(
Expand Down Expand Up @@ -670,8 +691,16 @@ export function* diffVolumeTracing(
yield removeFallbackLayer();
}

if (prevVolumeTracing.mappingName !== volumeTracing.mappingName) {
yield updateMappingName(volumeTracing.mappingName, volumeTracing.mappingIsEditable);
if (
prevVolumeTracing.mappingName !== volumeTracing.mappingName ||
prevVolumeTracing.mappingIsPinned !== volumeTracing.mappingIsPinned
) {
const action = updateMappingName(
volumeTracing.mappingName,
volumeTracing.mappingIsEditable,
volumeTracing.mappingIsPinned,
);
yield action;
}
}
}
Expand Down
1 change: 1 addition & 0 deletions frontend/javascripts/oxalis/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,7 @@ export type VolumeTracing = TracingBase & {
readonly fallbackLayer?: string;
readonly mappingName?: string | null | undefined;
readonly mappingIsEditable?: boolean;
readonly mappingIsPinned?: boolean;
readonly hasSegmentIndex: boolean;
};
export type ReadOnlyTracing = TracingBase & {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import * as Utils from "libs/utils";
import {
getEditableMappingForVolumeTracingId,
hasEditableMapping,
isMappingPinned,
} from "oxalis/model/accessors/volumetracing_accessor";

const { Option, OptGroup } = Select;
Expand All @@ -43,6 +44,7 @@ type StateProps = {
mappingType: MappingType;
mappingColors: Array<number> | null | undefined;
editableMapping: EditableMapping | null | undefined;
isMappingPinned: boolean;
isMergerModeEnabled: boolean;
allowUpdate: boolean;
isEditableMappingActive: boolean;
Expand Down Expand Up @@ -91,7 +93,6 @@ class MappingSettingsView extends React.Component<Props, State> {
handleChangeHideUnmappedSegments = (hideUnmappedIds: boolean) => {
this.props.setHideUnmappedIds(this.props.layerName, hideUnmappedIds);
};

handleChangeMapping = (packedMappingNameWithCategory: string): void => {
const [mappingName, mappingType] = unpackMappingNameAndCategory(packedMappingNameWithCategory);

Expand Down Expand Up @@ -173,6 +174,7 @@ class MappingSettingsView extends React.Component<Props, State> {
(shouldMappingBeEnabled || this.props.isMergerModeEnabled) &&
this.props.mapping &&
this.props.hideUnmappedIds != null;
const isDisabled = this.props.isEditableMappingActive || this.props.isMappingPinned;
return (
<React.Fragment>
{
Expand All @@ -191,7 +193,7 @@ class MappingSettingsView extends React.Component<Props, State> {
label="ID Mapping"
// Assume that the mappings are being loaded if they are null
loading={shouldMappingBeEnabled && this.props.segmentationLayer?.mappings == null}
disabled={this.props.isEditableMappingActive}
disabled={isDisabled}
/>
</div>

Expand All @@ -210,7 +212,7 @@ class MappingSettingsView extends React.Component<Props, State> {
{...selectValueProp}
onChange={this.handleChangeMapping}
notFoundContent="No mappings found."
disabled={this.props.isEditableMappingActive}
disabled={isDisabled}
MichaelBuessemeyer marked this conversation as resolved.
Show resolved Hide resolved
>
{renderCategoryOptions(availableMappings, "JSON")}
{renderCategoryOptions(availableAgglomerates, "HDF5")}
Expand Down Expand Up @@ -262,6 +264,7 @@ function mapStateToProps(state: OxalisState, ownProps: OwnProps) {
allowUpdate: state.tracing.restrictions.allowUpdate,
editableMapping,
isEditableMappingActive: hasEditableMapping(state, ownProps.layerName),
isMappingPinned: isMappingPinned(state, ownProps.layerName),
};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,15 @@ import { convertFrontendBoundingBoxToServer } from "oxalis/model/reducers/reduce
import { enforce } from "libs/utils";
import { pushSaveQueueTransaction } from "oxalis/model/actions/save_actions";
import * as VolumeTracingActions from "oxalis/model/actions/volumetracing_actions";
import { setMappingNameAction } from "oxalis/model/actions/settings_actions";
import VolumeTracingReducer from "oxalis/model/reducers/volumetracing_reducer";
import defaultState from "oxalis/default_state";
import mockRequire from "mock-require";
import sinon from "sinon";
import test from "ava";
import { expectValueDeepEqual, execCall } from "test/helpers/sagaHelpers";
import { withoutUpdateTracing } from "test/helpers/saveHelpers";
import { ActiveMappingInfo } from "oxalis/store";

mockRequire("app", {
currentUser: {
Expand Down Expand Up @@ -109,6 +111,18 @@ const initialState = update(defaultState, {
},
},
});

const dummyActiveMapping: ActiveMappingInfo = {
mappingName: "dummy-mapping-name",
mapping: {},
mappingKeys: [],
mappingColors: [],
hideUnmappedIds: false,
mappingStatus: "ENABLED",
mappingSize: 0,
mappingType: "HDF5",
};

const ACTIVE_CELL_ID = 5;
const setActiveCellAction = VolumeTracingActions.setActiveCellAction(ACTIVE_CELL_ID);
const startEditingAction = VolumeTracingActions.startEditingAction([0, 0, 0], OrthoViews.PLANE_XY);
Expand Down Expand Up @@ -185,6 +199,7 @@ test("VolumeTracingSaga should create a volume layer (saga test)", (t) => {
zoomStep: 0,
});
saga.next(ACTIVE_CELL_ID); // pass active cell id
saga.next({});
expectValueDeepEqual(
t,
saga.next([]), // pass empty additional coords
Expand Down Expand Up @@ -225,6 +240,7 @@ test("VolumeTracingSaga should add values to volume layer (saga test)", (t) => {
}); // pass labeled resolution

saga.next(ACTIVE_CELL_ID); // pass active cell id
saga.next({});
expectValueDeepEqual(
t,
saga.next([]), // pass empty additional coords
Expand Down Expand Up @@ -275,6 +291,7 @@ test("VolumeTracingSaga should finish a volume layer (saga test)", (t) => {
}); // pass labeled resolution

saga.next(ACTIVE_CELL_ID); // pass active cell id
saga.next({});
expectValueDeepEqual(
t,
saga.next([]), // pass empty additional coords
Expand Down Expand Up @@ -332,6 +349,7 @@ test("VolumeTracingSaga should finish a volume layer in delete mode (saga test)"
zoomStep: 0,
}); // pass labeled resolution
saga.next(ACTIVE_CELL_ID); // pass active cell id
saga.next({});
expectValueDeepEqual(
t,
saga.next([]), // pass empty additional coords
Expand Down Expand Up @@ -386,3 +404,40 @@ test("VolumeTracingSaga should ignore brush action when busy (saga test)", (t) =
take("START_EDITING"),
);
});

test("VolumeTracingSaga should pin an active mapping upon first volume annotation", (t) => {
const saga = editVolumeLayerAsync();
saga.next();
saga.next();
expectValueDeepEqual(t, saga.next(true), take("START_EDITING"));
saga.next(startEditingAction);
saga.next({
isBusy: false,
});
saga.next(volumeTracing);
saga.next(OverwriteModeEnum.OVERWRITE_ALL);
saga.next(AnnotationToolEnum.BRUSH);
saga.next(false);
// pass labeled resolution
saga.next({
resolution: [1, 1, 1],
zoomStep: 0,
});
saga.next(ACTIVE_CELL_ID); // pass active cell id
expectValueDeepEqual(
t,
saga.next({ [volumeTracing.tracingId]: dummyActiveMapping }),
put(
setMappingNameAction(
volumeTracing.tracingId,
dummyActiveMapping.mappingName as string,
dummyActiveMapping.mappingType,
),
),
);
expectValueDeepEqual(
t,
saga.next([]), // pass empty additional coords
put(VolumeTracingActions.setMappingIsPinnedAction()),
);
});
1 change: 1 addition & 0 deletions frontend/javascripts/types/api_flow_types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -765,6 +765,7 @@ export type ServerVolumeTracing = ServerTracingBase & {
resolutions?: Array<Point3>;
mappingName?: string | null | undefined;
mappingIsEditable?: boolean;
mappingIsPinned?: boolean;
hasSegmentIndex?: boolean;
};
export type ServerTracing = ServerSkeletonTracing | ServerVolumeTracing;
Expand Down
Loading