From 8936920e7a29f81c06d437089b8ec9ec0d2c090a Mon Sep 17 00:00:00 2001 From: Matt Dawkins Date: Sun, 21 Jun 2026 10:41:24 -0400 Subject: [PATCH 01/25] Stereo camera-file import/export, display, and length clearing Add desktop import/export of stereo camera/calibration files via the annotation Import/Export buttons, display the loaded camera file in stereo mode, and clear 'length' attributes from tracks/detections when a new camera file loads (toggle in user settings). On import/upload, any VIAME-supported calibration is converted to the JSON camera format via convert_cam_format.py and stored with the dataset, keeping the original. --- client/dive-common/apispec.ts | 6 ++ .../components/ImportAnnotations.vue | 98 ++++++++++++++++++- .../components/UserSettingsDialog.vue | 8 ++ client/dive-common/store/settings.ts | 8 ++ .../utils/clearLengthAttributes.ts | 35 +++++++ client/platform/desktop/backend/ipcService.ts | 10 ++ .../backend/native/calibrationConvert.ts | 78 +++++++++++++++ .../platform/desktop/backend/native/common.ts | 71 ++++++++++++++ client/platform/desktop/frontend/api.ts | 12 ++- .../desktop/frontend/components/Export.vue | 46 ++++++++- .../frontend/components/ViewerLoader.vue | 2 + .../desktop/frontend/store/dataset.ts | 2 + 12 files changed, 370 insertions(+), 6 deletions(-) create mode 100644 client/dive-common/utils/clearLengthAttributes.ts create mode 100644 client/platform/desktop/backend/native/calibrationConvert.ts diff --git a/client/dive-common/apispec.ts b/client/dive-common/apispec.ts index f3aad43c5..236ff3407 100644 --- a/client/dive-common/apispec.ts +++ b/client/dive-common/apispec.ts @@ -196,6 +196,8 @@ interface DatasetMeta extends DatasetMetaMutable { originalFps?: Readonly; subType: Readonly; // In future this could have stuff like IR/EO multiCamMedia: Readonly; + /** Stereo calibration / camera file currently associated with the dataset (desktop). */ + calibration?: Readonly; } interface Api { @@ -257,6 +259,10 @@ interface Api { // Desktop-only calibration persistence functions getLastCalibration?(): Promise; saveCalibration?(path: string): Promise<{ savedPath: string; updatedDatasetIds: string[] }>; + /** Desktop: set the stereo camera/calibration file for a single dataset. */ + importCalibrationFile?(datasetId: string, path: string): Promise<{ calibration: string }>; + /** Desktop: copy the dataset's current camera/calibration file out to destPath. */ + exportCalibrationFile?(datasetId: string, destPath: string): Promise<{ exportedPath: string }>; } const ApiSymbol = Symbol('api'); diff --git a/client/dive-common/components/ImportAnnotations.vue b/client/dive-common/components/ImportAnnotations.vue index c85786ea4..d5bc3ab74 100644 --- a/client/dive-common/components/ImportAnnotations.vue +++ b/client/dive-common/components/ImportAnnotations.vue @@ -1,9 +1,15 @@ + + diff --git a/client/dive-common/components/CalibrationMenu.vue b/client/dive-common/components/CalibrationMenu.vue new file mode 100644 index 000000000..d4ff6161d --- /dev/null +++ b/client/dive-common/components/CalibrationMenu.vue @@ -0,0 +1,83 @@ + + + + + \ No newline at end of file diff --git a/client/dive-common/components/RunPipelineToast.vue b/client/dive-common/components/RunPipelineToast.vue index 53c8bbcbe..e24c141f8 100644 --- a/client/dive-common/components/RunPipelineToast.vue +++ b/client/dive-common/components/RunPipelineToast.vue @@ -35,6 +35,8 @@ export default defineComponent({ return 'mdi-vector-polygon'; case 'TRACK': return 'mdi-gesture'; + case 'CALIBRATION': + return 'mdi-checkerboard'; default: return type; } diff --git a/client/dive-common/components/Viewer.vue b/client/dive-common/components/Viewer.vue index 4b60ed02d..9ef008a1d 100644 --- a/client/dive-common/components/Viewer.vue +++ b/client/dive-common/components/Viewer.vue @@ -64,6 +64,7 @@ import MultiCamToolsVue from './MultiCamTools.vue'; import MultiCamToolbar from './MultiCamToolbar.vue'; import PrimaryAttributeTrackFilter from './PrimaryAttributeTrackFilter.vue'; import UserSettingsDialog from './UserSettingsDialog.vue'; +import CalibrationMenu from './CalibrationMenu.vue'; export interface ImageDataItem { url: string; @@ -86,6 +87,7 @@ export default defineComponent({ UserGuideButton, UserSettingsDialog, EditorMenu, + CalibrationMenu, MultiCamToolbar, PrimaryAttributeTrackFilter, TrackList, @@ -1212,6 +1214,7 @@ export default defineComponent({ showConfidenceFirst, showTrackAttributesFirst, attributes, + datasetId, /* Attribute editing for bottom panel */ editIndividual, editingAttribute, @@ -1386,17 +1389,38 @@ export default defineComponent({ :value="selectedCamera" :items="multiCamList" label="Camera" - class="shrink" - style="width: 180px;" + class="mx-1 shrink camera-select" + style="width: 180px; font-size: 0.9em" outlined hide-details dense + variant="default" @change="changeCamera" > + + + + + diff --git a/client/platform/web-girder/App.vue b/client/platform/web-girder/App.vue index fbaaf0c11..7245b42b9 100644 --- a/client/platform/web-girder/App.vue +++ b/client/platform/web-girder/App.vue @@ -16,6 +16,7 @@ import { deleteTrainedPipeline, runPipeline, exportTrainedPipeline, + getDatasetCalibration, getTrainingConfigurations, runTraining, saveMetadata, @@ -57,6 +58,7 @@ export default defineComponent({ deleteTrainedPipeline: unwrap(deleteTrainedPipeline), runPipeline: unwrap(runPipeline), exportTrainedPipeline: unwrap(exportTrainedPipeline), + getDatasetCalibration: unwrap(getDatasetCalibration), getTrainingConfigurations: unwrap(getTrainingConfigurations), runTraining: unwrap(runTraining), loadDetections, diff --git a/client/platform/web-girder/api/rpc.service.ts b/client/platform/web-girder/api/rpc.service.ts index 7bc6ac109..7dec3d8c8 100644 --- a/client/platform/web-girder/api/rpc.service.ts +++ b/client/platform/web-girder/api/rpc.service.ts @@ -54,6 +54,12 @@ function exportTrainedPipeline(path: string, pipeline: Pipe) { }); } +function getDatasetCalibration(datasetId: string) { + return girderRest.get('dive_rpc/calibration', { + params: {folderId: datasetId} + }); +} + function convertLargeImage(folderId: string) { return girderRest.post(`dive_rpc/convert_large_image/${folderId}`, null, {}); } @@ -65,4 +71,5 @@ export { runTraining, deleteTrainedPipeline, exportTrainedPipeline, + getDatasetCalibration, }; diff --git a/server/dive_server/crud_rpc.py b/server/dive_server/crud_rpc.py index 0c84143d4..1a0e7f426 100644 --- a/server/dive_server/crud_rpc.py +++ b/server/dive_server/crud_rpc.py @@ -1,6 +1,7 @@ from datetime import datetime, timedelta import json from typing import Dict, List, Optional, Tuple, TypedDict +from pathlib import Path from girder.constants import AccessType from girder.exceptions import RestException @@ -367,6 +368,35 @@ def run_pipeline( return job +def get_calibration( + user: types.GirderUserModel, + folder: types.GirderModel, +) -> dict | None: + folder_id_str = str(folder["_id"]) + dataset_type = fromMeta(folder, "type", required=True) + + if dataset_type != constants.MultiType: + raise RestException('Cannot search for calibration file on non stereo/multicam datasets', code=400) + + print("Dataset Type", dataset_type) + calibration_item_id = crud_dataset.find_calibration_item_id(folder_id_str) + print("calibration item id", calibration_item_id) + + if calibration_item_id is None: + return None + + calibration_item = Item().load(calibration_item_id, level=AccessType.READ, user=user) + print("calibration item", calibration_item) + + files = Item().fileList(calibration_item, user, data=True) + for file in files: + if Path(file[0]).suffix == '.json': + data = json.loads(b"".join(list(file[1]())).decode("utf-8")) + return data + + raise RestException('Calibration file not supported', code=400) + + def export_trained_pipeline( user: types.GirderUserModel, model_folder: types.GirderModel, diff --git a/server/dive_server/views_rpc.py b/server/dive_server/views_rpc.py index 911d10419..75210d3cd 100644 --- a/server/dive_server/views_rpc.py +++ b/server/dive_server/views_rpc.py @@ -23,6 +23,7 @@ def __init__(self, resourceName): self.resourceName = resourceName self.route("POST", ("pipeline",), self.run_pipeline_task) + self.route("GET", ("calibration",), self.get_dataset_calibration) self.route("POST", ("export",), self.export_pipeline_onnx) self.route("POST", ("train",), self.run_training) self.route("POST", ("postprocess", ":id"), self.postprocess) @@ -68,6 +69,26 @@ def run_pipeline_task( return crud_rpc.run_pipeline( self.getCurrentUser(), folder, pipeline, forceTranscoded, pipelineParams ) + + @access.user + @autoDescribeRoute( + Description("Get calibration information of dataset") + .modelParam( + "folderId", + description="Folder id of a video clip", + model=Folder, + paramType="query", + required=True, + level=AccessType.READ, + ) + ) + def get_dataset_calibration( + self, + folder, + ): + return crud_rpc.get_calibration( + self.getCurrentUser(), folder + ) @access.user @autoDescribeRoute( From 8990b8c5f16ca81d45cdb0a6cbed2a4f38e9603a Mon Sep 17 00:00:00 2001 From: Louis Pagnier Date: Thu, 25 Jun 2026 15:22:18 +0200 Subject: [PATCH 03/25] refac calibration menu + implement delete & download (cherry picked from commit 72e39e21e7287584fba2989aaaef6b88de0f5bcd) --- client/dive-common/apispec.ts | 36 ++++++++- .../components/CalibrationDialog.vue | 64 ++++++++++------ .../components/CalibrationMenu.vue | 34 +++++++-- .../web-girder/api/dataset.service.ts | 7 ++ .../platform/web-girder/api/girder.service.ts | 5 ++ client/platform/web-girder/api/rpc.service.ts | 7 -- server/dive_server/crud_dataset.py | 76 +++++++++++++++++++ server/dive_server/crud_rpc.py | 29 ------- server/dive_server/views_dataset.py | 21 +++++ server/dive_server/views_rpc.py | 21 ----- server/dive_utils/types.py | 30 ++++++++ 11 files changed, 244 insertions(+), 86 deletions(-) diff --git a/client/dive-common/apispec.ts b/client/dive-common/apispec.ts index 6a14ff3e6..d72a39520 100644 --- a/client/dive-common/apispec.ts +++ b/client/dive-common/apispec.ts @@ -200,12 +200,43 @@ interface DatasetMeta extends DatasetMetaMutable { calibration?: Readonly; } +interface CameraCalibration { + cx: number + cy: number + fx: number + fy: number + k1: number + k2: number + k3: number + p1: number + p2: number + rmsError: number +} + +interface DatasetStereoCalibration { + R: number[] + T: number[] + gridHeight: number + gridWidth: number + imageHeight: number + imageWidth: number + squareSize: number + rmsError: number + calibrations: Record +} + +interface DatasetCalibrationResult { + calibration: DatasetStereoCalibration + itemId?: string; + path?: string; +} + interface Api { getPipelineList(): Promise; runPipeline(itemId: string, pipeline: Pipe, pipelineParams?: PipelineParams): Promise; deleteTrainedPipeline(pipeline: Pipe): Promise; exportTrainedPipeline(path: string, pipeline: Pipe): Promise; - getDatasetCalibration(datasetId: string): Promise; + getDatasetCalibration(datasetId: string): Promise; getTrainingConfigurations(): Promise; runTraining( @@ -293,6 +324,9 @@ export { DatasetMetaMutableKeys, DatasetType, DiveParam, + CameraCalibration, + DatasetStereoCalibration, + DatasetCalibrationResult, SubType, PipelineParamType, FrameImage, diff --git a/client/dive-common/components/CalibrationDialog.vue b/client/dive-common/components/CalibrationDialog.vue index 5e4ccfb7f..ed815f340 100644 --- a/client/dive-common/components/CalibrationDialog.vue +++ b/client/dive-common/components/CalibrationDialog.vue @@ -1,22 +1,38 @@ @@ -254,29 +267,27 @@ export default defineComponent({ - - - - mdi-eye - - - Visibility + - - diff --git a/client/dive-common/components/CalibrationMenu.vue b/client/dive-common/components/CalibrationMenu.vue index a9d8b8be6..67a87b6d1 100644 --- a/client/dive-common/components/CalibrationMenu.vue +++ b/client/dive-common/components/CalibrationMenu.vue @@ -20,7 +20,6 @@ export default defineComponent({ }, props: { datasetId: { type: String, required: true }, - buttonOptions: { type: Object, default: () => ({}) }, }, setup(props) { const showCalibrationDialog = ref(false); @@ -68,20 +67,19 @@ export default defineComponent({ @@ -376,23 +379,22 @@ export default defineComponent({ - + - diff --git a/client/dive-common/components/OutlinedLabeledGroup.vue b/client/dive-common/components/OutlinedLabeledGroup.vue new file mode 100644 index 000000000..38aa6c292 --- /dev/null +++ b/client/dive-common/components/OutlinedLabeledGroup.vue @@ -0,0 +1,43 @@ + + + + + diff --git a/client/dive-common/components/ToolbarExpandToggle.vue b/client/dive-common/components/ToolbarExpandToggle.vue new file mode 100644 index 000000000..cf394dbbc --- /dev/null +++ b/client/dive-common/components/ToolbarExpandToggle.vue @@ -0,0 +1,47 @@ + + + + + diff --git a/client/dive-common/components/Viewer.vue b/client/dive-common/components/Viewer.vue index 52806fe5f..df9ded714 100644 --- a/client/dive-common/components/Viewer.vue +++ b/client/dive-common/components/Viewer.vue @@ -1473,6 +1473,8 @@ export default defineComponent({ + + Menus for Advanced Tools/Settings - - diff --git a/client/dive-common/components/toolbarGroup.scss b/client/dive-common/components/toolbarGroup.scss new file mode 100644 index 000000000..8a49117fb --- /dev/null +++ b/client/dive-common/components/toolbarGroup.scss @@ -0,0 +1,16 @@ +.toolbar-group-host { + display: inline-flex; + align-items: center; + vertical-align: middle; + margin-top: -9px; +} + +.toolbar-group-activator.v-btn.v-size--small { + height: 28px !important; + min-height: 28px !important; +} + +.toolbar-group-activator.mode-button { + border: 1px solid grey; + min-width: 36px; +} diff --git a/client/platform/desktop/frontend/components/ViewerLoader.vue b/client/platform/desktop/frontend/components/ViewerLoader.vue index d0ecacdb0..5e3d0f9b4 100644 --- a/client/platform/desktop/frontend/components/ViewerLoader.vue +++ b/client/platform/desktop/frontend/components/ViewerLoader.vue @@ -1344,11 +1344,6 @@ export default defineComponent({ :time-filter="timeFilter" v-bind="{ buttonOptions, menuOptions }" /> - + - - - - -
{{ button.mousetrap[0].bind }}:
- - {{ button.icon }} - -
-
- - {{ button.id }} - -
-
+ + + + +
{{ button.mousetrap[0].bind }}:
+ + {{ button.icon }} + +
+
+ + {{ button.id }} + +
+
diff --git a/client/dive-common/components/Viewer.vue b/client/dive-common/components/Viewer.vue index df9ded714..6a656d3a3 100644 --- a/client/dive-common/components/Viewer.vue +++ b/client/dive-common/components/Viewer.vue @@ -1335,6 +1335,7 @@ export default defineComponent({ class="title pl-3 flex-row" style="white-space:nowrap;overflow:hidden;text-overflow: ellipsis;" > + {{ datasetName }} app.getPath(name)); - ipcMain.handle('desktop:open-path', (_, targetPath: string) => shell.openPath(targetPath)); + ipcMain.handle('desktop:open-path', async (_, targetPath: string) => ( + common.openPathInFileManager(targetPath) + )); ipcMain.on('update-settings', async (_, s: Settings) => { settings.set(s); }); diff --git a/client/platform/desktop/backend/native/common.ts b/client/platform/desktop/backend/native/common.ts index 56623d8a2..de7175d55 100644 --- a/client/platform/desktop/backend/native/common.ts +++ b/client/platform/desktop/backend/native/common.ts @@ -4,6 +4,7 @@ import npath from 'path'; import fs from 'fs-extra'; +import { spawn } from 'child_process'; import { shell } from 'electron'; import mime from 'mime-types'; import moment from 'moment'; @@ -1421,11 +1422,13 @@ async function finalizeMediaImport( // Store any stereo calibration / camera file alongside the media and normalize // it to the VIAME JSON camera-rig format (keeping the original). if (jsonMeta.multiCam?.calibration) { - jsonMeta.multiCam.calibrationOriginalName = realCalibrationName(jsonMeta.multiCam.calibration); + const calibrationSourcePath = npath.resolve(jsonMeta.multiCam.calibration); + jsonMeta.multiCam.calibrationSourcePath = calibrationSourcePath; + jsonMeta.multiCam.calibrationOriginalName = realCalibrationName(calibrationSourcePath); jsonMeta.multiCam.calibration = await prepareDatasetCalibration( settings, projectDirAbsPath, - jsonMeta.multiCam.calibration, + calibrationSourcePath, ); } @@ -1543,6 +1546,45 @@ async function openLink(url: string) { shell.openExternal(url); } +/** + * Open a file or folder in the system file manager. + * Returns an empty string on success or an error message on failure. + * + * shell.openPath can hang indefinitely on Linux (never resolving its promise), + * which breaks ipcMain.handle callers. Files use showItemInFolder; directories + * use a detached platform opener so the IPC handler always replies promptly. + */ +async function openPathInFileManager(targetPath: string): Promise { + if (!targetPath?.trim()) { + return 'No path specified'; + } + const resolved = npath.resolve(targetPath.trim()); + if (!(await fs.pathExists(resolved))) { + return `Path does not exist: ${resolved}`; + } + + const stat = await fs.stat(resolved); + if (stat.isFile()) { + shell.showItemInFolder(resolved); + return ''; + } + + if (process.platform === 'linux') { + spawn('xdg-open', [resolved], { detached: true, stdio: 'ignore' }).unref(); + return ''; + } + if (process.platform === 'win32') { + spawn('explorer', [resolved], { detached: true, stdio: 'ignore' }).unref(); + return ''; + } + if (process.platform === 'darwin') { + spawn('open', [resolved], { detached: true, stdio: 'ignore' }).unref(); + return ''; + } + + return shell.openPath(resolved); +} + async function exportDataset(settings: Settings, args: ExportDatasetArgs) { const projectDirInfo = await getValidatedProjectDir(settings, args.id); const meta = await loadJsonMetadata(projectDirInfo.metaFileAbsPath); @@ -1650,14 +1692,16 @@ async function applyCalibrationToUncalibratedStereoDatasets( // eslint-disable-next-line no-await-in-loop const fullMeta = await loadJsonMetadata(projectDirInfo.metaFileAbsPath); if (fullMeta.multiCam) { + const calibrationSourcePath = npath.resolve(calibrationPath); // eslint-disable-next-line no-await-in-loop fullMeta.multiCam.calibration = await prepareDatasetCalibration( settings, projectDirInfo.basePath, - calibrationPath, + calibrationSourcePath, ); + fullMeta.multiCam.calibrationSourcePath = calibrationSourcePath; fullMeta.multiCam.calibrationOriginalName = realCalibrationName(originalName) - ?? realCalibrationName(calibrationPath); + ?? realCalibrationName(calibrationSourcePath); // eslint-disable-next-line no-await-in-loop await _saveAsJson(projectDirInfo.metaFileAbsPath, fullMeta); updatedIds.push(meta.id); @@ -1726,6 +1770,7 @@ async function setDatasetCalibration( sourcePath, ); fullMeta.multiCam.calibration = calibrationPath; + fullMeta.multiCam.calibrationSourcePath = npath.resolve(sourcePath); fullMeta.multiCam.calibrationOriginalName = realCalibrationName(sourcePath); await _saveAsJson(projectDirInfo.metaFileAbsPath, fullMeta); return calibrationPath; @@ -1825,6 +1870,8 @@ async function deleteDatasetCalibration(settings: Settings, datasetId: string): } if (fullMeta.multiCam) { fullMeta.multiCam.calibration = undefined; + fullMeta.multiCam.calibrationSourcePath = undefined; + fullMeta.multiCam.calibrationOriginalName = undefined; await _saveAsJson(projectDirInfo.metaFileAbsPath, fullMeta); } } @@ -1852,6 +1899,7 @@ export { loadAnnotationFile, loadDetections, openLink, + openPathInFileManager, ingestDataFiles, saveDetections, saveMetadata, diff --git a/client/platform/desktop/backend/native/interactive.ts b/client/platform/desktop/backend/native/interactive.ts index 6911f0c18..3df410405 100644 --- a/client/platform/desktop/backend/native/interactive.ts +++ b/client/platform/desktop/backend/native/interactive.ts @@ -213,7 +213,9 @@ export class InteractiveServiceManager extends EventEmitter { const command = `${viameConstants.setupScriptAbs} && ${pyCommand}`; + // eslint-disable-next-line no-console console.log('[Interactive] Starting interactive service...'); + // eslint-disable-next-line no-console console.log(`[Interactive] Command: ${command}`); const stderrLines: string[] = []; @@ -239,6 +241,7 @@ export class InteractiveServiceManager extends EventEmitter { this.process.stderr.on('data', (data: Buffer) => { const message = data.toString().trim(); if (message) { + // eslint-disable-next-line no-console console.log(`[Interactive] ${message}`); stderrLines.push(message); if (stderrLines.length > maxStderrLines) { @@ -274,6 +277,7 @@ export class InteractiveServiceManager extends EventEmitter { }; this.process.on('exit', (code, signal) => { + // eslint-disable-next-line no-console console.log(`[Interactive] Process exited with code ${code}, signal ${signal}`); this.cleanup(); if (this.isStarting) { @@ -632,6 +636,7 @@ export class InteractiveServiceManager extends EventEmitter { if (!this.process) { return; } + // eslint-disable-next-line no-console console.log('[Interactive] Shutting down interactive service...'); await new Promise((resolve) => { const reqId = this.generateRequestId(); @@ -641,6 +646,7 @@ export class InteractiveServiceManager extends EventEmitter { } const timeoutId = setTimeout(() => { if (this.process) { + // eslint-disable-next-line no-console console.log('[Interactive] Force killing interactive service...'); this.process.kill('SIGTERM'); } diff --git a/client/platform/desktop/constants.ts b/client/platform/desktop/constants.ts index 027862da2..e5b53d6dc 100644 --- a/client/platform/desktop/constants.ts +++ b/client/platform/desktop/constants.ts @@ -55,6 +55,8 @@ export interface MultiCamDesktop { // Name of the user's original calibration file (preserved for display, since // `calibration` may point at a converted/normalized copy). calibrationOriginalName?: string; + // Absolute path of the calibration file at import (before project copy/conversion). + calibrationSourcePath?: string; // Default Display Key for showing multiCam defaultDisplay: string; } diff --git a/client/platform/desktop/frontend/components/DatasetSourceInfo.vue b/client/platform/desktop/frontend/components/DatasetSourceInfo.vue new file mode 100644 index 000000000..154331978 --- /dev/null +++ b/client/platform/desktop/frontend/components/DatasetSourceInfo.vue @@ -0,0 +1,277 @@ + + + + + diff --git a/client/platform/desktop/frontend/components/Recent.vue b/client/platform/desktop/frontend/components/Recent.vue index 3b95275d9..ae0fad5a2 100644 --- a/client/platform/desktop/frontend/components/Recent.vue +++ b/client/platform/desktop/frontend/components/Recent.vue @@ -27,6 +27,7 @@ import { } from '../store/settings'; import { setOrGetConversionJob, cpuJobQueue, queuedCpuJobs } from '../store/jobs'; import BrowserLink from './BrowserLink.vue'; +import DatasetSourceInfo from './DatasetSourceInfo.vue'; import NavigationBar from './NavigationBar.vue'; import ImportDialog from './ImportDialog.vue'; import BulkImportDialog from './BulkImportDialog.vue'; @@ -34,6 +35,7 @@ import BulkImportDialog from './BulkImportDialog.vue'; export default defineComponent({ components: { BrowserLink, + DatasetSourceInfo, ImportButton, ImportDialog, BulkImportDialog, @@ -544,12 +546,18 @@ export default defineComponent({ > {{ item.name }} -
- {{ - item.imageListPath - || item.originalBasePath - || 'Data imported from several locations' - }} +
+ + + {{ + item.imageListPath + || item.originalBasePath + || 'Data imported from several locations' + }} +
diff --git a/client/platform/desktop/frontend/components/ViewerLoader.vue b/client/platform/desktop/frontend/components/ViewerLoader.vue index 2cd27d2ba..74405d6cc 100644 --- a/client/platform/desktop/frontend/components/ViewerLoader.vue +++ b/client/platform/desktop/frontend/components/ViewerLoader.vue @@ -27,6 +27,7 @@ import { } from 'platform/desktop/frontend/api'; import Export from './Export.vue'; import JobTab from './JobTab.vue'; +import DatasetSourceInfo from './DatasetSourceInfo.vue'; import { datasets } from '../store/dataset'; import { settings } from '../store/settings'; import { runningJobs } from '../store/jobs'; @@ -56,6 +57,7 @@ export default defineComponent({ components: { Export, JobTab, + DatasetSourceInfo, RunPipelineMenu, SidebarContext, Viewer, @@ -1272,6 +1274,19 @@ export default defineComponent({ }); } + async function applyCalibrationAfterImport() { + try { + const hasStereo = await loadStereoMetadata(); + if (!hasStereo) return; + const result = await stereoEnable(undefined, stereoCalibrationFile); + if (!result.success) return; + stereoEnabled.value = true; + await ensureStereoFrame(getViewerFrame()); + } catch (err) { + console.warn('[Stereo] Failed to apply calibration after import:', err); + } + } + function onCalibrationImported(calibrationPath: string) { const dataset = datasets.value[props.id]; if (dataset) { @@ -1279,18 +1294,7 @@ export default defineComponent({ } stereoCalibrationFile = calibrationPath; if (!stereoServiceWanted()) return; - void (async () => { - try { - const hasStereo = await loadStereoMetadata(); - if (!hasStereo) return; - const result = await stereoEnable(undefined, stereoCalibrationFile); - if (!result.success) return; - stereoEnabled.value = true; - await ensureStereoFrame(getViewerFrame()); - } catch (err) { - console.warn('[Stereo] Failed to apply calibration after import:', err); - } - })(); + applyCalibrationAfterImport(); } return { @@ -1337,6 +1341,9 @@ export default defineComponent({ @stereo-segmentation-finalize="handleStereoSegmentationFinalize" @stereo-track-linked="handleStereoTrackLinked" > + + + diff --git a/client/package-lock.json b/client/package-lock.json index 01a1adfd1..0d84b5716 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -14,6 +14,7 @@ "@mdi/font": "^6.2.95", "@sentry/browser": "^5.24.2", "@sentry/integrations": "^5.24.2", + "archiver": "^7.0.1", "axios": "^1.17.0", "csv-stringify": "^5.6.0", "d3": "^5.12.0", @@ -2108,16 +2109,6 @@ "node": ">=16.4" } }, - "node_modules/@electron/universal/node_modules/brace-expansion": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz", - "integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, "node_modules/@electron/universal/node_modules/fs-extra": { "version": "11.3.4", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.4.tgz", @@ -2755,7 +2746,6 @@ "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", - "dev": true, "license": "ISC", "dependencies": { "string-width": "^5.1.2", @@ -2773,7 +2763,6 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", - "dev": true, "license": "MIT", "engines": { "node": ">=12" @@ -2786,7 +2775,6 @@ "version": "6.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", - "dev": true, "license": "MIT", "engines": { "node": ">=12" @@ -2799,14 +2787,12 @@ "version": "9.2.2", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "dev": true, "license": "MIT" }, "node_modules/@isaacs/cliui/node_modules/string-width": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", - "dev": true, "license": "MIT", "dependencies": { "eastasianwidth": "^0.2.0", @@ -2824,7 +2810,6 @@ "version": "7.1.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", - "dev": true, "license": "MIT", "dependencies": { "ansi-regex": "^6.0.1" @@ -2840,7 +2825,6 @@ "version": "8.1.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", - "dev": true, "license": "MIT", "dependencies": { "ansi-styles": "^6.1.0", @@ -3450,7 +3434,6 @@ "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", - "dev": true, "license": "MIT", "optional": true, "engines": { @@ -5449,6 +5432,18 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "license": "MIT", + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, "node_modules/accepts": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", @@ -5584,7 +5579,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -5594,7 +5588,6 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, "license": "MIT", "dependencies": { "color-convert": "^2.0.1" @@ -5871,6 +5864,182 @@ "node": "^18.17.0 || >=20.5.0" } }, + "node_modules/archiver": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/archiver/-/archiver-7.0.1.tgz", + "integrity": "sha512-ZcbTaIqJOfCc03QwD468Unz/5Ir8ATtvAHsK+FdXbDIbGfihqh9mrvdcYunQzqn4HrvWWaFyaxJhGZagaJJpPQ==", + "license": "MIT", + "dependencies": { + "archiver-utils": "^5.0.2", + "async": "^3.2.4", + "buffer-crc32": "^1.0.0", + "readable-stream": "^4.0.0", + "readdir-glob": "^1.1.2", + "tar-stream": "^3.0.0", + "zip-stream": "^6.0.1" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/archiver-utils": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-5.0.2.tgz", + "integrity": "sha512-wuLJMmIBQYCsGZgYLTy5FIB2pF6Lfb6cXMSF8Qywwk3t20zWnAi7zLcQFdKQmIB8wyZpY5ER38x08GbwtR2cLA==", + "license": "MIT", + "dependencies": { + "glob": "^10.0.0", + "graceful-fs": "^4.2.0", + "is-stream": "^2.0.1", + "lazystream": "^1.0.0", + "lodash": "^4.17.15", + "normalize-path": "^3.0.0", + "readable-stream": "^4.0.0" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/archiver-utils/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/archiver-utils/node_modules/glob": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/archiver-utils/node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/archiver-utils/node_modules/minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.2" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/archiver-utils/node_modules/readable-stream": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz", + "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==", + "license": "MIT", + "dependencies": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/archiver/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/archiver/node_modules/buffer-crc32": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-1.0.0.tgz", + "integrity": "sha512-Db1SbgBS/fg/392AblrMJk97KggmvYhr4pB5ZIMTWtaivCPMWLkmb7m21cJvpvgK+J3nsU2CmmixNBZx4vFj/w==", + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/archiver/node_modules/readable-stream": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz", + "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==", + "license": "MIT", + "dependencies": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, "node_modules/aria-query": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", @@ -6124,6 +6293,12 @@ "node": ">=8" } }, + "node_modules/async": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", + "license": "MIT" + }, "node_modules/async-exit-hook": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/async-exit-hook/-/async-exit-hook-2.0.1.tgz", @@ -6235,6 +6410,20 @@ "dequal": "^2.0.3" } }, + "node_modules/b4a": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.8.1.tgz", + "integrity": "sha512-aiqre1Nr0B/6DgE2N5vwTc+2/oQZ4Wh1t4NznYY4E00y8LCt6NqdRv81so00oo27D8MVKTpUa/MwUUtBLXCoDw==", + "license": "Apache-2.0", + "peerDependencies": { + "react-native-b4a": "*" + }, + "peerDependenciesMeta": { + "react-native-b4a": { + "optional": true + } + } + }, "node_modules/babel-plugin-polyfill-corejs2": { "version": "0.4.17", "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.17.tgz", @@ -6291,14 +6480,104 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "devOptional": true, "license": "MIT" }, + "node_modules/bare-events": { + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.9.1.tgz", + "integrity": "sha512-Z0oHEHAFDZkffN8Qc39zNZjQlMDkPJRyyyZieU1VH7u8c5S+qHZ2S8ixdKIAxEjfHO7FJxXmJWgteOghVanIsg==", + "license": "Apache-2.0", + "peerDependencies": { + "bare-abort-controller": "*" + }, + "peerDependenciesMeta": { + "bare-abort-controller": { + "optional": true + } + } + }, + "node_modules/bare-fs": { + "version": "4.7.2", + "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.7.2.tgz", + "integrity": "sha512-aTvMFUWkBmjzKtEQMDGGDNF8bkfpD5N1b/FCwt7A3wrU4t1o/e/85Wzkluh6JlODCjqVESYCkQCdTXqZ9G7VFg==", + "license": "Apache-2.0", + "dependencies": { + "bare-events": "^2.5.4", + "bare-path": "^3.0.0", + "bare-stream": "^2.6.4", + "bare-url": "^2.2.2", + "fast-fifo": "^1.3.2" + }, + "engines": { + "bare": ">=1.16.0" + }, + "peerDependencies": { + "bare-buffer": "*" + }, + "peerDependenciesMeta": { + "bare-buffer": { + "optional": true + } + } + }, + "node_modules/bare-os": { + "version": "3.9.2", + "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-3.9.2.tgz", + "integrity": "sha512-h530JsrkYi8518ZfR57GHaLoI5YzXkGGEV0Y+mf4KYPBn4OnNajiznwkDq7FgE+Vnmyss9Utnzi44y7sowiAXA==", + "license": "Apache-2.0", + "engines": { + "bare": ">=1.14.0" + } + }, + "node_modules/bare-path": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-3.0.1.tgz", + "integrity": "sha512-ghj2DSK/2e99a1anTVPCV4m4YIYtrbXhfM7V3D7XZLOTsybnYyaJloymGqssQc8l/or0UoDyRtNQkmkEF/ysgQ==", + "license": "Apache-2.0", + "dependencies": { + "bare-os": "^3.0.1" + } + }, + "node_modules/bare-stream": { + "version": "2.13.3", + "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.13.3.tgz", + "integrity": "sha512-Kc+brLqvEqGkjyfiwJmImAOqLZL7OsoLKuavx+hJjgVV3nLTOjloJyPMFxjUPerGGHrNH0fLU06jjykMLWrERQ==", + "license": "Apache-2.0", + "dependencies": { + "b4a": "^1.8.1", + "streamx": "^2.25.0", + "teex": "^1.0.1" + }, + "peerDependencies": { + "bare-abort-controller": "*", + "bare-buffer": "*", + "bare-events": "*" + }, + "peerDependenciesMeta": { + "bare-abort-controller": { + "optional": true + }, + "bare-buffer": { + "optional": true + }, + "bare-events": { + "optional": true + } + } + }, + "node_modules/bare-url": { + "version": "2.4.5", + "resolved": "https://registry.npmjs.org/bare-url/-/bare-url-2.4.5.tgz", + "integrity": "sha512-K+y9xF1tN+CdPu4qWwr0QiK1Al07eFPGYK5M2pDXcmHdMdgC/tT/bpmMe1hrmRHaidKLkXrC+cRNYf3XVDUhSQ==", + "license": "Apache-2.0", + "dependencies": { + "bare-path": "^3.0.0" + } + }, "node_modules/base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "dev": true, "funding": [ { "type": "github", @@ -6399,10 +6678,9 @@ "optional": true }, "node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.1.tgz", + "integrity": "sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA==", "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" @@ -6866,7 +7144,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, "license": "MIT", "dependencies": { "color-name": "~1.1.4" @@ -6909,6 +7186,62 @@ "node": ">=0.10.0" } }, + "node_modules/compress-commons": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-6.0.2.tgz", + "integrity": "sha512-6FqVXeETqWPoGcfzrXb37E50NP0LXT8kAMu5ooZayhWWdgEY4lBEEcbQNXtkuKQsGduxiIcI4gOTsxTmuq/bSg==", + "license": "MIT", + "dependencies": { + "crc-32": "^1.2.0", + "crc32-stream": "^6.0.0", + "is-stream": "^2.0.1", + "normalize-path": "^3.0.0", + "readable-stream": "^4.0.0" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/compress-commons/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/compress-commons/node_modules/readable-stream": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz", + "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==", + "license": "MIT", + "dependencies": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -7020,9 +7353,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==", - "dev": true, - "license": "MIT", - "optional": true + "license": "MIT" }, "node_modules/cors": { "version": "2.8.5", @@ -7049,6 +7380,71 @@ "buffer": "^5.1.0" } }, + "node_modules/crc-32": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz", + "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==", + "license": "Apache-2.0", + "bin": { + "crc32": "bin/crc32.njs" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/crc32-stream": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-6.0.0.tgz", + "integrity": "sha512-piICUB6ei4IlTv1+653yq5+KoqfBYmj9bw6LqXoOneTMDXk5nM1qt12mFW1caG3LlJXEKW1Bp0WggEmIfQB34g==", + "license": "MIT", + "dependencies": { + "crc-32": "^1.2.0", + "readable-stream": "^4.0.0" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/crc32-stream/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/crc32-stream/node_modules/readable-stream": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz", + "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==", + "license": "MIT", + "dependencies": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, "node_modules/cross-env": { "version": "10.1.0", "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-10.1.0.tgz", @@ -7071,7 +7467,6 @@ "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "dev": true, "license": "MIT", "dependencies": { "path-key": "^3.1.0", @@ -7086,7 +7481,6 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -7096,7 +7490,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, "license": "MIT", "dependencies": { "shebang-regex": "^3.0.0" @@ -7109,7 +7502,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -7119,7 +7511,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, "license": "ISC", "dependencies": { "isexe": "^2.0.0" @@ -7904,7 +8295,6 @@ "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", - "dev": true, "license": "MIT" }, "node_modules/editorconfig": { @@ -8198,7 +8588,6 @@ "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, "license": "MIT" }, "node_modules/encodeurl": { @@ -9139,16 +9528,33 @@ "node": ">= 0.6" } }, + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/events": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.8.x" } }, + "node_modules/events-universal": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/events-universal/-/events-universal-1.0.1.tgz", + "integrity": "sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==", + "license": "Apache-2.0", + "dependencies": { + "bare-events": "^2.7.0" + } + }, "node_modules/expect-type": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", @@ -9282,6 +9688,12 @@ "devOptional": true, "license": "MIT" }, + "node_modules/fast-fifo": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", + "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==", + "license": "MIT" + }, "node_modules/fast-glob": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", @@ -9546,7 +9958,6 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz", "integrity": "sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==", - "dev": true, "license": "ISC", "dependencies": { "cross-spawn": "^7.0.0", @@ -9563,7 +9974,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "dev": true, "license": "ISC", "engines": { "node": ">=14" @@ -10021,7 +10431,6 @@ "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "dev": true, "license": "ISC" }, "node_modules/graphemer": { @@ -10312,7 +10721,6 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "dev": true, "funding": [ { "type": "github", @@ -10327,8 +10735,7 @@ "url": "https://feross.org/support" } ], - "license": "BSD-3-Clause", - "optional": true + "license": "BSD-3-Clause" }, "node_modules/ignore": { "version": "5.3.1", @@ -10405,7 +10812,6 @@ "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "devOptional": true, "license": "ISC" }, "node_modules/ini": { @@ -10641,7 +11047,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -10791,6 +11196,18 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-string": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", @@ -10916,7 +11333,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true, "license": "ISC" }, "node_modules/istanbul-lib-coverage": { @@ -11025,13 +11441,6 @@ "node": ">=10" } }, - "node_modules/jake/node_modules/async": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", - "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", - "dev": true, - "license": "MIT" - }, "node_modules/jest-worker": { "version": "27.5.1", "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", @@ -11385,6 +11794,54 @@ "dev": true, "license": "MIT" }, + "node_modules/lazystream": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.1.tgz", + "integrity": "sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==", + "license": "MIT", + "dependencies": { + "readable-stream": "^2.0.5" + }, + "engines": { + "node": ">= 0.6.3" + } + }, + "node_modules/lazystream/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "license": "MIT" + }, + "node_modules/lazystream/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/lazystream/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/lazystream/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, "node_modules/lerc": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/lerc/-/lerc-3.0.0.tgz", @@ -11758,7 +12215,6 @@ "version": "7.1.3", "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", - "dev": true, "license": "BlueOak-1.0.0", "engines": { "node": ">=16 || 14 >=14.17" @@ -11999,7 +12455,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -12229,7 +12684,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", - "dev": true, "license": "BlueOak-1.0.0" }, "node_modules/pako": { @@ -12317,7 +12771,6 @@ "version": "1.11.1", "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", - "dev": true, "license": "BlueOak-1.0.0", "dependencies": { "lru-cache": "^10.2.0", @@ -12334,7 +12787,6 @@ "version": "10.2.2", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.2.tgz", "integrity": "sha512-9hp3Vp2/hFQUiIwKo8XCeFVnrg8Pk3TYNPIR7tJADKi5YfcF7vEaK7avFHTlSy3kOKYaJQaalfEo6YuXdceBOQ==", - "dev": true, "license": "ISC", "engines": { "node": "14 || >=16.14" @@ -12560,6 +13012,21 @@ "node": "^20.17.0 || >=22.9.0" } }, + "node_modules/process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "license": "MIT", + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "license": "MIT" + }, "node_modules/progress": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", @@ -12809,6 +13276,27 @@ "node": ">= 6" } }, + "node_modules/readdir-glob": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/readdir-glob/-/readdir-glob-1.1.3.tgz", + "integrity": "sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA==", + "license": "Apache-2.0", + "dependencies": { + "minimatch": "^5.1.0" + } + }, + "node_modules/readdir-glob/node_modules/minimatch": { + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz", + "integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", @@ -13151,7 +13639,6 @@ "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true, "funding": [ { "type": "github", @@ -13655,11 +14142,21 @@ "dev": true, "license": "MIT" }, + "node_modules/streamx": { + "version": "2.28.0", + "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.28.0.tgz", + "integrity": "sha512-1Yowhzjf0ivGMrTIkY9hav5TxobO9qIVqUE41fiCGMGgc3CLlf4MY+9AHmZqBWgDTue0fY9zWjYFVyf6Diuobw==", + "license": "MIT", + "dependencies": { + "events-universal": "^1.0.0", + "fast-fifo": "^1.3.2", + "text-decoder": "^1.1.0" + } + }, "node_modules/string_decoder": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "dev": true, "license": "MIT", "dependencies": { "safe-buffer": "~5.2.0" @@ -13669,7 +14166,6 @@ "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", @@ -13685,7 +14181,6 @@ "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", @@ -13779,7 +14274,6 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" @@ -13793,7 +14287,6 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" @@ -13922,6 +14415,18 @@ "node": ">=18" } }, + "node_modules/tar-stream": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.2.0.tgz", + "integrity": "sha512-ojzvCvVaNp6aOTFmG7jaRD0meowIAuPc3cMMhSgKiVWws1GyHbGd/xvnyuRKcKlMpt3qvxx6r0hreCNITP9hIg==", + "license": "MIT", + "dependencies": { + "b4a": "^1.6.4", + "bare-fs": "^4.5.5", + "fast-fifo": "^1.2.0", + "streamx": "^2.15.0" + } + }, "node_modules/tar/node_modules/yallist": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", @@ -13932,6 +14437,15 @@ "node": ">=18" } }, + "node_modules/teex": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/teex/-/teex-1.0.1.tgz", + "integrity": "sha512-eYE6iEI62Ni1H8oIa7KlDU6uQBtqr4Eajni3wX7rpfXD8ysFx8z0+dri+KWEPWpBsxXfxu58x/0jvTVT1ekOSg==", + "license": "MIT", + "dependencies": { + "streamx": "^2.12.5" + } + }, "node_modules/temp-file": { "version": "3.4.0", "resolved": "https://registry.npmjs.org/temp-file/-/temp-file-3.4.0.tgz", @@ -14068,6 +14582,15 @@ "url": "https://opencollective.com/webpack" } }, + "node_modules/text-decoder": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.7.tgz", + "integrity": "sha512-vlLytXkeP4xvEq2otHeJfSQIRyWxo/oZGEbXrtEEF9Hnmrdly59sUbzZ/QgyWuLYHctCHxFF4tRQZNQ9k60ExQ==", + "license": "Apache-2.0", + "dependencies": { + "b4a": "^1.6.4" + } + }, "node_modules/text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", @@ -14643,7 +15166,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "dev": true, "license": "MIT" }, "node_modules/utils-merge": { @@ -15567,7 +16089,6 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, "license": "MIT", "dependencies": { "ansi-styles": "^4.0.0", @@ -15752,6 +16273,60 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/zip-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-6.0.1.tgz", + "integrity": "sha512-zK7YHHz4ZXpW89AHXUPbQVGKI7uvkd3hzusTdotCg1UxyaVtg0zFJSTfW/Dq5f7OBBVnq6cZIaC8Ti4hb6dtCA==", + "license": "MIT", + "dependencies": { + "archiver-utils": "^5.0.0", + "compress-commons": "^6.0.2", + "readable-stream": "^4.0.0" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/zip-stream/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/zip-stream/node_modules/readable-stream": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz", + "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==", + "license": "MIT", + "dependencies": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, "node_modules/zstddec": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/zstddec/-/zstddec-0.2.0.tgz", diff --git a/client/package.json b/client/package.json index 580e19025..602997c67 100644 --- a/client/package.json +++ b/client/package.json @@ -36,6 +36,7 @@ "@mdi/font": "^6.2.95", "@sentry/browser": "^5.24.2", "@sentry/integrations": "^5.24.2", + "archiver": "^7.0.1", "axios": "^1.17.0", "csv-stringify": "^5.6.0", "d3": "^5.12.0", diff --git a/client/platform/desktop/backend/ipcService.ts b/client/platform/desktop/backend/ipcService.ts index 094ddbfba..91a5664d1 100644 --- a/client/platform/desktop/backend/ipcService.ts +++ b/client/platform/desktop/backend/ipcService.ts @@ -9,6 +9,7 @@ import { MultiCamImportArgs } from 'dive-common/apispec'; import type { Pipe } from 'dive-common/apispec'; import { DesktopJobUpdate, RunPipeline, RunTraining, Settings, ExportDatasetArgs, + ExportMulticamEverythingArgs, DesktopMediaImportResponse, ExportTrainedPipeline, ConversionArgs, @@ -126,6 +127,11 @@ export default function register() { return ret; }); + ipcMain.handle('export-multicam-everything', async (_, args: ExportMulticamEverythingArgs) => { + const ret = await common.exportMulticamEverything(settings.get(), args); + return ret; + }); + ipcMain.handle('autodiscover-data', async () => { const ret = await common.autodiscoverData(settings.get()); return ret; diff --git a/client/platform/desktop/backend/native/common.ts b/client/platform/desktop/backend/native/common.ts index de7175d55..6d4722cc6 100644 --- a/client/platform/desktop/backend/native/common.ts +++ b/client/platform/desktop/backend/native/common.ts @@ -3,8 +3,11 @@ */ import npath from 'path'; +import os from 'os'; import fs from 'fs-extra'; import { spawn } from 'child_process'; +import { createWriteStream } from 'fs'; +import archiver from 'archiver'; import { shell } from 'electron'; import mime from 'mime-types'; import moment from 'moment'; @@ -41,11 +44,12 @@ import { websafeImageTypes, websafeVideoTypes, otherImageTypes, otherVideoTypes, fileVideoTypes, MultiType, JsonMetaRegEx, largeImageDesktopTypes, } from 'dive-common/constants'; +import { orderedMultiCamCameraNames } from 'dive-common/multicamDisplay'; import { pickStereoCalibrationFileName } from 'dive-common/stereoParentFolder'; import { JsonMeta, Settings, JsonMetaCurrentVersion, DesktopMetadata, RunTraining, ExportDatasetArgs, DesktopMediaImportResponse, - ExportConfigurationArgs, JobsFolderName, JobsOutputFolderName, ProjectsFolderName, + ExportConfigurationArgs, ExportMulticamEverythingArgs, JobsFolderName, JobsOutputFolderName, ProjectsFolderName, PipelinesFolderName, ConversionArgs, JobType, LastCalibrationBaseName, } from 'platform/desktop/constants'; @@ -1585,6 +1589,131 @@ async function openPathInFileManager(targetPath: string): Promise { return shell.openPath(resolved); } +function buildExportMetaJson(meta: JsonMeta): Record { + const output: Record = { ...meta }; + if (meta.type === 'image-sequence') { + const files = meta.transcodedImageFiles?.length + ? meta.transcodedImageFiles + : meta.originalImageFiles?.map((filePath) => npath.basename(filePath)) ?? []; + output.imageData = files.map((filename) => ({ filename })); + } else if (meta.type === 'video') { + const filename = meta.transcodedVideoFile || meta.originalVideoFile; + if (filename) { + output.video = { filename }; + } + } + return output; +} + +async function writeDatasetExportContents( + settings: Settings, + destDir: string, + datasetId: string, + excludeBelowThreshold: boolean, + typeFilter: Set, +): Promise { + const projectDirInfo = await getValidatedProjectDir(settings, datasetId); + const meta = await loadJsonMetadata(projectDirInfo.metaFileAbsPath); + const data = await loadAnnotationFile(projectDirInfo.trackFileAbsPath); + const serializeOptions = { + excludeBelowThreshold, + header: true, + }; + + await fs.ensureDir(destDir); + await fs.writeJSON(npath.join(destDir, 'meta.json'), buildExportMetaJson(meta), { spaces: 2 }); + await dive.serializeFile( + npath.join(destDir, 'annotations.dive.json'), + data, + meta, + typeFilter, + serializeOptions, + ); + await viameSerializers.serializeFile( + npath.join(destDir, 'annotations.viame.csv'), + data, + meta, + typeFilter, + serializeOptions, + ); +} + +async function zipDirectory(sourceDir: string, destZipPath: string): Promise { + await new Promise((resolve, reject) => { + const output = createWriteStream(destZipPath); + const archive = archiver('zip', { zlib: { level: 9 } }); + output.on('close', () => resolve()); + output.on('error', reject); + archive.on('error', reject); + archive.pipe(output); + archive.directory(sourceDir, false); + archive.finalize(); + }); +} + +async function exportMulticamEverything( + settings: Settings, + args: ExportMulticamEverythingArgs, +): Promise { + const parentId = args.id.split('/')[0]; + const parentDirInfo = await getValidatedProjectDir(settings, parentId); + const parentMeta = await loadJsonMetadata(parentDirInfo.metaFileAbsPath); + if (parentMeta.type !== MultiType || !parentMeta.multiCam) { + throw new Error('Everything export is only available for multi-camera datasets.'); + } + + const cameraNames = orderedMultiCamCameraNames({ + cameras: parentMeta.multiCam.cameras, + defaultDisplay: parentMeta.multiCam.defaultDisplay, + }); + if (!cameraNames.length) { + throw new Error('Multi-camera dataset does not list any cameras.'); + } + + const tempDir = await fs.mkdtemp(npath.join(os.tmpdir(), 'dive-export-')); + try { + const datasetDir = npath.join(tempDir, parentMeta.name); + await fs.ensureDir(datasetDir); + await fs.writeJSON( + npath.join(datasetDir, 'multiCam.json'), + parentMeta.multiCam, + { spaces: 2 }, + ); + await writeDatasetExportContents( + settings, + datasetDir, + parentId, + args.exclude, + args.typeFilter, + ); + + const calibrationPath = parentMeta.multiCam.calibration + ?? await findParentFolderCalibrationFile(parentDirInfo.basePath); + if (calibrationPath && await fs.pathExists(calibrationPath)) { + const calibrationName = parentMeta.multiCam.calibrationOriginalName + ?? npath.basename(calibrationPath); + await fs.copy(calibrationPath, npath.join(datasetDir, calibrationName)); + } + + for (let i = 0; i < cameraNames.length; i += 1) { + const cameraName = cameraNames[i]; + // eslint-disable-next-line no-await-in-loop + await writeDatasetExportContents( + settings, + npath.join(datasetDir, cameraName), + `${parentId}/${cameraName}`, + args.exclude, + args.typeFilter, + ); + } + + await zipDirectory(tempDir, args.path); + } finally { + await fs.remove(tempDir); + } + return args.path; +} + async function exportDataset(settings: Settings, args: ExportDatasetArgs) { const projectDirInfo = await getValidatedProjectDir(settings, args.id); const meta = await loadJsonMetadata(projectDirInfo.metaFileAbsPath); @@ -1887,6 +2016,7 @@ export { checkDataset, exportConfiguration, exportDataset, + exportMulticamEverything, finalizeMediaImport, getPipelineList, deleteTrainedPipeline, diff --git a/client/platform/desktop/constants.ts b/client/platform/desktop/constants.ts index e5b53d6dc..5b24868ff 100644 --- a/client/platform/desktop/constants.ts +++ b/client/platform/desktop/constants.ts @@ -283,3 +283,10 @@ export interface ExportConfigurationArgs { id: string; path: string; } + +export interface ExportMulticamEverythingArgs { + id: string; + exclude: boolean; + path: string; + typeFilter: Set; +} diff --git a/client/platform/desktop/frontend/api.ts b/client/platform/desktop/frontend/api.ts index 6cde51edc..7d6acd865 100644 --- a/client/platform/desktop/frontend/api.ts +++ b/client/platform/desktop/frontend/api.ts @@ -17,6 +17,7 @@ import { import { DesktopMetadata, NvidiaSmiReply, RunPipeline, RunTraining, ExportTrainedPipeline, ExportDatasetArgs, ExportConfigurationArgs, + ExportMulticamEverythingArgs, DesktopMediaImportResponse, ConversionArgs, JobType, DesktopJob, } from 'platform/desktop/constants'; @@ -269,6 +270,32 @@ async function exportConfiguration(id: string): Promise { return ''; } +async function exportMulticamEverything( + id: string, + exclude: boolean, + typeFilter: readonly string[], +): Promise { + const parentId = id.split('/')[0]; + const location = await window.diveDesktop.showSaveDialog({ + title: 'Export Multicamera Dataset', + defaultPath: joinPath( + await window.diveDesktop.getAppPath('home'), + `${parentId}.zip`, + ), + filters: [{ name: 'Zip archive', extensions: ['zip'] }], + }); + if (!location.canceled && location.filePath) { + const args: ExportMulticamEverythingArgs = { + id: parentId, + exclude, + path: location.filePath, + typeFilter: new Set(typeFilter), + }; + return window.diveDesktop.invoke('export-multicam-everything', args); + } + return ''; +} + async function cancelJob(job: DesktopJob): Promise { return window.diveDesktop.invoke('cancel-job', job); } @@ -597,6 +624,7 @@ export { /* Nonstandard APIs */ exportDataset, exportConfiguration, + exportMulticamEverything, finalizeImport, convert, importMedia, diff --git a/client/platform/desktop/frontend/components/Export.vue b/client/platform/desktop/frontend/components/Export.vue index cfeacdfaf..561f216a2 100644 --- a/client/platform/desktop/frontend/components/Export.vue +++ b/client/platform/desktop/frontend/components/Export.vue @@ -3,13 +3,18 @@ import { defineComponent, reactive, computed, toRef, watch, ref, } from 'vue'; -import { usePendingSaveCount, useHandler, useTrackFilters } from 'vue-media-annotator/provides'; +import { + usePendingSaveCount, useHandler, useTrackFilters, useSelectedCamera, +} from 'vue-media-annotator/provides'; import AutosavePrompt from 'dive-common/components/AutosavePrompt.vue'; +import { MultiType } from 'dive-common/constants'; import { - loadMetadata, exportDataset, exportConfiguration, exportCalibrationFile, + loadMetadata, exportDataset, exportConfiguration, exportCalibrationFile, exportMulticamEverything, } from 'platform/desktop/frontend/api'; import type { JsonMeta } from 'platform/desktop/constants'; +type ExportType = 'dataset' | 'configuration' | 'trackJSON' | 'coco' | 'everything'; + export default defineComponent({ name: 'Export', @@ -41,14 +46,18 @@ export default defineComponent({ outPath: '', }); const savePrompt = ref(false); + const pendingExportType = ref('dataset'); const pendingSaveCount = usePendingSaveCount(); const { save } = useHandler(); const { checkedTypes } = useTrackFilters(); + const selectedCamera = useSelectedCamera(); + + const parentId = computed(() => props.id.split('/')[0]); watch(toRef(data, 'menuOpen'), async (newval) => { if (newval) { - data.meta = await loadMetadata(props.id); + data.meta = await loadMetadata(parentId.value); } else { data.err = null; data.outPath = ''; @@ -60,6 +69,16 @@ export default defineComponent({ ? Object.keys(data.meta.confidenceFilters || {}) : [])); + const isMulticamDataset = computed(() => data.meta?.type === MultiType); + + const activeCameraName = computed(() => { + if (selectedCamera.value) { + return selectedCamera.value; + } + const parts = props.id.split('/'); + return parts.length > 1 ? parts[1] : null; + }); + const calibrationFile = computed(() => data.meta?.multiCam?.calibration ?? null); const cameraFileSupported = computed( () => data.meta?.subType === 'stereo' && !!calibrationFile.value, @@ -75,7 +94,7 @@ export default defineComponent({ if (location.canceled || !location.filePath) return; try { data.err = null; - await exportCalibrationFile(props.id, location.filePath); + await exportCalibrationFile(parentId.value, location.filePath); data.outPath = location.filePath; } catch (err) { data.err = err; @@ -83,29 +102,35 @@ export default defineComponent({ } } - async function doExport({ type, forceSave = false }: { type: 'dataset' | 'configuration' | 'trackJSON' | 'coco'; forceSave?: boolean}) { + async function doExport({ type, forceSave = false }: { type: ExportType; forceSave?: boolean}) { if (pendingSaveCount.value > 0 && forceSave) { await save(); savePrompt.value = false; } else if (pendingSaveCount.value > 0) { + pendingExportType.value = type; savePrompt.value = true; return; } try { + const typeFilter = data.excludeUncheckedTypes ? checkedTypes.value : []; if (type === 'dataset') { - const typeFilter = data.excludeUncheckedTypes ? checkedTypes.value : []; data.err = null; data.outPath = await exportDataset(props.id, data.excludeBelowThreshold, typeFilter); } else if (type === 'trackJSON') { - const typeFilter = data.excludeUncheckedTypes ? checkedTypes.value : []; data.err = null; data.outPath = await exportDataset(props.id, data.excludeBelowThreshold, typeFilter, 'json'); } else if (type === 'coco') { - const typeFilter = data.excludeUncheckedTypes ? checkedTypes.value : []; data.err = null; data.outPath = await exportDataset(props.id, data.excludeBelowThreshold, typeFilter, 'coco'); } else if (type === 'configuration') { data.outPath = await exportConfiguration(props.id); + } else if (type === 'everything') { + data.err = null; + data.outPath = await exportMulticamEverything( + parentId.value, + data.excludeBelowThreshold, + typeFilter, + ); } } catch (err) { data.err = err; @@ -120,8 +145,11 @@ export default defineComponent({ cameraFileSupported, calibrationFile, savePrompt, + pendingExportType, thresholds, checkedTypes, + isMulticamDataset, + activeCameraName, }; }, }); @@ -197,7 +225,7 @@ export default defineComponent({ Export succeeded. -
Export to Annotations
+ +
+ Annotations export from the active camera +
+
+ + mdi-camera + + {{ activeCameraName }} +
+
+
+ Export to Annotations +
+ + + From 8b08c569bae2cc0166ed68112e67f8e97a4741e9 Mon Sep 17 00:00:00 2001 From: Bryon Lewis Date: Sat, 27 Jun 2026 16:11:50 -0400 Subject: [PATCH 22/25] use pipelines queue for VIAME access for calibration conversion --- server/dive_server/crud_dataset.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/server/dive_server/crud_dataset.py b/server/dive_server/crud_dataset.py index a3790fd51..51890f5d2 100644 --- a/server/dive_server/crud_dataset.py +++ b/server/dive_server/crud_dataset.py @@ -1247,11 +1247,9 @@ def enqueue_calibration_conversion( JSON camera-rig format (for display). The pipeline can consume the original file directly, so this is best-effort and never blocks the request. """ - queue = ( - f'{user["login"]}@private' - if user.get(constants.UserPrivateQueueEnabledMarker, False) - else 'celery' - ) + job_is_private = user.get(constants.UserPrivateQueueEnabledMarker, False) + # convert_cam_format.py lives on pipeline workers (VIAME image), not celery workers. + queue = f'{user["login"]}@private' if job_is_private else 'pipelines' token = Token().createToken(user=user, days=1) tasks.convert_calibration.apply_async( queue=queue, @@ -1259,7 +1257,7 @@ def enqueue_calibration_conversion( itemId=item_id, girder_job_title=f"Converting calibration {item_name}", girder_client_token=str(token["_id"]), - girder_job_type="private", + girder_job_type="private" if job_is_private else "pipelines", ), ) From 547f790cce43ce826bf0c5d19d15ea8bbb4a3ce4 Mon Sep 17 00:00:00 2001 From: Bryon Lewis Date: Sat, 27 Jun 2026 16:40:27 -0400 Subject: [PATCH 23/25] swap to creating a jsonCalibration file as another item --- client/dive-common/apispec.ts | 15 +- .../components/CalibrationDialog.vue | 28 +- .../components/CalibrationMenu.vue | 13 +- client/dive-common/constants.ts | 5 +- .../web-girder/api/dataset.service.ts | 21 +- client/platform/web-girder/api/index.ts | 11 +- server/dive_server/crud_dataset.py | 438 +++++++++++++----- server/dive_tasks/tasks.py | 59 ++- server/dive_utils/constants.py | 6 +- server/dive_utils/models.py | 2 + server/dive_utils/types.py | 9 +- server/tests/test_create_multicam.py | 25 +- server/tests/test_multicam_export_clone.py | 23 +- 13 files changed, 485 insertions(+), 170 deletions(-) diff --git a/client/dive-common/apispec.ts b/client/dive-common/apispec.ts index cd4b21b51..956215343 100644 --- a/client/dive-common/apispec.ts +++ b/client/dive-common/apispec.ts @@ -226,15 +226,18 @@ interface DatasetStereoCalibration { } interface DatasetCalibrationResult { - /** Parsed calibration parameters. May be absent when a calibration file is - * present but could not be parsed (e.g. an unconverted non-JSON format). */ + /** Parsed calibration parameters from the JSON camera-rig file. */ calibration?: DatasetStereoCalibration + /** Source calibration item (calibrationFile). Used by pipelines and download. */ itemId?: string; - /** Name of the calibration file currently associated with the dataset. */ - path?: string; - /** User's original calibration filename (may differ from `path` when the file - * was converted/normalized on import). */ + /** JSON camera-rig item (jsonCalibrationFile). Used for display parameters. */ + jsonItemId?: string; + /** Source calibration filename. */ originalName?: string; + /** JSON camera-rig filename. */ + jsonPath?: string; + /** Alias for jsonPath (legacy). */ + path?: string; } interface Api { diff --git a/client/dive-common/components/CalibrationDialog.vue b/client/dive-common/components/CalibrationDialog.vue index f13470856..15dd97e66 100644 --- a/client/dive-common/components/CalibrationDialog.vue +++ b/client/dive-common/components/CalibrationDialog.vue @@ -10,7 +10,8 @@ export default defineComponent({ props: { value: { type: Boolean, required: true }, calibration: { type: Object as PropType, default: undefined }, - fileName: { type: String as PropType, default: undefined }, + sourceFileName: { type: String as PropType, default: undefined }, + jsonFileName: { type: String as PropType, default: undefined }, showDownload: { type: Boolean, default: true }, showDelete: { type: Boolean, default: true }, }, @@ -57,9 +58,15 @@ export default defineComponent({ -
- File: - {{ fileName }} +
+
+ Source file: + {{ sourceFileName }} +
+
+ JSON file: + {{ jsonFileName }} +
@@ -162,6 +169,15 @@ export default defineComponent({
+
+
+ Calibration Not Ready +
+
+ A calibration file is linked, but its JSON parameters are not available yet. + Conversion to the JSON camera-rig format may still be running. +
+
No Calibration Loaded @@ -182,13 +198,13 @@ export default defineComponent({ Cancel - + mdi-delete-outline Delete - + mdi-download diff --git a/client/dive-common/components/CalibrationMenu.vue b/client/dive-common/components/CalibrationMenu.vue index 20cc7863b..d21878bc5 100644 --- a/client/dive-common/components/CalibrationMenu.vue +++ b/client/dive-common/components/CalibrationMenu.vue @@ -47,8 +47,14 @@ export default defineComponent({ const hasCalibration = computed(() => !!calibrationResult.value); + const sourceFileName = computed( + () => calibrationResult.value?.originalName, + ); + const jsonFileName = computed( + () => calibrationResult.value?.jsonPath ?? calibrationResult.value?.path, + ); const calibrationFileName = computed( - () => calibrationResult.value?.originalName ?? calibrationResult.value?.path, + () => sourceFileName.value ?? jsonFileName.value, ); const openCalibrationDialog = () => { @@ -71,6 +77,8 @@ export default defineComponent({ showCalibrationDialog, calibrationResult, hasCalibration, + sourceFileName, + jsonFileName, calibrationFileName, openCalibrationDialog, canDownload: !!downloadCalibration, @@ -117,7 +125,8 @@ export default defineComponent({