From 9ae01ea798121f241a005b00e10db40bcdc9aa4e Mon Sep 17 00:00:00 2001 From: Silvan Heller Date: Thu, 19 May 2022 14:06:46 +0200 Subject: [PATCH 01/10] logging improvements --- package.json | 2 +- src/app/app.component.ts | 4 - src/app/core/basics/database.service.ts | 1 - src/app/core/vbs/vbs-submission.service.ts | 122 +++++++++---------- src/config.json | 130 +++++++++++++++++++-- 5 files changed, 174 insertions(+), 85 deletions(-) diff --git a/package.json b/package.json index d194dc72..81bfaaa5 100644 --- a/package.json +++ b/package.json @@ -79,4 +79,4 @@ "ts-node": "10.7.0", "tslint": "^6.1.3" } -} +} \ No newline at end of file diff --git a/src/app/app.component.ts b/src/app/app.component.ts index e52f17f7..081b89df 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -93,8 +93,4 @@ export class AppComponent implements OnInit, AfterViewInit { this._active_view = view; } - isCompetitionActive() { - return this._configService.configAsObservable; - return this._config.dresEndpointRest && (this._config.get('competition.vbs') || this._config.get('competition.lsc')) - } } diff --git a/src/app/core/basics/database.service.ts b/src/app/core/basics/database.service.ts index 65a39552..19e06e37 100644 --- a/src/app/core/basics/database.service.ts +++ b/src/app/core/basics/database.service.ts @@ -11,7 +11,6 @@ export class DatabaseService { */ constructor() { DatabaseService.DB.version(1).stores({ - config: 'id,config', history: '++id,timestamp', log_results: '++id,log', log_interaction: '++id,log', diff --git a/src/app/core/vbs/vbs-submission.service.ts b/src/app/core/vbs/vbs-submission.service.ts index 9617720f..6dbff8fc 100644 --- a/src/app/core/vbs/vbs-submission.service.ts +++ b/src/app/core/vbs/vbs-submission.service.ts @@ -38,16 +38,9 @@ export class VbsSubmissionService { /** Reference to the subscription that is used to submit updates to the results list. */ private _resultsSubscription: Subscription; - /** Reference to the subscription for temporal scoring objects that is used to submit updates to the results list. */ - private _temporalResultsSubscription: Subscription; - - /** Reference to the subscription that is used to submit interaction logs on a regular basis to the VBS server. */ private _interactionlogSubscription: Subscription; - /** Reference to the subscription to the vitrivr NG configuration. */ - private _configSubscription: Subscription; - /** Table for persisting result logs. */ private _resultsLogTable: Dexie.Table; @@ -63,8 +56,11 @@ export class VbsSubmissionService { /** Internal flag used to determine if LSC competition is running. */ private _lsc = false; + /** Internal flag used to determine whether interactions, submissions and results should be logged. */ + private _log = false; + /** SessionID retrieved from DRES endpoint, automatically connected via second tab. Does not support private mode */ - private _sessionId:string = undefined; + private _sessionId: string = undefined; /** Observable used to query the DRES status.*/ private _status: BehaviorSubject = new BehaviorSubject(null) @@ -82,12 +78,18 @@ export class VbsSubmissionService { _db: DatabaseService) { /* This subscription registers the event-mapping, recording and submission stream if the VBS mode is active and un-registers it, if it is switched off! */ - this._configSubscription = _config.configAsObservable.subscribe(config => { - if (config?.dresEndpointRest) { + _config.configAsObservable.subscribe(config => { + if (!config) { + return + } + this._log = config._config.competition.log + if (this._log) { this._resultsLogTable = _db.db.table('log_results'); this._interactionLogTable = _db.db.table('log_interaction'); this._submissionLogTable = _db.db.table('log_submission'); this.reset(config) + } + if (config?.dresEndpointRest) { this._dresUser.getApiV1User().subscribe( { next: (user) => { @@ -105,8 +107,6 @@ export class VbsSubmissionService { console.error('failed to connect to DRES', e) } }) - } else { - this.cleanup() } }); @@ -143,8 +143,8 @@ export class VbsSubmissionService { } } - public submitText(text: string){ - if(this.isOn){ + public submitText(text: string) { + if (this.isOn) { // TODO how to log textual submissions? console.log(`Submitting text ${text}`); this._submitTextSubject.next(text); @@ -158,7 +158,9 @@ export class VbsSubmissionService { * @param time The video timestamp to submit. */ public submit(segment: MediaSegmentScoreContainer, time: number) { - this._submissionLogTable.add([segment.segmentId, time]) + if (this._log) { + this._submissionLogTable.add([segment.segmentId, time]) + } console.debug(`Submitting segment ${segment.segmentId} @ ${time}`); this._submitSubject.next([segment, time]); this._selection.add(this._selection._available[0], segment.segmentId); @@ -169,17 +171,17 @@ export class VbsSubmissionService { */ public reset(config: Config) { /* Update local flags. */ - this._lsc = config.get('competition.lsc'); - this._vbs = config.get('competition.vbs'); + this._lsc = config._config.competition.lsc + this._vbs = config._config.competition.vbs; /* Run cleanup. */ this.cleanup(); /* Setup interaction log subscription, which runs in a regular interval. */ - if (config.get('competition.log') === true) { + if (this._log) { this._interactionlogSubscription = DresTypeConverter.mapEventStream(this._eventbus.observable()).pipe( - bufferTime(config.get('competition.loginterval')), - map((events: QueryEventLog[], index: number) => { + bufferTime(config._config.competition.loginterval), + map((events: QueryEventLog[]) => { if (events && events.length > 0) { const composite = {timestamp: Date.now(), events: []} for (const e of events) { @@ -227,37 +229,18 @@ export class VbsSubmissionService { ); /* IMPORTANT: Limits the number of submissions to one per second. */ - this._resultsSubscription = combineLatest([resultSubscription, this._eventbus.currentView(), this._eventbus.lastQuery()]).pipe( - filter(([results, context, queryInfo]) => context !== TemporalListComponent.COMPONENT_NAME), - map(([results, context, queryInfo]) => DresTypeConverter.mapSegmentScoreContainer(context, results, queryInfo)), - filter(submission => submission != null), - mergeMap((submission: QueryResultLog) => { - this._resultsLogTable.add(submission) - - /* Stop if no sessionId is set */ - if (!this._sessionId) { - return EMPTY + this._resultsSubscription = combineLatest([resultSubscription, temporalResultsSubscription, this._eventbus.currentView(), this._eventbus.lastQuery()]).pipe( + map(([results, temporalResults, context, queryInfo]) => { + switch (context) { + case TemporalListComponent.COMPONENT_NAME: + return DresTypeConverter.mapTemporalScoreContainer(context, temporalResults, queryInfo) + default: + return DresTypeConverter.mapSegmentScoreContainer(context, results, queryInfo) } - - /* Do some logging and catch HTTP errors. */ - console.log(`Submitting result log to DRES...`); - return this._dresLog.postApiV1LogResult(this._sessionId, submission).pipe( - tap(o => { - console.log(`Successfully submitted result log to DRES!`); - }), - catchError((err) => { - return of(`Failed to submit segment to DRES due to a HTTP error (${err.status}).`) - }) - ); - }) - ).subscribe(); - - /* Heavily duplicated code from above */ - this._temporalResultsSubscription = combineLatest([temporalResultsSubscription, this._eventbus.currentView(), this._eventbus.lastQuery()]).pipe( - filter(([results, context, queryInfo]) => context === TemporalListComponent.COMPONENT_NAME), - map(([results, context, queryInfo]) => DresTypeConverter.mapTemporalScoreContainer(context, results, queryInfo)), + }), filter(submission => submission != null), mergeMap((submission: QueryResultLog) => { + console.log(`logging result log`) this._resultsLogTable.add(submission) /* Stop if no sessionId is set */ @@ -266,7 +249,7 @@ export class VbsSubmissionService { } /* Do some logging and catch HTTP errors. */ - console.log(`Submitting temporal result log to DRES...`); + console.log(`Submitting result log to DRES...`); return this._dresLog.postApiV1LogResult(this._sessionId, submission).pipe( tap(o => { console.log(`Successfully submitted result log to DRES!`); @@ -283,31 +266,36 @@ export class VbsSubmissionService { this._submitSubscription = this._submitSubject.pipe( map(([segment, time]): [string, number?] => this.convertToAppropriateRepresentation(segment, time)), mergeMap(([segment, frame]) => { + /* Stop if no sessionId is set */ + if (!this._sessionId) { + return EMPTY + } + /* Submit, do some logging and catch HTTP errors. */ return this._dresSubmit.getApiV1Submit(null, segment, null, frame).pipe( - tap((status: SuccessfulSubmissionsStatus) => { - this.handleSubmissionResponse(status); - }), - catchError(err => { - return this.handleSubmissionError(err); - }) + tap((status: SuccessfulSubmissionsStatus) => { + this.handleSubmissionResponse(status); + }), + catchError(err => { + return this.handleSubmissionError(err); + }) ) }) - ).subscribe() + ).subscribe(); /* Setup submission subscription, which is triggered manually. */ this._submitTextSubscription = this._submitTextSubject.pipe( - mergeMap((text) => { - /* Submit, do some logging and catch HTTP errors. */ - return this._dresSubmit.getApiV1Submit(null, null, text).pipe( - tap((status: SuccessfulSubmissionsStatus) => { - this.handleSubmissionResponse(status); - }), - catchError(err => { - return this.handleSubmissionError(err); - }) - ) - }) + mergeMap((text) => { + /* Submit, do some logging and catch HTTP errors. */ + return this._dresSubmit.getApiV1Submit(null, null, text).pipe( + tap((status: SuccessfulSubmissionsStatus) => { + this.handleSubmissionResponse(status); + }), + catchError(err => { + return this.handleSubmissionError(err); + }) + ) + }) ).subscribe() } diff --git a/src/config.json b/src/config.json index 0af6498a..20e7e879 100644 --- a/src/config.json +++ b/src/config.json @@ -1,42 +1,131 @@ { + "title": "LSC21 - vitrivr", + "api": { + "host": "10.34.58.176" + }, + "resources": { + "host_thumbnails": "http://10.34.58.176/lsc21/lsc21-thumbnails/:o/:s.jpg", + "host_objects": "http://10.34.58.176/lsc21/lsc21-image/:n/:S.jpg" + }, "competition": { "host": "", - "tls": false, "log": true, - "lsc": false, - "vbs": false + "lsc": true, + "vbs": false, + "tls": true }, "mlt": { - "VIDEO": [ - "visualtextcoembedding" - ], "IMAGE": [ - "visualtextcoembedding" + "clipimage" + ], + "IMAGE_SEQUENCE": [ + "clipimage" ] }, "query": { + "temporal_mode": "TEMPORAL_DISTANCE", + "temporal_max_length": 86400, + "default_temporal_distance": 1800, "history": 0, "options": { "image": true, "audio": false, "model3d": false, "text": true, - "tag": false, + "tag": true, "semantic": true, "boolean": true, + "map": true, "skeleton": false }, "text": { "categories": [ + [ + "ocr", + "Text on Screen" + ], [ "visualtextcoembedding", "Text Co-Embedding" + ], + [ + "clip", + "CLIP" ] ] }, "boolean": [ { - "display": "Segment Id", + "display": "Hour", + "input": "RANGE", + "table": "features_table_lsc20meta", + "col": "p_hour", + "range": [0, 23] + }, + { + "display": "Weekday", + "input": "OPTIONS", + "table": "features_table_lsc20meta", + "col": "p_day_of_week", + "options": [ + "MONDAY", + "TUESDAY", + "WEDNESDAY", + "THURSDAY", + "FRIDAY", + "SATURDAY", + "SUNDAY" + ] + }, + { + "display": "Day of Month", + "input": "DYNAMICOPTIONS", + "table": "features_table_lsc20meta", + "col": "p_day", + "type": "number" + }, + { + "display": "Location", + "input": "DYNAMICOPTIONS", + "table": "features_table_lsc20meta", + "col": "semantic_name" + }, + { + "display": "Year", + "input": "DYNAMICOPTIONS", + "table": "features_table_lsc20meta", + "col": "p_year", + "type": "number" + }, + { + "display": "Month", + "input": "DYNAMICOPTIONS", + "table": "features_table_lsc20meta", + "col": "p_month" + }, + { + "display": "Transport", + "input": "DYNAMICOPTIONS", + "table": "features_table_lsc20meta", + "col": "activity_type" + }, + { + "display": "Timezone", + "input": "DYNAMICOPTIONS", + "table": "features_table_lsc20meta", + "col": "timezone" + }, + { + "display": "Segment Id [Meta]", + "input": "TEXT", + "table": "features_table_lsc20meta", + "col": "id", + "operators": [ + "=" + ] + }, + { + "display": "Segment Id [Segments]", "input": "TEXT", "table": "cineast_segment", "col": "segmentid", @@ -45,7 +134,7 @@ ] }, { - "display": "Object Id", + "display": "Object Id [Segments]", "input": "TEXT", "table": "cineast_segment", "col": "objectid", @@ -58,13 +147,30 @@ "refinement": { "filters": [ [ - "dominantcolor.color", + "LSCMETA.semantic_name", + "CHECKBOX" + ], + [ + "LSCMETA.timezone", "CHECKBOX" ], [ - "technical.duration", + "LSCMETA.p_day_of_week", + "CHECKBOX" + ], + [ + "LSCMETA.p_hour", "SLIDER" + ], + [ + "LSCMETA.p_month", + "CHECKBOX" + ], + [ + "LSCMETA.p_year", + "CHECKBOX" ] ] } } + From 35f51003775ac4bfd6b13f4a932bc65f5caf3d40 Mon Sep 17 00:00:00 2001 From: Silvan Heller Date: Tue, 24 May 2022 14:52:07 +0200 Subject: [PATCH 02/10] reverting config changes --- src/config.json | 130 +++++------------------------------------------- 1 file changed, 12 insertions(+), 118 deletions(-) diff --git a/src/config.json b/src/config.json index 20e7e879..0af6498a 100644 --- a/src/config.json +++ b/src/config.json @@ -1,131 +1,42 @@ { - "title": "LSC21 - vitrivr", - "api": { - "host": "10.34.58.176" - }, - "resources": { - "host_thumbnails": "http://10.34.58.176/lsc21/lsc21-thumbnails/:o/:s.jpg", - "host_objects": "http://10.34.58.176/lsc21/lsc21-image/:n/:S.jpg" - }, "competition": { "host": "", + "tls": false, "log": true, - "lsc": true, - "vbs": false, - "tls": true + "lsc": false, + "vbs": false }, "mlt": { - "IMAGE": [ - "clipimage" + "VIDEO": [ + "visualtextcoembedding" ], - "IMAGE_SEQUENCE": [ - "clipimage" + "IMAGE": [ + "visualtextcoembedding" ] }, "query": { - "temporal_mode": "TEMPORAL_DISTANCE", - "temporal_max_length": 86400, - "default_temporal_distance": 1800, "history": 0, "options": { "image": true, "audio": false, "model3d": false, "text": true, - "tag": true, + "tag": false, "semantic": true, "boolean": true, - "map": true, "skeleton": false }, "text": { "categories": [ - [ - "ocr", - "Text on Screen" - ], [ "visualtextcoembedding", "Text Co-Embedding" - ], - [ - "clip", - "CLIP" ] ] }, "boolean": [ { - "display": "Hour", - "input": "RANGE", - "table": "features_table_lsc20meta", - "col": "p_hour", - "range": [0, 23] - }, - { - "display": "Weekday", - "input": "OPTIONS", - "table": "features_table_lsc20meta", - "col": "p_day_of_week", - "options": [ - "MONDAY", - "TUESDAY", - "WEDNESDAY", - "THURSDAY", - "FRIDAY", - "SATURDAY", - "SUNDAY" - ] - }, - { - "display": "Day of Month", - "input": "DYNAMICOPTIONS", - "table": "features_table_lsc20meta", - "col": "p_day", - "type": "number" - }, - { - "display": "Location", - "input": "DYNAMICOPTIONS", - "table": "features_table_lsc20meta", - "col": "semantic_name" - }, - { - "display": "Year", - "input": "DYNAMICOPTIONS", - "table": "features_table_lsc20meta", - "col": "p_year", - "type": "number" - }, - { - "display": "Month", - "input": "DYNAMICOPTIONS", - "table": "features_table_lsc20meta", - "col": "p_month" - }, - { - "display": "Transport", - "input": "DYNAMICOPTIONS", - "table": "features_table_lsc20meta", - "col": "activity_type" - }, - { - "display": "Timezone", - "input": "DYNAMICOPTIONS", - "table": "features_table_lsc20meta", - "col": "timezone" - }, - { - "display": "Segment Id [Meta]", - "input": "TEXT", - "table": "features_table_lsc20meta", - "col": "id", - "operators": [ - "=" - ] - }, - { - "display": "Segment Id [Segments]", + "display": "Segment Id", "input": "TEXT", "table": "cineast_segment", "col": "segmentid", @@ -134,7 +45,7 @@ ] }, { - "display": "Object Id [Segments]", + "display": "Object Id", "input": "TEXT", "table": "cineast_segment", "col": "objectid", @@ -147,30 +58,13 @@ "refinement": { "filters": [ [ - "LSCMETA.semantic_name", - "CHECKBOX" - ], - [ - "LSCMETA.timezone", + "dominantcolor.color", "CHECKBOX" ], [ - "LSCMETA.p_day_of_week", - "CHECKBOX" - ], - [ - "LSCMETA.p_hour", + "technical.duration", "SLIDER" - ], - [ - "LSCMETA.p_month", - "CHECKBOX" - ], - [ - "LSCMETA.p_year", - "CHECKBOX" ] ] } } - From 3dbfa50770ac90f5f99901c9823cb2b39aef2c60 Mon Sep 17 00:00:00 2001 From: Silvan Heller Date: Wed, 25 May 2022 17:49:40 +0200 Subject: [PATCH 03/10] improving local logging, clearer package structure --- src/app/core/basics/database.service.ts | 7 +- src/app/core/basics/event-bus.service.ts | 10 +- src/app/core/basics/notification.service.ts | 2 +- .../collabordinator.service.ts | 0 .../competition.module.ts} | 2 +- .../dres-type-converter.util.ts | 0 .../competition/logging/filter-information.ts | 50 +++++++ .../competition/logging/result-log-item.ts | 8 ++ .../logging/segment-score-log-container.ts | 15 ++ .../vbs-submission.service.ts | 81 ++++++++--- src/app/core/core.module.ts | 8 +- src/app/core/queries/filter.service.ts | 114 ++++++--------- src/app/core/queries/query.service.ts | 99 +++---------- src/app/core/selection/selection.service.ts | 2 +- .../objectdetails/objectdetails.component.ts | 2 +- .../objectdetails/quick-viewer.component.ts | 2 +- ...abstract-segment-results-view.component.ts | 2 +- src/app/results/gallery/gallery.module.ts | 4 +- src/app/results/list/list.module.ts | 4 +- .../result-segment-preview-tile.component.ts | 2 +- .../result-segment-preview-tile.module.ts | 4 +- .../results/temporal/temporal-list.module.ts | 4 +- .../preferences/preferences.component.html | 34 +++-- .../preferences/preferences.component.ts | 133 ++++++------------ .../refinement/refinement.component.ts | 4 +- .../textual-submission.component.ts | 2 +- .../video/advanced-media-player.component.ts | 2 +- .../video/advanced-video-player.module.ts | 4 +- .../pipes/util/competition-enabled.pipe.ts | 2 +- src/app/toolbar/ping.component.ts | 2 +- src/config.json | 130 +++++++++++++++-- 31 files changed, 419 insertions(+), 316 deletions(-) rename src/app/core/{vbs => competition}/collabordinator.service.ts (100%) rename src/app/core/{vbs/vbs.module.ts => competition/competition.module.ts} (95%) rename src/app/core/{vbs => competition}/dres-type-converter.util.ts (100%) create mode 100644 src/app/core/competition/logging/filter-information.ts create mode 100644 src/app/core/competition/logging/result-log-item.ts create mode 100644 src/app/core/competition/logging/segment-score-log-container.ts rename src/app/core/{vbs => competition}/vbs-submission.service.ts (81%) diff --git a/src/app/core/basics/database.service.ts b/src/app/core/basics/database.service.ts index 19e06e37..84baed23 100644 --- a/src/app/core/basics/database.service.ts +++ b/src/app/core/basics/database.service.ts @@ -12,9 +12,10 @@ export class DatabaseService { constructor() { DatabaseService.DB.version(1).stores({ history: '++id,timestamp', - log_results: '++id,log', - log_interaction: '++id,log', - log_submission: '++id,log' + log_results_dres: '++id,log', + log_interaction_dres: '++id,log', + log_submission_dres: '++id,log', + log_results: '++id,entry', }); } diff --git a/src/app/core/basics/event-bus.service.ts b/src/app/core/basics/event-bus.service.ts index 8b355194..978859b9 100644 --- a/src/app/core/basics/event-bus.service.ts +++ b/src/app/core/basics/event-bus.service.ts @@ -17,7 +17,7 @@ export class EventBusService { private _currentView: Subject = new BehaviorSubject(null); /** The subject used to track the currently active view. */ - private _lastQuery: Subject = new BehaviorSubject(null); + private _lastQuery = new BehaviorSubject(null); /** * Publishes a nev InteractionEvent to the bus. @@ -44,12 +44,10 @@ export class EventBusService { } /** - * Returns an observable that allows a consumer to be informed about the last query issued. - * - * @return {Observable} + * Returns the latest query */ - public lastQuery(): Observable { - return this._lastQuery.asObservable() + public lastQueryInteractionEvent(): InteractionEvent { + return this._lastQuery.getValue() } /** diff --git a/src/app/core/basics/notification.service.ts b/src/app/core/basics/notification.service.ts index ca58eb0c..e6b6505c 100644 --- a/src/app/core/basics/notification.service.ts +++ b/src/app/core/basics/notification.service.ts @@ -1,6 +1,6 @@ import {Injectable} from '@angular/core'; import {BehaviorSubject, combineLatest, of} from 'rxjs'; -import {VbsSubmissionService} from '../vbs/vbs-submission.service'; +import {VbsSubmissionService} from '../competition/vbs-submission.service'; import {NotificationUtil} from '../../shared/util/notification.util'; import {catchError, tap} from 'rxjs/operators'; import {AppConfig} from '../../app.config'; diff --git a/src/app/core/vbs/collabordinator.service.ts b/src/app/core/competition/collabordinator.service.ts similarity index 100% rename from src/app/core/vbs/collabordinator.service.ts rename to src/app/core/competition/collabordinator.service.ts diff --git a/src/app/core/vbs/vbs.module.ts b/src/app/core/competition/competition.module.ts similarity index 95% rename from src/app/core/vbs/vbs.module.ts rename to src/app/core/competition/competition.module.ts index 5ddaa0eb..39cd7fe7 100644 --- a/src/app/core/vbs/vbs.module.ts +++ b/src/app/core/competition/competition.module.ts @@ -13,4 +13,4 @@ import {AppConfig} from '../../app.config'; declarations: [], providers: [VbsSubmissionService, CollabordinatorService] }) -export class VbsModule { } +export class CompetitionModule { } diff --git a/src/app/core/vbs/dres-type-converter.util.ts b/src/app/core/competition/dres-type-converter.util.ts similarity index 100% rename from src/app/core/vbs/dres-type-converter.util.ts rename to src/app/core/competition/dres-type-converter.util.ts diff --git a/src/app/core/competition/logging/filter-information.ts b/src/app/core/competition/logging/filter-information.ts new file mode 100644 index 00000000..b0165334 --- /dev/null +++ b/src/app/core/competition/logging/filter-information.ts @@ -0,0 +1,50 @@ +import {MediaObjectDescriptor} from "../../../../../openapi/cineast"; +import {ColorLabel} from "../../../shared/model/misc/colorlabel.model"; +import {Tag} from "../../selection/tag.model"; + +/** + * A simple data class for logging + */ +export class FilterInformation { + + /** + * When set to true, objects who have metadata matching for any of the categories are displayed. + */ + _useOrForMetadataCategoriesFilter = false; + + _id: string; + + /** + * A filter by MediaType. Affects both MediaObjectScoreContainers and MediaSegmentScoreContainers. If non-empty, only objects + * that match one of the MediaTypes contained in this array will pass the filter. + */ + _mediatypes: Map = new Map(); + + /** + * A filter by dominant color. Affects only MediaSegmentScoreContainers. If non-empty, only segments + * that match at least one of the dominant colors contained in this array will pass the filter. + */ + _dominant: Map = new Map(); + + /** + * A filter by metadata. For each metadata category (e.g. day), a list of allowed values is kept. + * If empty, all results are displayed. If non-empty, only results are displayed where for every key in the filter, the metadata value of the object matches an allowed value in the list. + * Behavior across categories is determined by a different boolean. + * If the object does not have one of the metadata keys, it is filtered. + */ + _filterMetadata: Map> = new Map(); + + /** + * A filter for tags. This is the list of allowed tag names. If the set is empty, no filter is applied. + */ + _filterTags: Set = new Set(); + + /** + * A filter by metadata for numeric values. + * For each category, a min and max number is kept (or null) + */ + _filterRangeMetadata: Map = new Map(); + + /** Threshold for score filtering. */ + _threshold = 0.0; +} \ No newline at end of file diff --git a/src/app/core/competition/logging/result-log-item.ts b/src/app/core/competition/logging/result-log-item.ts new file mode 100644 index 00000000..da04f369 --- /dev/null +++ b/src/app/core/competition/logging/result-log-item.ts @@ -0,0 +1,8 @@ +import {FilterInformation} from "./filter-information"; +import {SegmentScoreLogContainer} from "./segment-score-log-container"; + +export interface ResultLogItem { + filter: FilterInformation + query: any, + results: SegmentScoreLogContainer[], +} \ No newline at end of file diff --git a/src/app/core/competition/logging/segment-score-log-container.ts b/src/app/core/competition/logging/segment-score-log-container.ts new file mode 100644 index 00000000..1dd40b0d --- /dev/null +++ b/src/app/core/competition/logging/segment-score-log-container.ts @@ -0,0 +1,15 @@ +export class SegmentScoreLogContainer { + public readonly objectId: string; + public readonly segmentId: string; + public readonly startabs: number; + public readonly endabs: number; + public readonly _score: number; + + constructor(objectId: string, segmentId: string, startabs: number, endabs: number, score: number) { + this.objectId = objectId; + this.segmentId = segmentId; + this.startabs = startabs; + this.endabs = endabs; + this._score = score; + } +} \ No newline at end of file diff --git a/src/app/core/vbs/vbs-submission.service.ts b/src/app/core/competition/vbs-submission.service.ts similarity index 81% rename from src/app/core/vbs/vbs-submission.service.ts rename to src/app/core/competition/vbs-submission.service.ts index 6dbff8fc..0dd6b018 100644 --- a/src/app/core/vbs/vbs-submission.service.ts +++ b/src/app/core/competition/vbs-submission.service.ts @@ -16,6 +16,9 @@ import {AppConfig} from '../../app.config'; import {MetadataService} from '../../../../openapi/cineast'; import {LogService, QueryEventLog, QueryResultLog, SubmissionService, SuccessfulSubmissionsStatus, UserDetails, UserService} from '../../../../openapi/dres'; import {TemporalListComponent} from '../../results/temporal/temporal-list.component'; +import {FilterService} from "../queries/filter.service"; +import {ResultLogItem} from "./logging/result-log-item"; +import {SegmentScoreLogContainer} from "./logging/segment-score-log-container"; /** * This service is used to submit segments to VBS web-service for the Video Browser Showdown challenge. Furthermore, if @@ -41,14 +44,17 @@ export class VbsSubmissionService { /** Reference to the subscription that is used to submit interaction logs on a regular basis to the VBS server. */ private _interactionlogSubscription: Subscription; - /** Table for persisting result logs. */ - private _resultsLogTable: Dexie.Table; + /** Table for persisting our result logs. */ + private _dresResultsLogTable: Dexie.Table; - /** Table for persisting submission logs */ - private _submissionLogTable: Dexie.Table; + /** Table for persisting DRES result logs. */ + private _resultsLogTable: Dexie.Table; - /** Table for persisting interaction logs. */ - private _interactionLogTable: Dexie.Table; + /** Table for persisting DRES submission logs */ + private _dresSubmissionLogTable: Dexie.Table; + + /** Table for persisting DRES interaction logs. */ + private _dresInteractionLogTable: Dexie.Table; /** Internal flag used to determine if VBS competition is running. */ private _vbs = false; @@ -75,7 +81,8 @@ export class VbsSubmissionService { private _dresSubmit: SubmissionService, private _dresLog: LogService, private _dresUser: UserService, - _db: DatabaseService) { + _db: DatabaseService, + private _filterService: FilterService) { /* This subscription registers the event-mapping, recording and submission stream if the VBS mode is active and un-registers it, if it is switched off! */ _config.configAsObservable.subscribe(config => { @@ -84,9 +91,10 @@ export class VbsSubmissionService { } this._log = config._config.competition.log if (this._log) { + this._dresResultsLogTable = _db.db.table('log_results_dres'); this._resultsLogTable = _db.db.table('log_results'); - this._interactionLogTable = _db.db.table('log_interaction'); - this._submissionLogTable = _db.db.table('log_submission'); + this._dresInteractionLogTable = _db.db.table('log_interaction_dres'); + this._dresSubmissionLogTable = _db.db.table('log_submission_dres'); this.reset(config) } if (config?.dresEndpointRest) { @@ -159,7 +167,7 @@ export class VbsSubmissionService { */ public submit(segment: MediaSegmentScoreContainer, time: number) { if (this._log) { - this._submissionLogTable.add([segment.segmentId, time]) + this._dresSubmissionLogTable.add([segment.segmentId, time]) } console.debug(`Submitting segment ${segment.segmentId} @ ${time}`); this._submitSubject.next([segment, time]); @@ -194,7 +202,7 @@ export class VbsSubmissionService { }), filter(submission => submission != null), mergeMap((submission: QueryEventLog) => { - this._interactionLogTable.add(submission); + this._dresInteractionLogTable.add(submission); /* Stop if no sessionId is set */ if (!this._sessionId) { @@ -215,7 +223,7 @@ export class VbsSubmissionService { ).subscribe(); /* Setup results subscription, which is triggered upon change to the result set. */ - const resultSubscription = this._queryService.observable.pipe( + const resultSubscription: Observable = this._queryService.observable.pipe( filter(f => f === 'ENDED'), mergeMap(f => this._queryService.results.segmentsAsObservable), debounceTime(1000) @@ -228,20 +236,55 @@ export class VbsSubmissionService { debounceTime(1000) ); /* IMPORTANT: Limits the number of submissions to one per second. */ - - this._resultsSubscription = combineLatest([resultSubscription, temporalResultsSubscription, this._eventbus.currentView(), this._eventbus.lastQuery()]).pipe( - map(([results, temporalResults, context, queryInfo]) => { + /* Setup results subscription, which is triggered upon change to the result set. */ + const filterSubscription = this._filterService.filterSubject.pipe( + debounceTime(1000) + ); /* IMPORTANT: Limits the number of filter updates to one per second. */ + + this._resultsSubscription = combineLatest([resultSubscription, temporalResultsSubscription, this._eventbus.currentView(), filterSubscription]).pipe( + debounceTime(200), + filter(() => { + if (this._eventbus.lastQueryInteractionEvent() === null) { + console.error('no query logged for interaction logging, not logging anything') + return false + } + if (this._queryService.lastQueryIssued() === null) { + console.error('no query logged in query service, not logging anything') + return false + } + return true + }), + tap(([results, temporalResults, context, filterInfo]) => { + console.log(`logging results`); + const query = this._queryService.lastQueryIssued() + let logResults: SegmentScoreLogContainer[] + switch (context) { + case TemporalListComponent.COMPONENT_NAME: + logResults = temporalResults.flatMap(seq => seq.segments.map(c => new SegmentScoreLogContainer(seq.object.objectid, c.segmentId, c.startabs, c.endabs, seq.score))) + break; + default: + logResults = results.map(c => new SegmentScoreLogContainer(c.objectId, c.segmentId, c.startabs, c.endabs, c.score)) + } + console.log(logResults) + const logItem: ResultLogItem = { + filter: filterInfo, + query: query, + results: logResults + }; + console.log(logItem) + this._resultsLogTable.add(logItem); + }), + map(([results, temporalResults, context, filterInfo]) => { + const query = this._eventbus.lastQueryInteractionEvent() switch (context) { case TemporalListComponent.COMPONENT_NAME: - return DresTypeConverter.mapTemporalScoreContainer(context, temporalResults, queryInfo) + return DresTypeConverter.mapTemporalScoreContainer(context, temporalResults, query) default: - return DresTypeConverter.mapSegmentScoreContainer(context, results, queryInfo) + return DresTypeConverter.mapSegmentScoreContainer(context, results, query) } }), filter(submission => submission != null), mergeMap((submission: QueryResultLog) => { - console.log(`logging result log`) - this._resultsLogTable.add(submission) /* Stop if no sessionId is set */ if (!this._sessionId) { diff --git a/src/app/core/core.module.ts b/src/app/core/core.module.ts index e589fac5..85651e8c 100644 --- a/src/app/core/core.module.ts +++ b/src/app/core/core.module.ts @@ -1,8 +1,8 @@ import {NgModule} from '@angular/core'; import {LookupModule} from './lookup/lookup.module'; import {BasicModule} from './basics/basic.module'; -import {VbsSubmissionService} from './vbs/vbs-submission.service'; -import {VbsModule} from './vbs/vbs.module'; +import {VbsSubmissionService} from './competition/vbs-submission.service'; +import {CompetitionModule} from './competition/competition.module'; import {SelectionModule} from './selection/selection.module'; import {SelectionService} from './selection/selection.service'; import {WebSocketFactoryService} from './api/web-socket-factory.service'; @@ -16,8 +16,8 @@ import {AppConfig} from '../app.config'; return new Configuration({ basePath: `${AppConfig.settings.cineastEndpointRest}` }) - }), LookupModule, BasicModule, VbsModule, SelectionModule, QueryModule], - exports: [ApiModule, LookupModule, BasicModule, VbsModule, SelectionModule, QueryModule], + }), LookupModule, BasicModule, CompetitionModule, SelectionModule, QueryModule], + exports: [ApiModule, LookupModule, BasicModule, CompetitionModule, SelectionModule, QueryModule], declarations: [], providers: [WebSocketFactoryService, VbsSubmissionService, SelectionService, PreviousRouteService] }) diff --git a/src/app/core/queries/filter.service.ts b/src/app/core/queries/filter.service.ts index 01c4c1fb..9dd2e1d6 100644 --- a/src/app/core/queries/filter.service.ts +++ b/src/app/core/queries/filter.service.ts @@ -6,6 +6,7 @@ import {ColorLabel, ColorLabels} from '../../shared/model/misc/colorlabel.model' import {SelectionService} from '../selection/selection.service'; import {Tag} from '../selection/tag.model'; import {MediaObjectDescriptor} from '../../../../openapi/cineast'; +import {FilterInformation} from "../competition/logging/filter-information"; /** @@ -15,11 +16,6 @@ import {MediaObjectDescriptor} from '../../../../openapi/cineast'; */ @Injectable() export class FilterService { - /** - * When set to true, objects who have metadata matching for any of the categories are displayed. - */ - public _useOrForMetadataCategoriesFilter = false; - _id: string; /** A BehaviorSubject that publishes changes to the filters affecting SegmentScoreContainers. */ _segmentFilters: BehaviorSubject<((v: MediaSegmentScoreContainer) => boolean)[]> = new BehaviorSubject([]); @@ -27,87 +23,58 @@ export class FilterService { /** A BehaviorSubject that publishes changes to the filters affecting MediaObjectScoreContainers. */ _objectFilters: BehaviorSubject<((v: MediaObjectScoreContainer) => boolean)[]> = new BehaviorSubject([]); - /** - * A filter by MediaType. Affects both MediaObjectScoreContainers and MediaSegmentScoreContainers. If non-empty, only objects - * that match one of the MediaTypes contained in this array will pass the filter. - */ - private _mediatypes: Map = new Map(); - - /** - * A filter by dominant color. Affects only MediaSegmentScoreContainers. If non-empty, only segments - * that match at least one of the dominant colors contained in this array will pass the filter. - */ - private _dominant: Map = new Map(); + _filters: FilterInformation = new FilterInformation(); - /** - * A filter by metadata. For each metadata category (e.g. day), a list of allowed values is kept. - * If empty, all results are displayed. If non-empty, only results are displayed where for every key in the filter, the metadata value of the object matches an allowed value in the list. - * Behavior across categories is determined by a different boolean. - * If the object does not have one of the metadata keys, it is filtered. - */ - private _filterMetadata: Map> = new Map(); - - /** - * A filter for tags. This is the list of allowed tag names. If the set is empty, no filter is applied. - */ - private _filterTags: Set = new Set(); - - /** - * A filter by metadata for numeric values. - * For each category, a min and max number is kept (or null) - */ - private _filterRangeMetadata: Map = new Map(); + filterSubject: BehaviorSubject = new BehaviorSubject(this._filters); - /** Threshold for score filtering. */ - private _threshold = 0.0; constructor(private _selectionService: SelectionService) { - Object.keys(MediaObjectDescriptor.MediatypeEnum).map(key => MediaObjectDescriptor.MediatypeEnum[key]).forEach(v => this._mediatypes.set(v, false)); - ColorLabels.forEach(v => this._dominant.set(v, false)); + Object.keys(MediaObjectDescriptor.MediatypeEnum).map(key => MediaObjectDescriptor.MediatypeEnum[key]).forEach(v => this._filters._mediatypes.set(v, false)); + ColorLabels.forEach(v => this._filters._dominant.set(v, false)); } /** * Returns an editable map of the mediatypes that should be used for filtering */ get mediatypes(): Map { - return this._mediatypes; + return this._filters._mediatypes; } /** * Returns an editable map of the colorlabels that should be used for filtering */ get dominant(): Map { - return this._dominant; + return this._filters._dominant; } /** * Returns an editable map of the metadata that should be used for filtering */ get filterMetadata(): Map> { - return this._filterMetadata + return this._filters._filterMetadata } /** * Returns the editable set of tags used for filtering */ get filterTags(): Set { - return this._filterTags; + return this._filters._filterTags; } /** * Returns an editable map of the metadata that should be used for filtering with ranges */ get filterRangeMetadata(): Map { - return this._filterRangeMetadata + return this._filters._filterRangeMetadata } get threshold(): number { - return this._threshold; + return this._filters._threshold; } set threshold(value: number) { if (value >= 0.0 && value <= 1.0) { - this._threshold = value; + this._filters._threshold = value; } } @@ -115,27 +82,27 @@ export class FilterService { * Returns a copy of the list of MediaTypes that should be used for filtering. */ get mediatypeKeys(): MediaObjectDescriptor.MediatypeEnum[] { - return Array.from(this._mediatypes.keys()); + return Array.from(this._filters._mediatypes.keys()); } /** * Returns a copy of the list of colors that should be used for filtering. */ get dominantKeys(): ColorLabel[] { - return Array.from(this._dominant.keys()); + return Array.from(this._filters._dominant.keys()); } /** * Clears all filters. Causes an update to be published. */ public clear() { - this._mediatypes.forEach((v, k) => this._mediatypes.set(k, false)); - this._dominant.forEach((v, k) => this._dominant.set(k, false)); - this._filterMetadata.clear(); - this._filterRangeMetadata.clear(); - this._filterTags.clear(); - this._threshold = 0.0; - this._id = null + this._filters._mediatypes.forEach((v, k) => this._filters._mediatypes.set(k, false)); + this._filters._dominant.forEach((v, k) => this._filters._dominant.set(k, false)); + this._filters._filterMetadata.clear(); + this._filters._filterRangeMetadata.clear(); + this._filters._filterTags.clear(); + this._filters._threshold = 0.0; + this._filters._id = null this.update() } @@ -144,8 +111,8 @@ export class FilterService { * Clear metadata filters. Causes an update to be published */ public clearMetadata() { - this._filterMetadata.clear(); - this._filterRangeMetadata.clear(); + this._filters._filterMetadata.clear(); + this._filters._filterRangeMetadata.clear(); this.update(); } @@ -157,9 +124,9 @@ export class FilterService { const objectFilters: ((v: MediaObjectScoreContainer) => boolean)[] = []; const segmentFilters: ((v: MediaSegmentScoreContainer) => boolean)[] = []; - if (this._id) { - objectFilters.push((o) => o.objectid === this._id) - segmentFilters.push((s) => s.objectId === this._id || s.segmentId === this._id) + if (this._filters._id) { + objectFilters.push((o) => o.objectid === this._filters._id) + segmentFilters.push((s) => s.objectId === this._filters._id || s.segmentId === this._filters._id) } /* Inline function for range metadata filters @@ -192,7 +159,7 @@ export class FilterService { return true } - if (!(this._filterMetadata.size === 0 && this._filterRangeMetadata.size === 0 && this._filterTags.size === 0)) { + if (!(this._filters._filterMetadata.size === 0 && this._filters._filterRangeMetadata.size === 0 && this._filters._filterTags.size === 0)) { console.debug(`updating filters`); // of course, the AND filter can only be falsified by non-matching filter criteria while the OR filter can only be set to true if one of the conditions match @@ -204,7 +171,7 @@ export class FilterService { let andFilter = Boolean(true); let orFilter = Boolean(false); let tagFilter = Boolean(false); - this._filterMetadata.forEach((mdAllowedValuesSet, mdKey) => { + this._filters._filterMetadata.forEach((mdAllowedValuesSet, mdKey) => { // check if either one of the underlying segments or the object itself has appropriate metadata if (obj.segments.findIndex(seg => mdAllowedValuesSet.has(seg.metadata.get(mdKey))) >= 0 || mdAllowedValuesSet.has(obj._metadata.get(mdKey))) { orFilter = true; @@ -212,7 +179,7 @@ export class FilterService { } andFilter = false; }); - this._filterRangeMetadata.forEach((range, mdKey) => { + this._filters._filterRangeMetadata.forEach((range, mdKey) => { // check if one of the segments fulfills both range conditions if (obj.segments.findIndex(seg => checkRange(range, seg.metadata.get(mdKey))) >= 0) { orFilter = true; @@ -225,46 +192,46 @@ export class FilterService { } andFilter = false; }); - if (this._filterTags.size === 0) { + if (this._filters._filterTags.size === 0) { tagFilter = true; } - this._filterTags.forEach(tag => { + this._filters._filterTags.forEach(tag => { if (obj.segments.findIndex(seg => this._selectionService.hasTag(seg.segmentId, tag)) >= 0) { tagFilter = true; } }); - return this._useOrForMetadataCategoriesFilter ? orFilter && tagFilter : andFilter && tagFilter; + return this._filters._useOrForMetadataCategoriesFilter ? orFilter && tagFilter : andFilter && tagFilter; }); segmentFilters.push((seg) => { let andFilter = Boolean(true); let orFilter = Boolean(false); // check whether the segment or the corresponding object has appropriate metadata - this._filterMetadata.forEach((mdAllowedValuesSet, mdKey) => { + this._filters._filterMetadata.forEach((mdAllowedValuesSet, mdKey) => { if (mdAllowedValuesSet.has(seg.metadata.get(mdKey)) || mdAllowedValuesSet.has(seg.objectScoreContainer._metadata.get(mdKey))) { orFilter = true; return; } andFilter = false; }); - this._filterRangeMetadata.forEach((range, mdKey) => { + this._filters._filterRangeMetadata.forEach((range, mdKey) => { if (checkRange(range, seg.metadata.get(mdKey)) || checkRange(range, seg.objectScoreContainer._metadata.get(mdKey))) { orFilter = true; return; } andFilter = false; }); - return this._useOrForMetadataCategoriesFilter ? orFilter : andFilter; + return this._filters._useOrForMetadataCategoriesFilter ? orFilter : andFilter; }); } - if (!this.mediatypeKeys.every(v => this._mediatypes.get(v) === false)) { - objectFilters.push((obj) => this._mediatypes.get(obj.mediatype) === true); - segmentFilters.push((seg) => this._mediatypes.get(seg.objectScoreContainer.mediatype) === true); + if (!this.mediatypeKeys.every(v => this._filters._mediatypes.get(v) === false)) { + objectFilters.push((obj) => this._filters._mediatypes.get(obj.mediatype) === true); + segmentFilters.push((seg) => this._filters._mediatypes.get(seg.objectScoreContainer.mediatype) === true); } - if (!this.dominantKeys.every(v => this._dominant.get(v) === false)) { - segmentFilters.push((seg) => seg.metadata.has('dominantcolor.color') && this._dominant.get(seg.metadata.get('dominantcolor.color').toUpperCase()) === true); + if (!this.dominantKeys.every(v => this._filters._dominant.get(v) === false)) { + segmentFilters.push((seg) => seg.metadata.has('dominantcolor.color') && this._filters._dominant.get(seg.metadata.get('dominantcolor.color').toUpperCase()) === true); } /* Filter for score threshold. */ @@ -276,5 +243,6 @@ export class FilterService { /* Publish changes. */ this._objectFilters.next(objectFilters); this._segmentFilters.next(segmentFilters); + this.filterSubject.next(this._filters) } } diff --git a/src/app/core/queries/query.service.ts b/src/app/core/queries/query.service.ts index db3b9e32..76c6cf70 100644 --- a/src/app/core/queries/query.service.ts +++ b/src/app/core/queries/query.service.ts @@ -10,7 +10,7 @@ import {ResultsContainer} from '../../shared/model/results/scores/results-contai import {NeighboringSegmentQuery} from '../../shared/model/messages/queries/neighboring-segment-query.model'; import {FeatureCategories} from '../../shared/model/results/feature-categories.model'; import {QueryContainerInterface} from '../../shared/model/queries/interfaces/query-container.interface'; -import {filter, first} from 'rxjs/operators'; +import {filter} from 'rxjs/operators'; import {WebSocketFactoryService} from '../api/web-socket-factory.service'; import {SegmentMetadataQueryResult} from '../../shared/model/messages/interfaces/responses/query-result-segment-metadata.interface'; import {ObjectMetadataQueryResult} from '../../shared/model/messages/interfaces/responses/query-result-object-metadata.interface'; @@ -30,8 +30,8 @@ import {AppConfig} from '../../app.config'; import {MediaObjectDescriptor, MediaObjectQueryResult, MediaSegmentDescriptor, MediaSegmentQueryResult, QueryConfig} from '../../../../openapi/cineast'; import {TemporalQuery} from '../../shared/model/messages/queries/temporal-query.model'; import {TemporalQueryResult} from '../../shared/model/messages/interfaces/responses/query-result-temporal.interface'; -import MediatypeEnum = MediaObjectDescriptor.MediatypeEnum; import {ReadableTemporalQueryConfig} from '../../shared/model/messages/queries/readable-temporal-query-config.model'; +import MediatypeEnum = MediaObjectDescriptor.MediatypeEnum; /** * Types of changes that can be emitted from the QueryService. @@ -63,6 +63,9 @@ export class QueryService { /** Flag indicating whether a query is currently being executed. */ private _running = 0; + /** last query which was issued */ + private _lastQuery = null; + constructor(@Inject(HistoryService) private _history, @Inject(WebSocketFactoryService) _factory: WebSocketFactoryService, @Inject(AppConfig) private _config: AppConfig, @@ -70,7 +73,7 @@ export class QueryService { _factory.asObservable().pipe(filter(ws => ws != null)).subscribe(ws => { this._socket = ws; this._socket.pipe( - filter(msg => ['QR_START', 'QR_END', 'QR_ERROR', 'QR_SIMILARITY', 'QR_OBJECT', 'QR_SEGMENT', 'QR_TEMPORAL', 'QR_METADATA_S', 'QR_METADATA_O'].indexOf(msg.messageType) > -1) + filter(msg => ['QR_START', 'QR_END', 'QR_ERROR', 'QR_SIMILARITY', 'QR_OBJECT', 'QR_SEGMENT', 'QR_TEMPORAL', 'QR_METADATA_S', 'QR_METADATA_O'].indexOf(msg.messageType) > -1) ).subscribe((msg: Message) => this.onApiMessage(msg)); }); this._config.configAsObservable.subscribe(config => { @@ -109,73 +112,6 @@ export class QueryService { return this._subject.asObservable(); } - /** - * Starts a new similarity query. Success is indicated by the return value. - * - * Note: Similarity queries can only be started if no query is currently running. - * - * @param containers The list of QueryContainers used to create the query. - * @returns {boolean} true if query was issued, false otherwise. - */ - public findSimilar(containers: QueryContainerInterface[]): boolean { - if (!this._socket) { - console.warn('No socket available, not executing similarity query'); - return false; - } - if (this._running > 0) { - console.warn('There is already a query running'); - } - this._config.configAsObservable.pipe(first()).subscribe(config => { - const query = new TemporalQuery( - containers.map(container => new StagedSimilarityQuery(container.stages, null)), - new ReadableTemporalQueryConfig(null, [], - null, -1), - config.metadataAccessSpec); - this._socket.next(query) - }); - - /** Log Interaction */ - const _components: InteractionEventComponent[] = [] - containers.forEach(container => { - _components.push(new InteractionEventComponent(InteractionEventType.NEW_QUERY_CONTAINER)) - container.stages.forEach(s => { - _components.push(new InteractionEventComponent(InteractionEventType.NEW_QUERY_STAGE)) - s.terms.forEach(t => { - const context: Map = new Map(); - context.set('q:categories', t.categories); - context.set('q:value', 'null') - switch (t.type) { - case 'IMAGE': - _components.push(new InteractionEventComponent(InteractionEventType.QUERY_IMAGE, context)); - return; - case 'AUDIO': - _components.push(new InteractionEventComponent(InteractionEventType.QUERY_AUDIO, context)); - return; - case 'MODEL3D': - _components.push(new InteractionEventComponent(InteractionEventType.QUERY_MODEL3D, context)); - return; - case 'SEMANTIC': - _components.push(new InteractionEventComponent(InteractionEventType.QUERY_SEMANTIC, context)); - return; - case 'TEXT': - context.set('q:value', (t as TextQueryTerm).data); // data = plaintext - _components.push(new InteractionEventComponent(InteractionEventType.QUERY_FULLTEXT, context)); - return; - case 'BOOLEAN': - context.set('q:value', (t as BoolQueryTerm).terms) - _components.push(new InteractionEventComponent(InteractionEventType.QUERY_BOOLEAN, context)); - return; - case 'TAG': - context.set('q:value', (t as TagQueryTerm).tags); - _components.push(new InteractionEventComponent(InteractionEventType.QUERY_TAG, context)); - return; - } - }) - }) - }); - this._eventBusService.publish(new InteractionEvent(..._components)) - } - /** * Starts a new temporal query. Success is indicated by the return value. * @@ -195,11 +131,12 @@ export class QueryService { console.warn('There is already a query running'); } const query = new TemporalQuery(containers.map(container => new StagedSimilarityQuery(container.stages, null)), - new ReadableTemporalQueryConfig(null, - [], - timeDistances, - maxLength), - this._config.config.metadataAccessSpec); + new ReadableTemporalQueryConfig(null, + [], + timeDistances, + maxLength), + this._config.config.metadataAccessSpec); + this._lastQuery = query; this._socket.next(query) /** Log Interaction */ @@ -287,10 +224,12 @@ export class QueryService { return; } _cat - .filter(c => categories.indexOf(c) === -1) - .forEach(c => categories.push(c)); + .filter(c => categories.indexOf(c) === -1) + .forEach(c => categories.push(c)); if (categories.length > 0) { - this._socket.next(new MoreLikeThisQuery(segment.segmentId, categories, {}, config.metadataAccessSpec)); + const query = new MoreLikeThisQuery(segment.segmentId, categories, {}, config.metadataAccessSpec) + this._lastQuery = query; + this._socket.next(query); } return true; @@ -494,4 +433,8 @@ export class QueryService { this._subject.next('ERROR' as QueryChange); console.log('QueryService received error: ' + message.message); } + + public lastQueryIssued() { + return this._lastQuery + } } diff --git a/src/app/core/selection/selection.service.ts b/src/app/core/selection/selection.service.ts index f1818ab8..fda5d4bc 100644 --- a/src/app/core/selection/selection.service.ts +++ b/src/app/core/selection/selection.service.ts @@ -1,7 +1,7 @@ import {Injectable} from '@angular/core'; import {Tag} from './tag.model'; import {BehaviorSubject} from 'rxjs'; -import {CollabordinatorService} from '../vbs/collabordinator.service'; +import {CollabordinatorService} from '../competition/collabordinator.service'; import {CollabordinatorMessage} from '../../shared/model/messages/collaboration/collabordinator-message.model'; import {AppConfig} from '../../app.config'; diff --git a/src/app/objectdetails/objectdetails.component.ts b/src/app/objectdetails/objectdetails.component.ts index 2a6e64ea..9f8a071a 100644 --- a/src/app/objectdetails/objectdetails.component.ts +++ b/src/app/objectdetails/objectdetails.component.ts @@ -20,7 +20,7 @@ import {ObjectviewerComponent} from './objectviewer.component'; import {AppConfig} from '../app.config'; import {MediaSegmentDescriptor, MetadataService, ObjectService, SegmentService, TagService} from '../../../openapi/cineast'; import {SegmentFeaturesComponent} from '../segmentdetails/segment-features.component'; -import {VbsSubmissionService} from '../core/vbs/vbs-submission.service'; +import {VbsSubmissionService} from '../core/competition/vbs-submission.service'; @Component({ diff --git a/src/app/objectdetails/quick-viewer.component.ts b/src/app/objectdetails/quick-viewer.component.ts index 23f35c1e..dd6c4171 100644 --- a/src/app/objectdetails/quick-viewer.component.ts +++ b/src/app/objectdetails/quick-viewer.component.ts @@ -3,7 +3,7 @@ import {MAT_DIALOG_DATA} from '@angular/material/dialog'; import {MediaObjectScoreContainer} from '../shared/model/results/scores/media-object-score-container.model'; import {MediaSegmentScoreContainer} from '../shared/model/results/scores/segment-score-container.model'; import {ResolverService} from '../core/basics/resolver.service'; -import {VbsSubmissionService} from '../core/vbs/vbs-submission.service'; +import {VbsSubmissionService} from '../core/competition/vbs-submission.service'; import * as openseadragon from 'openseadragon'; import {ConfigService} from '../core/basics/config.service'; diff --git a/src/app/results/abstract-segment-results-view.component.ts b/src/app/results/abstract-segment-results-view.component.ts index 818a5eb0..8b303327 100644 --- a/src/app/results/abstract-segment-results-view.component.ts +++ b/src/app/results/abstract-segment-results-view.component.ts @@ -11,7 +11,7 @@ import {Router} from '@angular/router'; import {MatSnackBar} from '@angular/material/snack-bar'; import {ResolverService} from '../core/basics/resolver.service'; import {MatDialog} from '@angular/material/dialog'; -import {VbsSubmissionService} from '../core/vbs/vbs-submission.service'; +import {VbsSubmissionService} from '../core/competition/vbs-submission.service'; import {AppConfig} from '../app.config'; /** diff --git a/src/app/results/gallery/gallery.module.ts b/src/app/results/gallery/gallery.module.ts index d5bd4e07..fa0cb365 100644 --- a/src/app/results/gallery/gallery.module.ts +++ b/src/app/results/gallery/gallery.module.ts @@ -7,13 +7,13 @@ import {MaterialModule} from '../../material.module'; import {FlexLayoutModule} from '@angular/flex-layout'; import {MiniGalleryComponent} from './mini-gallery.component'; import {PipesModule} from '../../shared/pipes/pipes.module'; -import {VbsModule} from '../../core/vbs/vbs.module'; +import {CompetitionModule} from '../../core/competition/competition.module'; import {InfiniteScrollModule} from 'ngx-infinite-scroll'; import {ResultSegmentPreviewTileModule} from '../result-segment-preview-tile/result-segment-preview-tile.module'; import {VgCoreModule} from '@videogular/ngx-videogular/core'; @NgModule({ - imports: [MaterialModule, BrowserModule, FormsModule, AppRoutingModule, FlexLayoutModule, PipesModule, InfiniteScrollModule, VbsModule, VgCoreModule, ResultSegmentPreviewTileModule], + imports: [MaterialModule, BrowserModule, FormsModule, AppRoutingModule, FlexLayoutModule, PipesModule, InfiniteScrollModule, CompetitionModule, VgCoreModule, ResultSegmentPreviewTileModule], declarations: [GalleryComponent, MiniGalleryComponent], exports: [GalleryComponent, MiniGalleryComponent] }) diff --git a/src/app/results/list/list.module.ts b/src/app/results/list/list.module.ts index cb3ea9c7..594fdc18 100644 --- a/src/app/results/list/list.module.ts +++ b/src/app/results/list/list.module.ts @@ -6,13 +6,13 @@ import {MaterialModule} from '../../material.module'; import {FlexLayoutModule} from '@angular/flex-layout'; import {ListComponent} from './list.component'; import {PipesModule} from '../../shared/pipes/pipes.module'; -import {VbsModule} from '../../core/vbs/vbs.module'; +import {CompetitionModule} from '../../core/competition/competition.module'; import {InfiniteScrollModule} from 'ngx-infinite-scroll'; import {ResultSegmentPreviewTileModule} from '../result-segment-preview-tile/result-segment-preview-tile.module'; import {VgCoreModule} from '@videogular/ngx-videogular/core'; @NgModule({ - imports: [MaterialModule, BrowserModule, FormsModule, AppRoutingModule, FlexLayoutModule, PipesModule, VbsModule, InfiniteScrollModule, VgCoreModule, ResultSegmentPreviewTileModule], + imports: [MaterialModule, BrowserModule, FormsModule, AppRoutingModule, FlexLayoutModule, PipesModule, CompetitionModule, InfiniteScrollModule, VgCoreModule, ResultSegmentPreviewTileModule], declarations: [ListComponent], exports: [ListComponent] }) diff --git a/src/app/results/result-segment-preview-tile/result-segment-preview-tile.component.ts b/src/app/results/result-segment-preview-tile/result-segment-preview-tile.component.ts index a93233c3..6b5f59ab 100644 --- a/src/app/results/result-segment-preview-tile/result-segment-preview-tile.component.ts +++ b/src/app/results/result-segment-preview-tile/result-segment-preview-tile.component.ts @@ -4,7 +4,7 @@ import {AbstractSegmentResultsViewComponent} from '../abstract-segment-results-v import {KeyboardService} from '../../core/basics/keyboard.service'; import {QueryService} from '../../core/queries/query.service'; import {EventBusService} from '../../core/basics/event-bus.service'; -import {VbsSubmissionService} from '../../core/vbs/vbs-submission.service'; +import {VbsSubmissionService} from '../../core/competition/vbs-submission.service'; import {ResolverService} from '../../core/basics/resolver.service'; import {ContextKey, InteractionEventComponent} from '../../shared/model/events/interaction-event-component.model'; import {InteractionEvent} from '../../shared/model/events/interaction-event.model'; diff --git a/src/app/results/result-segment-preview-tile/result-segment-preview-tile.module.ts b/src/app/results/result-segment-preview-tile/result-segment-preview-tile.module.ts index 7d9bae92..75c2b3d7 100644 --- a/src/app/results/result-segment-preview-tile/result-segment-preview-tile.module.ts +++ b/src/app/results/result-segment-preview-tile/result-segment-preview-tile.module.ts @@ -5,13 +5,13 @@ import {FormsModule} from '@angular/forms'; import {AppRoutingModule} from '../../app-routing.module'; import {FlexLayoutModule} from '@angular/flex-layout'; import {PipesModule} from '../../shared/pipes/pipes.module'; -import {VbsModule} from '../../core/vbs/vbs.module'; +import {CompetitionModule} from '../../core/competition/competition.module'; import {InfiniteScrollModule} from 'ngx-infinite-scroll'; import {ResultSegmentPreviewTileComponent} from './result-segment-preview-tile.component'; import {VgCoreModule} from '@videogular/ngx-videogular/core'; @NgModule({ - imports: [MaterialModule, BrowserModule, FormsModule, AppRoutingModule, FlexLayoutModule, PipesModule, VbsModule, InfiniteScrollModule, VgCoreModule], + imports: [MaterialModule, BrowserModule, FormsModule, AppRoutingModule, FlexLayoutModule, PipesModule, CompetitionModule, InfiniteScrollModule, VgCoreModule], declarations: [ResultSegmentPreviewTileComponent], exports: [ResultSegmentPreviewTileComponent] }) diff --git a/src/app/results/temporal/temporal-list.module.ts b/src/app/results/temporal/temporal-list.module.ts index 1298202d..d2ce2f58 100644 --- a/src/app/results/temporal/temporal-list.module.ts +++ b/src/app/results/temporal/temporal-list.module.ts @@ -5,14 +5,14 @@ import {AppRoutingModule} from '../../app-routing.module'; import {MaterialModule} from '../../material.module'; import {FlexLayoutModule} from '@angular/flex-layout'; import {PipesModule} from '../../shared/pipes/pipes.module'; -import {VbsModule} from '../../core/vbs/vbs.module'; +import {CompetitionModule} from '../../core/competition/competition.module'; import {InfiniteScrollModule} from 'ngx-infinite-scroll'; import {TemporalListComponent} from './temporal-list.component'; import {ResultSegmentPreviewTileModule} from '../result-segment-preview-tile/result-segment-preview-tile.module'; import {VgCoreModule} from '@videogular/ngx-videogular/core'; @NgModule({ - imports: [MaterialModule, BrowserModule, FormsModule, AppRoutingModule, FlexLayoutModule, PipesModule, VbsModule, InfiniteScrollModule, VgCoreModule, ResultSegmentPreviewTileModule], + imports: [MaterialModule, BrowserModule, FormsModule, AppRoutingModule, FlexLayoutModule, PipesModule, CompetitionModule, InfiniteScrollModule, VgCoreModule, ResultSegmentPreviewTileModule], declarations: [TemporalListComponent], exports: [TemporalListComponent] }) diff --git a/src/app/settings/preferences/preferences.component.html b/src/app/settings/preferences/preferences.component.html index 5be1baa5..dfc72900 100644 --- a/src/app/settings/preferences/preferences.component.html +++ b/src/app/settings/preferences/preferences.component.html @@ -80,35 +80,49 @@

Reset settings

-

Logs

+

vitrivr-ng Logs

-

Interaction log

+

Result log

- - + +
+ + +

DRES Logs

+ +

vitrivr-ng DRES Interaction log

+ + + -

Results log

+

DRES Results log

- - -

Submission log

+

DRES Submission log

- - diff --git a/src/app/settings/preferences/preferences.component.ts b/src/app/settings/preferences/preferences.component.ts index a76506dc..21746363 100644 --- a/src/app/settings/preferences/preferences.component.ts +++ b/src/app/settings/preferences/preferences.component.ts @@ -3,14 +3,15 @@ import {Config} from '../../shared/model/config/config.model'; import {first, map} from 'rxjs/operators'; import {DatabaseService} from '../../core/basics/database.service'; import Dexie from 'dexie'; -import {DresTypeConverter} from '../../core/vbs/dres-type-converter.util'; +import {DresTypeConverter} from '../../core/competition/dres-type-converter.util'; import * as JSZip from 'jszip'; -import {VbsSubmissionService} from '../../core/vbs/vbs-submission.service'; +import {VbsSubmissionService} from '../../core/competition/vbs-submission.service'; import {NotificationService} from '../../core/basics/notification.service'; import {AppConfig} from '../../app.config'; import {TemporalMode} from './temporal-mode-container.model'; import {from} from 'rxjs'; -import {ClientRunInfo, ClientRunInfoService, ClientTaskInfo, UserDetails} from "../../../../openapi/dres"; +import {ClientRunInfo, ClientRunInfoService, ClientTaskInfo, QueryEventLog, QueryResultLog, UserDetails} from "../../../../openapi/dres"; +import {ResultLogItem} from "../../core/competition/logging/result-log-item"; @Component({ selector: 'app-preferences', @@ -20,14 +21,17 @@ import {ClientRunInfo, ClientRunInfoService, ClientTaskInfo, UserDetails} from " export class PreferencesComponent implements AfterContentInit { _config: Config; - /** Table for persisting result logs. */ - private _resultsLogTable: Dexie.Table; + /** Table for persisting our result logs */ + private _resultsLogTable: Dexie.Table; - /** Table for persisting submission log */ - private _submissionLogTable: Dexie.Table; + /** Table for persisting DRES result logs */ + private _dresResultsLogTable: Dexie.Table; - /** Table for persisting interaction logs. */ - private _interactionLogTable: Dexie.Table; + /** Table for persisting DRES submission logs */ + private _dresSubmissionLogTable: Dexie.Table; + + /** Table for persisting DRES interaction logs. */ + private _dresInteractionLogTable: Dexie.Table; _dresStatusBadgeValue: string; _status: UserDetails = null @@ -59,8 +63,9 @@ export class PreferencesComponent implements AfterContentInit { this.maxLength = c._config.query.temporal_max_length }) this._resultsLogTable = _db.db.table('log_results'); - this._interactionLogTable = _db.db.table('log_interaction'); - this._submissionLogTable = _db.db.table('log_submission'); + this._dresResultsLogTable = _db.db.table('log_results_dres'); + this._dresInteractionLogTable = _db.db.table('log_interaction_dres'); + this._dresSubmissionLogTable = _db.db.table('log_submission_dres'); } public onModeChanged(mode: TemporalMode) { @@ -80,110 +85,62 @@ export class PreferencesComponent implements AfterContentInit { this._configService.load(); } - /** - * Downloads the interaction logs as zipped JSON. - */ - public onDownloadInteractionLog() { - const data = []; - from(this._interactionLogTable.orderBy('id').each((o, c) => { - data.push(o) - })) - .pipe( - first(), - map(h => { - const zip = new JSZip(); - const options = {base64: false, binary: false, date: new Date(), createFolders: false, dir: false}; - for (let i = 0; i < data.length; i++) { - zip.file(`vitrivrng-interaction-log_${i}.json`, JSON.stringify(data[i], null, 2), options); - } - return zip - }) - ) - .subscribe(zip => { - zip.generateAsync({type: 'blob', compression: 'DEFLATE'}).then( - (result) => { - window.open(window.URL.createObjectURL(result)); - }, - (error) => { - console.log(error); - } - ) - }); + public onDownloadResultsLog() { + this.onLogDownload("results", this._resultsLogTable) } - /** - * Downloads the results logs as zipped JSON. - */ - public onDownloadResultsLog() { - const data = []; - from(this._resultsLogTable.orderBy('id').each((o, c) => { - data.push(o) - })) - .pipe( - first(), - map(() => { - const zip = new JSZip(); - const options = {base64: false, binary: false, date: new Date(), createFolders: false, dir: false}; - for (let i = 0; i < data.length; i++) { - zip.file(`vitrivrng-results-log_${i}.json`, JSON.stringify(data[i], null, 2), options); - } - return zip - }) - ) - .subscribe(zip => { - zip.generateAsync({type: 'blob', compression: 'DEFLATE'}).then( - (result) => { - window.open(window.URL.createObjectURL(result)); - }, - (error) => { - console.log(error); - } - ) - }); + public onDownloadDRESInteractionLog() { + this.onLogDownload("dres-interaction", this._dresInteractionLogTable) + } + + public onDownloadDRESResultsLog() { + this.onLogDownload("dres-results", this._dresResultsLogTable) } - public onDownloadSubmissionLog() { + public onDownloadDRESSubmissionLog() { + this.onLogDownload("dres-submission", this._dresSubmissionLogTable) + } + + private onLogDownload(description: string, table: Dexie.Table){ const data = []; - from(this._submissionLogTable.orderBy('id').each((o, c) => { + from(table.orderBy('id').each((o, c) => { data.push(o) })) - .pipe( + .pipe( first(), map(() => { const zip = new JSZip(); const options = {base64: false, binary: false, date: new Date(), createFolders: false, dir: false}; for (let i = 0; i < data.length; i++) { - zip.file(`vitrivrng-submission-log_${i}.json`, JSON.stringify(data[i], null, 2), options); + zip.file(`vitrivrng-${description}-log_${i}.json`, JSON.stringify(data[i], null, 2), options); } return zip }) - ) - .subscribe(zip => { - zip.generateAsync({type: 'blob', compression: 'DEFLATE'}).then( + ) + .subscribe(zip => { + zip.generateAsync({type: 'blob', compression: 'DEFLATE'}).then( (result) => { window.open(window.URL.createObjectURL(result)); }, (error) => { console.log(error); } - ) - }); + ) + }); } - /** - * Clears the interaction logs. - */ - public onClearInteractionLog() { - this._interactionLogTable.clear().then(() => console.log('Interaction logs cleared.')) + public onClearDRESInteractionLog() { + this._dresInteractionLogTable.clear().then(() => console.log('DRES Interaction logs cleared.')) } - public onClearSubmissionLog() { - this._submissionLogTable.clear().then(() => console.log('Submission logs cleared.')) + public onClearDRESSubmissionLog() { + this._dresSubmissionLogTable.clear().then(() => console.log('DRES Submission logs cleared.')) + } + + public onClearDRESResultsLog() { + this._dresResultsLogTable.clear().then(() => console.log('DRES Results logs cleared.')) } - /** - * Clears the results logs. - */ public onClearResultsLog() { this._resultsLogTable.clear().then(() => console.log('Results logs cleared.')) } diff --git a/src/app/settings/refinement/refinement.component.ts b/src/app/settings/refinement/refinement.component.ts index 5b5b28e0..6a1821cc 100644 --- a/src/app/settings/refinement/refinement.component.ts +++ b/src/app/settings/refinement/refinement.component.ts @@ -206,7 +206,7 @@ export class RefinementComponent implements OnInit, OnDestroy { public set idFilterValue(id: string) { this._idFilterValue = id - this._filterService._id = id; + this._filterService._filters._id = id; this._filterService.update(); const context: Map = new Map(); context.set('f:type', 'id'); @@ -215,7 +215,7 @@ export class RefinementComponent implements OnInit, OnDestroy { } public onMdCatOperatorChange(event: MatSlideToggleChange) { - this._filterService._useOrForMetadataCategoriesFilter = event.checked; + this._filterService._filters._useOrForMetadataCategoriesFilter = event.checked; this._filterService.update(); const context: Map = new Map(); context.set('f:type', 'metadata_categoryfilter'); diff --git a/src/app/settings/textual-submission/textual-submission.component.ts b/src/app/settings/textual-submission/textual-submission.component.ts index 37c70353..6f6879ab 100644 --- a/src/app/settings/textual-submission/textual-submission.component.ts +++ b/src/app/settings/textual-submission/textual-submission.component.ts @@ -1,5 +1,5 @@ import {AfterViewInit, Component, Input, OnInit} from '@angular/core'; -import {VbsSubmissionService} from '../../core/vbs/vbs-submission.service'; +import {VbsSubmissionService} from '../../core/competition/vbs-submission.service'; @Component({ selector: 'app-textual-submission', diff --git a/src/app/shared/components/video/advanced-media-player.component.ts b/src/app/shared/components/video/advanced-media-player.component.ts index abc746a0..cad4dd8b 100644 --- a/src/app/shared/components/video/advanced-media-player.component.ts +++ b/src/app/shared/components/video/advanced-media-player.component.ts @@ -2,7 +2,7 @@ import {AfterViewChecked, ChangeDetectorRef, Component, Input} from '@angular/co import {MediaObjectScoreContainer} from '../../model/results/scores/media-object-score-container.model'; import {MediaSegmentScoreContainer} from '../../model/results/scores/segment-score-container.model'; import {ResolverService} from '../../../core/basics/resolver.service'; -import {VbsSubmissionService} from '../../../core/vbs/vbs-submission.service'; +import {VbsSubmissionService} from '../../../core/competition/vbs-submission.service'; import {BehaviorSubject, Observable} from 'rxjs'; import {first} from 'rxjs/operators'; import {VgApiService} from '@videogular/ngx-videogular/core'; diff --git a/src/app/shared/components/video/advanced-video-player.module.ts b/src/app/shared/components/video/advanced-video-player.module.ts index 1cd34f5c..de51a1f6 100644 --- a/src/app/shared/components/video/advanced-video-player.module.ts +++ b/src/app/shared/components/video/advanced-video-player.module.ts @@ -4,7 +4,7 @@ import {BrowserModule} from '@angular/platform-browser'; import {AdvancedMediaPlayerComponent} from './advanced-media-player.component'; import {MaterialModule} from '../../../material.module'; -import {VbsModule} from '../../../core/vbs/vbs.module'; +import {CompetitionModule} from '../../../core/competition/competition.module'; import {VgCoreModule} from '@videogular/ngx-videogular/core'; import {VgControlsModule} from '@videogular/ngx-videogular/controls'; import {VgOverlayPlayModule} from '@videogular/ngx-videogular/overlay-play'; @@ -12,7 +12,7 @@ import {VgBufferingModule} from '@videogular/ngx-videogular/buffering'; import {PipesModule} from '../../pipes/pipes.module'; @NgModule({ - imports: [MaterialModule, CommonModule, BrowserModule, VgCoreModule, VgControlsModule, VgOverlayPlayModule, VgBufferingModule, VbsModule, PipesModule], + imports: [MaterialModule, CommonModule, BrowserModule, VgCoreModule, VgControlsModule, VgOverlayPlayModule, VgBufferingModule, CompetitionModule, PipesModule], declarations: [AdvancedMediaPlayerComponent], exports: [AdvancedMediaPlayerComponent], bootstrap: [AdvancedMediaPlayerComponent] diff --git a/src/app/shared/pipes/util/competition-enabled.pipe.ts b/src/app/shared/pipes/util/competition-enabled.pipe.ts index 5d4fe94b..b856853b 100644 --- a/src/app/shared/pipes/util/competition-enabled.pipe.ts +++ b/src/app/shared/pipes/util/competition-enabled.pipe.ts @@ -1,5 +1,5 @@ import {Pipe, PipeTransform} from '@angular/core'; -import {VbsSubmissionService} from '../../../core/vbs/vbs-submission.service'; +import {VbsSubmissionService} from '../../../core/competition/vbs-submission.service'; import {map} from 'rxjs/operators'; import {AppConfig} from '../../../app.config'; import { Observable } from 'rxjs'; diff --git a/src/app/toolbar/ping.component.ts b/src/app/toolbar/ping.component.ts index 7bd189f8..eb34d67d 100644 --- a/src/app/toolbar/ping.component.ts +++ b/src/app/toolbar/ping.component.ts @@ -1,6 +1,6 @@ import {ChangeDetectionStrategy, ChangeDetectorRef, Component, OnInit} from '@angular/core'; import {PingService} from '../core/basics/ping.service'; -import {CollabordinatorService} from '../core/vbs/collabordinator.service'; +import {CollabordinatorService} from '../core/competition/collabordinator.service'; import {WebSocketFactoryService} from '../core/api/web-socket-factory.service'; @Component({ diff --git a/src/config.json b/src/config.json index 0af6498a..20e7e879 100644 --- a/src/config.json +++ b/src/config.json @@ -1,42 +1,131 @@ { + "title": "LSC21 - vitrivr", + "api": { + "host": "10.34.58.176" + }, + "resources": { + "host_thumbnails": "http://10.34.58.176/lsc21/lsc21-thumbnails/:o/:s.jpg", + "host_objects": "http://10.34.58.176/lsc21/lsc21-image/:n/:S.jpg" + }, "competition": { "host": "", - "tls": false, "log": true, - "lsc": false, - "vbs": false + "lsc": true, + "vbs": false, + "tls": true }, "mlt": { - "VIDEO": [ - "visualtextcoembedding" - ], "IMAGE": [ - "visualtextcoembedding" + "clipimage" + ], + "IMAGE_SEQUENCE": [ + "clipimage" ] }, "query": { + "temporal_mode": "TEMPORAL_DISTANCE", + "temporal_max_length": 86400, + "default_temporal_distance": 1800, "history": 0, "options": { "image": true, "audio": false, "model3d": false, "text": true, - "tag": false, + "tag": true, "semantic": true, "boolean": true, + "map": true, "skeleton": false }, "text": { "categories": [ + [ + "ocr", + "Text on Screen" + ], [ "visualtextcoembedding", "Text Co-Embedding" + ], + [ + "clip", + "CLIP" ] ] }, "boolean": [ { - "display": "Segment Id", + "display": "Hour", + "input": "RANGE", + "table": "features_table_lsc20meta", + "col": "p_hour", + "range": [0, 23] + }, + { + "display": "Weekday", + "input": "OPTIONS", + "table": "features_table_lsc20meta", + "col": "p_day_of_week", + "options": [ + "MONDAY", + "TUESDAY", + "WEDNESDAY", + "THURSDAY", + "FRIDAY", + "SATURDAY", + "SUNDAY" + ] + }, + { + "display": "Day of Month", + "input": "DYNAMICOPTIONS", + "table": "features_table_lsc20meta", + "col": "p_day", + "type": "number" + }, + { + "display": "Location", + "input": "DYNAMICOPTIONS", + "table": "features_table_lsc20meta", + "col": "semantic_name" + }, + { + "display": "Year", + "input": "DYNAMICOPTIONS", + "table": "features_table_lsc20meta", + "col": "p_year", + "type": "number" + }, + { + "display": "Month", + "input": "DYNAMICOPTIONS", + "table": "features_table_lsc20meta", + "col": "p_month" + }, + { + "display": "Transport", + "input": "DYNAMICOPTIONS", + "table": "features_table_lsc20meta", + "col": "activity_type" + }, + { + "display": "Timezone", + "input": "DYNAMICOPTIONS", + "table": "features_table_lsc20meta", + "col": "timezone" + }, + { + "display": "Segment Id [Meta]", + "input": "TEXT", + "table": "features_table_lsc20meta", + "col": "id", + "operators": [ + "=" + ] + }, + { + "display": "Segment Id [Segments]", "input": "TEXT", "table": "cineast_segment", "col": "segmentid", @@ -45,7 +134,7 @@ ] }, { - "display": "Object Id", + "display": "Object Id [Segments]", "input": "TEXT", "table": "cineast_segment", "col": "objectid", @@ -58,13 +147,30 @@ "refinement": { "filters": [ [ - "dominantcolor.color", + "LSCMETA.semantic_name", + "CHECKBOX" + ], + [ + "LSCMETA.timezone", "CHECKBOX" ], [ - "technical.duration", + "LSCMETA.p_day_of_week", + "CHECKBOX" + ], + [ + "LSCMETA.p_hour", "SLIDER" + ], + [ + "LSCMETA.p_month", + "CHECKBOX" + ], + [ + "LSCMETA.p_year", + "CHECKBOX" ] ] } } + From 41aa5360bcab8100cc30cdaa4fa4278af8166f8d Mon Sep 17 00:00:00 2001 From: silvanheller Date: Wed, 25 May 2022 19:47:02 +0200 Subject: [PATCH 04/10] reverting config --- src/config.json | 130 +++++------------------------------------------- 1 file changed, 12 insertions(+), 118 deletions(-) diff --git a/src/config.json b/src/config.json index 20e7e879..0af6498a 100644 --- a/src/config.json +++ b/src/config.json @@ -1,131 +1,42 @@ { - "title": "LSC21 - vitrivr", - "api": { - "host": "10.34.58.176" - }, - "resources": { - "host_thumbnails": "http://10.34.58.176/lsc21/lsc21-thumbnails/:o/:s.jpg", - "host_objects": "http://10.34.58.176/lsc21/lsc21-image/:n/:S.jpg" - }, "competition": { "host": "", + "tls": false, "log": true, - "lsc": true, - "vbs": false, - "tls": true + "lsc": false, + "vbs": false }, "mlt": { - "IMAGE": [ - "clipimage" + "VIDEO": [ + "visualtextcoembedding" ], - "IMAGE_SEQUENCE": [ - "clipimage" + "IMAGE": [ + "visualtextcoembedding" ] }, "query": { - "temporal_mode": "TEMPORAL_DISTANCE", - "temporal_max_length": 86400, - "default_temporal_distance": 1800, "history": 0, "options": { "image": true, "audio": false, "model3d": false, "text": true, - "tag": true, + "tag": false, "semantic": true, "boolean": true, - "map": true, "skeleton": false }, "text": { "categories": [ - [ - "ocr", - "Text on Screen" - ], [ "visualtextcoembedding", "Text Co-Embedding" - ], - [ - "clip", - "CLIP" ] ] }, "boolean": [ { - "display": "Hour", - "input": "RANGE", - "table": "features_table_lsc20meta", - "col": "p_hour", - "range": [0, 23] - }, - { - "display": "Weekday", - "input": "OPTIONS", - "table": "features_table_lsc20meta", - "col": "p_day_of_week", - "options": [ - "MONDAY", - "TUESDAY", - "WEDNESDAY", - "THURSDAY", - "FRIDAY", - "SATURDAY", - "SUNDAY" - ] - }, - { - "display": "Day of Month", - "input": "DYNAMICOPTIONS", - "table": "features_table_lsc20meta", - "col": "p_day", - "type": "number" - }, - { - "display": "Location", - "input": "DYNAMICOPTIONS", - "table": "features_table_lsc20meta", - "col": "semantic_name" - }, - { - "display": "Year", - "input": "DYNAMICOPTIONS", - "table": "features_table_lsc20meta", - "col": "p_year", - "type": "number" - }, - { - "display": "Month", - "input": "DYNAMICOPTIONS", - "table": "features_table_lsc20meta", - "col": "p_month" - }, - { - "display": "Transport", - "input": "DYNAMICOPTIONS", - "table": "features_table_lsc20meta", - "col": "activity_type" - }, - { - "display": "Timezone", - "input": "DYNAMICOPTIONS", - "table": "features_table_lsc20meta", - "col": "timezone" - }, - { - "display": "Segment Id [Meta]", - "input": "TEXT", - "table": "features_table_lsc20meta", - "col": "id", - "operators": [ - "=" - ] - }, - { - "display": "Segment Id [Segments]", + "display": "Segment Id", "input": "TEXT", "table": "cineast_segment", "col": "segmentid", @@ -134,7 +45,7 @@ ] }, { - "display": "Object Id [Segments]", + "display": "Object Id", "input": "TEXT", "table": "cineast_segment", "col": "objectid", @@ -147,30 +58,13 @@ "refinement": { "filters": [ [ - "LSCMETA.semantic_name", - "CHECKBOX" - ], - [ - "LSCMETA.timezone", + "dominantcolor.color", "CHECKBOX" ], [ - "LSCMETA.p_day_of_week", - "CHECKBOX" - ], - [ - "LSCMETA.p_hour", + "technical.duration", "SLIDER" - ], - [ - "LSCMETA.p_month", - "CHECKBOX" - ], - [ - "LSCMETA.p_year", - "CHECKBOX" ] ] } } - From 23ac2f8366a6a1b682c4f6ce69ab58229c54efef Mon Sep 17 00:00:00 2001 From: silvanheller Date: Wed, 25 May 2022 20:47:04 +0200 Subject: [PATCH 05/10] moving to no-config approach --- .gitignore | 1 + README.md | 6 ++-- src/app/shared/model/config/config.model.ts | 31 +++++++++++++++++---- src/{config.json => config.template.json} | 20 ++++++++++++- 4 files changed, 48 insertions(+), 10 deletions(-) rename src/{config.json => config.template.json} (73%) diff --git a/.gitignore b/.gitignore index 6c760e0a..a3da684b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +src/config.json deploy.log src/app/**/*.js diff --git a/README.md b/README.md index d0e1d970..b2257e0a 100644 --- a/README.md +++ b/README.md @@ -11,9 +11,9 @@ For setup information, consult our [Wiki](https://github.com/vitrivr/vitrivr-ng/ ## Config -There is a `config.json` which enables configuration of the UI to a certain extend. -While we provide a sensible [default config](src/config.json), the -some default values are better explored in the [code](src/app/shared/model/config/config.model.ts). +We follow a zero-config approach, where everything has reasonable defaults. +There is a `src/config.template.json` file which you can copy to `src/config.json` and modify if you have custom needs. +The default values are in the [code](src/app/shared/model/config/config.model.ts). Information about the configuration can be found in [the wiki](https://github.com/vitrivr/vitrivr-ng/wiki/Configuration). ## Development server diff --git a/src/app/shared/model/config/config.model.ts b/src/app/shared/model/config/config.model.ts index 784bc54e..24c752ab 100644 --- a/src/app/shared/model/config/config.model.ts +++ b/src/app/shared/model/config/config.model.ts @@ -48,7 +48,7 @@ export class Config { lsc: false, /* Host of the DRES endpoint (fqn + port, no protocol). */ - host: null, + host: "", /* Flag indicating whether or not TLS should be used to communicate with DRES. */ tls: false, @@ -71,10 +71,10 @@ export class Config { ], mlt: { 'MODEL3D': ['sphericalharmonicsdefault'], - 'IMAGE': ['globalcolor', 'localcolor', 'edge', 'localfeatures'], - 'VIDEO': ['globalcolor', 'localcolor', 'edge', 'localfeatures'], + 'IMAGE': ['visualtextcoembedding'], + 'VIDEO': ['visualtextcoembedding'], 'AUDIO': ['audiofingerprint'], - 'IMAGE_SEQUENCE': ['globalcolor', 'localcolor', 'edge'] + 'IMAGE_SEQUENCE': ['visualtextcoembedding'] }, query: { history: -1, @@ -83,7 +83,7 @@ export class Config { options: { image: true, audio: false, - model3d: true, + model3d: false, text: true, tag: false, map: false, @@ -107,7 +107,26 @@ export class Config { text: { categories: ['visualtextcoembedding', 'Text Co-Embedding'] }, - boolean: [], + boolean: [ + { + display: "Segment Id", + input: "TEXT", + table: "cineast_segment", + col: "segmentid", + operators: [ + "=" + ] + }, + { + display: "Object Id", + input: "TEXT", + table: "cineast_segment", + col: "objectid", + operators: [ + "=" + ] + } + ], temporal_mode: 'TEMPORAL_DISTANCE', enableTagPrioritisation: false, temporal_max_length: 600, diff --git a/src/config.json b/src/config.template.json similarity index 73% rename from src/config.json rename to src/config.template.json index 0af6498a..738d2454 100644 --- a/src/config.json +++ b/src/config.template.json @@ -1,6 +1,15 @@ { + "title": "Custom vitrivr", + "api": { + "host": "vitrivr-domain.com", + "port": "1234" + }, + "resources": { + "host_thumbnails": "http://localhost:4567/thumbnails/:s", + "host_objects": "http://localhost:4567/objects/:o" + }, "competition": { - "host": "", + "host": "dres.org", "tls": false, "log": true, "lsc": false, @@ -22,10 +31,19 @@ "model3d": false, "text": true, "tag": false, + "map": false, "semantic": true, "boolean": true, "skeleton": false }, + "metadata": { + "object": [ + ["technical", "duration"] + ], + "segment": [ + ["*", "*"] + ] + }, "text": { "categories": [ [ From c6de2d306624e0b86cd7eab66493e785644d458c Mon Sep 17 00:00:00 2001 From: silvanheller Date: Wed, 25 May 2022 22:25:08 +0200 Subject: [PATCH 06/10] fixing compilation error --- src/app/shared/model/config/config.model.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/app/shared/model/config/config.model.ts b/src/app/shared/model/config/config.model.ts index 24c752ab..71c9e579 100644 --- a/src/app/shared/model/config/config.model.ts +++ b/src/app/shared/model/config/config.model.ts @@ -4,6 +4,7 @@ import {QuerySettings} from './query-settings.model'; import * as DEEPMERGE from 'deepmerge'; import {MetadataAccessSpecification} from '../messages/queries/metadata-access-specification.model'; import {MetadataType} from '../messages/queries/metadata-type.model'; +import {InputType} from '../../../query/containers/bool/bool-attribute'; export class Config { /** Context of the Cineast API. */ @@ -110,7 +111,7 @@ export class Config { boolean: [ { display: "Segment Id", - input: "TEXT", + input: InputType.TEXT, table: "cineast_segment", col: "segmentid", operators: [ @@ -119,7 +120,7 @@ export class Config { }, { display: "Object Id", - input: "TEXT", + input: InputType.TEXT, table: "cineast_segment", col: "objectid", operators: [ From 63383d7684b53254a1b84364521a197ed0f40633 Mon Sep 17 00:00:00 2001 From: silvanheller Date: Thu, 26 May 2022 23:26:43 +0200 Subject: [PATCH 07/10] adding query logging with corresponding dres run information --- src/app/core/basics/basic.module.ts | 3 +- src/app/core/basics/database.service.ts | 1 + src/app/core/basics/dres.service.ts | 92 +++++ src/app/core/basics/notification.service.ts | 5 +- .../competition/logging/query-log-item.ts | 7 + .../competition/vbs-submission.service.ts | 329 +++++++++--------- src/app/core/queries/query.service.ts | 1 + .../preferences/preferences.component.ts | 48 ++- 8 files changed, 297 insertions(+), 189 deletions(-) create mode 100644 src/app/core/basics/dres.service.ts create mode 100644 src/app/core/competition/logging/query-log-item.ts diff --git a/src/app/core/basics/basic.module.ts b/src/app/core/basics/basic.module.ts index 0a5d811d..b5de1bc6 100644 --- a/src/app/core/basics/basic.module.ts +++ b/src/app/core/basics/basic.module.ts @@ -9,11 +9,12 @@ import {NotificationService} from './notification.service'; import {NoConnectDialogComponent} from './no-connect-dialog'; import {MatDialogModule} from '@angular/material/dialog'; import {MatButtonModule} from '@angular/material/button'; +import {DresService} from './dres.service'; @NgModule({ imports: [HttpClientModule, MatDialogModule, MatButtonModule], declarations: [NoConnectDialogComponent], - providers: [ResolverService, EventBusService, PingService, DatabaseService, KeyboardService, NotificationService] + providers: [ResolverService, EventBusService, PingService, DatabaseService, KeyboardService, NotificationService, DresService] }) export class BasicModule { diff --git a/src/app/core/basics/database.service.ts b/src/app/core/basics/database.service.ts index 84baed23..e64fb678 100644 --- a/src/app/core/basics/database.service.ts +++ b/src/app/core/basics/database.service.ts @@ -16,6 +16,7 @@ export class DatabaseService { log_interaction_dres: '++id,log', log_submission_dres: '++id,log', log_results: '++id,entry', + log_queries: '++id,entry', }); } diff --git a/src/app/core/basics/dres.service.ts b/src/app/core/basics/dres.service.ts new file mode 100644 index 00000000..f65850ca --- /dev/null +++ b/src/app/core/basics/dres.service.ts @@ -0,0 +1,92 @@ +import {Injectable} from '@angular/core'; +import {AppConfig} from '../../app.config'; +import {ClientRunInfo, ClientRunInfoService, ClientTaskInfo, UserDetails, UserService} from '../../../../openapi/dres'; +import {BehaviorSubject, Observable} from 'rxjs'; + +@Injectable() +export class DresService { + + private _status: BehaviorSubject = new BehaviorSubject(null) + private _activeRun: BehaviorSubject = new BehaviorSubject(null); + private _activeTask: BehaviorSubject = new BehaviorSubject(null); + + constructor(private _configService: AppConfig, private _runInfo: ClientRunInfoService, private _dresUser: UserService,) { + this._configService.configAsObservable.subscribe(config => { + if (config?.dresEndpointRest == null) { + return + } + this._dresUser.getApiV1User().subscribe( + { + next: (user) => { + this._status.next(user) + }, + error: (error) => { + this._status.error(error) + } + }) + } + ) + + // init dres info + this.updateDresInfo() + + // update every 5 seconds + setInterval( + () => { + this.updateDresInfo() + }, + 5 * 1000 + ) + + } + + private updateDresInfo() { + if (this.getStatus() == null) { + return + } + this._runInfo.getApiV1ClientRunInfoList(this.getStatus().sessionId).subscribe(list => { + const l = list.runs.filter(info => info.status == 'ACTIVE'); + const activeRun = l.length == 0 ? null : l[0] + this._activeRun.next(activeRun) + if (this._activeRun) { + this._runInfo.getApiV1ClientRunInfoCurrenttaskWithRunid(this._activeRun.getValue().id, this.getStatus().sessionId).subscribe(task => { + this._activeTask.next(task) + }) + } + }) + } + + public statusObservable(): Observable { + return this._status.asObservable() + } + + public activeTaskObservable(): Observable { + return this._activeTask.asObservable() + } + + public activeRunObservable(): Observable { + return this._activeRun.asObservable() + } + + public activeTask(): ClientTaskInfo { + return this._activeTask.getValue() + } + + public activeRun(): ClientRunInfo { + return this._activeRun.getValue() + } + + /** + * Returns null if an error was thrown during connection + */ + public getStatus(): UserDetails { + try { + if (this._status.getValue()) { + return this._status.getValue() + } + return null + } catch (e) { + return null + } + } +} \ No newline at end of file diff --git a/src/app/core/basics/notification.service.ts b/src/app/core/basics/notification.service.ts index e6b6505c..e2bf470f 100644 --- a/src/app/core/basics/notification.service.ts +++ b/src/app/core/basics/notification.service.ts @@ -4,14 +4,15 @@ import {VbsSubmissionService} from '../competition/vbs-submission.service'; import {NotificationUtil} from '../../shared/util/notification.util'; import {catchError, tap} from 'rxjs/operators'; import {AppConfig} from '../../app.config'; +import {DresService} from './dres.service'; @Injectable() export class NotificationService { private _dresStatusBadge = new BehaviorSubject('') - constructor(private _submissionService: VbsSubmissionService, private _configService: AppConfig) { - combineLatest([this._submissionService.statusObservable, this._configService.configAsObservable]).pipe( + constructor(private _submissionService: VbsSubmissionService, private _configService: AppConfig, private _dresService: DresService) { + combineLatest([this._dresService.statusObservable(), this._configService.configAsObservable]).pipe( tap(([status, config]) => { if (config._config.competition.host) { /* Do not update observable for undefined since that is the initial value*/ diff --git a/src/app/core/competition/logging/query-log-item.ts b/src/app/core/competition/logging/query-log-item.ts new file mode 100644 index 00000000..0bf60df7 --- /dev/null +++ b/src/app/core/competition/logging/query-log-item.ts @@ -0,0 +1,7 @@ +import {ClientRunInfo, ClientTaskInfo} from '../../../../../openapi/dres'; + +export interface QueryLogItem { + query: any, + dresTask: ClientTaskInfo, + dresRun: ClientRunInfo, +} diff --git a/src/app/core/competition/vbs-submission.service.ts b/src/app/core/competition/vbs-submission.service.ts index 0dd6b018..15b0b61d 100644 --- a/src/app/core/competition/vbs-submission.service.ts +++ b/src/app/core/competition/vbs-submission.service.ts @@ -2,7 +2,7 @@ import {Injectable} from '@angular/core'; import {MediaSegmentScoreContainer} from '../../shared/model/results/scores/segment-score-container.model'; import {VideoUtil} from '../../shared/util/video.util'; import {HttpClient} from '@angular/common/http'; -import {BehaviorSubject, combineLatest, EMPTY, Observable, of, Subject, Subscription} from 'rxjs'; +import {combineLatest, EMPTY, Observable, of, Subject, Subscription} from 'rxjs'; import {MatSnackBar} from '@angular/material/snack-bar'; import {Config} from '../../shared/model/config/config.model'; import {EventBusService} from '../basics/event-bus.service'; @@ -14,11 +14,13 @@ import {DatabaseService} from '../basics/database.service'; import Dexie from 'dexie'; import {AppConfig} from '../../app.config'; import {MetadataService} from '../../../../openapi/cineast'; -import {LogService, QueryEventLog, QueryResultLog, SubmissionService, SuccessfulSubmissionsStatus, UserDetails, UserService} from '../../../../openapi/dres'; +import {LogService, QueryEventLog, QueryResultLog, SubmissionService, SuccessfulSubmissionsStatus, UserService} from '../../../../openapi/dres'; import {TemporalListComponent} from '../../results/temporal/temporal-list.component'; -import {FilterService} from "../queries/filter.service"; -import {ResultLogItem} from "./logging/result-log-item"; -import {SegmentScoreLogContainer} from "./logging/segment-score-log-container"; +import {FilterService} from '../queries/filter.service'; +import {ResultLogItem} from './logging/result-log-item'; +import {SegmentScoreLogContainer} from './logging/segment-score-log-container'; +import {QueryLogItem} from './logging/query-log-item'; +import {DresService} from '../basics/dres.service'; /** * This service is used to submit segments to VBS web-service for the Video Browser Showdown challenge. Furthermore, if @@ -45,10 +47,13 @@ export class VbsSubmissionService { private _interactionlogSubscription: Subscription; /** Table for persisting our result logs. */ - private _dresResultsLogTable: Dexie.Table; + private _resultsLogTable: Dexie.Table; + + /** Table for persisting our query logs. */ + private _queryLogTable: Dexie.Table; /** Table for persisting DRES result logs. */ - private _resultsLogTable: Dexie.Table; + private _dresResultsLogTable: Dexie.Table; /** Table for persisting DRES submission logs */ private _dresSubmissionLogTable: Dexie.Table; @@ -68,9 +73,6 @@ export class VbsSubmissionService { /** SessionID retrieved from DRES endpoint, automatically connected via second tab. Does not support private mode */ private _sessionId: string = undefined; - /** Observable used to query the DRES status.*/ - private _status: BehaviorSubject = new BehaviorSubject(null) - constructor(private _config: AppConfig, private _eventbus: EventBusService, private _queryService: QueryService, @@ -82,7 +84,8 @@ export class VbsSubmissionService { private _dresLog: LogService, private _dresUser: UserService, _db: DatabaseService, - private _filterService: FilterService) { + private _filterService: FilterService, + private _dresService: DresService) { /* This subscription registers the event-mapping, recording and submission stream if the VBS mode is active and un-registers it, if it is switched off! */ _config.configAsObservable.subscribe(config => { @@ -93,30 +96,22 @@ export class VbsSubmissionService { if (this._log) { this._dresResultsLogTable = _db.db.table('log_results_dres'); this._resultsLogTable = _db.db.table('log_results'); + this._queryLogTable = _db.db.table('log_queries'); this._dresInteractionLogTable = _db.db.table('log_interaction_dres'); this._dresSubmissionLogTable = _db.db.table('log_submission_dres'); this.reset(config) } - if (config?.dresEndpointRest) { - this._dresUser.getApiV1User().subscribe( - { - next: (user) => { - this._status.next(user) - }, - error: (error) => this._status.error(error) - }) - this._status.subscribe({ - next: (user) => { - if (user) { - this._sessionId = user.sessionId; - } - }, - error: (e) => { - console.error('failed to connect to DRES', e) - } - }) - } }); + this._dresService.statusObservable().subscribe({ + next: (user) => { + if (user) { + this._sessionId = user.sessionId; + } + }, + error: (e) => { + console.error('failed to connect to DRES', e) + } + }) } @@ -126,7 +121,9 @@ export class VbsSubmissionService { * @return {boolean} */ get isOn(): Observable { - return this._config.configAsObservable.pipe(map(c => c.get('competition.host'))); + return this._config.configAsObservable.pipe( + map(c => c.dresEndpointRest == null) + ) } /** @@ -188,157 +185,174 @@ export class VbsSubmissionService { /* Setup interaction log subscription, which runs in a regular interval. */ if (this._log) { this._interactionlogSubscription = DresTypeConverter.mapEventStream(this._eventbus.observable()).pipe( - bufferTime(config._config.competition.loginterval), - map((events: QueryEventLog[]) => { - if (events && events.length > 0) { - const composite = {timestamp: Date.now(), events: []} - for (const e of events) { - composite.events.push(...e.events) - } - return composite - } else { - return null + bufferTime(config._config.competition.loginterval), + map((events: QueryEventLog[]) => { + if (events && events.length > 0) { + const composite = {timestamp: Date.now(), events: []} + for (const e of events) { + composite.events.push(...e.events) } - }), - filter(submission => submission != null), - mergeMap((submission: QueryEventLog) => { - this._dresInteractionLogTable.add(submission); + return composite + } else { + return null + } + }), + filter(submission => submission != null), + mergeMap((submission: QueryEventLog) => { + this._dresInteractionLogTable.add(submission); - /* Stop if no sessionId is set */ - if (!this._sessionId) { - return EMPTY - } + /* Stop if no sessionId is set */ + if (!this._sessionId) { + return EMPTY + } - /* Submit Log entry to DRES. */ - console.log(`Submitting interaction log to DRES.`); - return this._dresLog.postApiV1LogQuery(this._sessionId, submission).pipe( - tap(o => { - console.log(`Successfully submitted interaction log to DRES.`); - }), - catchError((err) => { - return of(`Failed to submit segment to DRES due to a HTTP error (${err}).`) - }) - ); - }) + /* Submit Log entry to DRES. */ + console.log(`Submitting interaction log to DRES.`); + return this._dresLog.postApiV1LogQuery(this._sessionId, submission).pipe( + tap(o => { + console.log(`Successfully submitted interaction log to DRES.`); + }), + catchError((err) => { + return of(`Failed to submit segment to DRES due to a HTTP error (${err}).`) + }) + ); + }) ).subscribe(); /* Setup results subscription, which is triggered upon change to the result set. */ const resultSubscription: Observable = this._queryService.observable.pipe( - filter(f => f === 'ENDED'), - mergeMap(f => this._queryService.results.segmentsAsObservable), - debounceTime(1000) + filter(f => f === 'ENDED'), + mergeMap(f => this._queryService.results.segmentsAsObservable), + debounceTime(1000) ); /* IMPORTANT: Limits the number of submissions to one per second. */ /* Setup results subscription, which is triggered upon change to the result set. */ const temporalResultsSubscription = this._queryService.observable.pipe( - filter(f => f === 'ENDED'), - mergeMap(f => this._queryService.results.temporalObjectsAsObservable), - debounceTime(1000) + filter(f => f === 'ENDED'), + mergeMap(f => this._queryService.results.temporalObjectsAsObservable), + debounceTime(1000) ); /* IMPORTANT: Limits the number of submissions to one per second. */ /* Setup results subscription, which is triggered upon change to the result set. */ const filterSubscription = this._filterService.filterSubject.pipe( - debounceTime(1000) + debounceTime(1000) ); /* IMPORTANT: Limits the number of filter updates to one per second. */ - this._resultsSubscription = combineLatest([resultSubscription, temporalResultsSubscription, this._eventbus.currentView(), filterSubscription]).pipe( - debounceTime(200), - filter(() => { - if (this._eventbus.lastQueryInteractionEvent() === null) { - console.error('no query logged for interaction logging, not logging anything') - return false - } - if (this._queryService.lastQueryIssued() === null) { - console.error('no query logged in query service, not logging anything') - return false - } - return true - }), - tap(([results, temporalResults, context, filterInfo]) => { - console.log(`logging results`); - const query = this._queryService.lastQueryIssued() - let logResults: SegmentScoreLogContainer[] - switch (context) { - case TemporalListComponent.COMPONENT_NAME: - logResults = temporalResults.flatMap(seq => seq.segments.map(c => new SegmentScoreLogContainer(seq.object.objectid, c.segmentId, c.startabs, c.endabs, seq.score))) - break; - default: - logResults = results.map(c => new SegmentScoreLogContainer(c.objectId, c.segmentId, c.startabs, c.endabs, c.score)) - } - console.log(logResults) - const logItem: ResultLogItem = { - filter: filterInfo, - query: query, - results: logResults - }; - console.log(logItem) - this._resultsLogTable.add(logItem); - }), - map(([results, temporalResults, context, filterInfo]) => { - const query = this._eventbus.lastQueryInteractionEvent() - switch (context) { - case TemporalListComponent.COMPONENT_NAME: - return DresTypeConverter.mapTemporalScoreContainer(context, temporalResults, query) - default: - return DresTypeConverter.mapSegmentScoreContainer(context, results, query) - } - }), - filter(submission => submission != null), - mergeMap((submission: QueryResultLog) => { - - /* Stop if no sessionId is set */ - if (!this._sessionId) { - return EMPTY - } + this._queryService.observable.pipe( + filter(el => el == 'STARTED'), + tap(() => { + const task = this._dresService.activeTask() + const run = this._dresService.activeRun() + const query = this._queryService.lastQueryIssued() + const logItem: QueryLogItem = { + query: query, + dresTask: task, + dresRun: run, + } + console.log('logging query') + this._queryLogTable.add(logItem) + }) + ).subscribe() - /* Do some logging and catch HTTP errors. */ - console.log(`Submitting result log to DRES...`); - return this._dresLog.postApiV1LogResult(this._sessionId, submission).pipe( - tap(o => { - console.log(`Successfully submitted result log to DRES!`); - }), - catchError((err) => { - return of(`Failed to submit segment to DRES due to a HTTP error (${err.status}).`) - }) - ); - }) - ).subscribe(); - } + this._resultsSubscription = combineLatest([resultSubscription, temporalResultsSubscription, this._eventbus.currentView(), filterSubscription]).pipe( + debounceTime(200), + filter(() => { + if (this._eventbus.lastQueryInteractionEvent() === null) { + console.error('no query logged for interaction logging, not logging anything') + return false + } + if (this._queryService.lastQueryIssued() === null) { + console.error('no query logged in query service, not logging anything') + return false + } + return true + }), + tap(([results, temporalResults, context, filterInfo]) => { + console.log(`logging results`); + const query = this._queryService.lastQueryIssued() + let logResults: SegmentScoreLogContainer[] + switch (context) { + case TemporalListComponent.COMPONENT_NAME: + logResults = temporalResults.flatMap(seq => seq.segments.map(c => new SegmentScoreLogContainer(seq.object.objectid, c.segmentId, c.startabs, c.endabs, seq.score))) + break; + default: + logResults = results.map(c => new SegmentScoreLogContainer(c.objectId, c.segmentId, c.startabs, c.endabs, c.score)) + } + const logItem: ResultLogItem = { + filter: filterInfo, + query: query, + results: logResults + }; + this._resultsLogTable.add(logItem); + }), + map(([results, temporalResults, context, filterInfo]) => { + const query = this._eventbus.lastQueryInteractionEvent() + switch (context) { + case TemporalListComponent.COMPONENT_NAME: + return DresTypeConverter.mapTemporalScoreContainer(context, temporalResults, query) + default: + return DresTypeConverter.mapSegmentScoreContainer(context, results, query) + } + }), + filter(submission => submission != null), + mergeMap((submission: QueryResultLog) => { - /* Setup submission subscription, which is triggered manually. */ - this._submitSubscription = this._submitSubject.pipe( - map(([segment, time]): [string, number?] => this.convertToAppropriateRepresentation(segment, time)), - mergeMap(([segment, frame]) => { /* Stop if no sessionId is set */ if (!this._sessionId) { return EMPTY } - - /* Submit, do some logging and catch HTTP errors. */ - return this._dresSubmit.getApiV1Submit(null, segment, null, frame).pipe( - tap((status: SuccessfulSubmissionsStatus) => { - this.handleSubmissionResponse(status); - }), - catchError(err => { - return this.handleSubmissionError(err); - }) - ) + this._dresResultsLogTable.add(submission) + + /* Do some logging and catch HTTP errors. */ + console.log(`Submitting result log to DRES...`); + return this._dresLog.postApiV1LogResult(this._sessionId, submission).pipe( + tap(o => { + console.log(`Successfully submitted result log to DRES!`); + }), + catchError((err) => { + return of(`Failed to submit segment to DRES due to a HTTP error (${err.status}).`) + }) + ); }) + ).subscribe(); + } + + /* Setup submission subscription, which is triggered manually. */ + this._submitSubscription = this._submitSubject.pipe( + map(([segment, time]): [string, number?] => this.convertToAppropriateRepresentation(segment, time)), + mergeMap(([segment, frame]) => { + /* Stop if no sessionId is set */ + if (!this._sessionId) { + return EMPTY + } + + this._dresSubmissionLogTable.add([segment, frame]) + /* Submit, do some logging and catch HTTP errors. */ + return this._dresSubmit.getApiV1Submit(null, segment, null, frame).pipe( + tap((status: SuccessfulSubmissionsStatus) => { + this.handleSubmissionResponse(status); + }), + catchError(err => { + return this.handleSubmissionError(err); + }) + ) + }) ).subscribe(); /* Setup submission subscription, which is triggered manually. */ this._submitTextSubscription = this._submitTextSubject.pipe( - mergeMap((text) => { - /* Submit, do some logging and catch HTTP errors. */ - return this._dresSubmit.getApiV1Submit(null, null, text).pipe( - tap((status: SuccessfulSubmissionsStatus) => { - this.handleSubmissionResponse(status); - }), - catchError(err => { - return this.handleSubmissionError(err); - }) - ) - }) + mergeMap((text) => { + this._dresSubmissionLogTable.add(text) + /* Submit, do some logging and catch HTTP errors. */ + return this._dresSubmit.getApiV1Submit(null, null, text).pipe( + tap((status: SuccessfulSubmissionsStatus) => { + this.handleSubmissionResponse(status); + }), + catchError(err => { + return this.handleSubmissionError(err); + }) + ) + }) ).subscribe() } @@ -366,13 +380,6 @@ export class VbsSubmissionService { } } - /** - * - */ - get statusObservable(): Observable { - return this._status - } - /** * Converts the given {MediaSegmentScoreContainer} and an optional timestamp to the exact form required by the respective * competition. DO YOUR CONVERSION and PRE-PROCESSING HERE please :-) diff --git a/src/app/core/queries/query.service.ts b/src/app/core/queries/query.service.ts index 76c6cf70..b76e248d 100644 --- a/src/app/core/queries/query.service.ts +++ b/src/app/core/queries/query.service.ts @@ -391,6 +391,7 @@ export class QueryService { */ private startNewQuery(queryId: string) { /* Start the actual query. */ + console.debug('received QR_START') if (!this._results || (this._results && this._results.queryId !== queryId)) { this._results = new ResultsContainer(queryId); this._interval_map.set(queryId, window.setInterval(() => this._results.checkUpdate(), 2500)); diff --git a/src/app/settings/preferences/preferences.component.ts b/src/app/settings/preferences/preferences.component.ts index 21746363..afd12320 100644 --- a/src/app/settings/preferences/preferences.component.ts +++ b/src/app/settings/preferences/preferences.component.ts @@ -12,6 +12,7 @@ import {TemporalMode} from './temporal-mode-container.model'; import {from} from 'rxjs'; import {ClientRunInfo, ClientRunInfoService, ClientTaskInfo, QueryEventLog, QueryResultLog, UserDetails} from "../../../../openapi/dres"; import {ResultLogItem} from "../../core/competition/logging/result-log-item"; +import {DresService} from '../../core/basics/dres.service'; @Component({ selector: 'app-preferences', @@ -56,16 +57,9 @@ export class PreferencesComponent implements AfterContentInit { private _submissionService: VbsSubmissionService, private _notificationService: NotificationService, private _cdr: ChangeDetectorRef, - private _runInfo: ClientRunInfoService + private _runInfo: ClientRunInfoService, + private _dresService: DresService ) { - this._configService.configAsObservable.subscribe(c => { - this._config = c - this.maxLength = c._config.query.temporal_max_length - }) - this._resultsLogTable = _db.db.table('log_results'); - this._dresResultsLogTable = _db.db.table('log_results_dres'); - this._dresInteractionLogTable = _db.db.table('log_interaction_dres'); - this._dresSubmissionLogTable = _db.db.table('log_submission_dres'); } public onModeChanged(mode: TemporalMode) { @@ -146,7 +140,16 @@ export class PreferencesComponent implements AfterContentInit { } ngAfterContentInit(): void { - this._submissionService.statusObservable.subscribe({ + this._configService.configAsObservable.subscribe(c => { + this._config = c + this.maxLength = c._config.query.temporal_max_length + }) + this._resultsLogTable = this._db.db.table('log_results'); + this._dresResultsLogTable = this._db.db.table('log_results_dres'); + this._dresInteractionLogTable = this._db.db.table('log_interaction_dres'); + this._dresSubmissionLogTable = this._db.db.table('log_submission_dres'); + + this._dresService.statusObservable().subscribe({ next: (status) => { if (status) { this._status = status @@ -154,21 +157,16 @@ export class PreferencesComponent implements AfterContentInit { } } }) - setInterval(() => { - if (this._status) { - this._runInfo.getApiV1ClientRunInfoList(this._status.sessionId).subscribe(list => { - const l = list.runs.filter(info => info.status == 'ACTIVE'); - this._activeRun = l.length == 0 ? null : l[0] - this._cdr.markForCheck() - if (this._activeRun) { - this._runInfo.getApiV1ClientRunInfoCurrenttaskWithRunid(this._activeRun.id, this._status.sessionId).subscribe(task => { - this._activeTask = task - this._cdr.markForCheck() - }) - } - }) - } - }, 5 * 1000); + + this._dresService.activeTaskObservable().subscribe(task => { + this._activeTask = task + this._cdr.markForCheck() + }) + this._dresService.activeRunObservable().subscribe(run => { + this._activeRun = run + this._cdr.markForCheck() + }) + this._notificationService.getDresStatusBadgeObservable().subscribe(el => this._dresStatusBadgeValue = el) } } From fe060c3c5493a74baf262a5d00bbfce5f7120281 Mon Sep 17 00:00:00 2001 From: silvanheller Date: Thu, 26 May 2022 23:29:39 +0200 Subject: [PATCH 08/10] adding download button --- .../preferences/preferences.component.html | 11 ++++ .../preferences/preferences.component.ts | 54 +++++++++++-------- 2 files changed, 44 insertions(+), 21 deletions(-) diff --git a/src/app/settings/preferences/preferences.component.html b/src/app/settings/preferences/preferences.component.html index dfc72900..c535d226 100644 --- a/src/app/settings/preferences/preferences.component.html +++ b/src/app/settings/preferences/preferences.component.html @@ -91,6 +91,17 @@

Result log

delete + +

Query log

+ + + + +
diff --git a/src/app/settings/preferences/preferences.component.ts b/src/app/settings/preferences/preferences.component.ts index afd12320..3bc6d702 100644 --- a/src/app/settings/preferences/preferences.component.ts +++ b/src/app/settings/preferences/preferences.component.ts @@ -3,16 +3,16 @@ import {Config} from '../../shared/model/config/config.model'; import {first, map} from 'rxjs/operators'; import {DatabaseService} from '../../core/basics/database.service'; import Dexie from 'dexie'; -import {DresTypeConverter} from '../../core/competition/dres-type-converter.util'; import * as JSZip from 'jszip'; import {VbsSubmissionService} from '../../core/competition/vbs-submission.service'; import {NotificationService} from '../../core/basics/notification.service'; import {AppConfig} from '../../app.config'; import {TemporalMode} from './temporal-mode-container.model'; import {from} from 'rxjs'; -import {ClientRunInfo, ClientRunInfoService, ClientTaskInfo, QueryEventLog, QueryResultLog, UserDetails} from "../../../../openapi/dres"; -import {ResultLogItem} from "../../core/competition/logging/result-log-item"; +import {ClientRunInfo, ClientRunInfoService, ClientTaskInfo, QueryEventLog, QueryResultLog, UserDetails} from '../../../../openapi/dres'; +import {ResultLogItem} from '../../core/competition/logging/result-log-item'; import {DresService} from '../../core/basics/dres.service'; +import {QueryLogItem} from '../../core/competition/logging/query-log-item'; @Component({ selector: 'app-preferences', @@ -25,6 +25,8 @@ export class PreferencesComponent implements AfterContentInit { /** Table for persisting our result logs */ private _resultsLogTable: Dexie.Table; + private _queryLogTable: Dexie.Table; + /** Table for persisting DRES result logs */ private _dresResultsLogTable: Dexie.Table; @@ -52,13 +54,13 @@ export class PreferencesComponent implements AfterContentInit { * Constructor for PreferencesComponent */ constructor( - private _configService: AppConfig, - private _db: DatabaseService, - private _submissionService: VbsSubmissionService, - private _notificationService: NotificationService, - private _cdr: ChangeDetectorRef, - private _runInfo: ClientRunInfoService, - private _dresService: DresService + private _configService: AppConfig, + private _db: DatabaseService, + private _submissionService: VbsSubmissionService, + private _notificationService: NotificationService, + private _cdr: ChangeDetectorRef, + private _runInfo: ClientRunInfoService, + private _dresService: DresService ) { } @@ -80,27 +82,31 @@ export class PreferencesComponent implements AfterContentInit { } public onDownloadResultsLog() { - this.onLogDownload("results", this._resultsLogTable) + this.onLogDownload('results', this._resultsLogTable) + } + + public onDownloadQueryLog() { + this.onLogDownload('queries', this._queryLogTable) } public onDownloadDRESInteractionLog() { - this.onLogDownload("dres-interaction", this._dresInteractionLogTable) + this.onLogDownload('dres-interaction', this._dresInteractionLogTable) } public onDownloadDRESResultsLog() { - this.onLogDownload("dres-results", this._dresResultsLogTable) + this.onLogDownload('dres-results', this._dresResultsLogTable) } public onDownloadDRESSubmissionLog() { - this.onLogDownload("dres-submission", this._dresSubmissionLogTable) + this.onLogDownload('dres-submission', this._dresSubmissionLogTable) } - private onLogDownload(description: string, table: Dexie.Table){ + private onLogDownload(description: string, table: Dexie.Table) { const data = []; from(table.orderBy('id').each((o, c) => { data.push(o) })) - .pipe( + .pipe( first(), map(() => { const zip = new JSZip(); @@ -110,17 +116,17 @@ export class PreferencesComponent implements AfterContentInit { } return zip }) - ) - .subscribe(zip => { - zip.generateAsync({type: 'blob', compression: 'DEFLATE'}).then( + ) + .subscribe(zip => { + zip.generateAsync({type: 'blob', compression: 'DEFLATE'}).then( (result) => { window.open(window.URL.createObjectURL(result)); }, (error) => { console.log(error); } - ) - }); + ) + }); } public onClearDRESInteractionLog() { @@ -139,12 +145,18 @@ export class PreferencesComponent implements AfterContentInit { this._resultsLogTable.clear().then(() => console.log('Results logs cleared.')) } + public onClearQueryLog() { + this._queryLogTable.clear().then(() => console.log('Query logs cleared.')) + } + + ngAfterContentInit(): void { this._configService.configAsObservable.subscribe(c => { this._config = c this.maxLength = c._config.query.temporal_max_length }) this._resultsLogTable = this._db.db.table('log_results'); + this._queryLogTable = this._db.db.table('log_queries'); this._dresResultsLogTable = this._db.db.table('log_results_dres'); this._dresInteractionLogTable = this._db.db.table('log_interaction_dres'); this._dresSubmissionLogTable = this._db.db.table('log_submission_dres'); From 67bd352a9312fe2556cde46bd43d1ec90b386ea3 Mon Sep 17 00:00:00 2001 From: silvanheller Date: Fri, 27 May 2022 23:01:15 +0200 Subject: [PATCH 09/10] fixing thumbnails not being colored upon submission --- src/app/core/competition/vbs-submission.service.ts | 1 - .../result-segment-preview-tile.component.ts | 8 +++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/app/core/competition/vbs-submission.service.ts b/src/app/core/competition/vbs-submission.service.ts index 15b0b61d..bdb38758 100644 --- a/src/app/core/competition/vbs-submission.service.ts +++ b/src/app/core/competition/vbs-submission.service.ts @@ -168,7 +168,6 @@ export class VbsSubmissionService { } console.debug(`Submitting segment ${segment.segmentId} @ ${time}`); this._submitSubject.next([segment, time]); - this._selection.add(this._selection._available[0], segment.segmentId); } /** diff --git a/src/app/results/result-segment-preview-tile/result-segment-preview-tile.component.ts b/src/app/results/result-segment-preview-tile/result-segment-preview-tile.component.ts index 6b5f59ab..4fb77a8e 100644 --- a/src/app/results/result-segment-preview-tile/result-segment-preview-tile.component.ts +++ b/src/app/results/result-segment-preview-tile/result-segment-preview-tile.component.ts @@ -126,7 +126,13 @@ export class ResultSegmentPreviewTileComponent implements OnInit { * Invoked when a user clicks the selection/favourie button. Toggles the selection mode of the SegmentScoreContainer. */ public onSubmitButtonClicked() { + this.submit() + } + + private submit(){ this._vbs.submitSegment(this.segment); + this._selectionService.add(this._selectionService._available[0], this.segment.segmentId); + this._tags = this._selectionService.getTags(this.segment.segmentId) } /** @@ -135,7 +141,7 @@ export class ResultSegmentPreviewTileComponent implements OnInit { public onTileClicked(event: MouseEvent) { if (event.shiftKey) { /* Shift-Click will trigger VBS submit. */ - this._vbs.submitSegment(this.segment); + this.submit() } else { /* Normal click will display item. */ this._dialog.open(QuickViewerComponent, {data: this.segment}); From af9a9afbce08e720451fbb4b1f55e196e5d3789a Mon Sep 17 00:00:00 2001 From: Silvan Heller Date: Wed, 1 Jun 2022 17:39:42 +0200 Subject: [PATCH 10/10] fixing popup when there is no config to not be alarmistic --- src/app/app.config.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/app.config.ts b/src/app/app.config.ts index ec641a71..9bd5dd9f 100644 --- a/src/app/app.config.ts +++ b/src/app/app.config.ts @@ -63,13 +63,13 @@ export class AppConfig { AppConfig.settingsSubject.next(AppConfig.settings); resolve(); }).catch((response: any) => { - this._snackBar.open('Could not parse config.json. Using default config, your UI may not work', 'Dismiss', { + this._snackBar.open('No config file present, using default config.', 'Dismiss', { duration: 1_000_000, verticalPosition: 'top' }) AppConfig.settings = new Proxy(new Config(), this.handler); AppConfig.settingsSubject.next(AppConfig.settings); - console.log(`Could not load config file '${jsonFile}'. Fallback to default.`); + console.log(`No config present at '${jsonFile}'. Using default.`); resolve(); }); });