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
38 changes: 36 additions & 2 deletions client/dive-common/apispec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,24 @@ interface FrameImage {
timestamp?: number;
}

/** One metadata attachment loaded by a platform implementation. */
interface FrameMetadataAttachmentText {
/** Preserved original name, falling back to the resolved item/path basename. */
name: string;
/** Present for a readable TXT/CSV attachment. */
text?: string;
/** Present when the selected locator could not be read. */
error?: string;
}

/** Complete normalized attachment response for one dataset. */
interface FrameMetadataSourcesResponse {
/** Single-camera dataset attachment or multicamera parent attachment. */
shared?: FrameMetadataAttachmentText;
/** Camera-local attachments only, keyed by camera name. */
cameras: Record<string, FrameMetadataAttachmentText>;
}

export interface MultiCamImportFolderArgs {
datasetName?: string; // Girder parent folder name (required on web)
defaultDisplay: string; // In multicam the default camera to display
Expand All @@ -163,6 +181,8 @@ export interface MultiCamImportFolderArgs {
* dataset's saved camera registration.
*/
transformFile?: string;
/** Optional camera-local metadata attachment. */
metadataFile?: string;
/** Per-camera media type when cameras differ (e.g. EO JPG + IR TIFF on web). */
type?: 'image-sequence' | 'video' | 'large-image';
/**
Expand Down Expand Up @@ -208,6 +228,10 @@ interface MediaImportResponse {
globPattern: string;
mediaConvertList: string[];
}

/** User-editable datasetInfo stored on the dataset's backing metadata object. */
type DatasetInfoFields = Record<string, unknown>;

/**
* The parts of metadata a user should be able to modify.
*/
Expand All @@ -219,7 +243,7 @@ interface DatasetMetaMutable {
imageEnhancements?: ImageEnhancements;
attributes?: Readonly<Record<string, Attribute>>;
attributeTrackFilters?: Readonly<Record<string, AttributeTrackFilter>>;
datasetInfo?: Record<string, unknown>;
datasetInfo?: DatasetInfoFields;
cameraHomographies?: CameraHomographies;
cameraCorrespondences?: CameraCorrespondences;
cameraTransformTypes?: CameraTransformTypes;
Expand Down Expand Up @@ -331,6 +355,7 @@ interface Api {

loadMetadata(datasetId: string): Promise<DatasetMeta>;
loadDetections(datasetId: string, revision?: number, set?: string): Promise<AnnotationSchemaList>;
loadFrameMetadata(datasetId: string): Promise<FrameMetadataSourcesResponse>;

saveDetections(datasetId: string, args: SaveDetectionsArgs): Promise<unknown>;
saveMetadata(datasetId: string, metadata: DatasetMetaMutable): Promise<unknown>;
Expand All @@ -339,7 +364,13 @@ interface Api {
args: SaveAttributeTrackFilterArgs): Promise<unknown>;
// Non-Endpoint shared functions
openFromDisk(datasetType: DatasetType | 'bulk' | 'calibration' | 'annotation' | 'text' | 'zip' | 'transform' | 'metadata', directory?: boolean):
Promise<{canceled?: boolean; filePaths: string[]; fileList?: File[]; root?: string}>;
Promise<{
canceled?: boolean;
filePaths: string[];
fileList?: File[];
root?: string;
selectionId?: string;
}>;
/** Desktop: immediate child directory names under a parent folder (multicam subfolder import). */
listImmediateSubfolders?(parentPath: string): Promise<string[]>;
/** Desktop: subfolders or root-level video files under a parent folder (multicam import). */
Expand Down Expand Up @@ -591,6 +622,7 @@ export {
AnnotationSchema,
Api,
DatasetMeta,
DatasetInfoFields,
DatasetMetaMutable,
DatasetMetaMutableKeys,
MulticamSharedMutableKeys,
Expand All @@ -602,6 +634,8 @@ export {
SubType,
PipelineParamType,
FrameImage,
FrameMetadataAttachmentText,
FrameMetadataSourcesResponse,
MultiTrackRecord,
MultiGroupRecord,
Pipe,
Expand Down
17 changes: 17 additions & 0 deletions client/dive-common/components/ImportAnnotations.vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
import { useApi } from 'dive-common/apispec';
import { usePrompt } from 'dive-common/vue-utilities/prompt-service';
import { clientSettings, isStereoInteractiveModeEnabled } from 'dive-common/store/settings';
import isFrameMetadataSourceName from 'dive-common/frameMetadata/naming';
import clearLengthAttributes from 'dive-common/utils/clearLengthAttributes';
import warpAnnotationsAcrossCameras from 'dive-common/utils/warpAnnotationsAcrossCameras';
import { cloneDeep } from 'lodash';
Expand Down Expand Up @@ -188,6 +189,22 @@ export default defineComponent({
if (!ret.canceled) {
menuOpen.value = false;
const path = ret.filePaths[0];
// A reserved-name sidecar is frame metadata, not annotations. Reject before
// the import call: web uploads the file and postprocesses afterwards, so a
// later check would leave it sitting in the dataset folder.
const name = ret.fileList?.[0]?.name ?? path;
if (isFrameMetadataSourceName(name)) {
await prompt({
title: 'Not an Annotation File',
text: [
`"${name.replace(/^.*[\\/]/, '')}" is a frame metadata file, not annotations.`,
'Attach frame metadata with the "Metadata File (Optional)" field when the dataset is created.',
'Attached that way, a frame metadata file may have any name.',
],
positiveButton: 'OK',
});
return;
}
let importFile: boolean | string[] = false;
processing.value = true;
const set = currentSet.value === 'default' ? undefined : currentSet.value;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<script lang="ts">
import { defineComponent } from 'vue';

export default defineComponent({
name: 'ImportMultiCamChooseMetadata',
props: {
metadataFile: {
type: String,
default: '',
},
},
});
</script>

<template>
<v-row
class="align-center"
no-gutters
>
<v-text-field
label="Camera Metadata File (Optional)"
placeholder="Not selected"
hint="A JSON, TXT, or CSV attachment for this camera. Pipelines that request it receive it;
rows in a TXT/CSV that match this camera's media are shown per frame."
persistent-hint
readonly
outlined
dense
:value="metadataFile"
class="mr-3"
/>
<v-btn
v-if="metadataFile"
icon
class="mr-2"
aria-label="Clear camera metadata file"
@click="$emit('clear')"
>
<v-icon>mdi-close</v-icon>
</v-btn>
<v-btn
color="primary"
@click="$emit('open')"
>
Choose metadata
<v-icon class="ml-2">
mdi-file-cog
</v-icon>
</v-btn>
</v-row>
</template>
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<!--
Optional per-dataset metadata file picker (e.g. sea-lion flight log). Requires
`ctx`; uses metadataFile and open from ctx. Unlike calibration this is not gated
`ctx`; uses metadata and open from ctx. Unlike calibration this is not gated
to stereo datasets.
-->
<script lang="ts">
Expand All @@ -14,12 +14,12 @@ export default defineComponent({
},
setup(props) {
const {
metadataFile,
metadata,
open,
clearMetadataFile,
} = props.ctx;
return {
metadataFile,
metadata,
open,
clearMetadataFile,
};
Expand All @@ -39,12 +39,12 @@ export default defineComponent({
outlined
dense
hide-details
:value="metadataFile"
:value="metadata && metadata.name"
hint="A .json, .txt, or .csv file passed to pipelines that request it."
class="mr-3"
/>
<v-btn
v-if="metadataFile"
v-if="metadata"
icon
class="mr-2"
aria-label="Clear metadata file"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import ImportMultiCamCameraGroup from './ImportMultiCamCameraGroup.vue';
import ImportMultiCamChooseSource from './ImportMultiCamChooseSource.vue';
import ImportMultiCamChooseAnnotation from './ImportMultiCamChooseAnnotation.vue';
import ImportMultiCamChooseTransform from './ImportMultiCamChooseTransform.vue';
import ImportMultiCamChooseMetadata from './ImportMultiCamChooseMetadata.vue';
import ImportMultiCamAddType from './ImportMultiCamAddType.vue';
import ImportMultiCamCameraOrderControls from './ImportMultiCamCameraOrderControls.vue';
import { importMultiCamContextProp } from './importMultiCamContext';
Expand All @@ -20,6 +21,7 @@ export default defineComponent({
ImportMultiCamChooseSource,
ImportMultiCamChooseAnnotation,
ImportMultiCamChooseTransform,
ImportMultiCamChooseMetadata,
ImportMultiCamAddType,
ImportMultiCamCameraOrderControls,
},
Expand Down Expand Up @@ -48,6 +50,8 @@ export default defineComponent({
showTransformFileField: props.ctx.showTransformFileField,
openTransformFile: props.ctx.openTransformFile,
clearTransformFile: props.ctx.clearTransformFile,
openCameraMetadataFile: props.ctx.openCameraMetadataFile,
clearCameraMetadataFile: props.ctx.clearCameraMetadataFile,
};
},
});
Expand Down Expand Up @@ -94,6 +98,13 @@ export default defineComponent({
@clear="clearTransformFile(key)"
@open="openTransformFile(key)"
/>
<ImportMultiCamChooseMetadata
v-if="folderList[key].sourcePath"
:metadata-file="folderList[key].metadata && folderList[key].metadata.name"
class="my-3"
@clear="clearCameraMetadataFile(key)"
@open="openCameraMetadataFile(key)"
/>
</ImportMultiCamCameraGroup>
<ImportMultiCamAddType
v-if="!stereo"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { ImageSequenceType } from 'dive-common/constants';
import ImportMultiCamCameraGroup from './ImportMultiCamCameraGroup.vue';
import ImportMultiCamChooseSource from './ImportMultiCamChooseSource.vue';
import ImportMultiCamChooseTransform from './ImportMultiCamChooseTransform.vue';
import ImportMultiCamChooseMetadata from './ImportMultiCamChooseMetadata.vue';
import ImportMultiCamCameraOrderControls from './ImportMultiCamCameraOrderControls.vue';
import ImportMultiCamFinalizeStep from './ImportMultiCamFinalizeStep.vue';
import { importMultiCamContextProp } from './importMultiCamContext';
Expand All @@ -19,6 +20,7 @@ export default defineComponent({
ImportMultiCamCameraGroup,
ImportMultiCamChooseSource,
ImportMultiCamChooseTransform,
ImportMultiCamChooseMetadata,
ImportMultiCamCameraOrderControls,
ImportMultiCamFinalizeStep,
},
Expand Down Expand Up @@ -51,6 +53,8 @@ export default defineComponent({
showTransformFileField: props.ctx.showTransformFileField,
openTransformFile: props.ctx.openTransformFile,
clearTransformFile: props.ctx.clearTransformFile,
openCameraMetadataFile: props.ctx.openCameraMetadataFile,
clearCameraMetadataFile: props.ctx.clearCameraMetadataFile,
ImageSequenceType,
};
},
Expand Down Expand Up @@ -150,6 +154,13 @@ export default defineComponent({
@clear="clearTransformFile(key)"
@open="openTransformFile(key)"
/>
<ImportMultiCamChooseMetadata
v-if="folderList[key] && folderList[key].sourcePath"
:metadata-file="folderList[key].metadata && folderList[key].metadata.name"
class="my-3"
@clear="clearCameraMetadataFile(key)"
@open="openCameraMetadataFile(key)"
/>
</ImportMultiCamCameraGroup>
<ImportMultiCamFinalizeStep
v-if="camerasReady"
Expand Down
Loading
Loading