修复旧传播分离片段联动选择
- 为缺少稳定 lineage 的旧传播结果增加兼容分组 token - 同一传播来源、来源帧、方向、分类/标签/颜色的分离片段可联动选中高亮 - 同步 Canvas 和工作区传播链 token 逻辑,保持选择和清空链路一致 - 补充 CanvasArea 回归测试覆盖旧传播结果的不连通片段选中 - 更新项目指南和设计冻结文档
This commit is contained in:
@@ -508,6 +508,59 @@ describe('CanvasArea', () => {
|
||||
expect(screen.getAllByTestId('konva-group').map((group) => group.getAttribute('data-opacity'))).toEqual(['0.5', '0.5']);
|
||||
});
|
||||
|
||||
it('selects legacy separated propagated pieces without stable seed ids on the frame', () => {
|
||||
useStore.setState({
|
||||
maskPreviewOpacity: 35,
|
||||
masks: [
|
||||
{
|
||||
id: 'annotation-30',
|
||||
annotationId: '30',
|
||||
frameId: 'frame-1',
|
||||
pathData: 'M 10 10 L 30 10 L 30 30 Z',
|
||||
label: '胆囊',
|
||||
color: '#facc15',
|
||||
segmentation: [[10, 10, 30, 10, 30, 30]],
|
||||
metadata: {
|
||||
source: 'sam2_propagation',
|
||||
propagated_from_frame_id: 'seed-frame',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'annotation-31',
|
||||
annotationId: '31',
|
||||
frameId: 'frame-1',
|
||||
pathData: 'M 80 80 L 100 80 L 100 100 Z',
|
||||
label: '胆囊',
|
||||
color: '#facc15',
|
||||
segmentation: [[80, 80, 100, 80, 100, 100]],
|
||||
metadata: {
|
||||
source: 'sam2_propagation',
|
||||
propagated_from_frame_id: 'seed-frame',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'annotation-40',
|
||||
annotationId: '40',
|
||||
frameId: 'frame-1',
|
||||
pathData: 'M 130 130 L 150 130 L 150 150 Z',
|
||||
label: '肝脏',
|
||||
color: '#22c55e',
|
||||
segmentation: [[130, 130, 150, 130, 150, 150]],
|
||||
metadata: {
|
||||
source: 'sam2_propagation',
|
||||
propagated_from_frame_id: 'seed-frame',
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
render(<CanvasArea activeTool="edit_polygon" frame={frame} />);
|
||||
fireEvent.click(screen.getAllByTestId('konva-path')[0]);
|
||||
|
||||
expect(useStore.getState().selectedMaskIds).toEqual(['annotation-30', 'annotation-31']);
|
||||
expect(screen.getAllByTestId('konva-group').map((group) => group.getAttribute('data-opacity'))).toEqual(['0.5', '0.5', '0.35']);
|
||||
});
|
||||
|
||||
it('does not render stored GT seed points as visible editable handles', () => {
|
||||
useStore.setState({
|
||||
masks: [
|
||||
|
||||
@@ -82,16 +82,38 @@ function propagationLineageTokens(mask: Mask): Set<string> {
|
||||
if (mask.annotationId) {
|
||||
tokens.add(`annotation:${mask.annotationId}`);
|
||||
}
|
||||
let hasStablePropagationToken = false;
|
||||
const sourceAnnotationId = metadataNumber(metadata.source_annotation_id);
|
||||
if (sourceAnnotationId !== null) {
|
||||
tokens.add(`annotation:${sourceAnnotationId}`);
|
||||
hasStablePropagationToken = true;
|
||||
}
|
||||
const sourceMaskTokens = propagationSourceMaskTokens(metadata.source_mask_id);
|
||||
if (sourceMaskTokens.length > 0) {
|
||||
sourceMaskTokens.forEach((token) => tokens.add(token));
|
||||
hasStablePropagationToken = true;
|
||||
}
|
||||
propagationSourceMaskTokens(metadata.source_mask_id).forEach((token) => tokens.add(token));
|
||||
if (typeof metadata.propagation_seed_key === 'string' && metadata.propagation_seed_key.length > 0) {
|
||||
tokens.add(`seed-key:${metadata.propagation_seed_key}`);
|
||||
hasStablePropagationToken = true;
|
||||
}
|
||||
if (typeof metadata.propagation_seed_signature === 'string' && metadata.propagation_seed_signature.length > 0) {
|
||||
tokens.add(`seed-signature:${metadata.propagation_seed_signature}`);
|
||||
hasStablePropagationToken = true;
|
||||
}
|
||||
if (isPropagationMask(mask) && !hasStablePropagationToken) {
|
||||
const source = typeof metadata.source === 'string' ? metadata.source : '';
|
||||
const classKey = mask.classId || mask.className || '';
|
||||
tokens.add([
|
||||
'legacy-propagation',
|
||||
source,
|
||||
metadata.propagated_from_frame_id ?? '',
|
||||
metadata.propagated_from_frame_index ?? '',
|
||||
metadata.propagation_direction ?? '',
|
||||
classKey,
|
||||
mask.label || '',
|
||||
mask.color || '',
|
||||
].join(':'));
|
||||
}
|
||||
return tokens;
|
||||
}
|
||||
|
||||
@@ -287,14 +287,38 @@ const propagationLineageTokens = (mask: Mask): Set<string> => {
|
||||
const metadata = mask.metadata || {};
|
||||
const tokens = new Set<string>([`mask:${mask.id}`]);
|
||||
if (mask.annotationId) tokens.add(`annotation:${mask.annotationId}`);
|
||||
let hasStablePropagationToken = false;
|
||||
const sourceAnnotationId = metadataNumber(metadata.source_annotation_id);
|
||||
if (sourceAnnotationId !== null) tokens.add(`annotation:${sourceAnnotationId}`);
|
||||
propagationSourceMaskTokens(metadata.source_mask_id).forEach((token) => tokens.add(token));
|
||||
if (sourceAnnotationId !== null) {
|
||||
tokens.add(`annotation:${sourceAnnotationId}`);
|
||||
hasStablePropagationToken = true;
|
||||
}
|
||||
const sourceMaskTokens = propagationSourceMaskTokens(metadata.source_mask_id);
|
||||
if (sourceMaskTokens.length > 0) {
|
||||
sourceMaskTokens.forEach((token) => tokens.add(token));
|
||||
hasStablePropagationToken = true;
|
||||
}
|
||||
if (typeof metadata.propagation_seed_key === 'string' && metadata.propagation_seed_key.length > 0) {
|
||||
tokens.add(`seed-key:${metadata.propagation_seed_key}`);
|
||||
hasStablePropagationToken = true;
|
||||
}
|
||||
if (typeof metadata.propagation_seed_signature === 'string' && metadata.propagation_seed_signature.length > 0) {
|
||||
tokens.add(`seed-signature:${metadata.propagation_seed_signature}`);
|
||||
hasStablePropagationToken = true;
|
||||
}
|
||||
if (isPropagatedMask(mask) && !hasStablePropagationToken) {
|
||||
const source = typeof metadata.source === 'string' ? metadata.source : '';
|
||||
const classKey = mask.classId || mask.className || '';
|
||||
tokens.add([
|
||||
'legacy-propagation',
|
||||
source,
|
||||
metadata.propagated_from_frame_id ?? '',
|
||||
metadata.propagated_from_frame_index ?? '',
|
||||
metadata.propagation_direction ?? '',
|
||||
classKey,
|
||||
mask.label || '',
|
||||
mask.color || '',
|
||||
].join(':'));
|
||||
}
|
||||
return tokens;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user