Skip to content
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
51 changes: 15 additions & 36 deletions client/README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
# VIAMEWeb
# VIAME Web Frontent

This directory contains the code for both

* The specific VIAME-Web client deployed to [viame.kitware.com](https://viame.kitware.com)
* The web annotation library published to npm as [`vue-media-annotator`](https://developer.aliyun.com/mirror/npm/package/vue-media-annotator)

## Development

Expand All @@ -20,66 +25,40 @@ See [this issue](https://github.com/vuejs/vue-cli/issues/3065) for details on wh

## Architecture

This application is built with GeoJS, and is rather unique in its structure.

### src/components/annotators

These components form the base of an annotator instance. They construct the geojs instance and maintain state. State is shared with layers through a special provide/inject mechanism. The annotator API is documented in `src/components/annotators/README.md`

* This provide/inject mechanism uses a distinct Vue instance as a convenience to share reactive state and provide a means for injectors to signal back through `$emit`.
* The somewhat uncommon `provide()` function is used because the special instance is tied to its parent's lifecycle and cannot be hoisted.

### src/components/layers
### src/layers

These layers are provided to an annotator through slots and can inject annotator state. Generally, a layer will set up a watcher on that state and update their own GeoJS layer features based on that. These watchers may run at up to 60hz so performance considerations matter.
These layers are provided to an annotator as slots and can inject their parent annotator state. Generally, a layer will set up a watcher on that state and update their own GeoJS features based on that. These watchers may run at up to 60hz so performance considerations matter.

* Layers must be vue instances to integrate with Vue's reactivity system.
* Layers must be independent instances (not mixins or composition functions) because they need their own lifecycle hooks (and otherwise, would have to maintain state about whether or not they are enabled). Layers rely on the Vue lifecycle to destroy them when their features are not needed to prevent unnecessary updates in the cricial path.

#### example

Like layers, controls are provided to an annotatior via slots and inject state.

```vue
<script>
export default {
inject: ['annotator'],
watch: {
'annotator.state': (newval) => {
// react to changes in annotator state
},
},
mounted() {
// setup geojs layer
this.$geojsLayer = this.annotator.geoViewer.createLayer(/*... */);
},
beforeDestroy() {
this.annotator.geoViewer.deleteLayer(this.$geojsLayer);
delete this.$geojsLayer;
},
methods: {
onEvent(e) {
this.annotator.$emit('method-name', e);
},
},
};
```
This application has many layers that interact, requiring a manager `src/components/LayerManager.vue`.

### src/components/controls

Controllers are like layers, but without geojs functionality. They usually provide some UI wigetry to manipulate the annotator state (such as playblack position or playpause state).

### src/use

The modules in this directory are mostly used in `views/Viewer`, and follow Vue 3's composition API reusabiliity patterns. These modules, or composition functions, seek to encapsulate state and functionality as a half-step before further refactoring some parts into vuex.
These are Vue 3 composition functions that an annotation application can use. They mostly provide the data structures that the above layers and consumers need. For example:

You can think of some parts of this application as being generically useful, like components on a switchboard. Everything under `components/layers`, for example, is flexible enough that it could conceivably be used in any application dealing with time series annotations over imagery. The code in `src/use`, on the other hand, is highly specialized to this application. It is the business logic that unites all the disparate layers, controls, and events. In MVC, `src/use` contains the models and controllers.
* `src/use/useTrackStore.ts` provides an efficient data structure for holding track instances. It provides reactivity when individual tracks are updated, added, and removed, and can provide fast lookup by trackid and frame.
* `src/use/useTrackFilters.ts` takes a trackstore's return values as params and provides filtering by type and trackid.
* `src/use/useTrackSelectionControls.ts` takes trackstore return values and provides state and mutations for selection
* `src/use/useEventChart.ts` takes trackstore, filter, and selection as params and returns an object used by the `EventChart.vue` component to display a contextual timeline of all tracks in the store.

The major benefits of the `src/use` style are:

* testability. These composition functions are easy to harness with unit tests.
* modularity. Private behavior is hidden, and further refactors and features have less opportunity to break neighboring code
* sanity. All this logic and state is technically contained in a single component. For the sake of developer quality of life, it was necessary to break the 1000-line `Viewer.vue` file down into more digestable chunks.
* sanity. All this logic and state is technically contained in a single component.
* typescript adoption. Typescript will be easier to incrementally adopt.

## Tests
Expand Down
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import girderRest from '@/plugins/girder';
import { GirderModel } from '@girder/components/src';

import girderRest from 'app/plugins/girder';

async function getItemsInFolder(folderId: string, limit: number) {
const { data: items } = await girderRest.get('item', {
params: { folderId, limit },
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import girderRest from '@/plugins/girder';
import girderRest from 'app/plugins/girder';

interface GirderModel {
_id: string;
Expand Down Expand Up @@ -102,7 +102,7 @@ async function validateUploadGroup(names: string[]): Promise<ValidationResponse>
}

async function getValidWebImages(folderId: string) {
const { data } = await girderRest.get('viame/valid_images', {
const { data } = await girderRest.get<GirderModel[]>('viame/valid_images', {
params: { folderId },
});
return data;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import girderRest from '@/plugins/girder';
import Track, { TrackData, TrackId } from '@/lib/track';
import Track, { TrackData, TrackId } from 'vue-media-annotator/track';

import girderRest from 'app/plugins/girder';

interface ExportUrlsResponse {
mediaType: string;
Expand Down
File renamed without changes
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@
import { Ref } from '@vue/composition-api';
import Vue, { PropType } from 'vue';

import { getAttributes, Attribute } from '@/lib/api/viame.service';
import Track, { TrackId, Feature } from '@/lib/track';
import Track, { TrackId, Feature } from 'vue-media-annotator/track';

import AttributeInput from '@/components/AttributeInput.vue';
import { getAttributes, Attribute } from 'app/api/viame.service';
import AttributeInput from 'app/components/AttributeInput.vue';

function getTrack(trackMap: Map<TrackId, Track>, trackId: TrackId): Track {
const track = trackMap.get(trackId);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import Vue, { PropType } from 'vue';
import { Ref } from '@vue/composition-api';
import { cloneDeep } from 'lodash';
import { NewTrackSettings } from '@/use/useSettings';
import { NewTrackSettings } from 'app/use/useSettings';

export default Vue.extend({
name: 'CreationMode',
Expand Down Expand Up @@ -50,7 +50,7 @@ export default Vue.extend({
* Each submodule because of Typescript needs to be referenced like this
* Allows us to add more sub settings in the future
*/
saveTrackSubSettings(event: true | null, target: 'autoAdvanceFrame') {
saveTrackSubSettings(event: true | null, target: 'autoAdvanceFrame' | 'interpolate') {
// Copy the newTrackSettings for modification
const copy: NewTrackSettings = cloneDeep(this.newTrackSettings.value);
const modeSettings = copy.modeSettings.Track;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<script lang="ts">
import Vue, { PropType } from 'vue';
import { Ref } from '@vue/composition-api';
import { EditAnnotationTypes } from '@/components/layers/EditAnnotationLayer';
import { EditAnnotationTypes } from 'vue-media-annotator/layers/EditAnnotationLayer';

export default Vue.extend({
name: 'EditorMenu',
Expand All @@ -10,11 +10,12 @@ export default Vue.extend({
type: Object as PropType<Ref<boolean>>,
required: true,
},
annotationState: {
type: Object as PropType<Ref<{
visible: EditAnnotationTypes[];
editing: EditAnnotationTypes;
}>>,
visibleModes: {
type: Object as PropType<Ref<EditAnnotationTypes[]>>,
required: true,
},
editingMode: {
type: Object as PropType<Ref<EditAnnotationTypes>>,
required: true,
},
},
Expand All @@ -29,14 +30,23 @@ export default Vue.extend({
},

computed: {
config() {
config(): {
color: string;
class: string[];
text: string;
icon: string;
model: string;
value: string | string[];
multiple: boolean;
} {
if (this.editingTrack.value) {
return {
color: 'primary',
class: ['primary'],
text: 'Edit',
icon: 'mdi-pencil',
model: 'editing',
value: this.editingMode.value,
multiple: false,
};
}
Expand All @@ -46,6 +56,7 @@ export default Vue.extend({
text: 'View',
icon: 'mdi-eye',
model: 'visible',
value: this.visibleModes.value,
multiple: true,
};
},
Expand All @@ -66,7 +77,7 @@ export default Vue.extend({
</span>
</span>
<v-btn-toggle
:value="annotationState.value[config.model]"
:value="config.value"
:multiple="config.multiple"
:mandatory="!config.multiple"
group
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<script>
import { getExportUrls } from '@/lib/api/viameDetection.service';
import { MediaTypes } from '@/constants';
import { getExportUrls } from 'app/api/viameDetection.service';
import { MediaTypes } from 'app/constants';

export default {
props: {
Expand Down Expand Up @@ -35,6 +35,9 @@ export default {
mediaType() {
return MediaTypes[this.exportUrls.mediaType];
},
thresholds() {
return Object.keys(this.exportUrls.currentThresholds || {});
},
},
};
</script>
Expand Down Expand Up @@ -93,7 +96,7 @@ export default {

<v-card-text class="pb-0">
<div>Get latest detections csv only</div>
<template v-if="Object.keys(exportUrls.currentThresholds).length">
<template v-if="thresholds.length">
<v-checkbox
v-model="excludeFiltered"
label="exclude tracks below confidence threshold"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@
import { mapState } from 'vuex';
import { all } from '@girder/components/src/components/Job/status';

import NavigationTitle from '@/components/NavigationTitle.vue';
import UserGuideButton from '@/components/UserGuideButton.vue';
import { getPathFromLocation } from '@/utils';
import NavigationTitle from 'app/components/NavigationTitle.vue';
import UserGuideButton from 'app/components/UserGuideButton.vue';
import { getPathFromLocation } from 'app/utils';

export default {
name: 'GenericNavigationBar',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<script>
import { runPipeline } from '@/lib/api/viame.service';
import { mapActions, mapState } from 'vuex';
import { runPipeline } from 'app/api/viame.service';

export default {
props: {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
<script>
import Dropzone from '@girder/components/src/components/Presentation/Dropzone.vue';
import { fileUploader, sizeFormatter } from '@girder/components/src/utils/mixins';
import { ImageSequenceType, VideoType } from '@/constants';
import { makeViameFolder, validateUploadGroup, postProcess } from '@/lib/api/viame.service';
import { getResponseError } from '@/lib/utils';
import { ImageSequenceType, VideoType } from 'app/constants';
import { makeViameFolder, validateUploadGroup, postProcess } from 'app/api/viame.service';
import { getResponseError } from 'app/utils';

function entryToFile(entry) {
return new Promise((resolve) => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<script>
import UserGuideDialog from '@/components/UserGuideDialog.vue';
import UserGuideDialog from 'app/components/UserGuideDialog.vue';

export default {
components: {
Expand Down
File renamed without changes.
12 changes: 6 additions & 6 deletions client/src/main.ts → client/app/main.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import Vue from 'vue';
import VueCompositionApi from '@vue/composition-api';

import NotificationBus from '@girder/components/src/utils/notifications';
import snackbarService from '@/lib/vue-utilities/snackbar-service';
import promptService from '@/lib/vue-utilities/prompt-service';
import vMousetrap from '@/lib/vue-utilities/v-mousetrap';

import vuetify from '@/plugins/vuetify';
import girderRest from '@/plugins/girder';
import snackbarService from 'app/vue-utilities/snackbar-service';
import promptService from 'app/vue-utilities/prompt-service';
import vMousetrap from 'app/vue-utilities/v-mousetrap';
import vuetify from 'app/plugins/vuetify';
import girderRest from 'app/plugins/girder';

import App from './App.vue';
import router from './router';
import store from './store';
Expand Down
File renamed without changes.
File renamed without changes.
12 changes: 6 additions & 6 deletions client/src/router.js → client/app/router.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import Vue from 'vue';
import Router from 'vue-router';

import girderRest from '@/plugins/girder';
import Viewer from '@/views/TrackViewer/Viewer.vue';
import Home from '@/views/Home.vue';
import Jobs from '@/views/Jobs.vue';
import Login from '@/views/Login.vue';
import Settings from '@/views/Settings.vue';
import girderRest from 'app/plugins/girder';
import Viewer from 'app/views/TrackViewer/Viewer.vue';
import Home from 'app/views/Home.vue';
import Jobs from 'app/views/Jobs.vue';
import Login from 'app/views/Login.vue';
import Settings from 'app/views/Settings.vue';

Vue.use(Router);

Expand Down
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Module } from 'vuex';
import { GirderModel } from '@/lib/api/viame.service';
import { GirderModel } from 'app/api/viame.service';

export interface LocationState {
location: null | GirderModel;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Module } from 'vuex';
import { getPipelineList, Category } from '@/lib/api/viame.service';
import { getPipelineList, Category } from 'app/api/viame.service';

export interface PipelineState {
pipelines: null | Record<string, Category>;
Expand Down
1 change: 0 additions & 1 deletion client/src/store/index.ts → client/app/store/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import Vuex from 'vuex';
import Pipelines from './Pipelines';
import Location from './Location';


Vue.use(Vuex);

export default new Vuex.Store({
Expand Down
13 changes: 13 additions & 0 deletions client/app/use/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import useFeaturePointing from './useFeaturePointing';
import useGirderDataset from './useGirderDataset';
import useModeManager from './useModeManager';
import useSave from './useSave';
import useSettings from './useSettings';

export {
useFeaturePointing,
useGirderDataset,
useModeManager,
useSave,
useSettings,
};
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { reactive, toRefs, Ref } from '@vue/composition-api';
import Track, { TrackId } from '@/lib/track';
import Track, { TrackId } from 'vue-media-annotator/track';

export type FeaturePointingTarget = 'head' | 'tail' | null;

Expand Down
Loading