Skip to content

Commit 5a9ecee

Browse files
frcrothphilippotto
andauthored
Add delete segment data update action (#7435)
Co-authored-by: Philipp Otto <[email protected]>
1 parent ae6d9ab commit 5a9ecee

File tree

12 files changed

+220
-12
lines changed

12 files changed

+220
-12
lines changed

CHANGELOG.unreleased.md

+1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ For upgrade instructions, please check the [migration guide](MIGRATIONS.released
1111
[Commits](https://github.com/scalableminds/webknossos/compare/23.12.0...HEAD)
1212

1313
### Added
14+
- The data of segments can now be deleted in the segment side panel. [#7435](https://github.com/scalableminds/webknossos/pull/7435)
1415
- Added support for S3-compliant object storage services (e.g. MinIO) as a storage backend for remote datasets. [#7453](https://github.com/scalableminds/webknossos/pull/7453)
1516
- Added support for blosc compressed N5 datasets. [#7465](https://github.com/scalableminds/webknossos/pull/7465)
1617
- Added route for triggering the compute segment index worker job. [#7471](https://github.com/scalableminds/webknossos/pull/7471)

frontend/javascripts/oxalis/model/actions/volumetracing_actions.ts

+16
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ export type SetLargestSegmentIdAction = ReturnType<typeof setLargestSegmentIdAct
3939
export type SetSegmentsAction = ReturnType<typeof setSegmentsAction>;
4040
export type UpdateSegmentAction = ReturnType<typeof updateSegmentAction>;
4141
export type RemoveSegmentAction = ReturnType<typeof removeSegmentAction>;
42+
export type DeleteSegmentDataAction = ReturnType<typeof deleteSegmentDataAction>;
4243
export type SetSegmentGroupsAction = ReturnType<typeof setSegmentGroupsAction>;
4344
export type SetMappingIsEditableAction = ReturnType<typeof setMappingIsEditableAction>;
4445

@@ -80,6 +81,7 @@ export type VolumeTracingAction =
8081
| SetSegmentsAction
8182
| UpdateSegmentAction
8283
| RemoveSegmentAction
84+
| DeleteSegmentDataAction
8385
| SetSegmentGroupsAction
8486
| AddBucketToUndoAction
8587
| ImportVolumeTracingAction
@@ -232,6 +234,20 @@ export const removeSegmentAction = (
232234
timestamp,
233235
} as const);
234236

237+
export const deleteSegmentDataAction = (
238+
segmentId: number,
239+
layerName: string,
240+
callback?: () => void,
241+
timestamp: number = Date.now(),
242+
) =>
243+
({
244+
type: "DELETE_SEGMENT_DATA",
245+
segmentId,
246+
layerName,
247+
callback,
248+
timestamp,
249+
} as const);
250+
235251
export const setSegmentGroupsAction = (
236252
segmentGroups: Array<SegmentGroup>,
237253
layerName: string,

frontend/javascripts/oxalis/model/bucket_data_handling/bucket.ts

+2
Original file line numberDiff line numberDiff line change
@@ -543,6 +543,8 @@ export class DataBucket {
543543
}
544544
}
545545
}
546+
547+
this.invalidateValueSet();
546548
}
547549

548550
markAsPulled(): void {

frontend/javascripts/oxalis/model/sagas/update_actions.ts

+10
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ type UpdateVolumeTracingUpdateAction = ReturnType<typeof updateVolumeTracing>;
3232
export type CreateSegmentUpdateAction = ReturnType<typeof createSegmentVolumeAction>;
3333
export type UpdateSegmentUpdateAction = ReturnType<typeof updateSegmentVolumeAction>;
3434
export type DeleteSegmentUpdateAction = ReturnType<typeof deleteSegmentVolumeAction>;
35+
export type DeleteSegmentDataUpdateAction = ReturnType<typeof deleteSegmentDataVolumeAction>;
3536
type UpdateUserBoundingBoxesUpdateAction = ReturnType<typeof updateUserBoundingBoxes>;
3637
export type UpdateBucketUpdateAction = ReturnType<typeof updateBucket>;
3738
type UpdateSegmentGroupsUpdateAction = ReturnType<typeof updateSegmentGroups>;
@@ -63,6 +64,7 @@ export type UpdateAction =
6364
| CreateSegmentUpdateAction
6465
| UpdateSegmentUpdateAction
6566
| DeleteSegmentUpdateAction
67+
| DeleteSegmentDataUpdateAction
6668
| UpdateBucketUpdateAction
6769
| UpdateTreeVisibilityUpdateAction
6870
| UpdateTreeEdgesVisibilityUpdateAction
@@ -346,6 +348,14 @@ export function deleteSegmentVolumeAction(id: number) {
346348
},
347349
} as const;
348350
}
351+
export function deleteSegmentDataVolumeAction(id: number) {
352+
return {
353+
name: "deleteSegmentData",
354+
value: {
355+
id,
356+
},
357+
} as const;
358+
}
349359
export function updateBucket(bucketInfo: SendBucketInfo, base64Data: string) {
350360
return {
351361
name: "updateBucket",

frontend/javascripts/oxalis/model/sagas/volumetracing_saga.tsx

+35-2
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ import type {
6565
ClickSegmentAction,
6666
SetActiveCellAction,
6767
CreateCellAction,
68+
DeleteSegmentDataAction,
6869
} from "oxalis/model/actions/volumetracing_actions";
6970
import {
7071
finishAnnotationStrokeAction,
@@ -79,7 +80,11 @@ import { select, take } from "oxalis/model/sagas/effect-generators";
7980
import listenToMinCut from "oxalis/model/sagas/min_cut_saga";
8081
import listenToQuickSelect from "oxalis/model/sagas/quick_select_saga";
8182
import { takeEveryUnlessBusy } from "oxalis/model/sagas/saga_helpers";
82-
import { UpdateAction, updateSegmentGroups } from "oxalis/model/sagas/update_actions";
83+
import {
84+
deleteSegmentDataVolumeAction,
85+
UpdateAction,
86+
updateSegmentGroups,
87+
} from "oxalis/model/sagas/update_actions";
8388
import {
8489
createSegmentVolumeAction,
8590
deleteSegmentVolumeAction,
@@ -90,7 +95,7 @@ import {
9095
updateMappingName,
9196
} from "oxalis/model/sagas/update_actions";
9297
import VolumeLayer from "oxalis/model/volumetracing/volumelayer";
93-
import { Model } from "oxalis/singletons";
98+
import { Model, api } from "oxalis/singletons";
9499
import type { Flycam, SegmentMap, VolumeTracing } from "oxalis/store";
95100
import React from "react";
96101
import { actionChannel, call, fork, put, takeEvery, takeLatest } from "typed-redux-saga";
@@ -101,6 +106,7 @@ import {
101106
} from "./volume/helpers";
102107
import maybeInterpolateSegmentationLayer from "./volume/volume_interpolation_saga";
103108
import messages from "messages";
109+
import { pushSaveQueueTransaction } from "../actions/save_actions";
104110

105111
export function* watchVolumeTracingAsync(): Saga<void> {
106112
yield* take("WK_READY");
@@ -842,8 +848,35 @@ function* ensureValidBrushSize(): Saga<void> {
842848
);
843849
}
844850

851+
function* handleDeleteSegmentData(): Saga<void> {
852+
yield* take("WK_READY");
853+
while (true) {
854+
const action = (yield* take("DELETE_SEGMENT_DATA")) as DeleteSegmentDataAction;
855+
856+
yield* put(setBusyBlockingInfoAction(true, "Segment is being deleted."));
857+
yield* put(
858+
pushSaveQueueTransaction(
859+
[deleteSegmentDataVolumeAction(action.segmentId)],
860+
"volume",
861+
action.layerName,
862+
),
863+
);
864+
yield* call([Model, Model.ensureSavedState]);
865+
866+
yield* call([api.data, api.data.reloadBuckets], action.layerName, (bucket) =>
867+
bucket.containsValue(action.segmentId),
868+
);
869+
870+
yield* put(setBusyBlockingInfoAction(false));
871+
if (action.callback) {
872+
action.callback();
873+
}
874+
}
875+
}
876+
845877
export default [
846878
editVolumeLayerAsync,
879+
handleDeleteSegmentData,
847880
ensureToolIsAllowedInResolution,
848881
floodFill,
849882
watchVolumeTracingAsync,

frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segment_list_item.tsx

+66-3
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import {
55
VerticalAlignBottomOutlined,
66
EllipsisOutlined,
77
} from "@ant-design/icons";
8-
import { List, Tooltip, Dropdown, MenuProps } from "antd";
8+
import { List, Tooltip, Dropdown, MenuProps, Modal } from "antd";
99
import { useDispatch, useSelector } from "react-redux";
1010
import Checkbox, { CheckboxChangeEvent } from "antd/lib/checkbox/Checkbox";
1111
import React from "react";
@@ -21,7 +21,13 @@ import {
2121
refreshMeshAction,
2222
} from "oxalis/model/actions/annotation_actions";
2323
import EditableTextLabel from "oxalis/view/components/editable_text_label";
24-
import type { ActiveMappingInfo, MeshInformation, OxalisState, Segment } from "oxalis/store";
24+
import type {
25+
ActiveMappingInfo,
26+
MeshInformation,
27+
OxalisState,
28+
Segment,
29+
VolumeTracing,
30+
} from "oxalis/store";
2531
import Store from "oxalis/store";
2632
import { getSegmentColorAsHSLA } from "oxalis/model/accessors/volumetracing_accessor";
2733
import Toast from "libs/toast";
@@ -33,6 +39,8 @@ import { withMappingActivationConfirmation } from "./segments_view_helper";
3339
import { type AdditionalCoordinate } from "types/api_flow_types";
3440
import { getAdditionalCoordinatesAsString } from "oxalis/model/accessors/flycam_accessor";
3541

42+
const ALSO_DELETE_SEGMENT_FROM_LIST_KEY = "also-delete-segment-from-list";
43+
3644
function ColoredDotIconForSegment({ segmentColorHSLA }: { segmentColorHSLA: Vector4 }) {
3745
const hslaCss = hslaToCSS(segmentColorHSLA);
3846

@@ -192,6 +200,7 @@ type Props = {
192200
createsNewUndoState: boolean,
193201
) => void;
194202
removeSegment: (arg0: number, arg2: string) => void;
203+
deleteSegmentData: (arg0: number, arg2: string, callback?: () => void) => void;
195204
onSelectSegment: (arg0: Segment) => void;
196205
visibleSegmentationLayer: APISegmentationLayer | null | undefined;
197206
loadAdHocMesh: (
@@ -219,6 +228,7 @@ type Props = {
219228
onRenameStart: () => void;
220229
onRenameEnd: () => void;
221230
multiSelectMenu: MenuProps;
231+
activeVolumeTracing: VolumeTracing | null | undefined;
222232
};
223233

224234
function _MeshInfoItem(props: {
@@ -381,6 +391,7 @@ function _SegmentListItem({
381391
allowUpdate,
382392
updateSegment,
383393
removeSegment,
394+
deleteSegmentData,
384395
onSelectSegment,
385396
visibleSegmentationLayer,
386397
loadAdHocMesh,
@@ -393,6 +404,7 @@ function _SegmentListItem({
393404
onRenameStart,
394405
onRenameEnd,
395406
multiSelectMenu,
407+
activeVolumeTracing,
396408
}: Props) {
397409
const isEditingDisabled = !allowUpdate;
398410

@@ -497,6 +509,52 @@ function _SegmentListItem({
497509
},
498510
label: "Remove Segment From List",
499511
},
512+
{
513+
key: "deleteSegmentData",
514+
onClick: () => {
515+
if (visibleSegmentationLayer == null) {
516+
return;
517+
}
518+
519+
Modal.confirm({
520+
content: `Are you sure you want to delete the data of segment ${getSegmentName(
521+
segment,
522+
true,
523+
)}? This operation will set all voxels with id ${segment.id} to 0.`,
524+
okText: "Yes, delete",
525+
okType: "danger",
526+
onOk: async () => {
527+
await new Promise<void>((resolve) =>
528+
deleteSegmentData(segment.id, visibleSegmentationLayer.name, resolve),
529+
);
530+
531+
Toast.info(
532+
<span>
533+
The data of segment {getSegmentName(segment, true)} was deleted.{" "}
534+
<a
535+
href="#"
536+
onClick={() => {
537+
removeSegment(segment.id, visibleSegmentationLayer.name);
538+
Toast.close(ALSO_DELETE_SEGMENT_FROM_LIST_KEY);
539+
}}
540+
>
541+
Also remove from list.
542+
</a>
543+
</span>,
544+
{ key: ALSO_DELETE_SEGMENT_FROM_LIST_KEY },
545+
);
546+
},
547+
});
548+
549+
andCloseContextMenu();
550+
},
551+
disabled:
552+
activeVolumeTracing == null ||
553+
!activeVolumeTracing.hasSegmentIndex ||
554+
// Not supported for fallback layers, yet.
555+
activeVolumeTracing.fallbackLayer != null,
556+
label: "Delete Segment's Data",
557+
},
500558
],
501559
});
502560

@@ -551,7 +609,7 @@ function _SegmentListItem({
551609
<div style={{ display: "inline-flex", alignItems: "center" }}>
552610
<ColoredDotIconForSegment segmentColorHSLA={segmentColorHSLA} />
553611
<EditableTextLabel
554-
value={segment.name || `Segment ${segment.id}`}
612+
value={getSegmentName(segment)}
555613
label="Segment Name"
556614
onClick={() => onSelectSegment(segment)}
557615
onRenameStart={onRenameStart}
@@ -681,4 +739,9 @@ function getComputeMeshAdHocTooltipInfo(
681739
};
682740
}
683741

742+
function getSegmentName(segment: Segment, fallbackToId: boolean = false): string {
743+
const fallback = fallbackToId ? `${segment.id}` : `Segment ${segment.id}`;
744+
return segment.name || fallback;
745+
}
746+
684747
export default SegmentListItem;

frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segments_view.tsx

+7
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ import { updateTemporarySettingAction } from "oxalis/model/actions/settings_acti
7676
import {
7777
batchUpdateGroupsAndSegmentsAction,
7878
removeSegmentAction,
79+
deleteSegmentDataAction,
7980
setActiveCellAction,
8081
updateSegmentAction,
8182
} from "oxalis/model/actions/volumetracing_actions";
@@ -281,6 +282,10 @@ const mapDispatchToProps = (dispatch: Dispatch<any>) => ({
281282
removeSegment(segmentId: number, layerName: string) {
282283
dispatch(removeSegmentAction(segmentId, layerName));
283284
},
285+
286+
deleteSegmentData(segmentId: number, layerName: string, callback?: () => void) {
287+
dispatch(deleteSegmentDataAction(segmentId, layerName, callback));
288+
},
284289
});
285290

286291
type DispatchProps = ReturnType<typeof mapDispatchToProps>;
@@ -1602,6 +1607,7 @@ class SegmentsView extends React.Component<Props, State> {
16021607
allowUpdate={this.props.allowUpdate}
16031608
updateSegment={this.props.updateSegment}
16041609
removeSegment={this.props.removeSegment}
1610+
deleteSegmentData={this.props.deleteSegmentData}
16051611
visibleSegmentationLayer={this.props.visibleSegmentationLayer}
16061612
loadAdHocMesh={this.props.loadAdHocMesh}
16071613
loadPrecomputedMesh={this.props.loadPrecomputedMesh}
@@ -1612,6 +1618,7 @@ class SegmentsView extends React.Component<Props, State> {
16121618
onRenameStart={this.onRenameStart}
16131619
onRenameEnd={this.onRenameEnd}
16141620
multiSelectMenu={multiSelectMenu()}
1621+
activeVolumeTracing={this.props.activeVolumeTracing}
16151622
/>
16161623
);
16171624
} else {

frontend/javascripts/oxalis/view/version_entry.tsx

+5
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ import type {
3737
MoveTreeComponentUpdateAction,
3838
MergeTreeUpdateAction,
3939
UpdateMappingNameUpdateAction,
40+
DeleteSegmentDataUpdateAction,
4041
} from "oxalis/model/sagas/update_actions";
4142
import FormattedDate from "components/formatted_date";
4243
import { MISSING_GROUP_ID } from "oxalis/view/right-border-tabs/tree_hierarchy_view_helpers";
@@ -167,6 +168,10 @@ const descriptionFns: Record<ServerUpdateAction["name"], (...args: any) => Descr
167168
description: `Deleted the segment with id ${action.value.id} from the segments list.`,
168169
icon: <DeleteOutlined />,
169170
}),
171+
deleteSegmentData: (action: DeleteSegmentDataUpdateAction): Description => ({
172+
description: `Deleted the data of segment ${action.value.id}. All voxels with that id were overwritten with 0.`,
173+
icon: <DeleteOutlined />,
174+
}),
170175
addSegmentIndex: (): Description => ({
171176
description: "Added segment index to enable segment statistics.",
172177
icon: <EditOutlined />,

frontend/javascripts/test/controller/url_manager.spec.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ test("UrlManager should build csv url hash and parse it again", (t) => {
111111
t.deepEqual(UrlManager.parseUrlHash(), urlState);
112112
});
113113

114-
test.only("UrlManager should build csv url hash with additional coordinates and parse it again", (t) => {
114+
test("UrlManager should build csv url hash with additional coordinates and parse it again", (t) => {
115115
const mode = Constants.MODE_ARBITRARY;
116116
const urlState = {
117117
position: [0, 0, 0] as Vector3,

tools/assert-no-test-only.sh

+4-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
#!/bin/bash
2+
exit_code=0
23
echo "Checking for test.only() in test files."
3-
! grep -r "test\.only(" frontend/javascripts/test || echo "Found test files with test.only() which disables other tests. Please remove the only modifier."
4-
! grep -r "test\.serial\.only(" frontend/javascripts/test || echo "Found test files with test.only() which disables other tests. Please remove the only modifier."
4+
! grep -r "test\.only(" frontend/javascripts/test || { echo "Found test files with test.only() which disables other tests. Please remove the only modifier."; exit_code=1; }
5+
! grep -r "test\.serial\.only(" frontend/javascripts/test || { echo "Found test files with test.only() which disables other tests. Please remove the only modifier."; exit_code=1; }
56
echo "Done"
7+
exit $exit_code

0 commit comments

Comments
 (0)