From 90c84692753f081ae90c587f4e5497c41da4f516 Mon Sep 17 00:00:00 2001 From: frcroth Date: Mon, 13 Nov 2023 14:20:44 +0100 Subject: [PATCH 1/8] Add delete segment data update action --- .../volume/VolumeTracingService.scala | 58 +++++++++++++++++-- .../tracings/volume/VolumeUpdateActions.scala | 19 ++++++ 2 files changed, 73 insertions(+), 4 deletions(-) diff --git a/webknossos-tracingstore/app/com/scalableminds/webknossos/tracingstore/tracings/volume/VolumeTracingService.scala b/webknossos-tracingstore/app/com/scalableminds/webknossos/tracingstore/tracings/volume/VolumeTracingService.scala index 94d8eb0cdb7..4005d25602a 100644 --- a/webknossos-tracingstore/app/com/scalableminds/webknossos/tracingstore/tracings/volume/VolumeTracingService.scala +++ b/webknossos-tracingstore/app/com/scalableminds/webknossos/tracingstore/tracings/volume/VolumeTracingService.scala @@ -10,10 +10,16 @@ import com.scalableminds.webknossos.datastore.dataformats.wkw.WKWDataFormatHelpe import com.scalableminds.webknossos.datastore.geometry.NamedBoundingBoxProto import com.scalableminds.webknossos.datastore.helpers.ProtoGeometryImplicits import com.scalableminds.webknossos.datastore.models.DataRequestCollection.DataRequestCollection -import com.scalableminds.webknossos.datastore.models.datasource.{AdditionalAxis, ElementClass} +import com.scalableminds.webknossos.datastore.models.datasource.{AdditionalAxis, DataLayer, ElementClass} import com.scalableminds.webknossos.datastore.VolumeTracing.VolumeTracing.ElementClassProto import com.scalableminds.webknossos.datastore.models.requests.DataServiceDataRequest -import com.scalableminds.webknossos.datastore.models.{AdditionalCoordinate, BucketPosition, WebknossosAdHocMeshRequest} +import com.scalableminds.webknossos.datastore.models.{ + AdditionalCoordinate, + BucketPosition, + UnsignedInteger, + UnsignedIntegerArray, + WebknossosAdHocMeshRequest +} import com.scalableminds.webknossos.datastore.services._ import com.scalableminds.webknossos.tracingstore.tracings.TracingType.TracingType import com.scalableminds.webknossos.tracingstore.tracings._ @@ -113,11 +119,11 @@ class VolumeTracingService @Inject()( tracingFox.futureBox.flatMap { case Full(tracing) => action match { - case action: UpdateBucketVolumeAction => + case a: UpdateBucketVolumeAction => if (tracing.getMappingIsEditable) { Fox.failure("Cannot mutate volume data in annotation with editable mapping.") } else - updateBucket(tracingId, tracing, action, segmentIndexBuffer, updateGroup.version, userToken) ?~> "Failed to save volume data." + updateBucket(tracingId, tracing, a, segmentIndexBuffer, updateGroup.version, userToken) ?~> "Failed to save volume data." case a: UpdateTracingVolumeAction => Fox.successful( tracing.copy( @@ -131,6 +137,11 @@ class VolumeTracingService @Inject()( )) case a: RevertToVersionVolumeAction => revertToVolumeVersion(tracingId, a.sourceVersion, updateGroup.version, tracing) + case a: DeleteSegmentDataVolumeAction => + if (!tracing.getHasSegmentIndex) { + Fox.failure("Cannot delete segment data for annotations without segment index.") + } else + deleteSegmentData(tracingId, tracing, a, segmentIndexBuffer, updateGroup.version) ?~> "Failed to delete segment data." case _: UpdateTdCamera => Fox.successful(tracing) case a: ApplyableVolumeAction => Fox.successful(a.applyOn(tracing)) case _ => Fox.failure("Unknown action.") @@ -178,6 +189,45 @@ class VolumeTracingService @Inject()( _ <- segmentIndexBuffer.flush() } yield volumeTracing + private def deleteSegmentData(tracingId: String, + volumeTracing: VolumeTracing, + a: DeleteSegmentDataVolumeAction, + segmentIndexBuffer: VolumeSegmentIndexBuffer, + version: Long): Fox[VolumeTracing] = + for { + _ <- Fox.successful(()) + dataLayer = volumeTracingLayer(tracingId, volumeTracing) + _ <- Fox.serialCombined(volumeTracing.resolutions.toList)(resolution => { + val mag = vec3IntFromProto(resolution) + for { + bucketPositionsRaw <- volumeSegmentIndexService + .getSegmentToBucketIndexWithEmptyFallbackWithoutBuffer(tracingId, a.id, mag) + bucketPositions = bucketPositionsRaw.values + .map(vec3IntFromProto) + .map(_ * mag * DataLayer.bucketLength) + .map(bp => BucketPosition(bp.x, bp.y, bp.z, mag, a.additionalCoordinates)) + .toList + _ <- Fox.serialCombined(bucketPositions) { + bucketPosition => + for { + data <- loadBucket(dataLayer, bucketPosition) + typedData = UnsignedIntegerArray.fromByteArray(data, volumeTracing.elementClass) + filteredData = typedData.map(elem => + if (elem.toLong == a.id) UnsignedInteger.zeroFromElementClass(volumeTracing.elementClass) else elem) + filteredBytes = UnsignedIntegerArray.toByteArray(filteredData, volumeTracing.elementClass) + _ <- saveBucket(dataLayer, bucketPosition, filteredBytes, version) + _ <- updateSegmentIndex(segmentIndexBuffer, + bucketPosition, + filteredBytes, + Some(data), + volumeTracing.elementClass) + } yield () + } + } yield () + }) + _ <- segmentIndexBuffer.flush() + } yield volumeTracing + private def assertMagIsValid(tracing: VolumeTracing, mag: Vec3Int): Fox[Unit] = if (tracing.resolutions.nonEmpty) { bool2Fox(tracing.resolutions.exists(r => vec3IntFromProto(r) == mag)) diff --git a/webknossos-tracingstore/app/com/scalableminds/webknossos/tracingstore/tracings/volume/VolumeUpdateActions.scala b/webknossos-tracingstore/app/com/scalableminds/webknossos/tracingstore/tracings/volume/VolumeUpdateActions.scala index 1a267d40b1d..55eef799fa5 100644 --- a/webknossos-tracingstore/app/com/scalableminds/webknossos/tracingstore/tracings/volume/VolumeUpdateActions.scala +++ b/webknossos-tracingstore/app/com/scalableminds/webknossos/tracingstore/tracings/volume/VolumeUpdateActions.scala @@ -329,6 +329,24 @@ object DeleteSegmentVolumeAction { implicit val jsonFormat: OFormat[DeleteSegmentVolumeAction] = Json.format[DeleteSegmentVolumeAction] } +case class DeleteSegmentDataVolumeAction(id: Long, + additionalCoordinates: Option[Seq[AdditionalCoordinate]] = None, + actionTimestamp: Option[Long] = None, + actionAuthorId: Option[String] = None) + extends VolumeUpdateAction { + override def addTimestamp(timestamp: Long): VolumeUpdateAction = this.copy(actionTimestamp = Some(timestamp)) + + override def addAuthorId(authorId: Option[String]): VolumeUpdateAction = + this.copy(actionAuthorId = authorId) + + override def transformToCompact: CompactVolumeUpdateAction = + CompactVolumeUpdateAction("deleteSegmentData", actionTimestamp, actionAuthorId, Json.obj()) +} + +object DeleteSegmentDataVolumeAction { + implicit val jsonFormat: OFormat[DeleteSegmentDataVolumeAction] = Json.format[DeleteSegmentDataVolumeAction] +} + case class UpdateMappingNameAction(mappingName: Option[String], isEditable: Option[Boolean], actionTimestamp: Option[Long], @@ -409,6 +427,7 @@ object VolumeUpdateAction { case "updateSegment" => (json \ "value").validate[UpdateSegmentVolumeAction] case "updateSegmentGroups" => (json \ "value").validate[UpdateSegmentGroupsVolumeAction] case "deleteSegment" => (json \ "value").validate[DeleteSegmentVolumeAction] + case "deleteSegmentData" => (json \ "value").validate[DeleteSegmentDataVolumeAction] case "updateMappingName" => (json \ "value").validate[UpdateMappingNameAction] case unknownAction: String => JsError(s"Invalid update action s'$unknownAction'") } From b1820e7f1b6183718ec19e0629131b1071c93e9e Mon Sep 17 00:00:00 2001 From: Philipp Otto Date: Tue, 14 Nov 2023 17:45:36 +0100 Subject: [PATCH 2/8] expose delete-segment-data in context menu of segment list --- .../model/actions/volumetracing_actions.ts | 14 ++++++++ .../model/bucket_data_handling/bucket.ts | 2 ++ .../oxalis/model/sagas/update_actions.ts | 10 ++++++ .../oxalis/model/sagas/volumetracing_saga.tsx | 34 +++++++++++++++++-- .../segments_tab/segment_list_item.tsx | 13 +++++++ .../segments_tab/segments_view.tsx | 6 ++++ .../javascripts/oxalis/view/version_entry.tsx | 5 +++ 7 files changed, 82 insertions(+), 2 deletions(-) diff --git a/frontend/javascripts/oxalis/model/actions/volumetracing_actions.ts b/frontend/javascripts/oxalis/model/actions/volumetracing_actions.ts index da21c096b74..06c5be3196c 100644 --- a/frontend/javascripts/oxalis/model/actions/volumetracing_actions.ts +++ b/frontend/javascripts/oxalis/model/actions/volumetracing_actions.ts @@ -39,6 +39,7 @@ export type SetLargestSegmentIdAction = ReturnType; export type UpdateSegmentAction = ReturnType; export type RemoveSegmentAction = ReturnType; +export type DeleteSegmentDataAction = ReturnType; export type SetSegmentGroupsAction = ReturnType; export type SetMappingIsEditableAction = ReturnType; @@ -80,6 +81,7 @@ export type VolumeTracingAction = | SetSegmentsAction | UpdateSegmentAction | RemoveSegmentAction + | DeleteSegmentDataAction | SetSegmentGroupsAction | AddBucketToUndoAction | ImportVolumeTracingAction @@ -232,6 +234,18 @@ export const removeSegmentAction = ( timestamp, } as const); +export const deleteSegmentDataAction = ( + segmentId: number, + layerName: string, + timestamp: number = Date.now(), +) => + ({ + type: "DELETE_SEGMENT_DATA", + segmentId, + layerName, + timestamp, + } as const); + export const setSegmentGroupsAction = ( segmentGroups: Array, layerName: string, diff --git a/frontend/javascripts/oxalis/model/bucket_data_handling/bucket.ts b/frontend/javascripts/oxalis/model/bucket_data_handling/bucket.ts index 91090d06101..8f0befc9e6f 100644 --- a/frontend/javascripts/oxalis/model/bucket_data_handling/bucket.ts +++ b/frontend/javascripts/oxalis/model/bucket_data_handling/bucket.ts @@ -543,6 +543,8 @@ export class DataBucket { } } } + + this.invalidateValueSet(); } markAsPulled(): void { diff --git a/frontend/javascripts/oxalis/model/sagas/update_actions.ts b/frontend/javascripts/oxalis/model/sagas/update_actions.ts index 69cffebd1fb..504e84eeade 100644 --- a/frontend/javascripts/oxalis/model/sagas/update_actions.ts +++ b/frontend/javascripts/oxalis/model/sagas/update_actions.ts @@ -32,6 +32,7 @@ type UpdateVolumeTracingUpdateAction = ReturnType; export type CreateSegmentUpdateAction = ReturnType; export type UpdateSegmentUpdateAction = ReturnType; export type DeleteSegmentUpdateAction = ReturnType; +export type DeleteSegmentDataUpdateAction = ReturnType; type UpdateUserBoundingBoxesUpdateAction = ReturnType; export type UpdateBucketUpdateAction = ReturnType; type UpdateSegmentGroupsUpdateAction = ReturnType; @@ -63,6 +64,7 @@ export type UpdateAction = | CreateSegmentUpdateAction | UpdateSegmentUpdateAction | DeleteSegmentUpdateAction + | DeleteSegmentDataUpdateAction | UpdateBucketUpdateAction | UpdateTreeVisibilityUpdateAction | UpdateTreeEdgesVisibilityUpdateAction @@ -346,6 +348,14 @@ export function deleteSegmentVolumeAction(id: number) { }, } as const; } +export function deleteSegmentDataVolumeAction(id: number) { + return { + name: "deleteSegmentData", + value: { + id, + }, + } as const; +} export function updateBucket(bucketInfo: SendBucketInfo, base64Data: string) { return { name: "updateBucket", diff --git a/frontend/javascripts/oxalis/model/sagas/volumetracing_saga.tsx b/frontend/javascripts/oxalis/model/sagas/volumetracing_saga.tsx index 512a425a75b..5c5e3eba691 100644 --- a/frontend/javascripts/oxalis/model/sagas/volumetracing_saga.tsx +++ b/frontend/javascripts/oxalis/model/sagas/volumetracing_saga.tsx @@ -65,6 +65,7 @@ import type { ClickSegmentAction, SetActiveCellAction, CreateCellAction, + DeleteSegmentDataAction, } from "oxalis/model/actions/volumetracing_actions"; import { finishAnnotationStrokeAction, @@ -79,7 +80,11 @@ import { select, take } from "oxalis/model/sagas/effect-generators"; import listenToMinCut from "oxalis/model/sagas/min_cut_saga"; import listenToQuickSelect from "oxalis/model/sagas/quick_select_saga"; import { takeEveryUnlessBusy } from "oxalis/model/sagas/saga_helpers"; -import { UpdateAction, updateSegmentGroups } from "oxalis/model/sagas/update_actions"; +import { + deleteSegmentDataVolumeAction, + UpdateAction, + updateSegmentGroups, +} from "oxalis/model/sagas/update_actions"; import { createSegmentVolumeAction, deleteSegmentVolumeAction, @@ -90,7 +95,7 @@ import { updateMappingName, } from "oxalis/model/sagas/update_actions"; import VolumeLayer from "oxalis/model/volumetracing/volumelayer"; -import { Model } from "oxalis/singletons"; +import { Model, api } from "oxalis/singletons"; import type { Flycam, SegmentMap, VolumeTracing } from "oxalis/store"; import React from "react"; import { actionChannel, call, fork, put, takeEvery, takeLatest } from "typed-redux-saga"; @@ -101,6 +106,7 @@ import { } from "./volume/helpers"; import maybeInterpolateSegmentationLayer from "./volume/volume_interpolation_saga"; import messages from "messages"; +import { pushSaveQueueTransaction } from "../actions/save_actions"; export function* watchVolumeTracingAsync(): Saga { yield* take("WK_READY"); @@ -842,8 +848,32 @@ function* ensureValidBrushSize(): Saga { ); } +function* handleDeleteSegmentData(): Saga { + yield* take("WK_READY"); + while (true) { + const action = (yield* take("DELETE_SEGMENT_DATA")) as DeleteSegmentDataAction; + + yield* put(setBusyBlockingInfoAction(true, "Segment is being deleted.")); + yield* put( + pushSaveQueueTransaction( + [deleteSegmentDataVolumeAction(action.segmentId)], + "volume", + action.layerName, + ), + ); + yield* call([Model, Model.ensureSavedState]); + + yield* call([api.data, api.data.reloadBuckets], action.layerName, (bucket) => + bucket.containsValue(action.segmentId), + ); + + yield* put(setBusyBlockingInfoAction(false)); + } +} + export default [ editVolumeLayerAsync, + handleDeleteSegmentData, ensureToolIsAllowedInResolution, floodFill, watchVolumeTracingAsync, diff --git a/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segment_list_item.tsx b/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segment_list_item.tsx index 690aae9c31c..1ef2ea59b84 100644 --- a/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segment_list_item.tsx +++ b/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segment_list_item.tsx @@ -192,6 +192,7 @@ type Props = { createsNewUndoState: boolean, ) => void; removeSegment: (arg0: number, arg2: string) => void; + deleteSegmentData: (arg0: number, arg2: string) => void; onSelectSegment: (arg0: Segment) => void; visibleSegmentationLayer: APISegmentationLayer | null | undefined; loadAdHocMesh: ( @@ -373,6 +374,7 @@ function _SegmentListItem({ allowUpdate, updateSegment, removeSegment, + deleteSegmentData, onSelectSegment, visibleSegmentationLayer, loadAdHocMesh, @@ -489,6 +491,17 @@ function _SegmentListItem({ }, label: "Remove Segment From List", }, + { + key: "deleteSegmentData", + onClick: () => { + if (visibleSegmentationLayer == null) { + return; + } + deleteSegmentData(segment.id, visibleSegmentationLayer.name); + andCloseContextMenu(); + }, + label: "Delete Segment Data", + }, ], }); diff --git a/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segments_view.tsx b/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segments_view.tsx index 7e9374165c9..d958b9f512a 100644 --- a/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segments_view.tsx +++ b/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segments_view.tsx @@ -72,6 +72,7 @@ import { updateTemporarySettingAction } from "oxalis/model/actions/settings_acti import { batchUpdateGroupsAndSegmentsAction, removeSegmentAction, + deleteSegmentDataAction, setActiveCellAction, updateSegmentAction, } from "oxalis/model/actions/volumetracing_actions"; @@ -271,6 +272,10 @@ const mapDispatchToProps = (dispatch: Dispatch) => ({ removeSegment(segmentId: number, layerName: string) { dispatch(removeSegmentAction(segmentId, layerName)); }, + + deleteSegmentData(segmentId: number, layerName: string) { + dispatch(deleteSegmentDataAction(segmentId, layerName)); + }, }); type DispatchProps = ReturnType; @@ -1588,6 +1593,7 @@ class SegmentsView extends React.Component { allowUpdate={this.props.allowUpdate} updateSegment={this.props.updateSegment} removeSegment={this.props.removeSegment} + deleteSegmentData={this.props.deleteSegmentData} visibleSegmentationLayer={this.props.visibleSegmentationLayer} loadAdHocMesh={this.props.loadAdHocMesh} loadPrecomputedMesh={this.props.loadPrecomputedMesh} diff --git a/frontend/javascripts/oxalis/view/version_entry.tsx b/frontend/javascripts/oxalis/view/version_entry.tsx index 24f4f0198fc..a0a12e3f685 100644 --- a/frontend/javascripts/oxalis/view/version_entry.tsx +++ b/frontend/javascripts/oxalis/view/version_entry.tsx @@ -37,6 +37,7 @@ import type { MoveTreeComponentUpdateAction, MergeTreeUpdateAction, UpdateMappingNameUpdateAction, + DeleteSegmentDataUpdateAction, } from "oxalis/model/sagas/update_actions"; import FormattedDate from "components/formatted_date"; import { MISSING_GROUP_ID } from "oxalis/view/right-border-tabs/tree_hierarchy_view_helpers"; @@ -167,6 +168,10 @@ const descriptionFns: Record Descr description: `Deleted the segment with id ${action.value.id} from the segments list.`, icon: , }), + deleteSegmentData: (action: DeleteSegmentDataUpdateAction): Description => ({ + description: `Deleted the data of segment ${action.value.id}. All voxels with that id were overwritten with 0.`, + icon: , + }), addSegmentIndex: (): Description => ({ description: "Added segment index to enable segment statistics.", icon: , From 43abbee5cfbf8b5892a3746c4dde6ac6e33a5684 Mon Sep 17 00:00:00 2001 From: Philipp Otto Date: Tue, 14 Nov 2023 17:54:45 +0100 Subject: [PATCH 3/8] wrap delete-segments-data into modal with spinner --- .../model/actions/volumetracing_actions.ts | 2 ++ .../oxalis/model/sagas/volumetracing_saga.tsx | 3 +++ .../segments_tab/segment_list_item.tsx | 18 ++++++++++++++---- .../segments_tab/segments_view.tsx | 4 ++-- 4 files changed, 21 insertions(+), 6 deletions(-) diff --git a/frontend/javascripts/oxalis/model/actions/volumetracing_actions.ts b/frontend/javascripts/oxalis/model/actions/volumetracing_actions.ts index 06c5be3196c..f9c7c0269cb 100644 --- a/frontend/javascripts/oxalis/model/actions/volumetracing_actions.ts +++ b/frontend/javascripts/oxalis/model/actions/volumetracing_actions.ts @@ -237,12 +237,14 @@ export const removeSegmentAction = ( export const deleteSegmentDataAction = ( segmentId: number, layerName: string, + callback?: () => void, timestamp: number = Date.now(), ) => ({ type: "DELETE_SEGMENT_DATA", segmentId, layerName, + callback, timestamp, } as const); diff --git a/frontend/javascripts/oxalis/model/sagas/volumetracing_saga.tsx b/frontend/javascripts/oxalis/model/sagas/volumetracing_saga.tsx index 5c5e3eba691..59bd1a1c5b6 100644 --- a/frontend/javascripts/oxalis/model/sagas/volumetracing_saga.tsx +++ b/frontend/javascripts/oxalis/model/sagas/volumetracing_saga.tsx @@ -868,6 +868,9 @@ function* handleDeleteSegmentData(): Saga { ); yield* put(setBusyBlockingInfoAction(false)); + if (action.callback) { + action.callback(); + } } } diff --git a/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segment_list_item.tsx b/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segment_list_item.tsx index 1ef2ea59b84..9b8f2ea901c 100644 --- a/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segment_list_item.tsx +++ b/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segment_list_item.tsx @@ -5,7 +5,7 @@ import { VerticalAlignBottomOutlined, EllipsisOutlined, } from "@ant-design/icons"; -import { List, Tooltip, Dropdown, MenuProps } from "antd"; +import { List, Tooltip, Dropdown, MenuProps, Modal } from "antd"; import { useDispatch, useSelector } from "react-redux"; import Checkbox, { CheckboxChangeEvent } from "antd/lib/checkbox/Checkbox"; import React from "react"; @@ -192,7 +192,7 @@ type Props = { createsNewUndoState: boolean, ) => void; removeSegment: (arg0: number, arg2: string) => void; - deleteSegmentData: (arg0: number, arg2: string) => void; + deleteSegmentData: (arg0: number, arg2: string, callback?: () => void) => void; onSelectSegment: (arg0: Segment) => void; visibleSegmentationLayer: APISegmentationLayer | null | undefined; loadAdHocMesh: ( @@ -497,10 +497,20 @@ function _SegmentListItem({ if (visibleSegmentationLayer == null) { return; } - deleteSegmentData(segment.id, visibleSegmentationLayer.name); + + Modal.confirm({ + content: `Are you sure you want to delete the segment's data? This operation will set all voxels with id ${segment.id} to 0.`, + okText: "Yes, delete", + okType: "danger", + onOk: async () => + new Promise((resolve) => + deleteSegmentData(segment.id, visibleSegmentationLayer.name, resolve), + ), + }); + andCloseContextMenu(); }, - label: "Delete Segment Data", + label: "Delete Segment's Data", }, ], }); diff --git a/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segments_view.tsx b/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segments_view.tsx index d958b9f512a..60bc7463973 100644 --- a/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segments_view.tsx +++ b/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segments_view.tsx @@ -273,8 +273,8 @@ const mapDispatchToProps = (dispatch: Dispatch) => ({ dispatch(removeSegmentAction(segmentId, layerName)); }, - deleteSegmentData(segmentId: number, layerName: string) { - dispatch(deleteSegmentDataAction(segmentId, layerName)); + deleteSegmentData(segmentId: number, layerName: string, callback?: () => void) { + dispatch(deleteSegmentDataAction(segmentId, layerName, callback)); }, }); From 4e529f1eaab51ec7cf6da70e28fa95ceefe5b0fd Mon Sep 17 00:00:00 2001 From: Philipp Otto Date: Tue, 14 Nov 2023 18:01:59 +0100 Subject: [PATCH 4/8] fix exit code in assert-no-test-only.sh --- frontend/javascripts/test/controller/url_manager.spec.ts | 2 +- tools/assert-no-test-only.sh | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/frontend/javascripts/test/controller/url_manager.spec.ts b/frontend/javascripts/test/controller/url_manager.spec.ts index 556892ebb5d..383e651b7d1 100644 --- a/frontend/javascripts/test/controller/url_manager.spec.ts +++ b/frontend/javascripts/test/controller/url_manager.spec.ts @@ -111,7 +111,7 @@ test("UrlManager should build csv url hash and parse it again", (t) => { t.deepEqual(UrlManager.parseUrlHash(), urlState); }); -test.only("UrlManager should build csv url hash with additional coordinates and parse it again", (t) => { +test("UrlManager should build csv url hash with additional coordinates and parse it again", (t) => { const mode = Constants.MODE_ARBITRARY; const urlState = { position: [0, 0, 0] as Vector3, diff --git a/tools/assert-no-test-only.sh b/tools/assert-no-test-only.sh index 0ee1f606a9b..1cff0d40459 100755 --- a/tools/assert-no-test-only.sh +++ b/tools/assert-no-test-only.sh @@ -1,5 +1,7 @@ #!/bin/bash +exit_code=0 echo "Checking for test.only() in test files." -! grep -r "test\.only(" frontend/javascripts/test || echo "Found test files with test.only() which disables other tests. Please remove the only modifier." -! grep -r "test\.serial\.only(" frontend/javascripts/test || echo "Found test files with test.only() which disables other tests. Please remove the only modifier." +! 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; } +! 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; } echo "Done" +exit $exit_code From 862a6cefe17405b04518290688b95d269f114dc9 Mon Sep 17 00:00:00 2001 From: frcroth Date: Wed, 15 Nov 2023 09:20:09 +0100 Subject: [PATCH 5/8] Update changelog --- CHANGELOG.unreleased.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.unreleased.md b/CHANGELOG.unreleased.md index b69b7dc3324..550bec5727e 100644 --- a/CHANGELOG.unreleased.md +++ b/CHANGELOG.unreleased.md @@ -15,6 +15,7 @@ For upgrade instructions, please check the [migration guide](MIGRATIONS.released - Added support for reading uint24 rgb layers in datasets with zarr2/zarr3/n5/neuroglancerPrecomputed format, as used for voxelytics predictions. [#7413](https://github.com/scalableminds/webknossos/pull/7413) - Adding a remote dataset can now be done by providing a Neuroglancer URI. [#7416](https://github.com/scalableminds/webknossos/pull/7416) - Added a filter to the Task List->Stats column to quickly filter for tasks with "Prending", "In-Progress" or "Finished" instances. [#7430](https://github.com/scalableminds/webknossos/pull/7430) +- The data of segments can now be deleted in the segment side panel. [#7435](https://github.com/scalableminds/webknossos/pull/7435) ### Changed - An appropriate error is returned when requesting an API version that is higher that the current version. [#7424](https://github.com/scalableminds/webknossos/pull/7424) From c5af42077aaf530bd92d5e313af67dcb2ba19544 Mon Sep 17 00:00:00 2001 From: Philipp Otto Date: Wed, 22 Nov 2023 14:43:06 +0100 Subject: [PATCH 6/8] disable deleting the data of a segment for fallback layers or when no segment index exists --- .../segments_tab/segment_list_item.tsx | 15 ++++++++++++++- .../segments_tab/segments_view.tsx | 1 + 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segment_list_item.tsx b/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segment_list_item.tsx index 9b8f2ea901c..034925686a6 100644 --- a/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segment_list_item.tsx +++ b/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segment_list_item.tsx @@ -21,7 +21,13 @@ import { refreshMeshAction, } from "oxalis/model/actions/annotation_actions"; import EditableTextLabel from "oxalis/view/components/editable_text_label"; -import type { ActiveMappingInfo, MeshInformation, OxalisState, Segment } from "oxalis/store"; +import type { + ActiveMappingInfo, + MeshInformation, + OxalisState, + Segment, + VolumeTracing, +} from "oxalis/store"; import Store from "oxalis/store"; import { getSegmentColorAsHSLA } from "oxalis/model/accessors/volumetracing_accessor"; import Toast from "libs/toast"; @@ -218,6 +224,7 @@ type Props = { onRenameStart: () => void; onRenameEnd: () => void; multiSelectMenu: MenuProps; + activeVolumeTracing: VolumeTracing | null | undefined; }; function _MeshInfoItem(props: { @@ -387,6 +394,7 @@ function _SegmentListItem({ onRenameStart, onRenameEnd, multiSelectMenu, + activeVolumeTracing, }: Props) { const isEditingDisabled = !allowUpdate; @@ -510,6 +518,11 @@ function _SegmentListItem({ andCloseContextMenu(); }, + disabled: + activeVolumeTracing == null || + !activeVolumeTracing.hasSegmentIndex || + // Not supported for fallback layers, yet. + activeVolumeTracing.fallbackLayer != null, label: "Delete Segment's Data", }, ], diff --git a/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segments_view.tsx b/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segments_view.tsx index 60bc7463973..0e86e0a547d 100644 --- a/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segments_view.tsx +++ b/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segments_view.tsx @@ -1604,6 +1604,7 @@ class SegmentsView extends React.Component { onRenameStart={this.onRenameStart} onRenameEnd={this.onRenameEnd} multiSelectMenu={multiSelectMenu()} + activeVolumeTracing={this.props.activeVolumeTracing} /> ); } else { From 28a11890c372c7ee73a49d1c2282a2e480469b4b Mon Sep 17 00:00:00 2001 From: Philipp Otto Date: Thu, 7 Dec 2023 16:15:08 +0100 Subject: [PATCH 7/8] show segment name when deleting its contents --- .../segments_tab/segment_list_item.tsx | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segment_list_item.tsx b/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segment_list_item.tsx index 034925686a6..8c9e8afe77d 100644 --- a/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segment_list_item.tsx +++ b/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segment_list_item.tsx @@ -507,7 +507,10 @@ function _SegmentListItem({ } Modal.confirm({ - content: `Are you sure you want to delete the segment's data? This operation will set all voxels with id ${segment.id} to 0.`, + content: `Are you sure you want to delete the data of segment ${getSegmentName( + segment, + true, + )}? This operation will set all voxels with id ${segment.id} to 0.`, okText: "Yes, delete", okType: "danger", onOk: async () => @@ -579,7 +582,7 @@ function _SegmentListItem({
onSelectSegment(segment)} onRenameStart={onRenameStart} @@ -709,4 +712,9 @@ function getComputeMeshAdHocTooltipInfo( }; } +function getSegmentName(segment: Segment, fallbackToId: boolean = false): string { + const fallback = fallbackToId ? `${segment.id}` : `Segment ${segment.id}`; + return segment.name || fallback; +} + export default SegmentListItem; From 411ee7114d4c32988b3d5bb8c2b7e83bfb8e76ae Mon Sep 17 00:00:00 2001 From: Philipp Otto Date: Thu, 7 Dec 2023 16:44:45 +0100 Subject: [PATCH 8/8] ask whether segment should also be removed from list --- .../segments_tab/segment_list_item.tsx | 25 ++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segment_list_item.tsx b/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segment_list_item.tsx index 8c9e8afe77d..72b2a249e2e 100644 --- a/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segment_list_item.tsx +++ b/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segment_list_item.tsx @@ -38,6 +38,8 @@ import { MenuItemType } from "antd/lib/menu/hooks/useItems"; import { withMappingActivationConfirmation } from "./segments_view_helper"; import { type AdditionalCoordinate } from "types/api_flow_types"; +const ALSO_DELETE_SEGMENT_FROM_LIST_KEY = "also-delete-segment-from-list"; + function ColoredDotIconForSegment({ segmentColorHSLA }: { segmentColorHSLA: Vector4 }) { const hslaCss = hslaToCSS(segmentColorHSLA); @@ -513,10 +515,27 @@ function _SegmentListItem({ )}? This operation will set all voxels with id ${segment.id} to 0.`, okText: "Yes, delete", okType: "danger", - onOk: async () => - new Promise((resolve) => + onOk: async () => { + await new Promise((resolve) => deleteSegmentData(segment.id, visibleSegmentationLayer.name, resolve), - ), + ); + + Toast.info( + + The data of segment {getSegmentName(segment, true)} was deleted.{" "} + { + removeSegment(segment.id, visibleSegmentationLayer.name); + Toast.close(ALSO_DELETE_SEGMENT_FROM_LIST_KEY); + }} + > + Also remove from list. + + , + { key: ALSO_DELETE_SEGMENT_FROM_LIST_KEY }, + ); + }, }); andCloseContextMenu();