diff --git a/CHANGELOG.unreleased.md b/CHANGELOG.unreleased.md index 6f9a0636925..d85ecd4e071 100644 --- a/CHANGELOG.unreleased.md +++ b/CHANGELOG.unreleased.md @@ -11,6 +11,7 @@ For upgrade instructions, please check the [migration guide](MIGRATIONS.released [Commits](https://github.com/scalableminds/webknossos/compare/24.06.0...HEAD) ### Added +- Added that proofreading merge actions reuse custom names of segments. A merge action now combines the potenial existing custom names of both segments and a split-action copies the custom name to the split-off segment. [#7877](https://github.com/scalableminds/webknossos/pull/7877) - Added the option for the owner to lock explorative annotations. Locked annotations cannot be modified by any user. An annotation can be locked in the annotations table and when viewing the annotation via the navbar dropdown menu. [#7801](https://github.com/scalableminds/webknossos/pull/7801) - Added the option to set a default mapping for a dataset in the dataset view configuration. The default mapping is loaded when the dataset is opened and the user / url does not configure something else. [#7858](https://github.com/scalableminds/webknossos/pull/7858) - Uploading an annotation into a dataset that it was not created for now also works if the dataset is in a different organization. [#7816](https://github.com/scalableminds/webknossos/pull/7816) diff --git a/frontend/javascripts/oxalis/model/accessors/volumetracing_accessor.ts b/frontend/javascripts/oxalis/model/accessors/volumetracing_accessor.ts index e4de8609845..20006947acc 100644 --- a/frontend/javascripts/oxalis/model/accessors/volumetracing_accessor.ts +++ b/frontend/javascripts/oxalis/model/accessors/volumetracing_accessor.ts @@ -15,6 +15,7 @@ import type { HybridTracing, LabelAction, OxalisState, + Segment, SegmentGroup, SegmentMap, Tracing, @@ -621,6 +622,11 @@ export function getLabelActionFromPreviousSlice( ); } +export function getSegmentName(segment: Segment, fallbackToId: boolean = false): string { + const fallback = fallbackToId ? `${segment.id}` : `Segment ${segment.id}`; + return segment.name || fallback; +} + // Output is in [0,1] for R, G, B, and A export function getSegmentColorAsRGBA( state: OxalisState, diff --git a/frontend/javascripts/oxalis/model/sagas/proofread_saga.ts b/frontend/javascripts/oxalis/model/sagas/proofread_saga.ts index 2a2ccd777c1..f880bcb4998 100644 --- a/frontend/javascripts/oxalis/model/sagas/proofread_saga.ts +++ b/frontend/javascripts/oxalis/model/sagas/proofread_saga.ts @@ -14,6 +14,7 @@ import { initializeEditableMappingAction, removeSegmentAction, setMappingIsEditableAction, + updateSegmentAction, } from "oxalis/model/actions/volumetracing_actions"; import type { ProofreadAtPositionAction } from "oxalis/model/actions/proofread_actions"; import { @@ -39,6 +40,7 @@ import { getSegmentsForLayer, getMeshInfoForSegment, getEditableMappingForVolumeTracingId, + getSegmentName, } from "oxalis/model/accessors/volumetracing_accessor"; import { getLayerByName, @@ -711,6 +713,8 @@ function* handleProofreadMergeOrMinCut(action: Action) { const [sourceInfo, targetInfo] = idInfos; const sourceAgglomerateId = sourceInfo.agglomerateId; const targetAgglomerateId = targetInfo.agglomerateId; + const sourceAgglomerate = volumeTracing.segments.getNullable(sourceAgglomerateId); + const targetAgglomerate = volumeTracing.segments.getNullable(targetAgglomerateId); /* Send the respective split/merge update action to the backend (by pushing to the save queue and saving immediately) */ @@ -786,6 +790,36 @@ function* handleProofreadMergeOrMinCut(action: Action) { volumeTracing.tracingId, targetInfo.unmappedId, ); + // Preserving custom names across merges & splits. + if ( + action.type === "PROOFREAD_MERGE" && + sourceAgglomerate && + targetAgglomerate && + (sourceAgglomerate.name || targetAgglomerate.name) + ) { + const mergedName = _.uniq([sourceAgglomerate.name, targetAgglomerate.name]) + .filter((name) => name != null) + .join(","); + if (mergedName !== sourceAgglomerate.name) { + yield* put(updateSegmentAction(sourceAgglomerateId, { name: mergedName }, volumeTracingId)); + Toast.info(`Renamed segment "${getSegmentName(sourceAgglomerate)}" to "${mergedName}."`); + } + } else if ( + action.type === "MIN_CUT_AGGLOMERATE_WITH_POSITION" && + sourceAgglomerate && + sourceAgglomerate.name != null + ) { + // Assign custom name to split-off target. + yield* put( + updateSegmentAction( + newTargetAgglomerateId, + { name: sourceAgglomerate.name }, + volumeTracingId, + ), + ); + + Toast.info(`Assigned name "${sourceAgglomerate.name}" to new split-off segment.`); + } yield* spawn(refreshAffectedMeshes, volumeTracingId, [ { @@ -847,6 +881,8 @@ function* handleProofreadCutNeighbors(action: Action) { const editableMappingId = volumeTracing.mappingName; + const targetAgglomerate = volumeTracing.segments.getNullable(targetAgglomerateId); + /* Send the respective split/merge update action to the backend (by pushing to the save queue and saving immediately) */ @@ -884,6 +920,22 @@ function* handleProofreadCutNeighbors(action: Action) { ...neighborInfo.neighbors.map((neighbor) => call(getDataValue, neighbor.position)), ]); + if (targetAgglomerate != null && targetAgglomerate.name != null) { + // Assign custom name to split-off target. + const updateNeighborNamesActions = newNeighborAgglomerateIds.map((newNeighborAgglomerateId) => + put( + updateSegmentAction( + newNeighborAgglomerateId, + { name: targetAgglomerate.name }, + volumeTracingId, + ), + ), + ); + yield* all(updateNeighborNamesActions); + + Toast.info(`Assigned name "${targetAgglomerate.name}" to all new split-off segments.`); + } + /* Reload meshes */ yield* spawn(refreshAffectedMeshes, volumeTracingId, [ { 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 f9b4b1487a8..8b2170da54c 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 @@ -29,7 +29,10 @@ import type { VolumeTracing, } from "oxalis/store"; import Store from "oxalis/store"; -import { getSegmentColorAsHSLA } from "oxalis/model/accessors/volumetracing_accessor"; +import { + getSegmentColorAsHSLA, + getSegmentName, +} from "oxalis/model/accessors/volumetracing_accessor"; import Toast from "libs/toast"; import { hslaToCSS } from "oxalis/shaders/utils.glsl"; import { V4 } from "libs/mjs"; @@ -747,9 +750,4 @@ 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;