Skip to content

Commit

Permalink
add trackJSON to desktop exporting (#1424)
Browse files Browse the repository at this point in the history
* add trackJSON to desktop exporting

* fixing type errors

* fixing web export of filtered annotations

* remove logging
  • Loading branch information
BryonLewis authored Jun 3, 2024
1 parent ec6de7f commit 225abd1
Show file tree
Hide file tree
Showing 10 changed files with 139 additions and 23 deletions.
1 change: 0 additions & 1 deletion client/dive-common/components/ImportAnnotations.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
6 changes: 6 additions & 0 deletions client/platform/desktop/backend/native/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
54 changes: 52 additions & 2 deletions client/platform/desktop/backend/serializers/dive.ts
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down Expand Up @@ -40,7 +41,56 @@ function migrate(jsonData: any): AnnotationSchema {
}
}

function filterTracks(
data: AnnotationSchema,
meta: JsonMeta,
typeFilter = new Set<string>(),
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<number, TrackData> = {};
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<string>(),
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,
};
3 changes: 1 addition & 2 deletions client/platform/desktop/backend/serializers/viame.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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}`);
});

Expand Down
1 change: 1 addition & 0 deletions client/platform/desktop/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,7 @@ export interface ExportDatasetArgs {
exclude: boolean;
path: string;
typeFilter: Set<string>;
type?: 'csv' | 'json';
}

export interface ExportConfigurationArgs {
Expand Down
6 changes: 3 additions & 3 deletions client/platform/desktop/frontend/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,15 +144,15 @@ function finalizeImport(args: DesktopMediaImportResponse): Promise<JsonMeta> {
}

async function exportDataset(
id: string, exclude: boolean, typeFilter: readonly string[],
id: string, exclude: boolean, typeFilter: readonly string[], type?: 'csv' | 'json',
): Promise<string> {
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);
}
Expand Down
36 changes: 26 additions & 10 deletions client/platform/desktop/frontend/components/Export.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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);
}
Expand Down Expand Up @@ -169,7 +173,7 @@ export default defineComponent({
>
Export succeeded.
</v-alert>
<div>Export to VIAME CSV format</div>
<div>Export to Annotations</div>
<template v-if="thresholds.length">
<v-checkbox
v-model="data.excludeBelowThreshold"
Expand Down Expand Up @@ -204,14 +208,26 @@ export default defineComponent({
</template>
</v-card-text>
<v-card-actions>
<v-spacer />
<v-btn
depressed
block
@click="doExport({ type: 'dataset' })"
>
<span>VIAME CSV</span>
</v-btn>
<v-row>
<v-col>
<v-btn
depressed
block
class="my-1"
@click="doExport({ type: 'dataset' })"
>
<span>VIAME CSV</span>
</v-btn>
<v-btn
depressed
block
class="my-1"
@click="doExport({ type: 'trackJSON' })"
>
<span>TRACK JSON</span>
</v-btn>
</v-col>
</v-row>
</v-card-actions>
<v-card-text class="pb-0">
Export the dataset configuration, including
Expand Down
6 changes: 3 additions & 3 deletions client/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -5340,9 +5340,9 @@ caniuse-api@^3.0.0:
lodash.uniq "^4.5.0"

caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001109, caniuse-lite@^1.0.30001219, caniuse-lite@^1.0.30001271:
version "1.0.30001519"
resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001519.tgz"
integrity sha512-0QHgqR+Jv4bxHMp8kZ1Kn8CH55OikjKJ6JmKkZYP1F3D7w+lnFXF70nG5eNfsZS89jadi5Ywy5UCSKLAglIRkg==
version "1.0.30001625"
resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001625.tgz"
integrity sha512-4KE9N2gcRH+HQhpeiRZXd+1niLB/XNLAhSy4z7fI8EzcbcPoAqjNInxVHTiTwWfTIV4w096XG8OtCOCQQKPv3w==

capture-exit@^2.0.0:
version "2.0.0"
Expand Down
24 changes: 23 additions & 1 deletion server/dive_server/crud_dataset.py
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,10 @@ def makeAnnotationAndMedia(dsFolder: types.GirderModel):

def stream():
z = ziputil.ZipGenerator()
nestedExcludeBelowThreshold = excludeBelowThreshold
nestedTypeFilter = typeFilter
if nestedTypeFilter is None:
nestedTypeFilter = set()
for dsFolder in dsFolders:
zip_path = f"./{dsFolder['name']}/"
try:
Expand All @@ -289,7 +293,25 @@ def makeMetajson():
def makeDiveJson():
"""Include DIVE JSON output annotation file"""
annotations = crud_annotation.get_annotations(dsFolder)
print(annotations)
tracks = annotations['tracks']

if nestedExcludeBelowThreshold:
thresholds = fromMeta(dsFolder, "confidenceFilters", {})
if thresholds is None:
thresholds = {}

updated_tracks = {}
for t in tracks:
track = models.Track(**tracks[t])
if (not nestedExcludeBelowThreshold) or track.exceeds_thresholds(thresholds):
# filter by types if applicable
if nestedTypeFilter:
confidence_pairs = [item for item in track.confidencePairs if item[0] in nestedTypeFilter]
# skip line if no confidence pairs
if not confidence_pairs:
continue
updated_tracks[t] = tracks[t]
annotations['tracks'] = updated_tracks
yield json.dumps(annotations)

for data in z.addFile(makeMetajson, Path(f'{zip_path}meta.json')):
Expand Down
25 changes: 24 additions & 1 deletion server/dive_server/views_annotation.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from girder.exceptions import RestException
from girder.models.folder import Folder

from dive_utils import constants, setContentDisposition
from dive_utils import constants, setContentDisposition, models, fromMeta

from . import crud, crud_annotation

Expand Down Expand Up @@ -152,6 +152,29 @@ def export(
setContentDisposition(f'{folder["name"]}.dive.json', mime='application/json')
setRawResponse()
annotations = crud_annotation.get_annotations(folder, revision=revisionId)
tracks = annotations['tracks']
if excludeBelowThreshold:
thresholds = fromMeta(folder, "confidenceFilters", {})
if thresholds is None:
thresholds = {}

updated_tracks = []
if typeFilter is None:
typeFilter = set()
print(tracks)
for t in tracks:
print(t)
print(tracks[t])
track = models.Track(**tracks[t])
if (not excludeBelowThreshold) or track.exceeds_thresholds(thresholds):
# filter by types if applicable
if typeFilter:
confidence_pairs = [item for item in track.confidencePairs if item[0] in typeFilter]
# skip line if no confidence pairs
if not confidence_pairs:
continue
updated_tracks.append(tracks[t])
annotations['tracks'] = updated_tracks
return json.dumps(annotations).encode('utf-8')
else:
raise RestException(f'Format {format} is not a valid option.')
Expand Down

0 comments on commit 225abd1

Please sign in to comment.