diff --git a/client/dive-common/components/TrackDetailsPanel.vue b/client/dive-common/components/TrackDetailsPanel.vue
index c8d866eb7..87f562145 100644
--- a/client/dive-common/components/TrackDetailsPanel.vue
+++ b/client/dive-common/components/TrackDetailsPanel.vue
@@ -298,7 +298,7 @@ export default defineComponent({
{{ editingGroup.id }}
{
cameraStore.removeGroups(id);
};
+ const setTrackType = (id: AnnotationId, newType: string,
+ confidenceVal?: number, currentType?: string) => {
+ cameraStore.setTrackType(id, newType, confidenceVal, currentType);
+ };
+ const removeTypes = (id: AnnotationId, types: string[]) => cameraStore.removeTypes(id, types);
+ const getTracksMerged = (id: AnnotationId) => cameraStore.getTracksMerged(id);
const groupFilters = new GroupFilterControls({
sorted: cameraStore.sortedGroups,
markChangesPending,
remove: removeGroups,
+ setType: setTrackType,
+ removeTypes,
});
// This context for removal
@@ -180,6 +188,8 @@ export default defineComponent({
markChangesPending,
lookupGroups: cameraStore.lookupGroups,
groupFilterControls: groupFilters,
+ setType: setTrackType,
+ removeTypes,
});
clientSettingsSetup(trackFilters.allTypes);
@@ -251,6 +261,7 @@ export default defineComponent({
enabledTracks: trackFilters.enabledAnnotations,
selectedTrackIds: allSelectedIds,
typeStyling: trackStyleManager.typeStyling,
+ getTracksMerged,
});
const { eventChartData: groupChartData } = useEventChart({
@@ -262,6 +273,7 @@ export default defineComponent({
}
return [];
}),
+ getTracksMerged,
});
async function trackSplit(trackId: AnnotationId | null, frame: number) {
@@ -538,6 +550,11 @@ export default defineComponent({
const trackStore = cameraStore.camMap.value.get(camera)?.trackStore;
const groupStore = cameraStore.camMap.value.get(camera)?.groupStore;
if (trackStore && groupStore) {
+ // We can start sorting if our total tracks are less than 20000
+ // If greater we do one sort at the end instead to speed loading.
+ if (tracks.length < 20000) {
+ trackStore.setEnableSorting();
+ }
for (let j = 0; j < tracks.length; j += 1) {
if (j % 4000 === 0) {
/* Every N tracks, yeild some cycles for other scheduled tasks */
@@ -558,13 +575,19 @@ export default defineComponent({
}
}
}
- progress.loaded = true;
- cameraStore.camMap.value.forEach((_cam, key) => {
+ cameraStore.camMap.value.forEach((cam, key) => {
+ const { trackStore } = cam;
+ // Enable Sorting after loading is complete if it isn't enabled already
+ if (trackStore) {
+ trackStore.setEnableSorting();
+ }
+
if (!multiCamList.value.includes(key)) {
cameraStore.removeCamera(key);
removeSaveCamera(key);
}
});
+ progress.loaded = true;
// If multiCam add Tools and remove group Tools
if (cameraStore.camMap.value.size > 1) {
context.unregister({
diff --git a/client/dive-common/use/useModeManager.ts b/client/dive-common/use/useModeManager.ts
index f0ff54498..e3c63fdd0 100644
--- a/client/dive-common/use/useModeManager.ts
+++ b/client/dive-common/use/useModeManager.ts
@@ -10,12 +10,12 @@ import { AggregateMediaController } from 'vue-media-annotator/components/annotat
import Recipe from 'vue-media-annotator/recipe';
import type { AnnotationId } from 'vue-media-annotator/BaseAnnotation';
import type TrackFilterControls from 'vue-media-annotator/TrackFilterControls';
-import BaseAnnotation from 'vue-media-annotator/BaseAnnotation';
import { usePrompt } from 'dive-common/vue-utilities/prompt-service';
import { clientSettings } from 'dive-common/store/settings';
import GroupFilterControls from 'vue-media-annotator/GroupFilterControls';
import CameraStore from 'vue-media-annotator/CameraStore';
+import { SortedAnnotation } from 'vue-media-annotator/BaseAnnotationStore';
type SupportedFeature = GeoJSON.Feature;
@@ -23,7 +23,7 @@ type SupportedFeature = GeoJSON.Feature(
+function selectNext(
filtered: Readonly[], selected: Readonly, delta = 1,
): AnnotationId | null {
if (filtered.length > 0) {
@@ -334,7 +334,7 @@ export default function useModeManager({
frame.value, trackType,
selectedTrackId.value || undefined,
overrideTrackId,
- ).trackId;
+ ).id;
selectTrack(newTrackId, true);
creating = true;
return newTrackId;
@@ -684,7 +684,7 @@ export default function useModeManager({
));
handleRemoveTrack(otherTrackIds, true);
handleToggleMerge();
- handleSelectTrack(track.trackId, false);
+ handleSelectTrack(track.id, false);
}
}
diff --git a/client/platform/desktop/frontend/components/ImportDialog.vue b/client/platform/desktop/frontend/components/ImportDialog.vue
index cc0174deb..d38a5ddf4 100644
--- a/client/platform/desktop/frontend/components/ImportDialog.vue
+++ b/client/platform/desktop/frontend/components/ImportDialog.vue
@@ -20,6 +20,10 @@ export default defineComponent({
type: Object as PropType,
required: true,
},
+ disabled: {
+ type: Boolean,
+ default: false,
+ },
},
setup(props) {
const argCopy = ref(cloneDeep(props.importData));
@@ -316,7 +320,7 @@ export default defineComponent({
Finish Import
diff --git a/client/platform/desktop/frontend/components/Recent.vue b/client/platform/desktop/frontend/components/Recent.vue
index 7d5120478..5b9b19910 100644
--- a/client/platform/desktop/frontend/components/Recent.vue
+++ b/client/platform/desktop/frontend/components/Recent.vue
@@ -47,6 +47,7 @@ export default defineComponent({
const searchText: Ref = ref('');
const stereo = ref(false);
const multiCamOpenType: Ref<'image-sequence'|'video'> = ref('image-sequence');
+ const importing = ref(false);
const { prompt } = usePrompt();
const {
error, loading: checkingMedia, request, reset: resetError,
@@ -61,6 +62,7 @@ export default defineComponent({
/** Accept args from the dialog, as it may have modified some parts */
async function finalizeImport(args: DesktopMediaImportResponse) {
+ importing.value = true;
await request(async () => {
const jsonMeta = await api.finalizeImport(args);
pendingImportPayload.value = null; // close dialog
@@ -75,6 +77,7 @@ export default defineComponent({
setRecents(recentsMeta);
}
});
+ importing.value = false;
}
function openMultiCamDialog(args: {stereo: boolean; openType: 'image-sequence' | 'video'}) {
@@ -195,6 +198,7 @@ export default defineComponent({
pendingImportPayload,
searchText,
error,
+ importing,
importMultiCamDialog,
headers,
upgradedVersion,
@@ -220,6 +224,7 @@ export default defineComponent({
diff --git a/client/src/BaseAnnotationStore.ts b/client/src/BaseAnnotationStore.ts
index a2b90d56a..aae8c87d6 100644
--- a/client/src/BaseAnnotationStore.ts
+++ b/client/src/BaseAnnotationStore.ts
@@ -2,7 +2,7 @@ import { ref, Ref, computed } from '@vue/composition-api';
import IntervalTree from '@flatten-js/interval-tree';
import type Track from './track';
import type Group from './Group';
-import type { AnnotationId, NotifierFuncParams } from './BaseAnnotation';
+import type { AnnotationId, ConfidencePair, NotifierFuncParams } from './BaseAnnotation';
export type MarkChangesPending = ({
action,
@@ -21,6 +21,15 @@ export interface InsertArgs {
afterId?: AnnotationId;
}
+//A subset of Track so copies of full track aren't passed around
+export interface SortedAnnotation {
+ id: AnnotationId;
+ begin: number;
+ end: number;
+ confidencePairs: ConfidencePair[];
+ getType: (index?: number) => string;
+}
+
function isTrack(value: Track | Group): value is Track {
return (value as Track).features !== undefined;
}
@@ -55,12 +64,14 @@ export default abstract class BaseAnnotationStore {
*/
annotationIds: Ref;
- sorted: Ref[]>;
+ sorted: Ref;
cameraName: string;
private canary: Ref;
+ enableSorting: Ref; //Sorting is false to start with
+
constructor({ markChangesPending, cameraName }:
{ markChangesPending: MarkChangesPending; cameraName: string }) {
this.markChangesPending = markChangesPending;
@@ -69,10 +80,24 @@ export default abstract class BaseAnnotationStore {
this.annotationIds = ref([]);
this.intervalTree = new IntervalTree();
this.canary = ref(0);
+ this.enableSorting = ref(false);
this.sorted = computed(() => {
this.depend();
+ // Prevent sorting when loading data
+ if (!this.enableSorting.value) {
+ return [];
+ }
return this.annotationIds.value
- .map((trackId) => this.get(trackId))
+ .map((trackId) => {
+ const track = this.get(trackId);
+ return {
+ begin: track.begin,
+ end: track.end,
+ id: track.id,
+ confidencePairs: track.confidencePairs,
+ getType: (index?: number) => ((track.confidencePairs[index || 0][0]) || 'unknown'),
+ };
+ })
.sort((a, b) => a.begin - b.begin);
});
}
@@ -87,6 +112,10 @@ export default abstract class BaseAnnotationStore {
return this.canary.value;
}
+ setEnableSorting() {
+ this.enableSorting.value = true;
+ }
+
get(annotationId: AnnotationId) {
const value = this.annotationMap.get(annotationId);
if (value === undefined) {
diff --git a/client/src/BaseFilterControls.ts b/client/src/BaseFilterControls.ts
index 0e0a3ca64..b52ae1f01 100644
--- a/client/src/BaseFilterControls.ts
+++ b/client/src/BaseFilterControls.ts
@@ -1,7 +1,8 @@
import {
ref, computed, Ref, watch,
} from '@vue/composition-api';
-import type { AnnotationId } from './BaseAnnotation';
+import type { AnnotationId, ConfidencePair } from './BaseAnnotation';
+import { SortedAnnotation } from './BaseAnnotationStore';
import type Group from './Group';
import type Track from './track';
import { updateSubset } from './utils';
@@ -13,7 +14,7 @@ export const DefaultConfidence = 0.1;
* or function.
*/
export interface AnnotationWithContext {
- annotation: Readonly>;
+ annotation: Readonly;
context: {
// confidencePair index within annotation that makes this annotation a positive filter result
confidencePairIndex: number;
@@ -21,9 +22,12 @@ export interface AnnotationWithContext {
}
export interface FilterControlsParams {
- sorted: Ref;
+ sorted: Ref;
markChangesPending: () => void;
remove: (id: AnnotationId) => void;
+ setType: (id: AnnotationId, newType: string,
+ confidenceVal?: number, currentType?: string) => void;
+ removeTypes: (id: AnnotationId, types: string[]) => ConfidencePair[];
}
export type TrackWithContext = AnnotationWithContext