Skip to content
22 changes: 11 additions & 11 deletions client/dive-common/apispec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -222,16 +222,16 @@ interface MultiCamMedia {
}

interface MediaImportResponse {
jsonMeta: {
jsonConfig: {
originalImageFiles: string[];
};
globPattern: string;
mediaConvertList: string[];
}
/**
* The parts of metadata a user should be able to modify.
* The parts of dataset config a user should be able to modify.
*/
interface DatasetMetaMutable {
interface DatasetConfigMutable {
customTypeStyling?: Record<string, CustomStyle>;
customGroupStyling?: Record<string, CustomStyle>;
confidenceFilters?: Record<string, number>;
Expand All @@ -247,7 +247,7 @@ interface DatasetMetaMutable {
cameraRegistrationSource?: RegistrationSource | null;
error?: string;
}
const DatasetMetaMutableKeys = ['attributes', 'confidenceFilters', 'timeFilters', 'imageEnhancements', 'customTypeStyling', 'customGroupStyling', 'attributeTrackFilters', 'datasetInfo', 'cameraHomographies', 'cameraCorrespondences', 'cameraTransformTypes', 'cameraRegistrationSource'];
const DatasetConfigMutableKeys = ['attributes', 'confidenceFilters', 'timeFilters', 'imageEnhancements', 'customTypeStyling', 'customGroupStyling', 'attributeTrackFilters', 'datasetInfo', 'cameraHomographies', 'cameraCorrespondences', 'cameraTransformTypes', 'cameraRegistrationSource'];
/**
* Mutable keys the multicam/stereo viewer loads from the parent dataset.
* Camera-targeted imports sync only these onto the parent — not per-camera
Expand All @@ -264,7 +264,7 @@ const MulticamSharedMutableKeys = [
'datasetInfo',
];

interface DatasetMeta extends DatasetMetaMutable {
interface DatasetConfig extends DatasetConfigMutable {
id: Readonly<string>;
imageData: Readonly<FrameImage[]>;
videoUrl: Readonly<string | undefined>;
Expand Down Expand Up @@ -349,16 +349,16 @@ interface Api {
},
): Promise<unknown>;

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

saveDetections(datasetId: string, args: SaveDetectionsArgs): Promise<unknown>;
saveMetadata(datasetId: string, metadata: DatasetMetaMutable): Promise<unknown>;
saveConfig(datasetId: string, config: DatasetConfigMutable): Promise<unknown>;
saveAttributes(datasetId: string, args: SaveAttributeArgs): Promise<unknown>;
saveAttributeTrackFilters(datasetId: string,
args: SaveAttributeTrackFilterArgs): Promise<unknown>;
// Non-Endpoint shared functions
openFromDisk(datasetType: DatasetType | 'bulk' | 'calibration' | 'annotation' | 'text' | 'zip' | 'transform' | 'metadata', directory?: boolean):
openFromDisk(datasetType: DatasetType | 'bulk' | 'calibration' | 'annotation' | 'config' | 'text' | 'zip' | 'transform' | 'metadata', directory?: boolean):
Promise<{canceled?: boolean; filePaths: string[]; fileList?: File[]; root?: string}>;
/** Desktop: immediate child directory names under a parent folder (multicam subfolder import). */
listImmediateSubfolders?(parentPath: string): Promise<string[]>;
Expand Down Expand Up @@ -610,9 +610,9 @@ export {
export {
AnnotationSchema,
Api,
DatasetMeta,
DatasetMetaMutable,
DatasetMetaMutableKeys,
DatasetConfig,
DatasetConfigMutable,
DatasetConfigMutableKeys,
MulticamSharedMutableKeys,
DatasetType,
DiveParam,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export default defineComponent({
const registration = useCameraRegistration();
const datasetId = useDatasetId();
const alignedView = useAlignedView();
const { saveMetadata } = useApi();
const { saveConfig } = useApi();
const { prompt } = usePrompt();

const cameras = computed(() => [...cameraStore.camMap.value.keys()]);
Expand Down Expand Up @@ -357,7 +357,7 @@ export default defineComponent({
}
saving.value = true;
try {
await saveMetadata(datasetId.value, {
await saveConfig(datasetId.value, {
cameraHomographies: registration.homographies.value,
cameraCorrespondences: registration.correspondences.value,
cameraTransformTypes: registration.transformTypes.value,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {
} from 'vue';

export default defineComponent({
name: 'DatasetMetaEditorDialog',
name: 'DatasetConfigEditorDialog',

props: {
value: {
Expand Down
16 changes: 8 additions & 8 deletions client/dive-common/components/DatasetInfo.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,21 @@ import {
computed, defineComponent, ref, watch,
} from 'vue';
import { useDatasetId, useReadOnlyMode } from 'vue-media-annotator/provides';
import { useApi, DatasetMeta } from 'dive-common/apispec';
import { useApi, DatasetConfig } from 'dive-common/apispec';
import { usePrompt } from 'dive-common/vue-utilities/prompt-service';
import DatasetMetaEditorDialog from 'dive-common/components/DatasetMetaEditorDialog.vue';
import DatasetConfigEditorDialog from 'dive-common/components/DatasetConfigEditorDialog.vue';

export default defineComponent({
name: 'DatasetInfo',

components: { DatasetMetaEditorDialog },
components: { DatasetConfigEditorDialog },

setup() {
const datasetId = useDatasetId();
const readOnlyMode = useReadOnlyMode();
const { loadMetadata, saveMetadata } = useApi();
const { loadConfig, saveConfig } = useApi();
const { prompt } = usePrompt();
const meta = ref<DatasetMeta | null>(null);
const meta = ref<DatasetConfig | null>(null);
const customMeta = ref<Record<string, unknown>>({});
const newKey = ref('');
const newValue = ref('');
Expand All @@ -31,7 +31,7 @@ export default defineComponent({
customMeta.value = {};
return;
}
meta.value = await loadMetadata(datasetId.value);
meta.value = await loadConfig(datasetId.value);
customMeta.value = { ...(meta.value.datasetInfo || {}) };
};

Expand Down Expand Up @@ -73,7 +73,7 @@ export default defineComponent({
return;
}
try {
await saveMetadata(datasetId.value, { datasetInfo: { ...customMeta.value } });
await saveConfig(datasetId.value, { datasetInfo: { ...customMeta.value } });
} catch (err) {
const saveErr = err as { response?: { status?: number } };
const status = saveErr.response?.status;
Expand Down Expand Up @@ -300,7 +300,7 @@ export default defineComponent({
</div>
</v-container>

<DatasetMetaEditorDialog
<DatasetConfigEditorDialog
v-model="editorOpen"
:field-name="editorKey"
:field-value="editorValue"
Expand Down
2 changes: 1 addition & 1 deletion client/dive-common/components/ImportAnnotations.vue
Original file line number Diff line number Diff line change
Expand Up @@ -317,7 +317,7 @@ export default defineComponent({
// hydrate() clears it, and the camera list doesn't change on import,
// so nothing else would re-establish the panel's state.
const priorPair = cameraRegistration.activePair.value;
const meta = await api.loadMetadata(parentDatasetId(props.datasetId));
const meta = await api.loadConfig(parentDatasetId(props.datasetId));
cameraRegistration.hydrate(
meta.cameraHomographies,
meta.cameraCorrespondences,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ export default defineComponent({
class="ml-3"
>
"{{ globList[key].glob }}" matches {{ filteredImages[key].length }}
out of {{ pendingImportPayloads.keyword.jsonMeta.originalImageFiles.length }} images
out of {{ pendingImportPayloads.keyword.jsonConfig.originalImageFiles.length }} images
</v-chip>
</v-row>
<ImportMultiCamChooseAnnotation
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -261,7 +261,7 @@ export function useImportMultiCamDialog(
const filtered: Record<string, string[]> = {};
Object.entries(globList.value).forEach(([cameraName, val]) => {
const payload = pendingImportPayloads.value.keyword;
filtered[cameraName] = filterByGlob(val.glob, payload?.jsonMeta.originalImageFiles);
filtered[cameraName] = filterByGlob(val.glob, payload?.jsonConfig.originalImageFiles);
});
return filtered;
});
Expand All @@ -270,7 +270,7 @@ export function useImportMultiCamDialog(
const filtered: Record<string, string[]> = {};
Object.entries(folderList.value).forEach(([cameraName, entry]) => {
const payload = pendingImportPayloads.value[cameraName];
const images = payload?.jsonMeta.originalImageFiles || [];
const images = payload?.jsonConfig.originalImageFiles || [];
filtered[cameraName] = entry.glob ? filterByGlob(entry.glob, images) : images;
});
return filtered;
Expand Down Expand Up @@ -352,7 +352,7 @@ export function useImportMultiCamDialog(
// Not importable as a flat image folder; fall back to subfolder handling
return false;
}
imageNames = sharedPayload.jsonMeta.originalImageFiles || [];
imageNames = sharedPayload.jsonConfig.originalImageFiles || [];
}
const modalities = detectFolderModalities(imageNames);
if (!modalities.length) {
Expand Down
4 changes: 2 additions & 2 deletions client/dive-common/components/TypeThreshold.vue
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,10 @@ export default defineComponent({
setup() {
const trackFilters = useTrackFilters();
const datasetIdRef = useDatasetId();
const { saveMetadata } = useApi();
const { saveConfig } = useApi();

function saveThreshold() {
saveMetadata(datasetIdRef.value, {
saveConfig(datasetIdRef.value, {
confidenceFilters: trackFilters.confidenceFilters.value,
});
}
Expand Down
14 changes: 7 additions & 7 deletions client/dive-common/components/Viewer.vue
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ export default defineComponent({
const saveInProgress = ref(false);
const videoUrl: Ref<Record<string, string>> = ref({});
const {
loadDetections, loadMetadata, saveMetadata, getTiles, getTileURL, getTileHistogram,
loadDetections, loadConfig, saveConfig, getTiles, getTileURL, getTileHistogram,
} = useApi();
const progress = reactive({
// Loaded flag prevents annotator window from populating
Expand Down Expand Up @@ -857,13 +857,13 @@ export default defineComponent({
}

function saveThreshold() {
saveMetadata(datasetId.value, {
saveConfig(datasetId.value, {
confidenceFilters: trackFilters.confidenceFilters.value,
});
}

function saveTimeFilter() {
saveMetadata(datasetId.value, {
saveConfig(datasetId.value, {
timeFilters: trackFilters.timeFilters.value,
});
}
Expand All @@ -879,7 +879,7 @@ export default defineComponent({
function getDebouncedSave(camera: string) {
if (!debouncedSaves[camera]) {
debouncedSaves[camera] = debounce(
() => saveMetadata(
() => saveConfig(
getCameraId(camera),
{ imageEnhancements: imageEnhancementsByCamera.value[camera] },
),
Expand Down Expand Up @@ -1162,7 +1162,7 @@ export default defineComponent({
return;
}
cameraRegistration.maybeFitActivePair();
await saveMetadata(datasetId.value, {
await saveConfig(datasetId.value, {
cameraHomographies: cameraRegistration.homographies.value,
cameraCorrespondences: cameraRegistration.correspondences.value,
cameraTransformTypes: cameraRegistration.transformTypes.value,
Expand Down Expand Up @@ -1388,7 +1388,7 @@ export default defineComponent({
try {
// Close and reset sideBar
context.resetActive();
const meta = await loadMetadata(datasetId.value);
const meta = await loadConfig(datasetId.value);
baseMulticamDatasetId.value = datasetId.value;
if (meta.multiCamMedia) {
/* We're loading a multicamera dataset */
Expand Down Expand Up @@ -1442,7 +1442,7 @@ export default defineComponent({
cameraId = `${baseMulticamDatasetId.value}/${camera}`;
}
// eslint-disable-next-line no-await-in-loop
const subCameraMeta = await loadMetadata(cameraId);
const subCameraMeta = await loadConfig(cameraId);
VueSet(cameraTypesByCamera.value, camera, subCameraMeta.type as DatasetType);
if (multiCamList.value.length <= 1) {
datasetType.value = subCameraMeta.type as DatasetType;
Expand Down
34 changes: 32 additions & 2 deletions client/dive-common/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,14 @@ const websafeImageTypes = [
// 'image/webp',
];

/** Dotted extensions for HTML file-input accept (multi-dot names like a.b.c.png need these). */
const websafeImageFileExtensions = [
'gif',
'jpg',
'jpeg',
'png',
];

const otherImageTypes = [
'image/avif',
'image/tiff',
Expand All @@ -155,6 +163,25 @@ const otherImageTypes = [
'image/x-portable-graymap',
];

const otherImageFileExtensions = [
'avif',
'tif',
'tiff',
'bmp',
'sgi',
'pgm',
];

/** MIME types plus dotted extensions for image-sequence file inputs. */
function getImageSequenceFileAccept(): string {
return [
...websafeImageFileExtensions.map((ext) => `.${ext}`),
...otherImageFileExtensions.map((ext) => `.${ext}`),
...websafeImageTypes,
...otherImageTypes,
].join(',');
}

const inputAnnotationTypes = [
'application/json',
'text/csv',
Expand Down Expand Up @@ -199,7 +226,7 @@ const webExcludedPipelineTerms = ['seagis'];
const multiCamPipelineMarkers = ['2-cam', '3-cam'];
const pipelineCreatesDatasetMarkers = ['transcode', 'filter'];

const JsonMetaRegEx = /^.*\.?(meta|config)\.json$/;
const JsonConfigRegEx = /^.*\.?(meta|config)\.json$/;

function simplifyTrainingName(item: string) {
return item.replace('.conf', '');
Expand All @@ -218,9 +245,12 @@ export {
metadataFileTypes,
fileVideoTypes,
otherImageTypes,
otherImageFileExtensions,
otherVideoTypes,
websafeImageTypes,
websafeImageFileExtensions,
websafeVideoTypes,
getImageSequenceFileAccept,
inputAnnotationTypes,
largeImageTypes,
largeImageFileExtensions,
Expand All @@ -241,6 +271,6 @@ export {
webExcludedPipelineTerms,
multiCamPipelineMarkers,
pipelineCreatesDatasetMarkers,
JsonMetaRegEx,
JsonConfigRegEx,
simplifyTrainingName,
};
14 changes: 7 additions & 7 deletions client/dive-common/multicamDisplay.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ import {
getMultiCamIcon,
getMultiCamSubType,
getMultiCamTooltip,
isMultiCamDatasetMeta,
isMultiCamDatasetConfig,
isMultiCamTrainingTarget,
isStereoscopicDatasetMeta,
isStereoscopicDatasetConfig,
orderedMultiCamCameraNames,
referenceCameraName,
} from './multicamDisplay';
Expand Down Expand Up @@ -46,14 +46,14 @@ describe('multicamDisplay', () => {
});

it('detects multicam dataset meta for training guards', () => {
expect(isMultiCamDatasetMeta({ type: 'multi', subType: 'stereo' })).toBe(true);
expect(isMultiCamDatasetMeta({ type: 'video', subType: null })).toBe(false);
expect(isMultiCamDatasetConfig({ type: 'multi', subType: 'stereo' })).toBe(true);
expect(isMultiCamDatasetConfig({ type: 'video', subType: null })).toBe(false);
});

it('detects stereoscopic vs plain multicam datasets', () => {
expect(isStereoscopicDatasetMeta({ type: 'multi', subType: 'stereo' })).toBe(true);
expect(isStereoscopicDatasetMeta({ type: 'multi', subType: 'multicam' })).toBe(false);
expect(isStereoscopicDatasetMeta({ type: 'video', subType: 'stereo' })).toBe(false);
expect(isStereoscopicDatasetConfig({ type: 'multi', subType: 'stereo' })).toBe(true);
expect(isStereoscopicDatasetConfig({ type: 'multi', subType: 'multicam' })).toBe(false);
expect(isStereoscopicDatasetConfig({ type: 'video', subType: 'stereo' })).toBe(false);
});

it('disables training for multicam parent and child camera selection', () => {
Expand Down
Loading
Loading