diff --git a/client/dive-common/components/ImportAnnotations.vue b/client/dive-common/components/ImportAnnotations.vue index b5a38b612..8993d310a 100644 --- a/client/dive-common/components/ImportAnnotations.vue +++ b/client/dive-common/components/ImportAnnotations.vue @@ -74,7 +74,6 @@ export default defineComponent({ set, ); } - console.log(importFile); if (Array.isArray(importFile) && importFile.length) { const text = ['There were warnings when importing. While the data imported properly please double check your annotations', 'Below is a list of information that can help with debugging', diff --git a/client/platform/desktop/backend/native/common.ts b/client/platform/desktop/backend/native/common.ts index 73ba8b239..6f3c59910 100644 --- a/client/platform/desktop/backend/native/common.ts +++ b/client/platform/desktop/backend/native/common.ts @@ -1117,6 +1117,12 @@ async function exportDataset(settings: Settings, args: ExportDatasetArgs) { const projectDirInfo = await getValidatedProjectDir(settings, args.id); const meta = await loadJsonMetadata(projectDirInfo.metaFileAbsPath); const data = await loadAnnotationFile(projectDirInfo.trackFileAbsPath); + if (args.type === 'json') { + return dive.serializeFile(args.path, data, meta, args.typeFilter, { + excludeBelowThreshold: args.exclude, + header: true, + }); + } return viameSerializers.serializeFile(args.path, data, meta, args.typeFilter, { excludeBelowThreshold: args.exclude, header: true, diff --git a/client/platform/desktop/backend/serializers/dive.ts b/client/platform/desktop/backend/serializers/dive.ts index 0bac1f9a5..655a81e90 100644 --- a/client/platform/desktop/backend/serializers/dive.ts +++ b/client/platform/desktop/backend/serializers/dive.ts @@ -1,7 +1,8 @@ import { AnnotationSchema, MultiTrackRecord } from 'dive-common/apispec'; import { has } from 'lodash'; -import { AnnotationsCurrentVersion } from 'platform/desktop/constants'; -import { TrackData, TrackId } from 'vue-media-annotator/track'; +import { AnnotationsCurrentVersion, JsonMeta } from 'platform/desktop/constants'; +import Track, { TrackData, TrackId } from 'vue-media-annotator/track'; +import fs from 'fs-extra'; function makeEmptyAnnotationFile(): AnnotationSchema { return { @@ -40,7 +41,56 @@ function migrate(jsonData: any): AnnotationSchema { } } +function filterTracks( + data: AnnotationSchema, + meta: JsonMeta, + typeFilter = new Set(), + options = { + excludeBelowThreshold: false, + header: true, + }, +): AnnotationSchema { + const filteredTracks = Object.values(data.tracks).filter((track) => { + const filters = meta.confidenceFilters || {}; + /* Include only the pairs that exceed the threshold in CSV output */ + const confidencePairs = options.excludeBelowThreshold + ? Track.exceedsThreshold(track.confidencePairs, filters) + : track.confidencePairs; + const filteredPairs = typeFilter.size > 0 + ? confidencePairs.filter((x) => typeFilter.has(x[0])) + : confidencePairs; + return filteredPairs.length > 0; + }); + // Convert the track list back into an object + const updatedFilteredTracks: Record = {}; + filteredTracks.forEach((track) => { + updatedFilteredTracks[track.id] = track; + }); + const updatedData = { ...data }; + updatedData.tracks = updatedFilteredTracks; + // Write out the tracks to a file + return updatedData; +} + +async function serializeFile( + path: string, + data: AnnotationSchema, + meta: JsonMeta, + typeFilter = new Set(), + options = { + excludeBelowThreshold: false, + header: true, + }, +) { + const updatedData = filterTracks(data, meta, typeFilter, options); + // write updatedData JSON to a path + const jsonData = JSON.stringify(updatedData, null, 2); + await fs.writeFile(path, jsonData, 'utf8'); +} + export { makeEmptyAnnotationFile, migrate, + filterTracks, + serializeFile, }; diff --git a/client/platform/desktop/backend/serializers/viame.ts b/client/platform/desktop/backend/serializers/viame.ts index da7a26f89..cf37d866a 100644 --- a/client/platform/desktop/backend/serializers/viame.ts +++ b/client/platform/desktop/backend/serializers/viame.ts @@ -553,9 +553,8 @@ async function serialize( Object.entries(feature.attributes || {}).forEach(([key, val]) => { row.push(`${AtrToken} ${key} ${val}`); }); - /* Track Attributes */ - Object.entries(track.attributes).forEach(([key, val]) => { + Object.entries(track.attributes || {}).forEach(([key, val]) => { row.push(`${TrackAtrToken} ${key} ${val}`); }); diff --git a/client/platform/desktop/constants.ts b/client/platform/desktop/constants.ts index 3b640e064..cd1a61e35 100644 --- a/client/platform/desktop/constants.ts +++ b/client/platform/desktop/constants.ts @@ -220,6 +220,7 @@ export interface ExportDatasetArgs { exclude: boolean; path: string; typeFilter: Set; + type?: 'csv' | 'json'; } export interface ExportConfigurationArgs { diff --git a/client/platform/desktop/frontend/api.ts b/client/platform/desktop/frontend/api.ts index 83e1b9917..c8712947f 100644 --- a/client/platform/desktop/frontend/api.ts +++ b/client/platform/desktop/frontend/api.ts @@ -144,15 +144,15 @@ function finalizeImport(args: DesktopMediaImportResponse): Promise { } async function exportDataset( - id: string, exclude: boolean, typeFilter: readonly string[], + id: string, exclude: boolean, typeFilter: readonly string[], type?: 'csv' | 'json', ): Promise { const location = await dialog.showSaveDialog({ title: 'Export Dataset', - defaultPath: npath.join(app.getPath('home'), `result_${id}.csv`), + defaultPath: npath.join(app.getPath('home'), type === 'json' ? `result_${id}.json` : `result_${id}.csv`), }); if (!location.canceled && location.filePath) { const args: ExportDatasetArgs = { - id, exclude, path: location.filePath, typeFilter: new Set(typeFilter), + id, exclude, path: location.filePath, typeFilter: new Set(typeFilter), type, }; return ipcRenderer.invoke('export-dataset', args); } diff --git a/client/platform/desktop/frontend/components/Export.vue b/client/platform/desktop/frontend/components/Export.vue index b314c1931..c3adb26f1 100644 --- a/client/platform/desktop/frontend/components/Export.vue +++ b/client/platform/desktop/frontend/components/Export.vue @@ -54,7 +54,7 @@ export default defineComponent({ ? Object.keys(data.meta.confidenceFilters || {}) : [])); - async function doExport({ type, forceSave = false }: { type: 'dataset' | 'configuration'; forceSave?: boolean}) { + async function doExport({ type, forceSave = false }: { type: 'dataset' | 'configuration' | 'trackJSON'; forceSave?: boolean}) { if (pendingSaveCount.value > 0 && forceSave) { await save(); savePrompt.value = false; @@ -67,6 +67,10 @@ export default defineComponent({ 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 === 'configuration') { data.outPath = await exportConfiguration(props.id); } @@ -169,7 +173,7 @@ export default defineComponent({ > Export succeeded. -
Export to VIAME CSV format
+
Export to Annotations