Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add trackJSON to desktop exporting #1424

Merged
merged 4 commits into from
Jun 3, 2024
Merged
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
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
Loading