Skip to content
Draft
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
678 changes: 678 additions & 0 deletions client/dive-common/components/CameraCalibration/CalibrationTools.vue

Large diffs are not rendered by default.

88 changes: 84 additions & 4 deletions client/dive-common/components/Viewer.vue
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import {
LargeImageAnnotator,
LayerManager,
useMediaController,
useCalibrationNavigation,
useAlignedNavigation,
TrackList,
FilterList,
Expand Down Expand Up @@ -87,6 +88,7 @@ import context from 'dive-common/store/context';
import { MarkChangesPendingFilter } from 'vue-media-annotator/BaseFilterControls';
import GroupSidebarVue from './GroupSidebar.vue';
import MultiCamToolsVue from './MultiCamTools.vue';
import CalibrationToolsVue from './CameraCalibration/CalibrationTools.vue';
import MultiCamToolbar from './MultiCamToolbar.vue';
import PrimaryAttributeTrackFilter from './PrimaryAttributeTrackFilter.vue';
import UserSettingsDialog from './UserSettingsDialog.vue';
Expand Down Expand Up @@ -320,6 +322,29 @@ export default defineComponent({
});
const showUserSettingsDialog = ref(false);

// When the Manual Alignment panel opens, minimize the workspace chrome to
// give the picking view more room: collapse the left type-filter sidebar and
// the bottom detections graph. This is a soft default -- the normal sidebar
// and timeline toggles still work while calibrating, so the user can bring
// either back -- and whatever layout they had before is restored on close.
const calibrationActive = computed(() => context.state.active === CalibrationToolsVue.name);
let preCalibrationSidebarMode: 'left' | 'bottom' | 'collapsed' | null = null;
let preCalibrationControlsCollapsed = false;
watch(calibrationActive, (active) => {
if (active) {
preCalibrationSidebarMode = sidebarMode.value;
preCalibrationControlsCollapsed = controlsCollapsed.value;
if (sidebarMode.value === 'left') {
sidebarMode.value = 'collapsed';
}
controlsCollapsed.value = true;
} else if (preCalibrationSidebarMode !== null) {
sidebarMode.value = preCalibrationSidebarMode;
controlsCollapsed.value = preCalibrationControlsCollapsed;
preCalibrationSidebarMode = null;
}
});

watch(sidebarMode, (mode) => {
if (mode === 'left' || mode === 'bottom') {
clientSettings.layoutSettings.sidebarPosition = mode;
Expand Down Expand Up @@ -407,10 +432,15 @@ export default defineComponent({
* warp displays and link pan/zoom across all cameras during normal
* review. Reference camera = first camera in display order
* (multiCamList[0]). Transforms come from the calibration store's pair
* homographies (loaded from a calibration file or the dataset's saved
* meta), composed through the pair graph.
* homographies (picked in-app or loaded from a calibration file via the
* panel), composed through the pair graph -- the single calibration the
* panel edits and saves is exactly what the Align button applies.
*/
const alignedView = new AlignedViewStore();
// The calibration pair link maps through the homography for UNWARPED
// panes, so it needs the aligned view state to stand down while displays
// are warped into reference space.
useCalibrationNavigation(aggregateController, cameraCalibration, alignedView);
const alignedResolution = computed(() => {
if (multiCamList.value.length < 2) {
return null;
Expand All @@ -429,6 +459,12 @@ export default defineComponent({
resolution?.toReference ?? null,
);
}, { immediate: true });
// Calibration point picking records raw native-space clicks and renders
// its own aligned preview; suspend the general warp while it is active
// so picks are never taken against a warped display (risk K2).
watch(cameraCalibration.pickingEnabled, (picking) => {
alignedView.setSuspended(picking);
}, { immediate: true });
useAlignedNavigation(aggregateController, alignedView, multiCamList);
const alignedViewAvailable = computed(() => alignedView.available.value);
const alignedViewEnabled = computed(() => alignedView.enabled.value);
Expand All @@ -454,6 +490,34 @@ export default defineComponent({
}
return `Align View${mixedNote}`;
});
// The aligned view is suspended while picking, so the button reads as
// unavailable rather than accepting a toggle that has no visible effect.
const calibrationPickingEnabled = computed(() => cameraCalibration.pickingEnabled.value);
/**
* Camera panes currently displayed. While the Manual Alignment panel is
* open with an active pair on a 3+ camera dataset, only the pair's two
* panes show, so the left/right alignment flow reads without unrelated
* panes in between (regardless of whether Pick points is toggled on).
* Panes are hidden (v-show), not unmounted, so their viewers keep state.
*/
const displayedCameras = computed(() => {
const pair = cameraCalibration.activePair.value;
if (calibrationActive.value && pair) {
const pairCameras = multiCamList.value.filter(
(camera) => camera === pair.camA || camera === pair.camB,
);
if (pairCameras.length === 2) {
return pairCameras;
}
}
return multiCamList.value;
});
watch(displayedCameras, async () => {
// Hidden/shown siblings change the remaining panes' sizes; resize the
// geojs maps once the DOM has settled.
await nextTick();
handleResize();
});
// This context for removal
const removeGroups = (id: AnnotationId) => {
cameraStore.removeGroups(id);
Expand Down Expand Up @@ -1464,11 +1528,19 @@ export default defineComponent({
component: MultiCamToolsVue,
description: 'Multi Camera Tools',
});
context.register({
component: CalibrationToolsVue,
description: 'Manual Alignment',
});
} else {
context.unregister({
component: MultiCamToolsVue,
description: 'Multi Camera Tools',
});
context.unregister({
component: CalibrationToolsVue,
description: 'Manual Alignment',
});
context.register({
description: 'Group Manager',
component: GroupSidebarVue,
Expand Down Expand Up @@ -1882,6 +1954,8 @@ export default defineComponent({
alignedViewAvailable,
alignedViewEnabled,
alignedViewTooltip,
calibrationPickingEnabled,
displayedCameras,
toggleAlignedView,
seekToFrame,
resetAggregateZoom,
Expand Down Expand Up @@ -2086,7 +2160,7 @@ export default defineComponent({
small
class="mx-1"
:color="alignedViewEnabled ? 'primary' : 'default'"
:disabled="!alignedViewAvailable"
:disabled="!alignedViewAvailable || calibrationPickingEnabled"
@click="toggleAlignedView"
>
<v-icon>
Expand Down Expand Up @@ -2226,10 +2300,16 @@ export default defineComponent({
class="d-flex flex-column grow"
>
<div class="d-flex grow">
<!--
Hidden panes swap to Vuetify's d-none instead of using v-show:
d-flex is `display: flex !important`, which defeats v-show's
inline `display: none`. Panes stay mounted either way, so their
viewers keep state.
-->
<div
v-for="camera in multiCamList"
:key="camera"
class="d-flex flex-column grow"
:class="displayedCameras.includes(camera) ? 'd-flex flex-column grow' : 'd-none'"
:style="{ height: `calc(100% - ${controlsHeight}px)` }"
@mousedown.left="changeCamera(camera, $event)"
@mouseup.right="changeCamera(camera, $event)"
Expand Down
5 changes: 5 additions & 0 deletions client/dive-common/store/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import ImageEnhancements from 'vue-media-annotator/components/ImageEnhancements.
import GroupSidebar from 'dive-common/components/GroupSidebar.vue';
import AttributesSideBar from 'dive-common/components/Attributes/AttributesSideBar.vue';
import MultiCamTools from 'dive-common/components/MultiCamTools.vue';
import CalibrationTools from 'dive-common/components/CameraCalibration/CalibrationTools.vue';
import AttributeTrackFilters from 'vue-media-annotator/components/AttributeTrackFilters.vue';
import DatasetInfo from 'dive-common/components/DatasetInfo.vue';

Expand Down Expand Up @@ -46,6 +47,10 @@ const componentMap: Record<string, ComponentMapItem> = {
description: 'Multi Camera Tools',
component: MultiCamTools,
},
[CalibrationTools.name]: {
description: 'Manual Alignment',
component: CalibrationTools,
},
[AttributesSideBar.name]: {
description: 'Attribute Details',
component: AttributesSideBar,
Expand Down
Loading
Loading