Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
307 changes: 180 additions & 127 deletions client/src/components/LayerManager.vue
Original file line number Diff line number Diff line change
Expand Up @@ -93,66 +93,25 @@ export default defineComponent({
const frameNumberRef = annotator.frame;
const flickNumberRef = annotator.flick;

const rectAnnotationLayer = new RectangleLayer({
annotator,
stateStyling: trackStyleManager.stateStyles,
typeStyling: typeStylingRef,
});
const overlapLayer = new OverlapLayer({
annotator,
stateStyling: trackStyleManager.stateStyles,
typeStyling: typeStylingRef,
});

const polyAnnotationLayer = new PolygonLayer({
annotator,
stateStyling: trackStyleManager.stateStyles,
typeStyling: typeStylingRef,
});

const lineLayer = new LineLayer({
annotator,
stateStyling: trackStyleManager.stateStyles,
typeStyling: typeStylingRef,
});
const pointLayer = new PointLayer({
annotator,
stateStyling: trackStyleManager.stateStyles,
typeStyling: typeStylingRef,
});
const tailLayer = new TailLayer({
annotator,
stateStyling: trackStyleManager.stateStyles,
typeStyling: typeStylingRef,
}, trackStore);

const textLayer = new TextLayer({
annotator,
stateStyling: trackStyleManager.stateStyles,
typeStyling: typeStylingRef,
formatter: props.formatTextRow,
});

const attributeBoxLayer = new AttributeBoxLayer({
annotator,
stateStyling: trackStyleManager.stateStyles,
typeStyling: typeStylingRef,
});

const attributeLayer = new AttributeLayer({
annotator,
stateStyling: trackStyleManager.stateStyles,
typeStyling: typeStylingRef,
});
// Track initialization state to prevent race conditions with GeoJS
const layersInitialized = ref(false);
const hoverOvered: Ref<ToolTipWidgetData[]> = ref([]);

const editAnnotationLayer = new EditAnnotationLayer({
annotator,
stateStyling: trackStyleManager.stateStyles,
typeStyling: typeStylingRef,
type: 'rectangle',
});
// Layer references - initialized after mount to ensure GeoJS is ready
let rectAnnotationLayer: RectangleLayer;
let overlapLayer: OverlapLayer;
let polyAnnotationLayer: PolygonLayer;
let lineLayer: LineLayer;
let pointLayer: PointLayer;
let tailLayer: TailLayer;
let textLayer: TextLayer;
let attributeBoxLayer: AttributeBoxLayer;
let attributeLayer: AttributeLayer;
let editAnnotationLayer: EditAnnotationLayer;
let uiLayer: UILayer;

const updateAttributes = () => {
if (!layersInitialized.value) return;
const newList = attributes.value.filter((item) => item.render).sort((a, b) => {
if (a.render && b.render) {
return (a.render.order - b.render.order);
Expand All @@ -163,16 +122,89 @@ export default defineComponent({
attributeLayer.updateRenderAttributes(newList, user);
attributeBoxLayer.updateRenderAttributes(newList);
};
updateAttributes();
const uiLayer = new UILayer(annotator);
const hoverOvered: Ref<ToolTipWidgetData[]> = ref([]);
const toolTipWidgetProps = {
color: typeStylingRef.value.color,
dataList: hoverOvered,
selected: selectedTrackIdRef,
stateStyling: trackStyleManager.stateStyles,
};
uiLayer.addDOMWidget('customToolTip', ToolTipWidget, toolTipWidgetProps, { x: 10, y: 10 });

/**
* Initialize all annotation layers. This is deferred to onMounted to ensure
* the GeoJS viewer is fully initialized before creating layers.
*/
function initializeLayers() {
rectAnnotationLayer = new RectangleLayer({
annotator,
stateStyling: trackStyleManager.stateStyles,
typeStyling: typeStylingRef,
});
overlapLayer = new OverlapLayer({
annotator,
stateStyling: trackStyleManager.stateStyles,
typeStyling: typeStylingRef,
});

polyAnnotationLayer = new PolygonLayer({
annotator,
stateStyling: trackStyleManager.stateStyles,
typeStyling: typeStylingRef,
});

lineLayer = new LineLayer({
annotator,
stateStyling: trackStyleManager.stateStyles,
typeStyling: typeStylingRef,
});
pointLayer = new PointLayer({
annotator,
stateStyling: trackStyleManager.stateStyles,
typeStyling: typeStylingRef,
});
tailLayer = new TailLayer({
annotator,
stateStyling: trackStyleManager.stateStyles,
typeStyling: typeStylingRef,
}, trackStore!);

textLayer = new TextLayer({
annotator,
stateStyling: trackStyleManager.stateStyles,
typeStyling: typeStylingRef,
formatter: props.formatTextRow,
});

attributeBoxLayer = new AttributeBoxLayer({
annotator,
stateStyling: trackStyleManager.stateStyles,
typeStyling: typeStylingRef,
});

attributeLayer = new AttributeLayer({
annotator,
stateStyling: trackStyleManager.stateStyles,
typeStyling: typeStylingRef,
});

editAnnotationLayer = new EditAnnotationLayer({
annotator,
stateStyling: trackStyleManager.stateStyles,
typeStyling: typeStylingRef,
type: 'rectangle',
});

uiLayer = new UILayer(annotator);
const toolTipWidgetProps = {
color: typeStylingRef.value.color,
dataList: hoverOvered,
selected: selectedTrackIdRef,
stateStyling: trackStyleManager.stateStyles,
};
uiLayer.addDOMWidget('customToolTip', ToolTipWidget, toolTipWidgetProps, { x: 10, y: 10 });

// Set up event listeners
setupEventListeners();

// Mark as initialized
layersInitialized.value = true;

// Update attributes now that layers are ready
updateAttributes();
}

function updateLayers(
frame: number,
Expand All @@ -184,6 +216,10 @@ export default defineComponent({
selectedKey: string,
colorBy: string,
) {
// Guard against calling before layers are initialized
if (!layersInitialized.value) {
return;
}
const currentFrameIds: AnnotationId[] | undefined = trackStore?.intervalTree
.search([frame, frame])
.map((str) => parseInt(str, 10));
Expand Down Expand Up @@ -357,22 +393,32 @@ export default defineComponent({
}

/**
* TODO: for some reason, GeoJS requires us to initialize
* by calling the render function twice. This is a bug.
* https://github.com/Kitware/dive/issues/365
* Watch for the GeoJS viewer to be ready before initializing layers.
* The annotator sets ready=true only after initializeViewer() completes,
* which guarantees geoViewerRef.value is properly initialized.
* This fixes a race condition where layers were created before GeoJS
* was fully initialized, causing annotations to not display on first load.
* See: https://github.com/Kitware/dive/issues/365
*/
[1, 2].forEach(() => {
updateLayers(
frameNumberRef.value,
editingModeRef.value,
selectedTrackIdRef.value,
multiSeletListRef.value,
enabledTracksRef.value,
visibleModesRef.value,
selectedKeyRef.value,
props.colorBy,
);
});
watch(
() => annotator.ready.value,
(ready) => {
if (ready && !layersInitialized.value) {
initializeLayers();
updateLayers(
frameNumberRef.value,
editingModeRef.value,
selectedTrackIdRef.value,
multiSeletListRef.value,
enabledTracksRef.value,
visibleModesRef.value,
selectedKeyRef.value,
props.colorBy,
);
}
},
{ immediate: true },
);

/** Shallow watch */
watch(
Expand Down Expand Up @@ -445,49 +491,6 @@ export default defineComponent({
}
};

//Sync of internal geoJS state with the application
editAnnotationLayer.bus.$on('editing-annotation-sync', (editing: boolean) => {
handler.trackSelect(selectedTrackIdRef.value, editing);
});
rectAnnotationLayer.bus.$on('annotation-clicked', Clicked);
rectAnnotationLayer.bus.$on('annotation-right-clicked', Clicked);
rectAnnotationLayer.bus.$on('annotation-ctrl-clicked', Clicked);
polyAnnotationLayer.bus.$on('annotation-clicked', Clicked);
polyAnnotationLayer.bus.$on('annotation-right-clicked', Clicked);
polyAnnotationLayer.bus.$on('annotation-ctrl-clicked', Clicked);
editAnnotationLayer.bus.$on('update:geojson', (
mode: 'in-progress' | 'editing',
geometryCompleteEvent: boolean,
data: GeoJSON.Feature<GeoJSON.Polygon | GeoJSON.LineString | GeoJSON.Point>,
type: string,
key = '',
cb: () => void = () => (undefined),
) => {
if (type === 'rectangle') {
const bounds = geojsonToBound(data as GeoJSON.Feature<GeoJSON.Polygon>);
cb();
handler.updateRectBounds(frameNumberRef.value, flickNumberRef.value, bounds);
} else {
handler.updateGeoJSON(mode, frameNumberRef.value, flickNumberRef.value, data, key, cb);
}
// Jump into edit mode if we completed a new shape
if (geometryCompleteEvent) {
updateLayers(
frameNumberRef.value,
editingModeRef.value,
selectedTrackIdRef.value,
multiSeletListRef.value,
enabledTracksRef.value,
visibleModesRef.value,
selectedKeyRef.value,
props.colorBy,
);
}
});
editAnnotationLayer.bus.$on(
'update:selectedIndex',
(index: number, _type: EditAnnotationTypes, key = '') => handler.selectFeatureHandle(index, key),
);
const annotationHoverTooltip = (
found: {
styleType: [string, number];
Expand Down Expand Up @@ -518,8 +521,58 @@ export default defineComponent({
hoverOvered.value = hoveredVals.sort((a, b) => a.maxX - b.maxX);
uiLayer.setToolTipWidget('customToolTip', (hoverOvered.value.length > 0));
};
rectAnnotationLayer.bus.$on('annotation-hover', annotationHoverTooltip);
polyAnnotationLayer.bus.$on('annotation-hover', annotationHoverTooltip);

/**
* Set up event listeners for annotation layers.
* Called after layers are initialized.
*/
function setupEventListeners() {
//Sync of internal geoJS state with the application
editAnnotationLayer.bus.$on('editing-annotation-sync', (editing: boolean) => {
handler.trackSelect(selectedTrackIdRef.value, editing);
});
rectAnnotationLayer.bus.$on('annotation-clicked', Clicked);
rectAnnotationLayer.bus.$on('annotation-right-clicked', Clicked);
rectAnnotationLayer.bus.$on('annotation-ctrl-clicked', Clicked);
polyAnnotationLayer.bus.$on('annotation-clicked', Clicked);
polyAnnotationLayer.bus.$on('annotation-right-clicked', Clicked);
polyAnnotationLayer.bus.$on('annotation-ctrl-clicked', Clicked);
editAnnotationLayer.bus.$on('update:geojson', (
mode: 'in-progress' | 'editing',
geometryCompleteEvent: boolean,
data: GeoJSON.Feature<GeoJSON.Polygon | GeoJSON.LineString | GeoJSON.Point>,
type: string,
key = '',
cb: () => void = () => (undefined),
) => {
if (type === 'rectangle') {
const bounds = geojsonToBound(data as GeoJSON.Feature<GeoJSON.Polygon>);
cb();
handler.updateRectBounds(frameNumberRef.value, flickNumberRef.value, bounds);
} else {
handler.updateGeoJSON(mode, frameNumberRef.value, flickNumberRef.value, data, key, cb);
}
// Jump into edit mode if we completed a new shape
if (geometryCompleteEvent) {
updateLayers(
frameNumberRef.value,
editingModeRef.value,
selectedTrackIdRef.value,
multiSeletListRef.value,
enabledTracksRef.value,
visibleModesRef.value,
selectedKeyRef.value,
props.colorBy,
);
}
});
editAnnotationLayer.bus.$on(
'update:selectedIndex',
(index: number, _type: EditAnnotationTypes, key = '') => handler.selectFeatureHandle(index, key),
);
rectAnnotationLayer.bus.$on('annotation-hover', annotationHoverTooltip);
polyAnnotationLayer.bus.$on('annotation-hover', annotationHoverTooltip);
}
},
});
</script>
Expand Down
2 changes: 2 additions & 0 deletions client/src/components/annotators/mediaControllerType.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ export interface MediaController extends AggregateMediaController {
flick: Readonly<Ref<number>>;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
geoViewerRef: Readonly<Ref<any>>;
/** True when the GeoJS viewer is fully initialized and ready to create layers */
ready: Readonly<Ref<boolean>>;
/** @deprecated may be removed in a future release */
syncedFrame: Readonly<Ref<number>>;

Expand Down
1 change: 1 addition & 0 deletions client/src/components/annotators/useMediaController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -406,6 +406,7 @@ export function useMediaController() {
volume: toRef(state[camera], 'volume'),
maxFrame: toRef(state[camera], 'maxFrame'),
speed: toRef(state[camera], 'speed'),
ready: toRef(state[camera], 'ready'),
syncedFrame: toRef(state[camera], 'syncedFrame'),
prevFrame,
nextFrame,
Expand Down
Loading