You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@streampipes.apache.org by te...@apache.org on 2020/03/13 15:42:51 UTC
[incubator-streampipes] branch image-labeling updated:
[STREAMPIPES-79] big refactor using konvasjs to drav; add brush labeling
This is an automated email from the ASF dual-hosted git repository.
tex pushed a commit to branch image-labeling
in repository https://gitbox.apache.org/repos/asf/incubator-streampipes.git
The following commit(s) were added to refs/heads/image-labeling by this push:
new b2e92c6 [STREAMPIPES-79] big refactor using konvasjs to drav; add brush labeling
b2e92c6 is described below
commit b2e92c667d77e7b7e724112463bb25141881ffd6
Author: tex <te...@fzi.de>
AuthorDate: Fri Mar 13 16:42:22 2020 +0100
[STREAMPIPES-79]
big refactor using konvasjs to drav; add brush labeling
---
ui/package.json | 1 -
.../app/CustomMaterial/custom-material.module.ts | 23 +-
ui/src/app/core-model/coco/Annotation.ts | 24 +-
ui/src/app/core-model/coco/Coco.format.ts | 92 +++--
.../datalake/datalake-rest.service.ts | 54 +--
ui/src/app/core-ui/core-ui.module.ts | 55 ++-
.../image-annotations.component.css} | 24 --
.../image-annotations.component.html | 38 ++
.../image-annotations.component.ts | 63 +++
.../components/image-bar/image-bar.component.css} | 9 +-
.../components/image-bar/image-bar.component.html} | 22 +-
.../components/image-bar/image-bar.component.ts | 82 ++++
.../image-container/image-container.component.css} | 38 +-
.../image-container.component.html} | 20 +-
.../image-container/image-container.component.ts | 268 +++++++++++++
.../image-labels/image-labels.component.css} | 22 +-
.../image-labels/image-labels.component.html | 39 ++
.../image-labels/image-labels.component.ts | 55 +++
.../image-categorize.component.css} | 11 +-
.../image-categorize.component.html | 55 +++
.../image-categorize/image-categorize.component.ts | 101 +++++
.../image-labeling/image-labeling.component.css} | 11 +-
.../image-labeling/image-labeling.component.html | 68 ++++
.../image-labeling/image-labeling.component.ts | 278 ++++++++++++++
.../image-viewer/image-viewer.component.css} | 11 +-
.../image-viewer/image-viewer.component.html} | 33 +-
.../image-viewer/image-viewer.component.ts} | 51 ++-
.../image.component.css} | 11 +-
.../image/image.component.html} | 24 +-
.../annotationMode.ts => image/image.component.ts} | 29 +-
.../model/coordinates.ts} | 9 +-
.../model/labeling-mode.ts} | 6 +-
.../image/services/BrushLabeling.service.ts | 147 +++++++
.../image/services/PolygonLabeling.service.ts | 252 ++++++++++++
.../image/services/ReactLabeling.service.ts | 198 ++++++++++
.../services/color.service.ts} | 21 +-
.../util/imageTranslation.util.ts | 10 +-
.../imageLabeler/annotation/imageAnnotation.ts | 192 ----------
.../annotation/polygonAnnotation.util.ts | 155 --------
.../annotation/reactAnnotation.util.ts | 234 ------------
.../imageLabeler/imageLabeler.component.html | 125 ------
.../core-ui/imageLabeler/imageLabeler.component.ts | 424 ---------------------
.../app/data-explorer/data-explorer.component.html | 4 +-
43 files changed, 1942 insertions(+), 1447 deletions(-)
diff --git a/ui/package.json b/ui/package.json
index 113310f..d25c2d3 100644
--- a/ui/package.json
+++ b/ui/package.json
@@ -87,7 +87,6 @@
"ngmap": "1.18.0",
"ngx-color-picker": "^9.0.0",
"plotly.js": "^1.52.2",
- "point-in-polygon": "1.0.1",
"prismjs": "^1.19.0",
"rxjs": "^6.5.4",
"rxjs-compat": "^6.5.4",
diff --git a/ui/src/app/CustomMaterial/custom-material.module.ts b/ui/src/app/CustomMaterial/custom-material.module.ts
index cddada0..e6514ca 100644
--- a/ui/src/app/CustomMaterial/custom-material.module.ts
+++ b/ui/src/app/CustomMaterial/custom-material.module.ts
@@ -16,12 +16,15 @@
*
*/
-import {NgModule} from '@angular/core';
+import { NgModule } from '@angular/core';
+import { MatAutocompleteModule } from '@angular/material/autocomplete';
import { MatButtonModule } from '@angular/material/button';
import { MatButtonToggleModule } from '@angular/material/button-toggle';
import { MatCardModule } from '@angular/material/card';
import { MatCheckboxModule } from '@angular/material/checkbox';
+import { MatChipsModule } from '@angular/material/chips';
+import { MatDialogModule } from '@angular/material/dialog';
import { MatDividerModule } from '@angular/material/divider';
import { MatExpansionModule } from '@angular/material/expansion';
import { MatIconModule } from '@angular/material/icon';
@@ -30,19 +33,17 @@ import { MatListModule } from '@angular/material/list';
import { MatMenuModule } from '@angular/material/menu';
import { MatPaginatorModule } from '@angular/material/paginator';
import { MatProgressBarModule } from '@angular/material/progress-bar';
+import { MatRadioModule } from '@angular/material/radio';
import { MatSelectModule } from '@angular/material/select';
import { MatSidenavModule } from '@angular/material/sidenav';
import { MatSlideToggleModule } from '@angular/material/slide-toggle';
+import { MatSliderModule } from '@angular/material/slider';
import { MatSortModule } from '@angular/material/sort';
+import { MatStepperModule } from '@angular/material/stepper';
+import { MatTableModule } from '@angular/material/table';
import { MatTabsModule } from '@angular/material/tabs';
import { MatToolbarModule } from '@angular/material/toolbar';
-import {MatStepperModule} from '@angular/material/stepper';
-import {MatRadioModule} from '@angular/material/radio';
-import {MatTableModule} from '@angular/material/table';
-import {MatAutocompleteModule} from '@angular/material/autocomplete';
-import {MatDialogModule} from '@angular/material/dialog';
-import {MatTooltipModule} from '@angular/material/tooltip';
-import { MatChipsModule } from "@angular/material/chips";
+import { MatTooltipModule } from '@angular/material/tooltip';
@NgModule({
@@ -73,7 +74,8 @@ import { MatChipsModule } from "@angular/material/chips";
MatTooltipModule,
MatProgressBarModule,
MatButtonToggleModule,
- MatChipsModule
+ MatChipsModule,
+ MatSliderModule
],
exports: [
MatButtonModule,
@@ -100,7 +102,8 @@ import { MatChipsModule } from "@angular/material/chips";
MatTooltipModule,
MatProgressBarModule,
MatButtonToggleModule,
- MatChipsModule
+ MatChipsModule,
+ MatSliderModule
]
})
export class CustomMaterialModule {
diff --git a/ui/src/app/core-model/coco/Annotation.ts b/ui/src/app/core-model/coco/Annotation.ts
index 1ebc566..ce76d22 100644
--- a/ui/src/app/core-model/coco/Annotation.ts
+++ b/ui/src/app/core-model/coco/Annotation.ts
@@ -2,28 +2,34 @@ export class Annotation {
id: number;
image_id: number;
category_id: number;
- segmentation: [any]; //RLE or [polygon] -> polygon [[x1,y1,x2,y2,... xn, yn]]
+ segmentation: [any]; // RLE or [polygon] -> polygon [[x1,y1,x2,y2,... xn, yn]]
area: number;
- bbox: [number,number,number,number]; //[x,y,width,height]
- iscrowd: number; //(iscrowd=0 in which case polygons are used) or a collection of objects (iscrowd=1 in which case RLE is used)
+ bbox: [number, number, number, number]; // [x,y,width,height]
+ iscrowd: number; // (iscrowd=0 in which case polygons are used) or a collection of objects (iscrowd=1 in which case RLE is used)
+ brushSize: number [];
- //For UI
- isSelected: boolean = false;
- isHovered: boolean = false;
+ // For UI
+ isSelected = false;
+ isHovered = false;
+ category_name: string;
constructor() {
this.segmentation = undefined;
this.bbox = undefined;
+ this.brushSize = undefined;
}
isBox() {
return this.bbox !== undefined;
}
-
isPolygon() {
- return this.segmentation !== undefined;
+ return this.segmentation !== undefined && this.brushSize === undefined;
+ }
+
+ isBrush() {
+ return this.brushSize !== undefined;
}
-}
\ No newline at end of file
+}
diff --git a/ui/src/app/core-model/coco/Coco.format.ts b/ui/src/app/core-model/coco/Coco.format.ts
index 43c4571..160c6b1 100644
--- a/ui/src/app/core-model/coco/Coco.format.ts
+++ b/ui/src/app/core-model/coco/Coco.format.ts
@@ -1,66 +1,82 @@
-import { Image } from "./Image";
-import { Annotation } from "./Annotation";
-import { Category } from "./Category";
+import { Annotation } from './Annotation';
+import { Category } from './Category';
+import { Image } from './Image';
export class CocoFormat {
-
- info : {
- "year": number,
- "version": String,
- "description": String,
- "contributor": String,
- "url": String,
- "date_created": Date,
+ info: {
+ 'year': number,
+ 'version': string,
+ 'description': string,
+ 'contributor': string,
+ 'url': string,
+ 'date_created': Date,
};
licenses: [];
images: Image[] = [];
annotations: Annotation[] = [];
categories: Category[] = [];
- constructor(file_name, width, height) {
- let image = new Image();
- image.file_name = file_name;
- image.height = height;
- image.width = width;
- this.images.push(image)
+ constructor() { }
+
+ addImage(fileName) {
+ const image = new Image();
+ image.file_name = fileName;
+ image.id = this.images.length + 1;
+ this.images.push(image);
}
getLabelById(id) {
- return this.categories.find(elem => elem.id == id).name;
+ return this.categories.find(elem => elem.id === id).name;
}
- getLabelId(name, supercategory): number {
- let category = this.categories.find(elem => elem.name == name && elem.supercategory == supercategory);
+ getLabelId(supercategory, name): number {
+ let category = this.categories.find(elem => elem.name === name && elem.supercategory === supercategory);
if (category === undefined) {
category = new Category(this.categories.length + 1, name, supercategory);
- this.categories.push(category)
+ this.categories.push(category);
}
return category.id;
}
- addReactAnnotation(x, y, width, height, categoryId) {
- let annnotation = new Annotation();
- annnotation.id = this.annotations.length + 1;
- annnotation.iscrowd = 0;
- annnotation.image_id = 1;
- annnotation.bbox = [x, y, width, height];
- annnotation.category_id = categoryId;
- this.annotations.push(annnotation)
+ addReactAnnotationToFirstImage(cords, size, supercategory, category): Annotation {
+ const annotation = new Annotation();
+ annotation.id = this.annotations.length + 1;
+ annotation.iscrowd = 0;
+ annotation.image_id = 1;
+ annotation.bbox = [cords.x, cords.y, size.x, size.y];
+ annotation.category_id = this.getLabelId(supercategory, category);
+ annotation.category_name = category;
+ this.annotations.push(annotation);
+ return annotation;
+ }
+
+ addPolygonAnnotationFirstImage(points, supercategory, category): Annotation {
+ const annotation = new Annotation();
+ annotation.id = this.annotations.length + 1;
+ annotation.iscrowd = 0;
+ annotation.image_id = 1;
+ annotation.segmentation = [points];
+ annotation.category_id = this.getLabelId(supercategory, category);
+ annotation.category_name = category; this.annotations.push(annotation);
+ return annotation;
}
- addPolygonAnnotation(points, categoryId) {
- let annnotation = new Annotation();
- annnotation.id = this.annotations.length + 1;
- annnotation.iscrowd = 0;
- annnotation.image_id = 1;
- annnotation.segmentation = [points];
- annnotation.category_id = categoryId;
- this.annotations.push(annnotation)
+ addBrushAnnotationFirstImage(points, brushSize, supercategory, category): Annotation {
+ const annotation = new Annotation();
+ annotation.id = this.annotations.length + 1;
+ annotation.iscrowd = 0;
+ annotation.image_id = 1;
+ annotation.segmentation = [points];
+ annotation.brushSize = brushSize;
+ annotation.category_id = this.getLabelId(supercategory, category);
+ annotation.category_name = category;
+ this.annotations.push(annotation);
+ return annotation;
}
removeAnnotation(id) {
this.annotations = this.annotations.filter(anno => anno.id !== id);
}
-}
\ No newline at end of file
+}
diff --git a/ui/src/app/core-services/datalake/datalake-rest.service.ts b/ui/src/app/core-services/datalake/datalake-rest.service.ts
index a041d70..5550dbf 100644
--- a/ui/src/app/core-services/datalake/datalake-rest.service.ts
+++ b/ui/src/app/core-services/datalake/datalake-rest.service.ts
@@ -16,13 +16,13 @@
*
*/
-import {HttpClient, HttpRequest} from '@angular/common/http';
-import {InfoResult} from '../../core-model/datalake/InfoResult';
-import {AuthStatusService} from '../../services/auth-status.service';
-import {Injectable} from '@angular/core';
-import {PageResult} from '../../core-model/datalake/PageResult';
-import {DataResult} from '../../core-model/datalake/DataResult';
-import {GroupedDataResult} from '../../core-model/datalake/GroupedDataResult';
+import { HttpClient, HttpRequest } from '@angular/common/http';
+import { Injectable } from '@angular/core';
+import { DataResult } from '../../core-model/datalake/DataResult';
+import { GroupedDataResult } from '../../core-model/datalake/GroupedDataResult';
+import { InfoResult } from '../../core-model/datalake/InfoResult';
+import { PageResult } from '../../core-model/datalake/PageResult';
+import { AuthStatusService } from '../../services/auth-status.service';
@Injectable()
export class DatalakeRestService {
@@ -36,12 +36,12 @@ export class DatalakeRestService {
}
private get dataLakeUrlV3() {
- return this.baseUrl + '/api/v3/users/' + this.authStatusService.email + '/datalake'
+ return this.baseUrl + '/api/v3/users/' + this.authStatusService.email + '/datalake';
}
getAllInfos() {
- return this.http.get<InfoResult[]>(this.dataLakeUrlV3 + "/info");
+ return this.http.get<InfoResult[]>(this.dataLakeUrlV3 + '/info');
}
getDataPage(index, itemsPerPage, page) {
@@ -81,28 +81,28 @@ export class DatalakeRestService {
@deprecate
*/
getFile(index, format) {
- const request = new HttpRequest('GET', this.dataLakeUrlV3 + '/data/' + index + "?format=" + format, {
+ const request = new HttpRequest('GET', this.dataLakeUrlV3 + '/data/' + index + '?format=' + format, {
reportProgress: true,
responseType: 'text'
});
- return this.http.request(request)
+ return this.http.request(request);
}
downloadRowData(index, format) {
- const request = new HttpRequest('GET', this.dataLakeUrlV3 + '/data/' + index + "/download?format=" + format, {
+ const request = new HttpRequest('GET', this.dataLakeUrlV3 + '/data/' + index + '/download?format=' + format, {
reportProgress: true,
responseType: 'text'
});
- return this.http.request(request)
+ return this.http.request(request);
}
downloadRowDataTimeInterval(index, format, startDate, endDate) {
- const request = new HttpRequest('GET', this.dataLakeUrlV3 + '/data/' + index + '/' + startDate + '/' + endDate + "/download" +
- "?format=" + format, {
+ const request = new HttpRequest('GET', this.dataLakeUrlV3 + '/data/' + index + '/' + startDate + '/' + endDate + '/download' +
+ '?format=' + format, {
reportProgress: true,
responseType: 'text'
});
- return this.http.request(request)
+ return this.http.request(request);
}
getImageSrcs() {
@@ -122,17 +122,17 @@ export class DatalakeRestService {
getLabels() {
return {
- "person": ["person"],
- "vehicle": ["bicycle", "car", "motorcycle", "airplane", "bus", "train", "truck", "boat"],
- "outdoor": ["traffic light", "fire hydrant", "stop sign", "parking meter", "bench"],
- "animal": ["bird", "cat", "dog"],
- "accessory": ["backpack", "umbrella", "handbag", "suitcase"],
- "sports": ["frisbee", "sports ball", "skis", "frisbee", "baseball bat"],
- "kitchen": ["bottle", "cup", "fork", "knife", "spoon"],
- "furniture": ["chair", "couch", "bed", "table"],
- "electronic": ["tv", "laptop", "mouse", "keyboard"]
- }
+ 'person': ['person', 'Child'],
+ 'vehicle': ['bicycle', 'car', 'motorcycle', 'airplane', 'bus', 'train', 'truck', 'boat'],
+ 'outdoor': ['traffic light', 'fire hydrant', 'stop sign', 'parking meter', 'bench'],
+ 'animal': ['bird', 'cat', 'dog'],
+ 'accessory': ['backpack', 'umbrella', 'handbag', 'suitcase'],
+ 'sports': ['frisbee', 'sports ball', 'skis', 'frisbee', 'baseball bat'],
+ 'kitchen': ['bottle', 'cup', 'fork', 'knife', 'spoon'],
+ 'furniture': ['chair', 'couch', 'bed', 'table'],
+ 'electronic': ['tv', 'laptop', 'mouse', 'keyboard']
+ };
}
-}
\ No newline at end of file
+}
diff --git a/ui/src/app/core-ui/core-ui.module.ts b/ui/src/app/core-ui/core-ui.module.ts
index a6ad29b..dfdd7d9 100644
--- a/ui/src/app/core-ui/core-ui.module.ts
+++ b/ui/src/app/core-ui/core-ui.module.ts
@@ -16,26 +16,35 @@
*
*/
-import {NgModule} from '@angular/core';
-import {FlexLayoutModule} from '@angular/flex-layout';
-import {CommonModule} from '@angular/common';
+import { CommonModule } from '@angular/common';
+import { NgModule } from '@angular/core';
+import { FlexLayoutModule } from '@angular/flex-layout';
-import {CustomMaterialModule} from '../CustomMaterial/custom-material.module';
-import {FormsModule, ReactiveFormsModule} from '@angular/forms';
-import {CdkTableModule} from '@angular/cdk/table';
+import { CdkTableModule } from '@angular/cdk/table';
+import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { MatNativeDateModule } from '@angular/material/core';
import { MatDatepickerModule } from '@angular/material/datepicker';
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
import { MatSnackBarModule } from '@angular/material/snack-bar';
-import {TableComponent} from './table/table.component';
-import {LineChartComponent} from './linechart/lineChart.component';
+import { CustomMaterialModule } from '../CustomMaterial/custom-material.module';
+import { LineChartComponent } from './linechart/lineChart.component';
+import { TableComponent } from './table/table.component';
-//import * as PlotlyJS from 'plotly.js/dist/plotly.js';
+// import * as PlotlyJS from 'plotly.js/dist/plotly.js';
import { PlotlyViaWindowModule } from 'angular-plotly.js';
-import { ImageLabelerComponent } from "./imageLabeler/imageLabeler.component";
-import { ImageAnnotation } from "./imageLabeler/annotation/imageAnnotation";
-import { ImageClassification } from "./imageLabeler/classification/imageClassification";
-//PlotlyViaCDNModule.plotlyjs = PlotlyJS;
+import { ImageAnnotationsComponent } from './image/components/image-annotations/image-annotations.component';
+import { ImageBarComponent } from './image/components/image-bar/image-bar.component';
+import { ImageContainerComponent } from './image/components/image-container/image-container.component';
+import { ImageLabelsComponent } from './image/components/image-labels/image-labels.component';
+import { ImageCategorizeComponent } from './image/image-categorize/image-categorize.component';
+import { ImageLabelingComponent } from './image/image-labeling/image-labeling.component';
+import { ImageComponent } from './image/image.component';
+import { BrushLabelingService } from './image/services/BrushLabeling.service';
+import { ColorService } from './image/services/color.service';
+import { PolygonLabelingService } from './image/services/PolygonLabeling.service';
+import { ReactLabelingService } from './image/services/ReactLabeling.service';
+import { ImageViewerComponent } from './image/image-viewer/image-viewer.component';
+// PlotlyViaCDNModule.plotlyjs = PlotlyJS;
@NgModule({
imports: [
@@ -54,20 +63,30 @@ import { ImageClassification } from "./imageLabeler/classification/imageClassifi
declarations: [
TableComponent,
LineChartComponent,
- ImageLabelerComponent,
+ ImageComponent,
+ ImageContainerComponent,
+ ImageLabelingComponent,
+ ImageLabelsComponent,
+ ImageBarComponent,
+ ImageAnnotationsComponent,
+ ImageCategorizeComponent,
+ ImageViewerComponent,
],
providers: [
MatDatepickerModule,
- ImageAnnotation,
- ImageClassification
+ ColorService,
+ ReactLabelingService,
+ PolygonLabelingService,
+ BrushLabelingService,
],
entryComponents: [
],
exports: [
TableComponent,
LineChartComponent,
- ImageLabelerComponent,
+ ImageComponent,
+ ImageLabelingComponent,
]
})
export class CoreUiModule {
-}
\ No newline at end of file
+}
diff --git a/ui/src/app/core-ui/imageLabeler/imageLabeler.component.css b/ui/src/app/core-ui/image/components/image-annotations/image-annotations.component.css
similarity index 70%
copy from ui/src/app/core-ui/imageLabeler/imageLabeler.component.css
copy to ui/src/app/core-ui/image/components/image-annotations/image-annotations.component.css
index d8f0592..8e48f10 100644
--- a/ui/src/app/core-ui/imageLabeler/imageLabeler.component.css
+++ b/ui/src/app/core-ui/image/components/image-annotations/image-annotations.component.css
@@ -15,30 +15,6 @@
* limitations under the License.
*
*/
-canvas {
- background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAAIUlEQVQoU2NkYGCQYiACMEIVPiOgVmpUIb4QggcPwSAHAJ3ICQ/mI97EAAAAAElFTkSuQmCC);
- background-color: #fafafa;
- border-color: lightgrey;
- border-style: solid;
-}
-
-.controlButtons {
- top: 30px;
-}
-
-.imageBar {
- height: 70px;
- background-color: lightgrey;
-}
-
-img {
- max-height: 100%;
- margin: 5px;
-}
-
-labelCategorySelectContainer {
-
-}
.mat-list-item {
height: 5px;
diff --git a/ui/src/app/core-ui/image/components/image-annotations/image-annotations.component.html b/ui/src/app/core-ui/image/components/image-annotations/image-annotations.component.html
new file mode 100644
index 0000000..feedf72
--- /dev/null
+++ b/ui/src/app/core-ui/image/components/image-annotations/image-annotations.component.html
@@ -0,0 +1,38 @@
+<!--
+ ~ Licensed to the Apache Software Foundation (ASF) under one or more
+ ~ contributor license agreements. See the NOTICE file distributed with
+ ~ this work for additional information regarding copyright ownership.
+ ~ The ASF licenses this file to You under the Apache License, Version 2.0
+ ~ (the "License"); you may not use this file except in compliance with
+ ~ the License. You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ ~
+ -->
+
+<div fxLayout="column" fxLayoutAlign="space-between center">
+ <h2>Annotations</h2>
+ <mat-list>
+ <mat-list-item *ngFor="let annotation of annotations" (mouseover)="enterAnnotation(annotation)" (mouseout)="leaveAnnotation(annotation)"
+ style="height: 30px; padding-top: 5px; padding-buttom: 5px; border-radius: 50px; margin-bottom: 5px"
+ [style.background-color]="annotation.isHovered || annotation.isSelected ? 'lightgrey' : 'white'">
+ <mat-icon matListIcon [style.color]="colorService.getColor(annotation.category_name)" style="margin-top: -8px;">color_lens</mat-icon>
+ <mat-form-field>
+ <mat-select [value]="annotation.category_name">
+ <mat-optgroup *ngFor="let category of categories" [label]="category">
+ <mat-option *ngFor="let label of _labels[category]" [value]="label" (click)="changeLabel(annotation, label, category)">
+ {{label}}
+ </mat-option>
+ </mat-optgroup>
+ </mat-select>
+ </mat-form-field>
+ <button mat-icon-button (click)="delete(annotation)" style="margin-top: -10px; margin-left: 10px"> <mat-icon>delete_forever</mat-icon></button>
+ </mat-list-item>
+ </mat-list>
+</div>
\ No newline at end of file
diff --git a/ui/src/app/core-ui/image/components/image-annotations/image-annotations.component.ts b/ui/src/app/core-ui/image/components/image-annotations/image-annotations.component.ts
new file mode 100644
index 0000000..2fd7dca
--- /dev/null
+++ b/ui/src/app/core-ui/image/components/image-annotations/image-annotations.component.ts
@@ -0,0 +1,63 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
+import { Annotation } from '../../../../core-model/coco/Annotation';
+import { ColorService } from '../../services/color.service';
+
+@Component({
+ selector: 'sp-image-annotations',
+ templateUrl: './image-annotations.component.html',
+ styleUrls: ['./image-annotations.component.css']
+})
+export class ImageAnnotationsComponent implements OnInit {
+
+ @Input() annotations: Annotation[];
+ @Input()
+ set labels(labels) {
+ this._labels = labels;
+ this.categories = Object.keys(this._labels);
+ }
+ @Output() changeAnnotationLabel: EventEmitter<[Annotation, string, string]> = new EventEmitter<[Annotation, string, string]>();
+ @Output() deleteAnnotation: EventEmitter<Annotation> = new EventEmitter<Annotation>();
+
+ private _labels;
+ private categories;
+
+ constructor(public colorService: ColorService) { }
+
+ ngOnInit(): void {
+ }
+
+ changeLabel(annotation, label, category) {
+ this.changeAnnotationLabel.emit([annotation, category, label]);
+ }
+
+ delete(annotation) {
+ this.deleteAnnotation.emit(annotation);
+ }
+
+ enterAnnotation(annotation) {
+ annotation.isHovered = true;
+ }
+
+ leaveAnnotation(annotation) {
+ annotation.isHovered = false;
+ }
+
+}
diff --git a/ui/src/app/core-ui/imageLabeler/interactionMode.ts b/ui/src/app/core-ui/image/components/image-bar/image-bar.component.css
similarity index 85%
copy from ui/src/app/core-ui/imageLabeler/interactionMode.ts
copy to ui/src/app/core-ui/image/components/image-bar/image-bar.component.css
index 8ffb4bb..a4868af 100644
--- a/ui/src/app/core-ui/imageLabeler/interactionMode.ts
+++ b/ui/src/app/core-ui/image/components/image-bar/image-bar.component.css
@@ -6,17 +6,16 @@
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
+ *
*/
-export enum InteractionMode {
- imageViewing,
- imageAnnotate,
- imageClassify
+.imageBar {
+ height: 70px;
}
\ No newline at end of file
diff --git a/ui/src/app/data-explorer/data-explorer.component.html b/ui/src/app/core-ui/image/components/image-bar/image-bar.component.html
similarity index 54%
copy from ui/src/app/data-explorer/data-explorer.component.html
copy to ui/src/app/core-ui/image/components/image-bar/image-bar.component.html
index 778be74..e8f31dd 100644
--- a/ui/src/app/data-explorer/data-explorer.component.html
+++ b/ui/src/app/core-ui/image/components/image-bar/image-bar.component.html
@@ -16,21 +16,17 @@
~
-->
-<div flex class="page-container page-container-padding" style="height: 1px">
- <div flex="100" layout="column" style="padding:0px;background-color:#f6f6f6; display: inline-block; width: 100%;" >
- <div flex layout-fill layout="row" layout-align="start center"
- style="height:48px;padding-left:10px;font-size:14px;line-height:24px;border-bottom:1px solid #ccc">
- <label style="font-size: 30px; padding-top: 10px;">Data Explorer</label>
- </div>
- </div>
-
- <div class="container-fluid">
-
- <sp-image-labeler></sp-image-labeler>
+<div fxLayout="row" fxLayoutAlign="space-around center" >
+ <button mat-icon-button (click)="previousPage()"> <mat-icon>skip_previous</mat-icon></button>
+ <button mat-icon-button (click)="previousImage()"> <mat-icon>keyboard_arrow_left</mat-icon></button>
+ <div fxLayout="row" fxLayoutAlign="center " class="imageBar">
+ <img *ngFor="let src of imagesSrcs; let i = index" src="{{src}}" (click)="changeImage(i)"
+ [style.border]="i == selectedIndex ? '5px solid #39b54a' : 'none'" style="height: 65px">
+ </div>
+ <button mat-icon-button (click)="nextImage()"> <mat-icon>keyboard_arrow_right</mat-icon></button>
+ <button mat-icon-button (click)="nextPage()"> <mat-icon>skip_next</mat-icon></button>
- <!-- <sp-explorer></sp-explorer> -->
- </div>
</div>
diff --git a/ui/src/app/core-ui/image/components/image-bar/image-bar.component.ts b/ui/src/app/core-ui/image/components/image-bar/image-bar.component.ts
new file mode 100644
index 0000000..cb880d9
--- /dev/null
+++ b/ui/src/app/core-ui/image/components/image-bar/image-bar.component.ts
@@ -0,0 +1,82 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { Component, EventEmitter, HostListener, Input, OnInit, Output } from '@angular/core';
+
+@Component({
+ selector: 'sp-image-bar',
+ templateUrl: './image-bar.component.html',
+ styleUrls: ['./image-bar.component.css']
+})
+export class ImageBarComponent implements OnInit {
+
+ @Input() imagesSrcs: string [];
+ @Input() selectedIndex: number;
+ @Input() enableShortCuts: boolean;
+
+ @Output() indexChange: EventEmitter<number> = new EventEmitter<number>();
+ @Output() pageUp: EventEmitter<void> = new EventEmitter<void>();
+ @Output() pageDown: EventEmitter<void> = new EventEmitter<void>();
+
+
+ constructor() { }
+
+ ngOnInit(): void {
+ }
+
+ changeImage(index) {
+ this.indexChange.emit(index);
+ }
+
+ previousPage() {
+ this.pageDown.emit();
+
+ }
+ previousImage() {
+ if (this.selectedIndex > 0) {
+ this.indexChange.emit(this.selectedIndex - 1);
+ } else {
+ this.pageDown.emit();
+ }
+ }
+ nextImage() {
+ if (this.selectedIndex < this.imagesSrcs.length - 1) {
+ this.indexChange.emit(this.selectedIndex + 1);
+ } else {
+ this.pageUp.emit();
+ }
+ }
+ nextPage() {
+ this.pageUp.emit();
+ }
+
+ @HostListener('document:keydown', ['$event'])
+ handleShortCuts(event: KeyboardEvent) {
+ if (this.enableShortCuts) {
+ const key = event.key;
+ switch (key.toLowerCase()) {
+ case 'q':
+ this.previousImage();
+ break;
+ case 'e':
+ this.nextImage();
+ break;
+ }
+ }
+ }
+
+}
diff --git a/ui/src/app/core-ui/imageLabeler/imageLabeler.component.css b/ui/src/app/core-ui/image/components/image-container/image-container.component.css
similarity index 69%
rename from ui/src/app/core-ui/imageLabeler/imageLabeler.component.css
rename to ui/src/app/core-ui/image/components/image-container/image-container.component.css
index d8f0592..612fce8 100644
--- a/ui/src/app/core-ui/imageLabeler/imageLabeler.component.css
+++ b/ui/src/app/core-ui/image/components/image-container/image-container.component.css
@@ -15,43 +15,11 @@
* limitations under the License.
*
*/
-canvas {
+.canvas-container {
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAAIUlEQVQoU2NkYGCQYiACMEIVPiOgVmpUIb4QggcPwSAHAJ3ICQ/mI97EAAAAAElFTkSuQmCC);
background-color: #fafafa;
border-color: lightgrey;
border-style: solid;
-}
-
-.controlButtons {
- top: 30px;
-}
-
-.imageBar {
- height: 70px;
- background-color: lightgrey;
-}
-
-img {
- max-height: 100%;
- margin: 5px;
-}
-
-labelCategorySelectContainer {
-
-}
-
-.mat-list-item {
- height: 5px;
-}
-
-::ng-deep .mat-form-field-underline {
- background-color: rgba(255, 255, 255, 0.8);
-}
-
-::ng-deep .mat-optgroup-label {
- background-color: #FFFFFF !important;
-}
-
-::ng-deep .mat-option {
- background-color: #FFFFFF !important;
+ height: 500px;
+ width: 800px;
}
\ No newline at end of file
diff --git a/ui/src/app/data-explorer/data-explorer.component.html b/ui/src/app/core-ui/image/components/image-container/image-container.component.html
similarity index 56%
copy from ui/src/app/data-explorer/data-explorer.component.html
copy to ui/src/app/core-ui/image/components/image-container/image-container.component.html
index 778be74..b204c11 100644
--- a/ui/src/app/data-explorer/data-explorer.component.html
+++ b/ui/src/app/core-ui/image/components/image-container/image-container.component.html
@@ -15,22 +15,6 @@
~ limitations under the License.
~
-->
-
-<div flex class="page-container page-container-padding" style="height: 1px">
- <div flex="100" layout="column" style="padding:0px;background-color:#f6f6f6; display: inline-block; width: 100%;" >
- <div flex layout-fill layout="row" layout-align="start center"
- style="height:48px;padding-left:10px;font-size:14px;line-height:24px;border-bottom:1px solid #ccc">
- <label style="font-size: 30px; padding-top: 10px;">Data Explorer</label>
- </div>
- </div>
-
- <div class="container-fluid">
-
- <sp-image-labeler></sp-image-labeler>
-
-
-
-
- <!-- <sp-explorer></sp-explorer> -->
- </div>
+<div class="canvas-container">
+ <div id="canvas-container" (dblclick)="dblclick($event)"></div>
</div>
diff --git a/ui/src/app/core-ui/image/components/image-container/image-container.component.ts b/ui/src/app/core-ui/image/components/image-container/image-container.component.ts
new file mode 100644
index 0000000..7667a8f
--- /dev/null
+++ b/ui/src/app/core-ui/image/components/image-container/image-container.component.ts
@@ -0,0 +1,268 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { AfterViewInit, Component, EventEmitter, HostListener, Input, OnInit, Output } from '@angular/core';
+import Konva from 'konva';
+import { Context } from 'konva/types/Context';
+import { ICoordinates } from '../../model/coordinates';
+
+@Component({
+ selector: 'sp-image-container',
+ templateUrl: './image-container.component.html',
+ styleUrls: ['./image-container.component.css']
+})
+export class ImageContainerComponent implements OnInit, AfterViewInit {
+
+ @Input()
+ set imageSrc(src) {
+ this.loadImage(src);
+ }
+
+ @Output()
+ childRedraw: EventEmitter<[Konva.Layer, ICoordinates]> = new EventEmitter<[Konva.Layer, ICoordinates]>();
+ @Output()
+ mouseDownLeft: EventEmitter<[Konva.Layer, ICoordinates, ICoordinates]> = new EventEmitter<[Konva.Layer, ICoordinates, ICoordinates]>();
+ @Output()
+ mouseMove: EventEmitter<[Konva.Layer, ICoordinates, ICoordinates]> = new EventEmitter<[Konva.Layer, ICoordinates, ICoordinates]>();
+ @Output()
+ mouseMoveLeft: EventEmitter<[Konva.Layer, ICoordinates, ICoordinates]> = new EventEmitter<[Konva.Layer, ICoordinates, ICoordinates]>();
+ @Output()
+ mouseUpLeft: EventEmitter<[Konva.Layer, Konva.Layer, ICoordinates, ICoordinates]> = new EventEmitter<[Konva.Layer, Konva.Layer, ICoordinates, ICoordinates]>();
+ @Output()
+ shortCut: EventEmitter<string> = new EventEmitter<string>();
+ @Output()
+ dbclick: EventEmitter<[Konva.Layer, ICoordinates, ICoordinates]> = new EventEmitter<[Konva.Layer, ICoordinates, ICoordinates]>();
+
+
+ private image;
+
+ private mainCanvasStage: Konva.Stage;
+ private imageLayer: Konva.Layer;
+ private annotationLayer: Konva.Layer;
+ private drawLayer: Konva.Layer;
+
+ private scale: number;
+
+ private imageShift: ICoordinates;
+ private lastImageTranslation: ICoordinates;
+ private lastImagePointerPosition: ICoordinates;
+
+ private isLeftMouseDown: boolean;
+ private isRightMouseDown: boolean;
+
+ private isHoverComponent: boolean;
+
+ constructor() { }
+
+ ngOnInit(): void {
+ this.scale = 1;
+ this.imageShift = {x: 0, y: 0};
+ this.isLeftMouseDown = false;
+ this.isRightMouseDown = false;
+ this.isHoverComponent = false;
+ }
+
+ ngAfterViewInit(): void {
+ this.reset();
+ }
+
+ reset() {
+ this.scale = 1;
+ this.imageShift = {x: 0, y: 0};
+ // TODO fit to parent
+ this.mainCanvasStage = new Konva.Stage({
+ container: 'canvas-container',
+ width: 800,
+ height: 500
+ });
+ this.registerEventHandler();
+
+ }
+
+ loadImage(src) {
+ this.reset();
+ this.image = new window.Image();
+
+ this.image.onload = () => {
+ this.scale = Math.min(1, this.mainCanvasStage.width() / this.image.width, this.mainCanvasStage.height() / this.image.height);
+ this.initLayers();
+ this.redrawAll();
+ };
+ this.image.src = src;
+ }
+
+ getShift() {
+ const position = this.imageLayer.getChildren().toArray()[0].getPosition();
+ return {x: position.x, y: position.y};
+ }
+ /* mouse handler */
+
+ imageMouseDown(e) {
+ const button = e.evt.which;
+ if (button === 1) {
+ // left click
+ this.isLeftMouseDown = true;
+ this.mouseDownLeft.emit([this.drawLayer, this.getShift(), this.getImagePointerPosition()]);
+ this.drawLayer.batchDraw();
+ } else if (button === 2) {
+ // middle click
+
+ } else if (button === 3) {
+ // right click
+ this.isRightMouseDown = true;
+ this.mainCanvasStage.container().style.cursor = 'move';
+ this.lastImagePointerPosition = this.getImagePointerPosition();
+ this.lastImageTranslation = this.imageShift;
+ }
+ }
+
+ imageMouseMove(e) {
+ if (this.isLeftMouseDown) {
+ this.drawLayer.destroyChildren();
+ this.mouseMoveLeft.emit([this.drawLayer, this.getShift(), this.getImagePointerPosition()]);
+ this.drawLayer.batchDraw();
+ } else if (this.isRightMouseDown) {
+ const imagePointerPosition = this.getImagePointerPosition();
+ this.imageShift.x = this.lastImageTranslation.x + (imagePointerPosition.x - this.lastImagePointerPosition.x);
+ this.imageShift.y = this.lastImageTranslation.y + (imagePointerPosition.y - this.lastImagePointerPosition.y);
+ this.lastImagePointerPosition = this.getImagePointerPosition();
+ this.lastImageTranslation = this.imageShift;
+ this.shiftViewContent();
+ } else {
+ if (this.drawLayer !== undefined) { this.drawLayer.destroyChildren(); }
+ this.mouseMove.emit([this.drawLayer, this.getShift(), this.getImagePointerPosition()]);
+ if (this.drawLayer !== undefined) { this.drawLayer.destroyChildren(); }
+ }
+ }
+
+ imageMouseUp(e) {
+ if (this.isLeftMouseDown) {
+ this.isLeftMouseDown = false;
+ this.drawLayer.destroyChildren();
+ this.mouseUpLeft.emit([this.annotationLayer, this.drawLayer, this.getShift(), this.getImagePointerPosition()]);
+ this.drawLayer.batchDraw();
+ this.annotationLayer.batchDraw();
+ }
+ if (this.isRightMouseDown) {
+ this.isRightMouseDown = false;
+ this.mainCanvasStage.container().style.cursor = 'default';
+ }
+ }
+
+ dblclick (e) {
+ this.drawLayer.destroyChildren();
+ this.drawLayer.batchDraw();
+ this.dbclick.emit([this.annotationLayer, this.getShift(), this.getImagePointerPosition()]);
+ this.annotationLayer.batchDraw();
+ }
+
+ /* Draw */
+
+ redrawAll() {
+ this.drawLayer.destroyChildren();
+ this.annotationLayer.destroyChildren();
+ this.childRedraw.emit([this.annotationLayer, this.getShift()]);
+ this.shiftViewContent();
+ }
+
+ shiftViewContent() {
+ const newWidth = this.mainCanvasStage.width() * this.scale;
+ const newHeight = this.mainCanvasStage.height() * this.scale;
+
+ this.mainCanvasStage.position({
+ x: -((newWidth - this.mainCanvasStage.width()) / 2) + this.imageShift.x,
+ y: -((newHeight - this.mainCanvasStage.height()) / 2) + this.imageShift.y
+ });
+ this.mainCanvasStage.scale({ x: this.scale, y: this.scale });
+
+ this.mainCanvasStage.batchDraw();
+ }
+
+ initLayers() {
+ this.imageLayer = new Konva.Layer();
+ const konvaImage = new Konva.Image({
+ image: this.image,
+ x: this.mainCanvasStage.width() / 2 - this.image.width / 2,
+ y: this.mainCanvasStage.height() / 2 - this.image.height / 2,
+ });
+ this.imageLayer.add(konvaImage);
+ this.imageLayer.clearBeforeDraw();
+
+ this.annotationLayer = new Konva.Layer();
+ this.drawLayer = new Konva.Layer();
+
+ this.mainCanvasStage.add(this.imageLayer);
+ this.mainCanvasStage.add(this.annotationLayer);
+ this.mainCanvasStage.add(this.drawLayer);
+ }
+
+ @HostListener('document:keydown', ['$event'])
+ handleShortCuts(e) {
+ const key = e.key;
+ this.shortCut.emit(key.toLowerCase());
+ if (this.isHoverComponent) {
+ switch (key.toLowerCase()) {
+ case 'w': this.imageShift.y -= 5; this.redrawAll();
+ break;
+ case 'a': this.imageShift.x -= 5; this.redrawAll();
+ break;
+ case 's': this.imageShift.y += 5; this.redrawAll();
+ break;
+ case 'd': this.imageShift.x += 5; this.redrawAll();
+ break;
+ }
+ }
+ }
+
+ getPointerPosition(): ICoordinates {
+ return this.mainCanvasStage.getPointerPosition();
+ }
+
+ getImagePointerPosition(): ICoordinates {
+ const x = Math.floor((this.getPointerPosition().x / this.scale) -
+ ((this.mainCanvasStage.width() / this.scale - this.image.width) / 2) - (this.imageShift.x / this.scale));
+ const y = Math.floor((this.getPointerPosition().y / this.scale) -
+ ((this.mainCanvasStage.height() / this.scale - this.image.height) / 2) - (this.imageShift.y / this.scale));
+ return {x, y};
+ }
+
+ getImagePositionFromPosition(posistion: ICoordinates): ICoordinates {
+ const x = Math.floor((posistion.x / this.scale) -
+ ((this.mainCanvasStage.width() / this.scale - this.image.width) / 2) - (this.imageShift.x / this.scale));
+ const y = Math.floor((posistion.y / this.scale) -
+ ((this.mainCanvasStage.height() / this.scale - this.image.height) / 2) - (this.imageShift.y / this.scale));
+ return {x, y};
+ }
+
+ registerEventHandler() {
+ this.mainCanvasStage.on('wheel', e => this.scroll(e));
+ this.mainCanvasStage.on('contextmenu', e => e.evt.preventDefault());
+ this.mainCanvasStage.on('mousedown', e => this.imageMouseDown(e));
+ this.mainCanvasStage.on('mousemove', e => this.imageMouseMove(e));
+ this.mainCanvasStage.on('mouseup', e => this.imageMouseUp(e));
+ this.mainCanvasStage.on('mouseover', e => this.isHoverComponent = true);
+ this.mainCanvasStage.on('mouseout', e => this.isHoverComponent = false);
+ this.mainCanvasStage.on('dblclick', e => this.dblclick(e));
+ this.mainCanvasStage.on('dbclick', e => this.dblclick(e));
+ }
+
+ scroll(e) {
+ e.evt.preventDefault();
+ this.scale += e.evt.wheelDeltaY * (1 / 6000);
+ this.redrawAll();
+ }
+}
diff --git a/ui/src/app/core-ui/imageLabeler/annotation/annotationMode.ts b/ui/src/app/core-ui/image/components/image-labels/image-labels.component.css
similarity index 70%
copy from ui/src/app/core-ui/imageLabeler/annotation/annotationMode.ts
copy to ui/src/app/core-ui/image/components/image-labels/image-labels.component.css
index 9162ba3..fc51d70 100644
--- a/ui/src/app/core-ui/imageLabeler/annotation/annotationMode.ts
+++ b/ui/src/app/core-ui/image/components/image-labels/image-labels.component.css
@@ -6,18 +6,28 @@
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
+ *
*/
-export enum AnnotationMode {
- ReactLabeling,
- ReactResize,
- PolygonLabeling,
- PolygonTransform,
+.mat-list-item {
+ height: 5px;
+}
+
+::ng-deep .mat-form-field-underline {
+ background-color: rgba(255, 255, 255, 0.8);
}
+
+::ng-deep .mat-opt .group-label {
+ background-color: #FFFFFF !important;
+}
+
+::ng-deep .mat-option {
+ background-color: #FFFFFF !important;
+}
\ No newline at end of file
diff --git a/ui/src/app/core-ui/image/components/image-labels/image-labels.component.html b/ui/src/app/core-ui/image/components/image-labels/image-labels.component.html
new file mode 100644
index 0000000..a041e44
--- /dev/null
+++ b/ui/src/app/core-ui/image/components/image-labels/image-labels.component.html
@@ -0,0 +1,39 @@
+<!--
+ ~ Licensed to the Apache Software Foundation (ASF) under one or more
+ ~ contributor license agreements. See the NOTICE file distributed with
+ ~ this work for additional information regarding copyright ownership.
+ ~ The ASF licenses this file to You under the Apache License, Version 2.0
+ ~ (the "License"); you may not use this file except in compliance with
+ ~ the License. You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ ~
+ -->
+<div fxLayout="column" fxLayoutAlign="space-around center">
+
+ <h2>Labels</h2>
+ <div style="padding-left: 5px; padding-right: 5px; border-radius: 50px; font-size: 20px;">
+ <mat-form-field style="margin-top: -15px; margin-bottom: -10px">
+ <mat-select [(value)]="selectedCategory">
+ <mat-option *ngFor="let category of categories" [value]="category">
+ {{category}}
+ </mat-option>
+ </mat-select>
+ </mat-form-field>
+ </div>
+ <mat-list>
+ <mat-list-item *ngFor="let label of _labels[selectedCategory]; index as i"
+ [style.background-color]="_selectedLabel.label == label ? 'lightgrey' : 'white'"
+ style="height: 30px; padding-top: 5px; padding-buttom: 5px; border-radius: 50px; margin-bottom: 5px"
+ (click)="selectLabel({category: labelCategory, label: label})">
+ <mat-icon matListIcon [style.color]="colorService.getColor(label)" style="margin-top: -8px;">color_lens</mat-icon>
+ <h4 style="margin-top: -3px;"> {{label}} <label *ngIf="i < 10"> #{{i+1}}</label> </h4>
+ </mat-list-item>
+ </mat-list>
+</div>
\ No newline at end of file
diff --git a/ui/src/app/core-ui/image/components/image-labels/image-labels.component.ts b/ui/src/app/core-ui/image/components/image-labels/image-labels.component.ts
new file mode 100644
index 0000000..ea8948a
--- /dev/null
+++ b/ui/src/app/core-ui/image/components/image-labels/image-labels.component.ts
@@ -0,0 +1,55 @@
+import { Component, EventEmitter, HostListener, Input, OnInit, Output } from '@angular/core';
+import { ColorService } from "../../services/color.service";
+
+@Component({
+ selector: 'sp-image-labels',
+ templateUrl: './image-labels.component.html',
+ styleUrls: ['./image-labels.component.css']
+})
+export class ImageLabelsComponent implements OnInit {
+
+ @Input()
+ set labels(labels) {
+ this._labels = labels;
+ this.update();
+ }
+ @Input() enableShortCuts: boolean;
+ @Output() labelChange: EventEmitter<{category, label}> = new EventEmitter<{category, label}>();
+
+ public _labels;
+ public _selectedLabel: {category, label};
+ public categories;
+ public selectedCategory;
+
+ constructor(public colorService: ColorService) { }
+
+ ngOnInit(): void {
+
+ }
+
+ update() {
+ this.categories = Object.keys(this._labels);
+ this.selectedCategory = this.categories[0];
+ this._selectedLabel = {category: this.selectedCategory, label: this._labels[this.selectedCategory][0]};
+ this.labelChange.emit(this._selectedLabel);
+ }
+
+ selectLabel(e: {category, label}) {
+ this._selectedLabel = e;
+ this.labelChange.emit(this._selectedLabel);
+ }
+
+ @HostListener('document:keydown', ['$event'])
+ handleShortCuts(event: KeyboardEvent) {
+ if (this.enableShortCuts) {
+ if (event.code.toLowerCase().includes('digit')) {
+ // Number
+ const value = Number(event.key);
+ if (value !== 0 && value <= this._labels[this.selectedCategory].length) {
+ this.selectLabel({category: this.selectedCategory, label: this._labels[this.selectedCategory][value - 1]});
+ }
+ }
+ }
+ }
+
+}
diff --git a/ui/src/app/core-ui/imageLabeler/interactionMode.ts b/ui/src/app/core-ui/image/image-categorize/image-categorize.component.css
similarity index 85%
copy from ui/src/app/core-ui/imageLabeler/interactionMode.ts
copy to ui/src/app/core-ui/image/image-categorize/image-categorize.component.css
index 8ffb4bb..41ecef0 100644
--- a/ui/src/app/core-ui/imageLabeler/interactionMode.ts
+++ b/ui/src/app/core-ui/image/image-categorize/image-categorize.component.css
@@ -6,17 +6,12 @@
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
- */
-
-export enum InteractionMode {
- imageViewing,
- imageAnnotate,
- imageClassify
-}
\ No newline at end of file
+ *
+ */
\ No newline at end of file
diff --git a/ui/src/app/core-ui/image/image-categorize/image-categorize.component.html b/ui/src/app/core-ui/image/image-categorize/image-categorize.component.html
new file mode 100644
index 0000000..01ce6ff
--- /dev/null
+++ b/ui/src/app/core-ui/image/image-categorize/image-categorize.component.html
@@ -0,0 +1,55 @@
+<!--
+ ~ Licensed to the Apache Software Foundation (ASF) under one or more
+ ~ contributor license agreements. See the NOTICE file distributed with
+ ~ this work for additional information regarding copyright ownership.
+ ~ The ASF licenses this file to You under the Apache License, Version 2.0
+ ~ (the "License"); you may not use this file except in compliance with
+ ~ the License. You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ ~
+ -->
+<div>
+
+ <div fxLayout="column" fxLayoutAlign="space-between " >
+ <div fxLayout="row" fxLayoutAlign="space-around start">
+
+ <sp-image-labels
+ [labels]="labels"
+ [enableShortCuts]="true"
+ (labelChange)="handleLabelChange($event)">
+ </sp-image-labels>
+
+ <div fxLayout="column" fxLayoutAlign="space-between " >
+ <div fxLayout="row" fxLayoutAlign="center center" >
+ <mat-chip-list style="margin-bottom: 3px; min-height: 40px;">
+ <mat-chip *ngFor="let label of selectedLabels" [style.background-color]="colorService.getColor(label.label)"
+ (removed)="remove(label)">
+ {{label.label}}
+ <mat-icon matChipRemove>cancel</mat-icon>
+ </mat-chip>
+ </mat-chip-list>
+ </div>
+ <sp-image-container
+ [imageSrc]="imagesSrcs[imagesIndex]">
+ </sp-image-container>
+ </div>
+ </div>
+ <br />
+ <br />
+ <sp-image-bar style="width: 100%"
+ [imagesSrcs]="imagesSrcs"
+ [selectedIndex]="imagesIndex"
+ [enableShortCuts]="true"
+ (indexChange)="handleImageIndexChange($event)"
+ (pageUp)="handleImagePageUp($event)"
+ (pageDown)="handleImagePageDown($event)">
+ </sp-image-bar>
+ </div>
+</div>
diff --git a/ui/src/app/core-ui/image/image-categorize/image-categorize.component.ts b/ui/src/app/core-ui/image/image-categorize/image-categorize.component.ts
new file mode 100644
index 0000000..a396298
--- /dev/null
+++ b/ui/src/app/core-ui/image/image-categorize/image-categorize.component.ts
@@ -0,0 +1,101 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { AfterViewInit, Component, OnInit } from "@angular/core";
+import { MatSnackBar } from '@angular/material/snack-bar';
+import { DatalakeRestService } from '../../../core-services/datalake/datalake-rest.service';
+import { ColorService } from "../services/color.service";
+
+
+@Component({
+ selector: 'sp-image-categorize',
+ templateUrl: './image-categorize.component.html',
+ styleUrls: ['./image-categorize.component.css']
+})
+export class ImageCategorizeComponent implements OnInit, AfterViewInit {
+
+ // label
+ public labels;
+ public selectedLabels;
+
+ // images
+ public imagesSrcs;
+ public imagesIndex: number;
+
+ constructor(private restService: DatalakeRestService, public colorService: ColorService, private snackBar: MatSnackBar) { }
+
+ ngOnInit(): void {
+ this.selectedLabels = [];
+
+ // 1. get labels
+ this.labels = this.restService.getLabels();
+
+ // 2. get Images
+ this.imagesSrcs = this.restService.getImageSrcs();
+ }
+
+
+ ngAfterViewInit(): void {
+ this.selectedLabels = [];
+ this.imagesIndex = 0;
+ }
+
+ /* sp-image-view handler */
+
+
+ /* sp-image-labels handler */
+ handleLabelChange(label: {category, label}) {
+ this.selectedLabels.push(label);
+ }
+
+ /* sp-image-bar */
+ handleImageIndexChange(index) {
+ this.save();
+ this.selectedLabels = [];
+ this.imagesIndex = index;
+ }
+ handleImagePageUp(e) {
+ this.save();
+ this.selectedLabels = [];
+ alert('Page Up - Load new data');
+ }
+
+ handleImagePageDown(e) {
+ this.save();
+ this.selectedLabels = [];
+ alert('Page Down - Load new data');
+ }
+
+ remove(label) {
+ this.selectedLabels = this.selectedLabels.filter(l => l.label !== label.label);
+ }
+
+ private save() {
+ // TODO
+ this.openSnackBar('TODO: Save save class');
+ }
+
+ private openSnackBar(message: string) {
+ this.snackBar.open(message, '', {
+ duration: 2000,
+ verticalPosition: 'top',
+ horizontalPosition: 'right'
+ });
+ }
+
+
+}
diff --git a/ui/src/app/core-ui/imageLabeler/interactionMode.ts b/ui/src/app/core-ui/image/image-labeling/image-labeling.component.css
similarity index 85%
copy from ui/src/app/core-ui/imageLabeler/interactionMode.ts
copy to ui/src/app/core-ui/image/image-labeling/image-labeling.component.css
index 8ffb4bb..41ecef0 100644
--- a/ui/src/app/core-ui/imageLabeler/interactionMode.ts
+++ b/ui/src/app/core-ui/image/image-labeling/image-labeling.component.css
@@ -6,17 +6,12 @@
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
- */
-
-export enum InteractionMode {
- imageViewing,
- imageAnnotate,
- imageClassify
-}
\ No newline at end of file
+ *
+ */
\ No newline at end of file
diff --git a/ui/src/app/core-ui/image/image-labeling/image-labeling.component.html b/ui/src/app/core-ui/image/image-labeling/image-labeling.component.html
new file mode 100644
index 0000000..954c33a
--- /dev/null
+++ b/ui/src/app/core-ui/image/image-labeling/image-labeling.component.html
@@ -0,0 +1,68 @@
+<!--
+ ~ Licensed to the Apache Software Foundation (ASF) under one or more
+ ~ contributor license agreements. See the NOTICE file distributed with
+ ~ this work for additional information regarding copyright ownership.
+ ~ The ASF licenses this file to You under the Apache License, Version 2.0
+ ~ (the "License"); you may not use this file except in compliance with
+ ~ the License. You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ ~
+ -->
+
+<div (mouseover)="isHoverComponent = true" (mouseout)="isHoverComponent = false">
+
+ <div fxLayout="column" fxLayoutAlign="space-between " >
+ <div fxLayout="row" fxLayoutAlign="space-around start">
+
+ <sp-image-labels
+ [labels]="labels"
+ [enableShortCuts]="isHoverComponent"
+ (labelChange)="handleLabelChange($event)">
+ </sp-image-labels>
+
+ <div fxLayout="column" fxLayoutAlign="space-between " >
+ <div>
+ <button mat-button (click)="setReactMode()" [style.background-color]="isReactMode() ? 'lightgrey' : 'white'"> <mat-icon>crop_3_2</mat-icon></button>
+ <button mat-button (click)="setPolygonMode()" [style.background-color]="isPolygonMode() ? 'lightgrey' : 'white'"> <mat-icon>details</mat-icon></button>
+ <button mat-button (click)="setBrushMode()" [style.background-color]="isBrushMode() ? 'lightgrey' : 'white'"> <mat-icon>blur_circular</mat-icon></button>
+ <mat-slider [min]="1" [max]="50" [step]="1" [thumbLabel]="true" [(ngModel)]="brushSize"></mat-slider>
+ </div>
+
+ <sp-image-container
+ [imageSrc]="imagesSrcs[imagesIndex]"
+ (childRedraw)="handleChildRedraw($event[0], $event[1])"
+ (mouseDownLeft)="handleMouseDownLeft($event[0], $event[1], $event[2])"
+ (mouseMove)="handleMouseMove($event[0], $event[1], $event[2])"
+ (mouseMoveLeft)="handleMouseMoveLeft($event[0], $event[1], $event[2])"
+ (mouseUpLeft)="handleMouseUpLeft($event[0], $event[1], $event[2], $event[3])"
+ (shortCut)="handleImageViewShortCuts($event)"
+ (dbclick)="handleImageViewDBClick($event[0], $event[1], $event[2])">
+ </sp-image-container>
+ </div>
+
+ <sp-image-annotations
+ [annotations]="this.cocoFiles[this.imagesIndex].annotations"
+ [labels]="labels"
+ (changeAnnotationLabel)="handleChangeAnnotationLabel($event)"
+ (deleteAnnotation)="handleDeleteAnnotation($event)">
+ </sp-image-annotations>
+ </div>
+ <br />
+ <br />
+ <sp-image-bar style="width: 100%"
+ [imagesSrcs]="imagesSrcs"
+ [selectedIndex]="imagesIndex"
+ [enableShortCuts]="isHoverComponent"
+ (indexChange)="handleImageIndexChange($event)"
+ (pageUp)="handleImagePageUp($event)"
+ (pageDown)="handleImagePageDown($event)">
+ </sp-image-bar>
+ </div>
+</div>
\ No newline at end of file
diff --git a/ui/src/app/core-ui/image/image-labeling/image-labeling.component.ts b/ui/src/app/core-ui/image/image-labeling/image-labeling.component.ts
new file mode 100644
index 0000000..42fbd2d
--- /dev/null
+++ b/ui/src/app/core-ui/image/image-labeling/image-labeling.component.ts
@@ -0,0 +1,278 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+
+import { AfterViewInit, Component, OnInit, ViewChild } from "@angular/core";
+import { MatSnackBar } from '@angular/material/snack-bar';
+import Konva from 'konva';
+import { Annotation } from '../../../core-model/coco/Annotation';
+import { CocoFormat } from '../../../core-model/coco/Coco.format';
+import { DatalakeRestService } from '../../../core-services/datalake/datalake-rest.service';
+import { ImageContainerComponent } from '../components/image-container/image-container.component';
+import { ICoordinates } from '../model/coordinates';
+import { BrushLabelingService } from '../services/BrushLabeling.service';
+import { PolygonLabelingService } from '../services/PolygonLabeling.service';
+import { ReactLabelingService } from '../services/ReactLabeling.service';
+import { LabelingMode } from '../model/labeling-mode';
+
+@Component({
+ selector: 'sp-image-labeling',
+ templateUrl: './image-labeling.component.html',
+ styleUrls: ['./image-labeling.component.css']
+})
+export class ImageLabelingComponent implements OnInit, AfterViewInit {
+
+ // label
+ public labels;
+ public selectedLabel: {category, label};
+
+ // images
+ public imagesSrcs;
+ public imagesIndex: number;
+
+ public cocoFiles: CocoFormat[];
+
+ public isHoverComponent;
+ public brushSize: number;
+
+ @ViewChild(ImageContainerComponent) imageView: ImageContainerComponent;
+
+
+ public labelingMode: LabelingMode = LabelingMode.ReactLabeling;
+
+ constructor(private restService: DatalakeRestService, private reactLabelingService: ReactLabelingService,
+ private polygonLabelingService: PolygonLabelingService, private brushLabelingService: BrushLabelingService,
+ private snackBar: MatSnackBar) { }
+
+ ngOnInit(): void {
+ this.isHoverComponent = false;
+ this.brushSize = 5;
+
+ // 1. get labels
+ this.labels = this.restService.getLabels();
+
+ // 2. get Images
+ this.imagesSrcs = this.restService.getImageSrcs();
+ this.imagesIndex = 0;
+
+ // 3. get Coco files
+ this.cocoFiles = [];
+ for (const src of this.imagesSrcs) {
+ const coco = new CocoFormat();
+ coco.addImage(src);
+ this.cocoFiles.push(coco);
+ }
+ }
+
+ ngAfterViewInit(): void {
+ this.imagesIndex = 0;
+ }
+
+
+ /* sp-image-view handler */
+ handleMouseDownLeft(layer: Konva.Layer, shift: ICoordinates, position: ICoordinates) {
+ if (this.labelingEnabled()) {
+ switch (this.labelingMode) {
+ case LabelingMode.ReactLabeling: this.reactLabelingService.startLabeling(position);
+ break;
+ case LabelingMode.PolygonLabeling: this.polygonLabelingService.startLabeling(position);
+ break;
+ case LabelingMode.BrushLabeling: this.brushLabelingService.startLabeling(position, this.brushSize);
+ }
+ }
+ }
+
+ handleMouseMove(layer: Konva.Layer, shift: ICoordinates, position: ICoordinates) {
+ switch (this.labelingMode) {
+ case LabelingMode.PolygonLabeling: {
+ this.polygonLabelingService.executeLabeling(position);
+ this.polygonLabelingService.tempDraw(layer, shift, this.selectedLabel.label);
+ }
+ }
+ }
+
+ handleMouseMoveLeft(layer: Konva.Layer, shift: ICoordinates, position: ICoordinates) {
+ if (this.labelingEnabled()) {
+ switch (this.labelingMode) {
+ case LabelingMode.ReactLabeling: {
+ this.reactLabelingService.executeLabeling(position);
+ this.reactLabelingService.tempDraw(layer, shift, this.selectedLabel.label);
+ }
+ break;
+ case LabelingMode.BrushLabeling: {
+ this.brushLabelingService.executeLabeling(position);
+ this.brushLabelingService.tempDraw(layer, shift, this.selectedLabel.label);
+ }
+ }
+ }
+ }
+
+ handleMouseUpLeft(annotationLayer: Konva.Layer, drawLayer: Konva.Layer, shift: ICoordinates, position: ICoordinates) {
+ if (this.labelingEnabled()) {
+ switch (this.labelingMode) {
+ case LabelingMode.ReactLabeling: {
+ const result = this.reactLabelingService.endLabeling(position);
+ const coco = this.cocoFiles[this.imagesIndex];
+ const annotation = coco.addReactAnnotationToFirstImage(result[0], result[1],
+ this.selectedLabel.category, this.selectedLabel.label);
+ this.reactLabelingService.draw(annotationLayer, shift, annotation, this.imageView);
+ }
+ break;
+ case LabelingMode.PolygonLabeling: {
+ this.polygonLabelingService.tempDraw(drawLayer, shift, this.selectedLabel.label);
+ }
+ break;
+ case LabelingMode.BrushLabeling: {
+ const result = this.brushLabelingService.endLabeling(position);
+ const coco = this.cocoFiles[this.imagesIndex];
+ const annotation = coco.addBrushAnnotationFirstImage(result[0], result[1],
+ this.selectedLabel.category, this.selectedLabel.label);
+ this.brushLabelingService.draw(annotationLayer, shift, annotation, this.imageView);
+ }
+ }
+ }
+ }
+
+
+ handleImageViewDBClick(layer: Konva.Layer, shift: ICoordinates, position: ICoordinates) {
+ if (this.labelingEnabled()) {
+ switch (this.labelingMode) {
+ case LabelingMode.PolygonLabeling:
+ const points = this.polygonLabelingService.endLabeling(position);
+ const coco = this.cocoFiles[this.imagesIndex];
+ const annotation = coco.addPolygonAnnotationFirstImage(points,
+ this.selectedLabel.category, this.selectedLabel.label);
+ this.polygonLabelingService.draw(layer, shift, annotation, this.imageView);
+ }
+ }
+ }
+
+ handleChildRedraw(layer: Konva.Layer, shift: ICoordinates) {
+ const coco = this.cocoFiles[this.imagesIndex];
+ for (const annotation of coco.annotations) {
+ annotation.isHovered = false;
+ annotation.isSelected = false;
+ if (annotation.isBox()) {
+ this.reactLabelingService.draw(layer, shift, annotation, this.imageView);
+ } else if (annotation.isPolygon() && !annotation.isBrush()) {
+ this.polygonLabelingService.draw(layer, shift, annotation, this.imageView);
+ } else if (annotation.isBrush()) {
+ this.brushLabelingService.draw(layer, shift, annotation, this.imageView);
+ }
+ }
+ }
+
+ handleImageViewShortCuts(key) {
+ if (key === 'delete') {
+ const coco = this.cocoFiles[this.imagesIndex];
+ const toDelete = coco.annotations.filter(anno => anno.isSelected);
+ for (const anno of toDelete) {
+ this.handleDeleteAnnotation(anno);
+ }
+ }
+ }
+
+ /* sp-image-labels handler */
+ handleLabelChange(label: {category, label}) {
+ this.selectedLabel = label;
+ }
+
+ /* sp-image-bar */
+ handleImageIndexChange(index) {
+ this.save();
+ this.imagesIndex = index;
+ }
+ handleImagePageUp(e) {
+ this.save();
+ alert('Page Up - Load new data');
+ }
+
+ handleImagePageDown(e) {
+ this.save();
+ alert('Page Down - Load new data');
+ }
+
+ /* sp-image-annotations handlers */
+ handleChangeAnnotationLabel(change: [Annotation, string, string]) {
+ const coco = this.cocoFiles[this.imagesIndex];
+ const categoryId = coco.getLabelId(change[1], change[2]);
+ change[0].category_id = categoryId;
+ change[0].category_name = change[2];
+ this.imageView.redrawAll();
+ }
+
+ handleDeleteAnnotation(annotation) {
+ if (annotation !== undefined) {
+ const coco = this.cocoFiles[this.imagesIndex];
+ coco.removeAnnotation(annotation.id);
+ this.imageView.redrawAll();
+ }
+ }
+
+ /* utils */
+
+ private labelingEnabled() {
+ const coco = this.cocoFiles[this.imagesIndex];
+ const annotation = coco.annotations.find(anno => anno.isHovered);
+ if (annotation !== undefined) {
+ return false;
+ } else {
+ return true;
+ }
+ }
+
+ private save() {
+ // TODO
+ const coco = this.cocoFiles[this.imagesIndex];
+ console.log(coco);
+ this.openSnackBar('TODO: Save coco file');
+ }
+
+ private openSnackBar(message: string) {
+ this.snackBar.open(message, '', {
+ duration: 2000,
+ verticalPosition: 'top',
+ horizontalPosition: 'right'
+ });
+ }
+
+ /* UI */
+ isReactMode() {
+ return this.labelingMode === LabelingMode.ReactLabeling;
+ }
+
+ setReactMode() {
+ this.labelingMode = LabelingMode.ReactLabeling;
+ }
+
+ isPolygonMode() {
+ return this.labelingMode === LabelingMode.PolygonLabeling;
+ }
+
+ setPolygonMode() {
+ this.labelingMode = LabelingMode.PolygonLabeling;
+ }
+
+ isBrushMode() {
+ return this.labelingMode === LabelingMode.BrushLabeling;
+ }
+
+ setBrushMode() {
+ this.labelingMode = LabelingMode.BrushLabeling;
+ }
+}
diff --git a/ui/src/app/core-ui/imageLabeler/interactionMode.ts b/ui/src/app/core-ui/image/image-viewer/image-viewer.component.css
similarity index 85%
copy from ui/src/app/core-ui/imageLabeler/interactionMode.ts
copy to ui/src/app/core-ui/image/image-viewer/image-viewer.component.css
index 8ffb4bb..41ecef0 100644
--- a/ui/src/app/core-ui/imageLabeler/interactionMode.ts
+++ b/ui/src/app/core-ui/image/image-viewer/image-viewer.component.css
@@ -6,17 +6,12 @@
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
- */
-
-export enum InteractionMode {
- imageViewing,
- imageAnnotate,
- imageClassify
-}
\ No newline at end of file
+ *
+ */
\ No newline at end of file
diff --git a/ui/src/app/data-explorer/data-explorer.component.html b/ui/src/app/core-ui/image/image-viewer/image-viewer.component.html
similarity index 52%
copy from ui/src/app/data-explorer/data-explorer.component.html
copy to ui/src/app/core-ui/image/image-viewer/image-viewer.component.html
index 778be74..9b81ad1 100644
--- a/ui/src/app/data-explorer/data-explorer.component.html
+++ b/ui/src/app/core-ui/image/image-viewer/image-viewer.component.html
@@ -16,21 +16,24 @@
~
-->
-<div flex class="page-container page-container-padding" style="height: 1px">
- <div flex="100" layout="column" style="padding:0px;background-color:#f6f6f6; display: inline-block; width: 100%;" >
- <div flex layout-fill layout="row" layout-align="start center"
- style="height:48px;padding-left:10px;font-size:14px;line-height:24px;border-bottom:1px solid #ccc">
- <label style="font-size: 30px; padding-top: 10px;">Data Explorer</label>
+<div>
+ <div fxLayout="column" fxLayoutAlign="space-between " >
+ <div fxLayout="row" fxLayoutAlign="space-around start">
+ <div fxLayout="column" fxLayoutAlign="space-between " >
+ <sp-image-container
+ [imageSrc]="imagesSrcs[imagesIndex]">
+ </sp-image-container>
+ </div>
</div>
- </div>
-
- <div class="container-fluid">
-
- <sp-image-labeler></sp-image-labeler>
-
-
-
-
- <!-- <sp-explorer></sp-explorer> -->
+ <br />
+ <br />
+ <sp-image-bar style="width: 100%"
+ [imagesSrcs]="imagesSrcs"
+ [selectedIndex]="imagesIndex"
+ [enableShortCuts]="true"
+ (indexChange)="handleImageIndexChange($event)"
+ (pageUp)="handleImagePageUp($event)"
+ (pageDown)="handleImagePageDown($event)">
+ </sp-image-bar>
</div>
</div>
diff --git a/ui/src/app/core-ui/imageLabeler/util/imageTranslation.util.ts b/ui/src/app/core-ui/image/image-viewer/image-viewer.component.ts
similarity index 50%
copy from ui/src/app/core-ui/imageLabeler/util/imageTranslation.util.ts
copy to ui/src/app/core-ui/image/image-viewer/image-viewer.component.ts
index a16799c..bdd01aa 100644
--- a/ui/src/app/core-ui/imageLabeler/util/imageTranslation.util.ts
+++ b/ui/src/app/core-ui/image/image-viewer/image-viewer.component.ts
@@ -14,28 +14,39 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-export class ImageTranslationUtil {
-
- private static lastImageTranslationX = 0;
- private static lastImageTranslationY = 0;
- private static lastMouseX = 0;
- private static lastMouseY = 0;
-
- static mouseDown(mousePos, imageTranslationX, imageTranslationY) {
- this.lastMouseX = mousePos[0];
- this.lastMouseY = mousePos[1];
- this.lastImageTranslationX = imageTranslationX;
- this.lastImageTranslationY = imageTranslationY;
+
+import { Component, OnInit } from '@angular/core';
+import { DatalakeRestService } from "../../../core-services/datalake/datalake-rest.service";
+
+@Component({
+ selector: 'sp-image-viewer',
+ templateUrl: './image-viewer.component.html',
+ styleUrls: ['./image-viewer.component.css']
+})
+export class ImageViewerComponent implements OnInit {
+
+ // images
+ public imagesSrcs;
+ public imagesIndex: number;
+
+ constructor(private restService: DatalakeRestService) { }
+
+ ngOnInit(): void {
+ // 1. get Images
+ this.imagesSrcs = this.restService.getImageSrcs();
+ this.imagesIndex = 0;
}
- static mouseMove(mousePos): [number, number] {
- let mouseX = mousePos[0];
- let mouseY = mousePos[1];
- return [
- this.lastImageTranslationX + (mouseX - this.lastMouseX) ,
- this.lastImageTranslationY + (mouseY- this.lastMouseY )
- ]
+ /* sp-image-bar */
+ handleImageIndexChange(index) {
+ this.imagesIndex = index;
+ }
+ handleImagePageUp(e) {
+ alert('Page Up - Load new data');
}
+ handleImagePageDown(e) {
+ alert('Page Down - Load new data');
+ }
-}
\ No newline at end of file
+}
diff --git a/ui/src/app/core-ui/imageLabeler/interactionMode.ts b/ui/src/app/core-ui/image/image.component.css
similarity index 85%
copy from ui/src/app/core-ui/imageLabeler/interactionMode.ts
copy to ui/src/app/core-ui/image/image.component.css
index 8ffb4bb..41ecef0 100644
--- a/ui/src/app/core-ui/imageLabeler/interactionMode.ts
+++ b/ui/src/app/core-ui/image/image.component.css
@@ -6,17 +6,12 @@
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
- */
-
-export enum InteractionMode {
- imageViewing,
- imageAnnotate,
- imageClassify
-}
\ No newline at end of file
+ *
+ */
\ No newline at end of file
diff --git a/ui/src/app/data-explorer/data-explorer.component.html b/ui/src/app/core-ui/image/image.component.html
similarity index 56%
copy from ui/src/app/data-explorer/data-explorer.component.html
copy to ui/src/app/core-ui/image/image.component.html
index 778be74..d93a7ba 100644
--- a/ui/src/app/data-explorer/data-explorer.component.html
+++ b/ui/src/app/core-ui/image/image.component.html
@@ -16,21 +16,13 @@
~
-->
-<div flex class="page-container page-container-padding" style="height: 1px">
- <div flex="100" layout="column" style="padding:0px;background-color:#f6f6f6; display: inline-block; width: 100%;" >
- <div flex layout-fill layout="row" layout-align="start center"
- style="height:48px;padding-left:10px;font-size:14px;line-height:24px;border-bottom:1px solid #ccc">
- <label style="font-size: 30px; padding-top: 10px;">Data Explorer</label>
- </div>
- </div>
+<mat-button-toggle-group name="fontStyle" aria-label="Font Style" #group="matButtonToggleGroup" value="view">
+ <mat-button-toggle value="view">Viewer</mat-button-toggle>
+ <mat-button-toggle value="label">Label</mat-button-toggle>
+ <mat-button-toggle value="categorize">Categorize</mat-button-toggle>
+</mat-button-toggle-group>
- <div class="container-fluid">
- <sp-image-labeler></sp-image-labeler>
-
-
-
-
- <!-- <sp-explorer></sp-explorer> -->
- </div>
-</div>
+<sp-image-viewer *ngIf="group.value === 'view'"></sp-image-viewer>
+<sp-image-labeling *ngIf="group.value === 'label'"></sp-image-labeling>
+<sp-image-categorize *ngIf="group.value === 'categorize'"></sp-image-categorize>
\ No newline at end of file
diff --git a/ui/src/app/core-ui/imageLabeler/annotation/annotationMode.ts b/ui/src/app/core-ui/image/image.component.ts
similarity index 69%
copy from ui/src/app/core-ui/imageLabeler/annotation/annotationMode.ts
copy to ui/src/app/core-ui/image/image.component.ts
index 9162ba3..11b16dd 100644
--- a/ui/src/app/core-ui/imageLabeler/annotation/annotationMode.ts
+++ b/ui/src/app/core-ui/image/image.component.ts
@@ -6,18 +6,35 @@
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
+ *
*/
-export enum AnnotationMode {
- ReactLabeling,
- ReactResize,
- PolygonLabeling,
- PolygonTransform,
+import { Component, OnInit } from '@angular/core';
+
+@Component({
+ selector: 'sp-image',
+ templateUrl: './image.component.html',
+ styleUrls: ['./image.component.css']
+})
+export class ImageComponent implements OnInit {
+
+
+
+ constructor() {
+
+ }
+
+ ngOnInit(): void {
+
+ }
+
+
+
}
diff --git a/ui/src/app/core-ui/imageLabeler/interactionMode.ts b/ui/src/app/core-ui/image/model/coordinates.ts
similarity index 90%
rename from ui/src/app/core-ui/imageLabeler/interactionMode.ts
rename to ui/src/app/core-ui/image/model/coordinates.ts
index 8ffb4bb..9c7e2f3 100644
--- a/ui/src/app/core-ui/imageLabeler/interactionMode.ts
+++ b/ui/src/app/core-ui/image/model/coordinates.ts
@@ -15,8 +15,7 @@
* limitations under the License.
*/
-export enum InteractionMode {
- imageViewing,
- imageAnnotate,
- imageClassify
-}
\ No newline at end of file
+export interface ICoordinates {
+ x;
+ y;
+}
diff --git a/ui/src/app/core-ui/imageLabeler/annotation/annotationMode.ts b/ui/src/app/core-ui/image/model/labeling-mode.ts
similarity index 92%
rename from ui/src/app/core-ui/imageLabeler/annotation/annotationMode.ts
rename to ui/src/app/core-ui/image/model/labeling-mode.ts
index 9162ba3..6aae804 100644
--- a/ui/src/app/core-ui/imageLabeler/annotation/annotationMode.ts
+++ b/ui/src/app/core-ui/image/model/labeling-mode.ts
@@ -15,9 +15,9 @@
* limitations under the License.
*/
-export enum AnnotationMode {
+export enum LabelingMode {
ReactLabeling,
- ReactResize,
PolygonLabeling,
- PolygonTransform,
+ BrushLabeling,
+ NoneLabeling,
}
diff --git a/ui/src/app/core-ui/image/services/BrushLabeling.service.ts b/ui/src/app/core-ui/image/services/BrushLabeling.service.ts
new file mode 100644
index 0000000..d97b0dc
--- /dev/null
+++ b/ui/src/app/core-ui/image/services/BrushLabeling.service.ts
@@ -0,0 +1,147 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { Injectable } from '@angular/core';
+import Konva from 'konva';
+import { Annotation } from '../../../core-model/coco/Annotation';
+import { ICoordinates } from '../model/coordinates';
+import { ColorService } from './color.service';
+
+@Injectable()
+export class BrushLabelingService {
+
+ private points: number[];
+ private brushsize: number;
+
+ private isLabeling: boolean;
+
+ constructor(private colorService: ColorService) {
+
+ }
+
+
+ startLabeling(position: ICoordinates, brushSize: number) {
+ this.isLabeling = true;
+ this.points = [];
+ this.points.push(position.x);
+ this.points.push(position.y);
+ this.brushsize = brushSize;
+ }
+
+ executeLabeling(position: ICoordinates) {
+ this.points.push(position.x);
+ this.points.push(position.y);
+ }
+
+ endLabeling(position: ICoordinates) {
+ this.isLabeling = false;
+ return [this.points, this.brushsize];
+ }
+
+ tempDraw(layer: Konva.Layer, shift: ICoordinates, label: string) {
+ if (this.isLabeling) {
+ const tmp = [];
+ for (let i = 0; i < this.points.length; i += 2) {
+ tmp.push(this.points[i] + shift.x);
+ tmp.push(this.points[i + 1] + shift.y);
+ }
+
+ const line = new Konva.Line({
+ points: tmp,
+ stroke: this.colorService.getColor(label),
+ opacity: 0.8,
+ strokeWidth: this.brushsize,
+ closed: false
+ });
+ layer.add(line);
+ }
+ }
+
+ draw(layer: Konva.Layer, shift: ICoordinates, annotation: Annotation, imageView) {
+ const tmp = [];
+ for (let i = 0; i < annotation.segmentation[0].length; i += 2) {
+ tmp.push(annotation.segmentation[0][i] + shift.x);
+ tmp.push(annotation.segmentation[0][i + 1] + shift.y);
+ }
+
+ const line = new Konva.Line({
+ points: tmp,
+ stroke: this.colorService.getColor(annotation.category_name),
+ opacity: 0.5,
+ strokeWidth: this.brushsize,
+ closed: false,
+ draggable: false,
+ });
+ layer.add(line);
+
+ const transformer = new Konva.Transformer({
+ anchorFill: '#FFFFFF',
+ anchorSize: 10,
+ rotateEnabled: false,
+ enabledAnchors: [],
+ borderStroke: 'green',
+ });
+
+ if (annotation.isSelected) {
+ transformer.attachTo(line);
+ }
+
+ this.addMouseHandler(line, annotation, layer, transformer);
+ this.addClickHandler(line, annotation, layer, transformer);
+
+ layer.add(line);
+ layer.add(transformer);
+ }
+
+ private addClickHandler(rect, annotation, layer, transformer) {
+ rect.on('click', function() {
+ annotation.isSelected = true;
+ transformer.attachTo(this);
+ layer.batchDraw();
+ });
+
+ rect.on('dblclick', function() {
+ annotation.isSelected = false;
+ transformer.detach();
+ layer.batchDraw();
+ });
+
+ }
+
+ private addMouseHandler(rect, annotation, layer, transformer) {
+ rect.on('mouseover', function() {
+ annotation.isHovered = true;
+ rect.opacity(0.8);
+ layer.batchDraw();
+ });
+
+ rect.on('mouseout', function() {
+ annotation.isHovered = false;
+ rect.opacity(0.5);
+ layer.batchDraw();
+ });
+
+ transformer.on('mouseover', function() {
+ annotation.isHovered = true;
+ });
+
+ transformer.on('mouseout', function() {
+ annotation.isHovered = false;
+ });
+ }
+
+}
diff --git a/ui/src/app/core-ui/image/services/PolygonLabeling.service.ts b/ui/src/app/core-ui/image/services/PolygonLabeling.service.ts
new file mode 100644
index 0000000..153e8fd
--- /dev/null
+++ b/ui/src/app/core-ui/image/services/PolygonLabeling.service.ts
@@ -0,0 +1,252 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { Injectable } from '@angular/core';
+import Konva from 'konva';
+import { Annotation } from '../../../core-model/coco/Annotation';
+import { ICoordinates } from '../model/coordinates';
+import { ColorService } from './color.service';
+
+@Injectable()
+export class PolygonLabelingService {
+
+ private points: number[];
+ private tmpPoints: number[];
+
+ private isLabeling: boolean;
+
+ constructor(private colorService: ColorService) {
+ this.isLabeling = false;
+ }
+
+ static buildAnchors(layer, annotation, imageView, poly) {
+ this.removeAnchors(layer, annotation.id);
+ const anchorGroup = new Konva.Group({name: 'anchor_group_' + annotation.id})
+
+ for (let i = 0; i < annotation.segmentation[0].length; i += 2) {
+ PolygonLabelingService.buildAnchor(layer, annotation, i, imageView, poly, anchorGroup);
+ }
+ layer.add(anchorGroup);
+ }
+
+ static buildAnchor(layer, annotation, index, imageView, poly, anchorGroup) {
+ const shift: ICoordinates = imageView.getShift();
+
+ const xx = annotation.segmentation[0][index] + shift.x;
+ const yy = annotation.segmentation[0][index + 1] + shift.y;
+ const anchor = new Konva.Circle({
+ name: 'anchor_' + annotation.id,
+ x: xx,
+ y: yy,
+ radius: 10,
+ fill: 'white',
+ stroke: 'green',
+ strokeWidth: 4,
+ draggable: true,
+ });
+ anchorGroup.add(anchor);
+
+ anchor.on('mouseover', function() {
+ annotation.isHovered = true;
+ });
+
+ anchor.on('mouseout', function() {
+ annotation.isHovered = false;
+ });
+
+ /* let offset: ICoordinates;
+
+ anchor.on('dragstart', function() {
+ const position = imageView.getImagePointerPosition();
+ offset = {x: annotation.segmentation[0][index] - position.x,
+ y: annotation.segmentation[0][index + 1] - position.y};
+ });*/
+
+ anchor.on('dragmove', function() {
+ const position = imageView.getImagePointerPosition();
+ const _shift: ICoordinates = imageView.getShift();
+
+ annotation.segmentation[0][index] = position.x;
+ annotation.segmentation[0][index + 1] = position.y;
+
+ const tmp = [];
+ for (let i = 0; i < annotation.segmentation[0].length; i += 2) {
+ tmp.push(annotation.segmentation[0][i] + _shift.x);
+ tmp.push(annotation.segmentation[0][i + 1] + _shift.y );
+ }
+ poly.points(tmp);
+ poly.x(0);
+ poly.y(0);
+ poly.draw();
+ });
+ }
+
+ static removeAnchors(layer, annotationId) {
+ const tmp = [];
+ for (const child of layer.children) {
+ if (child.name() === 'anchor_group_' + annotationId) {
+ child.destroy();
+ }
+ }
+ layer.batchDraw();
+ }
+
+
+ startLabeling(position: ICoordinates) {
+ if (this.isLabeling) {
+ this.points.push(position.x);
+ this.points.push(position.y);
+ } else {
+ this.isLabeling = true;
+ this.points = [];
+ this.tmpPoints = [];
+ this.points.push(position.x);
+ this.points.push(position.y);
+ }
+ }
+
+ executeLabeling(position: ICoordinates) {
+ if (this.isLabeling) {
+ this.tmpPoints = Object.assign([], this.points);
+ this.tmpPoints.push(position.x);
+ this.tmpPoints.push(position.y);
+ }
+ }
+
+ endLabeling(position: ICoordinates) {
+ this.isLabeling = false;
+ return this.points.slice(0, this.points.length - 2);
+ }
+
+ tempDraw(layer: Konva.Layer, shift: ICoordinates, label: string) {
+ if (this.isLabeling) {
+ const tmp = [];
+ for (let i = 0; i < this.tmpPoints.length; i += 2) {
+ tmp.push(this.tmpPoints[i] + shift.x);
+ tmp.push(this.tmpPoints[i + 1] + shift.y);
+ }
+
+ const poly = new Konva.Line({
+ points: tmp,
+ fill: this.colorService.getColor(label),
+ stroke: 'black',
+ opacity: 0.8,
+ strokeWidth: 4,
+ closed: true
+ });
+ layer.add(poly);
+ }
+ }
+
+ draw(layer: Konva.Layer, shift: ICoordinates, annotation: Annotation, imageView) {
+ const tmp = [];
+ for (let i = 0; i < annotation.segmentation[0].length; i += 2) {
+ tmp.push(annotation.segmentation[0][i] + shift.x);
+ tmp.push(annotation.segmentation[0][i + 1] + shift.y);
+ }
+
+ const poly = new Konva.Line({
+ points: tmp,
+ fill: this.colorService.getColor(annotation.category_name),
+ stroke: 'black',
+ opacity: 0.5,
+ strokeWidth: 4,
+ closed: true,
+ draggable: true
+ });
+
+ const transformer = new Konva.Transformer({
+ anchorFill: '#FFFFFF',
+ anchorSize: 10,
+ rotateEnabled: false,
+ enabledAnchors: [],
+ borderStroke: 'green',
+ });
+
+ if (annotation.isSelected) {
+ transformer.attachTo(poly);
+ PolygonLabelingService.buildAnchors(layer, annotation, imageView, poly);
+ }
+
+ this.addDragHandler(poly, annotation, layer, imageView);
+ this.addMouseHandler(poly, annotation, layer, transformer);
+ this.addClickHandler(poly, annotation, layer, transformer, imageView);
+
+ layer.add(poly);
+ layer.add(transformer);
+ }
+
+ private addClickHandler(poly, annotation, layer, transformer, imageView) {
+ poly.on('click', function() {
+ annotation.isSelected = true;
+ transformer.attachTo(this);
+ PolygonLabelingService.buildAnchors(layer, annotation, imageView, poly);
+ layer.batchDraw();
+ });
+
+ poly.on('dblclick', function() {
+ annotation.isSelected = false;
+ PolygonLabelingService.removeAnchors(layer, annotation.id);
+ transformer.detach();
+ layer.batchDraw();
+ });
+
+ }
+
+ private addMouseHandler(poly, annotation, layer, transformer) {
+ poly.on('mouseover', function() {
+ annotation.isHovered = true;
+ this.opacity(0.8);
+ layer.batchDraw();
+ });
+
+ poly.on('mouseout', function() {
+ annotation.isHovered = false;
+ this.opacity(0.5);
+ layer.batchDraw();
+ });
+ }
+
+
+ private addDragHandler(poly, annotation, layer, imageView) {
+ let offset: number[];
+
+ poly.on('dragstart', function() {
+ const position = imageView.getImagePointerPosition();
+ offset = [];
+ for (let i = 0; i < annotation.segmentation[0].length; i += 2) {
+ offset.push(annotation.segmentation[0][i] - position.x);
+ offset.push(annotation.segmentation[0][i + 1] - position.y);
+ }
+ });
+
+ poly.on('dragmove', function() {
+ const position = imageView.getImagePointerPosition();
+ const tmp = [];
+ for (let i = 0; i < annotation.segmentation[0].length; i += 2) {
+ tmp.push(position.x + offset[i]);
+ tmp.push(position.y + offset[i + 1]);
+ }
+ annotation.segmentation[0] = tmp;
+ if (annotation.isSelected) {
+ PolygonLabelingService.buildAnchors(layer, annotation, imageView, poly);
+ }
+ });
+
+ }
+
+}
diff --git a/ui/src/app/core-ui/image/services/ReactLabeling.service.ts b/ui/src/app/core-ui/image/services/ReactLabeling.service.ts
new file mode 100644
index 0000000..1059dba
--- /dev/null
+++ b/ui/src/app/core-ui/image/services/ReactLabeling.service.ts
@@ -0,0 +1,198 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { Injectable } from '@angular/core';
+import Konva from 'konva';
+import { Annotation } from '../../../core-model/coco/Annotation';
+import { ICoordinates } from '../model/coordinates';
+import { ColorService } from './color.service';
+
+@Injectable()
+export class ReactLabelingService {
+
+ private lastPosition: ICoordinates;
+ private reactSize: ICoordinates;
+
+ private isLabeling: boolean;
+
+ constructor(private colorService: ColorService) {
+
+ }
+
+
+ startLabeling(position: ICoordinates) {
+ this.isLabeling = true;
+ this.lastPosition = position;
+ this.reactSize = {x: 0, y: 0};
+ }
+
+ executeLabeling(position: ICoordinates) {
+ this.reactSize.x = position.x - this.lastPosition.x;
+ this.reactSize.y = position.y - this.lastPosition.y;
+ }
+
+ endLabeling(position: ICoordinates) {
+ this.isLabeling = false;
+ if (this.reactSize.x > 0 || this.reactSize.y > 0) {
+ return [this.lastPosition, this.reactSize];
+ }
+ }
+
+ tempDraw(layer: Konva.Layer, shift: ICoordinates, label: string) {
+ if (this.isLabeling && (this.reactSize.x > 0 || this.reactSize.y > 0)) {
+ const box = new Konva.Rect({
+ x: this.lastPosition.x + shift.x,
+ y: this.lastPosition.y + shift.y,
+ width: this.reactSize.x,
+ height: this.reactSize.y,
+ fill: this.colorService.getColor(label),
+ opacity: 0.5,
+ stroke: 'black',
+ strokeWidth: 4,
+ draggable: false
+ });
+ layer.add(box);
+ }
+ }
+
+ draw(layer: Konva.Layer, shift: ICoordinates, annotation: Annotation, imageView) {
+ const rect = new Konva.Rect({
+ x: annotation.bbox[0] + shift.x,
+ y: annotation.bbox[1] + shift.y,
+ width: annotation.bbox[2],
+ height: annotation.bbox[3],
+ fill: this.colorService.getColor(annotation.category_name),
+ opacity: 0.5,
+ stroke: 'black',
+ strokeWidth: 4,
+ draggable: true,
+ });
+
+ const transformer = new Konva.Transformer({
+ anchorFill: '#FFFFFF',
+ anchorSize: 10,
+ rotateEnabled: false,
+ keepRatio: false,
+ borderStroke: 'green',
+ });
+
+ if (annotation.isSelected) {
+ transformer.attachTo(rect);
+ }
+
+ this.addDragHandler(rect, annotation, imageView);
+ this.addTransformHandler(rect, annotation, imageView);
+ this.addMouseHandler(rect, annotation, layer, transformer);
+ this.addClickHandler(rect, annotation, layer, transformer);
+
+ layer.add(rect);
+ layer.add(transformer);
+ }
+
+ private addClickHandler(rect, annotation, layer, transformer) {
+ rect.on('click', function() {
+ annotation.isSelected = true;
+ transformer.attachTo(this);
+ layer.batchDraw();
+ });
+
+ rect.on('dblclick', function() {
+ annotation.isSelected = false;
+ transformer.detach();
+ layer.batchDraw();
+ });
+
+ }
+
+ private addMouseHandler(rect, annotation, layer, transformer) {
+ rect.on('mouseover', function() {
+ annotation.isHovered = true;
+ rect.opacity(0.8);
+ layer.batchDraw();
+ });
+
+ rect.on('mouseout', function() {
+ annotation.isHovered = false;
+ rect.opacity(0.5);
+ layer.batchDraw();
+ });
+
+ transformer.on('mouseover', function() {
+ annotation.isHovered = true;
+ });
+
+ transformer.on('mouseout', function() {
+ annotation.isHovered = false;
+ });
+ }
+
+ private addTransformHandler(rect, annotation, imageView) {
+ let resizer: string;
+
+ rect.on('transformstart', function(e) {
+ resizer = e.evt.currentTarget.parent.movingResizer;
+ });
+
+ rect.on('transform', function(e) {
+ const position = imageView.getImagePointerPosition();
+ if (resizer === 'top-left') {
+ annotation.bbox[2] += annotation.bbox[0] - position.x;
+ annotation.bbox[3] += annotation.bbox[1] - position.y;
+ annotation.bbox[0] = position.x;
+ annotation.bbox[1] = position.y;
+ } else if (resizer === 'top-right') {
+ annotation.bbox[2] = Math.abs(annotation.bbox[0] - position.x);
+ annotation.bbox[3] += annotation.bbox[1] - position.y;
+ annotation.bbox[1] = position.y;
+ } else if (resizer === 'bottom-left') {
+ annotation.bbox[2] += annotation.bbox[0] - position.x;
+ annotation.bbox[3] = Math.abs(annotation.bbox[1] - position.y);
+ annotation.bbox[0] = position.x;
+ } else if (resizer === 'bottom-right') {
+ annotation.bbox[2] = Math.abs(annotation.bbox[0] - position.x);
+ annotation.bbox[3] = Math.abs(annotation.bbox[1] - position.y);
+ } else if (resizer === 'top-center') {
+ annotation.bbox[3] += annotation.bbox[1] - position.y;
+ annotation.bbox[1] = position.y;
+ } else if (resizer === 'middle-right') {
+ annotation.bbox[2] = Math.abs(annotation.bbox[0] - position.x);
+ } else if (resizer === 'middle-left') {
+ annotation.bbox[2] += annotation.bbox[0] - position.x;
+ annotation.bbox[0] = position.x;
+ } else if ( resizer === 'bottom-center') {
+ annotation.bbox[3] = Math.abs(annotation.bbox[1] - position.y);
+ }
+ });
+ }
+
+ private addDragHandler(rect, annotation, imageView) {
+ let offset: ICoordinates;
+
+ rect.on('dragstart', function() {
+ const position = imageView.getImagePointerPosition();
+ offset = {x: annotation.bbox[0] - position.x, y: annotation.bbox[1] - position.y};
+ });
+
+ rect.on('dragmove', function() {
+ const position = imageView.getImagePointerPosition();
+ annotation.bbox[0] = imageView.getImagePointerPosition().x + offset.x;
+ annotation.bbox[1] = imageView.getImagePointerPosition().y + offset.y;
+ });
+
+ }
+
+}
diff --git a/ui/src/app/core-ui/imageLabeler/util/color.util.ts b/ui/src/app/core-ui/image/services/color.service.ts
similarity index 73%
rename from ui/src/app/core-ui/imageLabeler/util/color.util.ts
rename to ui/src/app/core-ui/image/services/color.service.ts
index 65c4526..4ee5583 100644
--- a/ui/src/app/core-ui/imageLabeler/util/color.util.ts
+++ b/ui/src/app/core-ui/image/services/color.service.ts
@@ -15,19 +15,22 @@
* limitations under the License.
*/
-export class ColorUtil {
+import { Injectable } from '@angular/core';
- static getColor(label) {
- var hash = 0;
- for (var i = 0; i < label.length; i++) {
- hash = label.charCodeAt(i) + ((hash << 5) - hash);
+@Injectable()
+export class ColorService {
+
+ getColor(str) {
+ let hash = 0;
+ for (let i = 0; i < str.length; i++) {
+ hash = str.charCodeAt(i) + ((hash << 5) - hash);
}
- var colour = '#';
- for (var i = 0; i < 3; i++) {
- var value = (hash >> (i * 8)) & 0xFF;
+ let colour = '#';
+ for (let i = 0; i < 3; i++) {
+ const value = (hash >> (i * 8)) & 0xFF;
colour += ('00' + value.toString(16)).substr(-2);
}
return colour;
}
-}
\ No newline at end of file
+}
diff --git a/ui/src/app/core-ui/imageLabeler/util/imageTranslation.util.ts b/ui/src/app/core-ui/image/util/imageTranslation.util.ts
similarity index 90%
rename from ui/src/app/core-ui/imageLabeler/util/imageTranslation.util.ts
rename to ui/src/app/core-ui/image/util/imageTranslation.util.ts
index a16799c..b42ccd8 100644
--- a/ui/src/app/core-ui/imageLabeler/util/imageTranslation.util.ts
+++ b/ui/src/app/core-ui/image/util/imageTranslation.util.ts
@@ -29,13 +29,13 @@ export class ImageTranslationUtil {
}
static mouseMove(mousePos): [number, number] {
- let mouseX = mousePos[0];
- let mouseY = mousePos[1];
+ const mouseX = mousePos[0];
+ const mouseY = mousePos[1];
return [
this.lastImageTranslationX + (mouseX - this.lastMouseX) ,
- this.lastImageTranslationY + (mouseY- this.lastMouseY )
- ]
+ this.lastImageTranslationY + (mouseY - this.lastMouseY )
+ ];
}
-}
\ No newline at end of file
+}
diff --git a/ui/src/app/core-ui/imageLabeler/annotation/imageAnnotation.ts b/ui/src/app/core-ui/imageLabeler/annotation/imageAnnotation.ts
deleted file mode 100644
index a97afc0..0000000
--- a/ui/src/app/core-ui/imageLabeler/annotation/imageAnnotation.ts
+++ /dev/null
@@ -1,192 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-import { AnnotationMode } from "./annotationMode";
-import { ReactAnnotationUtil } from "./reactAnnotation.util";
-import { Injectable } from "@angular/core";
-import { CocoFormat } from "../../../core-model/coco/Coco.format";
-import { PolygonAnnotationUtil } from "./polygonAnnotation.util";
-
-@Injectable()
-export class ImageAnnotation {
-
- private interactionMode: AnnotationMode = AnnotationMode.ReactLabeling;
- private lastLabelMode: AnnotationMode = AnnotationMode.ReactLabeling;
- private selectedAnnotation;
-
- private coco: CocoFormat;
- private src;
- public saved: boolean = true;
-
-
- newImage(src, imageName, width, height) {
- this.src = src;
- //TODO get Coco file form backend if exists
- this.coco = new CocoFormat(imageName, width, height);
- this.saved = true;
- }
-
- mouseDown(imageCord, scale) {
- this.selectedAnnotation = this.checkForSelectedAnnotation(imageCord, this.coco.annotations, scale);
-
- if (this.interactionMode == AnnotationMode.PolygonTransform){
- PolygonAnnotationUtil.mouseDownTransform(imageCord,
- this.selectedAnnotation, scale)
- } else if (this.interactionMode == AnnotationMode.ReactLabeling) {
- ReactAnnotationUtil.mouseDownCreate(imageCord);
- } else if (this.interactionMode == AnnotationMode.ReactResize){
- ReactAnnotationUtil.mouseDownTransform(imageCord,
- this.selectedAnnotation, scale)
- } else if (this.interactionMode == AnnotationMode.PolygonLabeling){
- this.saved = false;
- PolygonAnnotationUtil.mouseDownCreate(imageCord)
- }
- }
-
- mouseMover(imageCord, imageXShift, imageYShift, context, label) {
- this.saved = false;
- if (this.interactionMode == AnnotationMode.PolygonTransform){
- PolygonAnnotationUtil.mouseMoveTransform(imageCord, this.selectedAnnotation)
- } else if (this.interactionMode == AnnotationMode.ReactLabeling) {
- ReactAnnotationUtil.mouseMoveCreate(imageCord, imageXShift, imageYShift, context, label);
- } else if (this.interactionMode == AnnotationMode.ReactResize){
- ReactAnnotationUtil.mouseMoveTansform(imageCord, this.selectedAnnotation);
- } else if (this.interactionMode == AnnotationMode.PolygonLabeling){
- PolygonAnnotationUtil.mouseMoveCreate(imageCord, imageXShift, imageYShift, context, label)
- }
- }
-
- mouseUp(imageCords, label, labelCategory) {
- if (this.interactionMode == AnnotationMode.ReactLabeling) {
- let labelId = this.coco.getLabelId(label, labelCategory);
- ReactAnnotationUtil.mouseUpCreate(imageCords, this.coco, labelId);
- }
- }
-
- dblclick (imageCords, label, labelCategory) {
- let labelId = this.coco.getLabelId(label, labelCategory);
- PolygonAnnotationUtil.finishCreate(imageCords, this.coco, labelId)
- }
-
- save(): boolean {
- //TODO save coco file in backend
- this.saved = true;
- return true;
- }
-
- deleteAnnotation(annotation) {
- if (annotation !== undefined) {
- this.coco.removeAnnotation(annotation.id);
- }
- }
-
- deleteSelectedAnnotation() {
- if (this.selectedAnnotation !== undefined) {
- this.coco.removeAnnotation(this.selectedAnnotation.id);
- }
- }
-
- annotationHovering(imageCords, scale) {
- for(let annotation of this.coco.annotations) {
- annotation.isHovered = false;
- if (!annotation.isSelected) {
- if (annotation.isBox()) {
- annotation.isHovered = ReactAnnotationUtil.checkIfClicked(imageCords, annotation)
- } else {
- annotation.isHovered = PolygonAnnotationUtil.checkIfClicked(imageCords, annotation, scale)
- }
- }
- }
- }
-
- annotationDraw(imageXShift, imageYShift, scale, context) {
- for(let annotation of this.coco.annotations) {
- let label = this.coco.getLabelById(annotation.category_id);
- if (annotation.isBox()) {
- ReactAnnotationUtil.draw(annotation, label, context, imageXShift, imageYShift, scale)
- } else {
- PolygonAnnotationUtil.draw(annotation, label, context, imageXShift, imageYShift, scale)
- }
- }
- }
-
- private checkForSelectedAnnotation(imageCord, annotations, scale) {
- let selectedAnnotation = undefined;
- for(let annotation of annotations) {
- let clicked = false;
- if (annotation.isBox()) {
- clicked = ReactAnnotationUtil.checkIfClicked(imageCord, annotation)
- } else {
- clicked = PolygonAnnotationUtil.checkIfClicked(imageCord, annotation, scale)
- }
- if (clicked) {
- annotation.isHovered = false;
- selectedAnnotation = annotation;
- }
- annotation.isSelected = clicked;
- }
-
- if (selectedAnnotation !== undefined) {
- if (selectedAnnotation.isBox()) {
- this.interactionMode = AnnotationMode.ReactResize;
- } else {
- this.interactionMode = AnnotationMode.PolygonTransform;
- }
- } else {
- this.interactionMode = this.lastLabelMode;
- }
-
- return selectedAnnotation
- }
-
- changeLabel(annonation, label, category) {
- let labelId = this.coco.getLabelId(label, category);
- annonation.category_id = labelId;
- }
-
- getAnnotations() {
- return this.coco?.annotations;
- }
-
- getLabelById(id) {
- return this.coco.getLabelById(id);
- }
-
- isReactMode() {
- return this.interactionMode == AnnotationMode.ReactLabeling || this.interactionMode == AnnotationMode.ReactResize;
- }
-
- isPolygonMode() {
- return this.interactionMode == AnnotationMode.PolygonLabeling || this.interactionMode == AnnotationMode.PolygonTransform;
- }
-
- setReactMode() {
- this.lastLabelMode = AnnotationMode.ReactLabeling;
- this.interactionMode = AnnotationMode.ReactLabeling;
- }
-
- setPolygonMode() {
- this.lastLabelMode = AnnotationMode.PolygonLabeling;
- this.interactionMode = AnnotationMode.PolygonLabeling;
- }
-
- isPolygonLabeling() {
- return PolygonAnnotationUtil.isLabeling;
- }
-
-
-}
diff --git a/ui/src/app/core-ui/imageLabeler/annotation/polygonAnnotation.util.ts b/ui/src/app/core-ui/imageLabeler/annotation/polygonAnnotation.util.ts
deleted file mode 100644
index 25c176b..0000000
--- a/ui/src/app/core-ui/imageLabeler/annotation/polygonAnnotation.util.ts
+++ /dev/null
@@ -1,155 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-import { ColorUtil } from "../util/color.util";
-import * as pointInPolygon from 'point-in-polygon';
-
-export class PolygonAnnotationUtil {
-
- private static backgroundHoverAlpha = 0.6;
- private static backgroundAlpha = 0.2;
-
- public static isLabeling;
- private static points;
-
- //mouse position
- private static lastImageCordX = 0;
- private static lastImageCordY = 0;
-
- private static selectedPoint = undefined;
-
- static mouseDownCreate(imageCord) {
- if (this.isLabeling) {
- this.points.push(imageCord)
- } else {
- this.isLabeling = true;
- this.points = [];
- this.points.push(imageCord)
- }
- }
-
- static mouseDownTransform(imageCord, annotation, scale) {
- this.lastImageCordX = imageCord[0];
- this.lastImageCordY = imageCord[1];
-
- this.selectedPoint = this.getPointId(imageCord, annotation, scale);
- }
-
- static mouseMoveCreate(imageCord, imageXShift, imageYShift, context, label) {
- context.strokeStyle = ColorUtil.getColor(label);
- context.fillStyle = context.strokeStyle;
- context.globalAlpha = this.backgroundHoverAlpha;
-
- context.beginPath();
- context.moveTo(this.points[0][0] + imageXShift, this.points[0][1] + imageYShift);
- for (var i = 1; i < this.points.length; i++) {
- context.lineTo(this.points[i][0] + imageXShift, this.points[i][1] + imageYShift);
- }
-
- context.lineTo(imageCord[0] + imageXShift, imageCord[1] + imageYShift);
- context.closePath();
- context.stroke();
- context.fill();
- context.globalAlpha = 1;
- }
-
- static mouseMoveTransform(imageCord, annotation) {
- if (this.selectedPoint !== undefined) {
- console.log(2 * this.selectedPoint)
- annotation.segmentation[0][2 * this.selectedPoint] = imageCord[0];
- annotation.segmentation[0][2 * this.selectedPoint + 1] = imageCord[1];
- }
- }
-
- static finishCreate(imageCords, coco, labelId) {
- this.isLabeling = false;
- //this.points.push(imageCords);
-
- let points = [];
- for (let i = 0; i < this.points.length-1 ; i+=1) {
- points.push(this.points[i][0]);
- points.push(this.points[i][1]);
- }
- console.log(points);
- coco.addPolygonAnnotation(points, labelId)
- }
-
- static draw(annotation, label, context, imageXShift, imageYShift, scale) {
- if (annotation.isHovered) {
- context.globalAlpha = this.backgroundHoverAlpha
- } else if (annotation.isSelected) {
- context.globalAlpha = this.backgroundHoverAlpha;
- } else {
- context.globalAlpha = this.backgroundAlpha;
- }
- context.strokeStyle = ColorUtil.getColor(label);
- context.fillStyle = context.strokeStyle;
-
- context.beginPath();
- let points = annotation.segmentation[0];
-
- context.moveTo(points[0] + imageXShift, points[1] + imageYShift);
- for (var i = 2; i < points.length; i+=2) {
- context.lineTo(points[i] + imageXShift, points[i+1] + imageYShift);
- }
- context.closePath();
- context.fill();
- context.globalAlpha = 1;
- context.stroke();
-
- if (annotation.isSelected) {
- let size = this.getResizeCirlceSize(scale);
-
- for (var i = 0; i < points.length; i+=2) {
- context.beginPath();
- context.arc(points[i] + imageXShift, points[i+1] + imageYShift, size, 0, 2 * Math.PI);
- context.fill();
- }
- }
- }
-
- private static getResizeCirlceSize(scale) {
- return Math.floor(10 / scale);
- }
-
- private static getPointId(imageCords, annotation, scale) {
- let points = annotation.segmentation[0];
- for (var i = 0; i < points.length; i+=2) {
- if (Math.abs(imageCords[0] - points[i]) < this.getResizeCirlceSize(scale) &&
- Math.abs(imageCords[1] - points[i+1]) < this.getResizeCirlceSize(scale)) {
- return i / 2;
- }
- }
- return undefined;
- }
-
- public static checkIfClicked(imageCords, annotation, scale) {
- let points = [];
- for (var i = 0; i < annotation.segmentation[0].length; i+=2) {
- points.push([annotation.segmentation[0][i], annotation.segmentation[0][i+1]]);
- }
- let result = pointInPolygon(imageCords, points);
- if (result) {
- return result;
- }
- if (annotation.isSelected && this.getPointId(imageCords, annotation, scale) !== undefined) {
- return true;
- }
- return false;
- }
-
-}
diff --git a/ui/src/app/core-ui/imageLabeler/annotation/reactAnnotation.util.ts b/ui/src/app/core-ui/imageLabeler/annotation/reactAnnotation.util.ts
deleted file mode 100644
index 505b78b..0000000
--- a/ui/src/app/core-ui/imageLabeler/annotation/reactAnnotation.util.ts
+++ /dev/null
@@ -1,234 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-import { ColorUtil } from "../util/color.util";
-
-export class ReactAnnotationUtil {
-
- private static backgroundHoverAlpha = 0.6;
- private static backgroundAlpha = 0.2;
-
- //mouse position
- private static lastImageCordX = 0;
- private static lastImageCordY = 0;
-
- private static isMouseDown = false;
-
- private static reactHeight = 0;
- private static reactWidth = 0;
-
- //resize
- private static pressedTopLeft = false;
- private static pressedTopRight = false;
- private static pressedButtomLeft = false;
- private static pressedButtomRight = false;
- private static pressedTop = false;
- private static pressedRight = false;
- private static pressedLeft = false;
- private static pressedButtom = false;
-
- private static offSetX;
- private static offSetY;
-
- static mouseDownCreate(imageCord) {
- this.lastImageCordX = imageCord[0];
- this.lastImageCordY = imageCord[1];
- this.isMouseDown = true;
- }
-
- static mouseDownTransform(imageCord, annotation, scale) {
- this.lastImageCordX = imageCord[0];
- this.lastImageCordY = imageCord[1];
-
- this.setSelectedResizeBox(annotation, scale);
-
- this.offSetX = annotation.bbox[0] - imageCord[0];
- this.offSetY = annotation.bbox[1] - imageCord[1];
- }
-
- static mouseMoveCreate(imageCord, imageXShift, imageYShift, context, label) {
- if(this.isMouseDown) {
- this.reactWidth = imageCord[0] - this.lastImageCordX;
- this.reactHeight = imageCord[1] - this.lastImageCordY;
-
- context.strokeStyle = ColorUtil.getColor(label);
- context.fillStyle = context.strokeStyle;
- context.beginPath();
- context.globalAlpha = this.backgroundHoverAlpha;
- context.fillRect(this.lastImageCordX + imageXShift, this.lastImageCordY + imageYShift,
- this.reactWidth, this.reactHeight);
- context.globalAlpha = 1;
- context.strokeRect(this.lastImageCordX + imageXShift, this.lastImageCordY + imageYShift,
- this.reactWidth, this.reactHeight);
- context.fillText(label, this.lastImageCordX + imageXShift, this.lastImageCordY,- 5 );
- }
- }
-
- static mouseMoveTansform(imageCord, annotation) {
-
- if (this.pressedTopLeft) {
- annotation.bbox[2] += annotation.bbox[0]- imageCord[0];
- annotation.bbox[3] += annotation.bbox[1]- imageCord[1];
- annotation.bbox[0] = imageCord[0];
- annotation.bbox[1] = imageCord[1];
- } else if (this.pressedTopRight) {
- annotation.bbox[2] = Math.abs(annotation.bbox[0] - imageCord[0]);
- annotation.bbox[3] += annotation.bbox[1] - imageCord[1];
- annotation.bbox[1] = imageCord[1];
- } else if (this.pressedButtomLeft) {
- annotation.bbox[2] += annotation.bbox[0]- imageCord[0];
- annotation.bbox[3] = Math.abs(annotation.bbox[1] - imageCord[1]);
- annotation.bbox[0] = imageCord[0];
- } else if (this.pressedButtomRight) {
- annotation.bbox[2] = Math.abs(annotation.bbox[0] - imageCord[0]);
- annotation.bbox[3] = Math.abs(annotation.bbox[1] - imageCord[1]);
- } else if (this.pressedTop) {
- annotation.bbox[3] += annotation.bbox[1] - imageCord[1];
- annotation.bbox[1] = imageCord[1];
- } else if (this.pressedRight) {
- annotation.bbox[2] = Math.abs(annotation.bbox[0] - imageCord[0]);
- } else if (this.pressedLeft) {
- annotation.bbox[2] += annotation.bbox[0] - imageCord[0];
- annotation.bbox[0] = imageCord[0];
- } else if (this.pressedButtom) {
- annotation.bbox[3] = Math.abs(annotation.bbox[1] - imageCord[1]);
- } else {
- annotation.bbox[0] = imageCord[0] + this.offSetX ;
- annotation.bbox[1] = imageCord[1] + this.offSetY;
- }
-
- }
-
- static mouseUpCreate(imageCords, coco, labelId) {
- this.isMouseDown = false;
-
- let reactWidth = imageCords[0] - this.lastImageCordX;
- let reactHeight = imageCords[1] - this.lastImageCordY;
-
- if (reactWidth > 0 && reactHeight > 0) {
- coco.addReactAnnotation(this.lastImageCordX, this.lastImageCordY , reactWidth, reactHeight, labelId);
- console.log('Add react Label:', this.lastImageCordX, this.lastImageCordY, reactWidth, reactHeight, labelId)
- }
- }
-
- static draw(annotation, label, context, imageXShift, imageYShift, scale) {
- let bbox = annotation.bbox;
- // this.drawBox(bbox[0] + imageXShift, bbox[1] + imageYShift, bbox[2], bbox[3], label, color, context, annotation.isHovered);
-
- if (annotation.isHovered) {
- context.globalAlpha = this.backgroundHoverAlpha
- } else if (annotation.isSelected) {
- context.globalAlpha = this.backgroundHoverAlpha;
- } else {
- context.globalAlpha = this.backgroundAlpha;
- }
- context.strokeStyle = ColorUtil.getColor(label);
- context.fillStyle = context.strokeStyle;
- context.beginPath();
- context.fillRect(bbox[0] + imageXShift, bbox[1] + imageYShift, bbox[2], bbox[3]);
- context.globalAlpha = 1;
- context.strokeRect(bbox[0] + imageXShift, bbox[1] + imageYShift, bbox[2], bbox[3]);
- context.font = '12px Arial';
- context.fillText(label, bbox[0] + imageXShift, bbox[1] + imageYShift - 5 );
- //context.stroke();
-
- //Control boxes for transformation
- if (annotation.isSelected) {
- let size = this.getResizeBoxSize(scale);
- this.drawTransformBox(bbox[0] + imageXShift, bbox[1] + imageYShift, size, size, context);
- this.drawTransformBox(bbox[0] + bbox[2] + imageXShift - size, bbox[1] + imageYShift, size, size, context);
- this.drawTransformBox(bbox[0] + imageXShift, bbox[1] + bbox[3] + imageYShift - size, size, size, context);
- this.drawTransformBox( bbox[0] + bbox[2] + imageXShift - size, bbox[1] + bbox[3] + imageYShift - size, size, size, context);
-
- this.drawTransformBox(bbox[0] + imageXShift + size, bbox[1] + imageYShift, annotation.bbox[2] - 2* size, size, context);
- this.drawTransformBox(bbox[0] + imageXShift, bbox[1] + imageYShift + size, size, annotation.bbox[3] - 2* size, context);
- this.drawTransformBox(bbox[0] + bbox[2] + imageXShift - size, bbox[1] + imageYShift + size, size, annotation.bbox[3] - 2* size, context);
- this.drawTransformBox(bbox[0] + imageXShift + size, bbox[1] + bbox[3] + imageYShift - size, annotation.bbox[2] - 2* size, size, context);
- }
-
- }
-
- private static getResizeBoxSize(scale) {
- return Math.floor(15 / scale);
- }
-
- private static drawTransformBox(x, y, widght, heigt, context) {
- //context.fillStyle = '#fafafa';
-
- context.beginPath();
- // context.fillRect(x, y, widght, heigt);
- //context.strokeStyle = 'black';
- context.strokeRect(x, y, widght, heigt);
- context.stroke();
- }
-
- private static setSelectedResizeBox(annotation, scale) {
- this.pressedTopLeft = false;
- this.pressedTopRight = false;
- this.pressedButtomLeft = false;
- this.pressedButtomRight = false;
- this.pressedTop = false;
- this.pressedRight = false;
- this.pressedLeft = false;
- this.pressedButtom = false;
-
- let size = this.getResizeBoxSize(scale);
-
- if (annotation.bbox[0] <= this.lastImageCordX && this.lastImageCordX <= annotation.bbox[0] + size &&
- annotation.bbox[1] <= this.lastImageCordY && this.lastImageCordY <= annotation.bbox[1] + size) {
- this.pressedTopLeft = true;
- } else if (annotation.bbox[0] + annotation.bbox[2] - size <= this.lastImageCordX && this.lastImageCordX <= annotation.bbox[0] + annotation.bbox[2] &&
- annotation.bbox[1] <= this.lastImageCordY && this.lastImageCordY <= annotation.bbox[1] + size) {
- this.pressedTopRight = true;
- } else if (annotation.bbox[0] <= this.lastImageCordX && this.lastImageCordX <= annotation.bbox[0] + size &&
- annotation.bbox[1] + annotation.bbox[3] >= this.lastImageCordY && this.lastImageCordY >= annotation.bbox[1] + annotation.bbox[3] - size) {
- this.pressedButtomLeft = true;
- return true;
- } else if (annotation.bbox[0] + annotation.bbox[2] - size <= this.lastImageCordX && this.lastImageCordX <= annotation.bbox[0] + annotation.bbox[2] &&
- annotation.bbox[1] + annotation.bbox[3] >= this.lastImageCordY && this.lastImageCordY >= annotation.bbox[1] + annotation.bbox[3] - size) {
- this.pressedButtomRight = true;
- } else if(annotation.bbox[0] + size <= this.lastImageCordX && this.lastImageCordX <= annotation.bbox[0] + annotation.bbox[2] - size &&
- annotation.bbox[1] <= this.lastImageCordY && this.lastImageCordY <= annotation.bbox[1] + size) {
- this.pressedTop = true;
- console.log('pressed top')
- } else if (annotation.bbox[0] + annotation.bbox[2] - size <= this.lastImageCordX && this.lastImageCordX <= annotation.bbox[0] + annotation.bbox[2] &&
- annotation.bbox[1] + size <= this.lastImageCordY && this.lastImageCordY <= annotation.bbox[1] + annotation.bbox[3] - size) {
- this.pressedRight = true;
- console.log('pressedRight')
- } else if (annotation.bbox[0] - size <= this.lastImageCordX && this.lastImageCordX <= annotation.bbox[0] + size &&
- annotation.bbox[1] + size <= this.lastImageCordY && this.lastImageCordY <= annotation.bbox[1] + annotation.bbox[3] - size) {
- this.pressedLeft = true;
- console.log('pressedLeft')
- } else if (annotation.bbox[0] + size <= this.lastImageCordX && this.lastImageCordX <= annotation.bbox[0] + annotation.bbox[2] - size &&
- annotation.bbox[1] + annotation.bbox[3] - size <= this.lastImageCordY && this.lastImageCordY <= annotation.bbox[1] + annotation.bbox[3]) {
- this.pressedButtom = true;
- console.log('pressedButtom')
- }
- }
-
- public static checkIfClicked(imageCords, annotation) {
- if (annotation.isBox()) {
- if (annotation.bbox[0] <= imageCords[0] && imageCords[0] <= annotation.bbox[0] + annotation.bbox[2] &&
- annotation.bbox[1] <= imageCords[1] && imageCords[1] <= annotation.bbox[1] + annotation.bbox[3]
- ) {
- return true;
- }
- }
- }
-
-
-}
\ No newline at end of file
diff --git a/ui/src/app/core-ui/imageLabeler/imageLabeler.component.html b/ui/src/app/core-ui/imageLabeler/imageLabeler.component.html
deleted file mode 100644
index fba9a88..0000000
--- a/ui/src/app/core-ui/imageLabeler/imageLabeler.component.html
+++ /dev/null
@@ -1,125 +0,0 @@
-<!--
- ~ Licensed to the Apache Software Foundation (ASF) under one or more
- ~ contributor license agreements. See the NOTICE file distributed with
- ~ this work for additional information regarding copyright ownership.
- ~ The ASF licenses this file to You under the Apache License, Version 2.0
- ~ (the "License"); you may not use this file except in compliance with
- ~ the License. You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License.
- ~
- -->
-
-<div>
- <button mat-stroked-button [style.background-color]="isImageViewingMode() ? 'lightgrey' : 'white'" (click)="setImageViewInteractionMode()">Viewing</button>
- <button mat-stroked-button [style.background-color]="isImageAnnotateMode() ? 'lightgrey' : 'white'" (click)="setImageAnnotateInteractionMode()">Annotate</button>
- <button mat-stroked-button [style.background-color]="isImageClassifyMode() ? 'lightgrey' : 'white'" (click)="setImageClassifyInteractionMode()">Classify</button>
-</div>
-
-<div fxLayout="column" fxLayoutAlign="space-between " (mouseover)="enterCanvas()" (mouseout)="leaveCanvas()">
- <div fxLayout="row" fxLayoutAlign="space-around start">
- <div fxLayout="column" fxLayoutAlign="space-around center" *ngIf="isImageAnnotateMode() || isImageClassifyMode()">
- <h2>Labels</h2>
- <div class="labelCategorySelectContainer"
- style="padding-left: 5px; padding-right: 5px; border-radius: 50px; font-size: 20px;">
- <mat-form-field style="margin-top: -15px; margin-bottom: -10px">
- <mat-select [(value)]="labelCategory">
- <mat-option *ngFor="let labelCategory of labelCategories" [value]="labelCategory">
- {{labelCategory}}
- </mat-option>
- </mat-select>
- </mat-form-field>
- </div>
- <mat-list>
- <mat-list-item *ngFor="let label of labels[labelCategory]; index as i"
- [style.background-color]="selectedLabel == label ? 'lightgrey' : 'white'"
- style="height: 30px; padding-top: 5px; padding-buttom: 5px; border-radius: 50px; margin-bottom: 5px"
- (click)="selectLabel(label)">
- <mat-icon matListIcon [style.color]="getColor(label)" style="margin-top: -8px;">color_lens</mat-icon>
- <h4 style="margin-top: -3px;"> {{label}} <label *ngIf="i < 10"> #{{i+1}}</label> </h4>
- </mat-list-item>
- </mat-list>
- </div>
-
- <div fxLayout="column" fxLayoutAlign="space-around center">
-
- <div *ngIf="isImageClassifyMode()" style="height: 40px">
- <mat-chip-list>
- <mat-chip *ngFor="let clazz of imageClassification.getClasses()" (removed)="removeClass(clazz)"
- [removable]="true" [style.background-color]="getColor(clazz)">
- {{clazz}}
- <mat-icon matChipRemove>cancel</mat-icon>
- </mat-chip>
- </mat-chip-list>
- </div>
-
- <!--
- <mat-hint>Tip: Move the image with the right mouse button and zoom with the mouse wheel, Use number shortcut to select label</mat-hint>
- <mat-hint>Tip: Use number 'E' and 'Q' to get previous/next image, Move image with WASD, Delete selected Annotatoin with 'Delete' button</mat-hint>
- -->
- <div *ngIf="isImageAnnotateMode()">
- <button mat-button (click)="imageAnnotation.setReactMode()" [style.background-color]="imageAnnotation.isReactMode() ? 'lightgrey' : 'white'"> <mat-icon>crop_3_2</mat-icon></button>
- <button mat-button (click)="imageAnnotation.setPolygonMode()" [style.background-color]="imageAnnotation.isPolygonMode() ? 'lightgrey' : 'white'"> <mat-icon>details</mat-icon></button>
- </div>
-
- <button mat-button [disabled]="imageAnnotation.saved && imageClassification.saved" (click)="save()"><mat-icon>save</mat-icon></button>
-
- <canvas (mousedown)="imageMouseDown($event)" (mousemove)="imageMouseMove($event)" (mouseup)="imageMouseUp($event)"
- #canvas width="800" height="500" (dblclick)="dblclick($event)">
- Your browser does not support the canvas element.
- </canvas>
- <div class="controlButtons">
- <button mat-button (click)="zoomin()"> <mat-icon>zoom_in</mat-icon></button>
- <button mat-button (click)="zoomout()"> <mat-icon>zoom_out</mat-icon></button>
- </div>
- </div>
-
- <div fxLayout="column" fxLayoutAlign="space-between center" *ngIf="isImageAnnotateMode()">
- <h2>Annotations</h2>
- <mat-list>
- <mat-list-item *ngFor="let annotation of imageAnnotation.getAnnotations()" (mouseover)="enterAnnotation(annotation)" (mouseout)="leaveAnnotation(annotation)"
- style="height: 30px; padding-top: 5px; padding-buttom: 5px; border-radius: 50px; margin-bottom: 5px"
- [style.background-color]="annotation.isHovered || annotation.isSelected ? 'lightgrey' : 'white'">
- <mat-icon matListIcon [style.color]="getColor(getLabelById(annotation.category_id))" style="margin-top: -8px;">color_lens</mat-icon>
- <mat-form-field>
- <mat-select [value]="getLabelById(annotation.category_id)">
- <mat-optgroup *ngFor="let labelCategor of labelCategories" [label]="labelCategor">
- <mat-option *ngFor="let label of labels[labelCategor]" [value]="label" (click)="changeLabel(annotation, label, labelCategory)">
- {{label}}
- </mat-option>
- </mat-optgroup>
- </mat-select>
- </mat-form-field>
- <button mat-icon-button (click)="deleteAnnotation(annotation)" style="margin-top: -10px; margin-left: 10px"> <mat-icon>delete_forever</mat-icon></button>
- </mat-list-item>
- </mat-list>
- </div>
-
-
- </div>
-
- <br />
- <br />
-
- <div fxLayout="row" fxLayoutAlign="space-around center" >
- <button mat-icon-button (click)="previousImage()"> <mat-icon>keyboard_arrow_left</mat-icon></button>
-
- <div fxLayout="row" fxLayoutAlign="center " class="imageBar">
- <img *ngFor="let src of imagesSrcs; let i = index" src="{{src}}" (click)="imagesSrcIndex = i; changeImage(imagesSrcIndex)"
- [style.border]="i == imagesSrcIndex ? '5px solid #39b54a' : 'none'">
- </div>
-
-
- <button mat-icon-button (click)="nextImage()"> <mat-icon>keyboard_arrow_right</mat-icon></button>
-
- </div>
-</div>
-
-
-
diff --git a/ui/src/app/core-ui/imageLabeler/imageLabeler.component.ts b/ui/src/app/core-ui/imageLabeler/imageLabeler.component.ts
deleted file mode 100644
index 4e18ce3..0000000
--- a/ui/src/app/core-ui/imageLabeler/imageLabeler.component.ts
+++ /dev/null
@@ -1,424 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-import { AfterViewInit, Component, HostListener, OnInit, ViewChild } from "@angular/core";
-import { ImageTranslationUtil } from "./util/imageTranslation.util";
-import { DatalakeRestService } from "../../core-services/datalake/datalake-rest.service";
-import { ColorUtil } from "./util/color.util";
-import { ImageAnnotation } from "./annotation/imageAnnotation";
-import { InteractionMode } from "./interactionMode";
-import { ImageClassification } from "./classification/imageClassification";
-import { MatSnackBar } from "@angular/material/snack-bar";
-
-@Component({
- selector: 'sp-image-labeler',
- templateUrl: './imageLabeler.component.html',
- styleUrls: ['./imageLabeler.component.css']
-})
-export class ImageLabelerComponent implements OnInit, AfterViewInit {
-
- @ViewChild('canvas') canvasRef;
- private canvas;
- private context;
-
- private isLeftMouseDown = false;
- private isRightMouseDown = false;
-
- //canvas properties
- private canvasWidth;
- private canvasHeight;
- private isHoverComponent;
-
- //image
- public imagesSrcs;
- public imagesSrcIndex;
- private image;
- private imageTranslationX = 0;
- private imageTranslationY = 0;
-
- public labels;
- public labelCategories;
- public labelCategory;
- private selectedLabel;
-
- public interactionMode: InteractionMode = InteractionMode.imageAnnotate;
-
- //scale
- private scale: number = 1;
-
-
- constructor(private restService: DatalakeRestService, public imageAnnotation: ImageAnnotation, public imageClassification: ImageClassification,
- private snackBar: MatSnackBar) {
-
- }
-
- ngOnInit(): void {
- //1. get Image Paths
- this.imagesSrcs = this.restService.getImageSrcs();
- this.imagesSrcIndex = 0;
- //2. get Images
-
- //3. get Labels
- this.labels = this.restService.getLabels();
- this.labelCategories = Object.keys(this.restService.getLabels());
- this.labelCategory = this.labelCategories[1];
- this.selectedLabel = this.labels[this.labelCategory][0];
-
- this.isHoverComponent = false;
- }
-
- ngAfterViewInit() {
- this.canvas = this.canvasRef.nativeElement;
- this.context = this.canvas.getContext('2d');
- this.canvasWidth = this.canvas.width;
- this.canvasHeight= this.canvas.height;
-
- this.canvas.addEventListener('contextmenu', event => event.preventDefault());
- this.canvas.addEventListener('DOMMouseScroll',event => this.scroll(event),false);
- this.canvas.addEventListener('mousewheel',event => this.scroll(event),false);
-
- this.changeImage('https://cdn.pixabay.com/photo/2017/10/29/21/05/bridge-2900839_1280.jpg');
- this.context.lineWidth = 2;
- }
-
- changeImage(index) {
- this.image = new Image();
-
- this.image.onload = () => {
- let src = this.imagesSrcs[this.imagesSrcIndex]
- this.imageAnnotation.newImage(src,"Test.png", this.image.width, this.image.height);
- this.imageClassification.newImage(src);
- console.log('Image width: ' + this.image.width);
- console.log('Image height: ' + this.image.height);
- this.scale = Math.min(1, this.canvasWidth / this.image.width, this.canvasHeight / this.image.height);
- console.log('Set Scale to: ' + this.scale);
- this.draw();
- };
- this.image.src = this.imagesSrcs[this.imagesSrcIndex];
- }
-
- imageMouseDown(e) {
- if (e.which == 1) {
- //left click
- this.isLeftMouseDown = true;
-
- switch (this.interactionMode) {
- case InteractionMode.imageAnnotate: this.imageAnnotation.mouseDown(this.getImageCords(e.clientX, e.clientY), this.scale);
- break;
- }
-
- } else if (e.which == 2) {
- //middle click
- } else {
- //right click
- this.isRightMouseDown = true;
- ImageTranslationUtil.mouseDown(this.getCanvasCords(e.clientX, e.clientY), this.imageTranslationX, this.imageTranslationY);
- }
- }
-
- imageMouseMove(e) {
- //TODO solve duplicated code
- if (this.imageAnnotation.isPolygonLabeling()) {
- this.startDraw();
- let imageXShift = (this.canvasWidth - this.image.width) / 2;
- let imageYShift =(this.canvasHeight - this.image.height) / 2;
-
- this.imageAnnotation.annotationDraw(imageXShift, imageYShift, this.scale, this.context);
- this.imageAnnotation.mouseMover(this.getImageCords(e.clientX, e.clientY), imageXShift, imageYShift,
- this.context, this.selectedLabel);
- this.endDraw();
- } else if (this.isLeftMouseDown) {
-
- switch (this.interactionMode) {
- case InteractionMode.imageAnnotate: {
- this.startDraw();
- let imageXShift = (this.canvasWidth - this.image.width) / 2;
- let imageYShift =(this.canvasHeight - this.image.height) / 2;
- this.imageAnnotation.annotationDraw(imageXShift, imageYShift, this.scale, this.context);
- this.imageAnnotation.mouseMover(this.getImageCords(e.clientX, e.clientY), imageXShift, imageYShift,
- this.context, this.selectedLabel);
- this.endDraw();
- }
- break;
- }
-
- } else if (this.isRightMouseDown) {
- let translation = ImageTranslationUtil.mouseMove(this.getCanvasCords(e.clientX, e.clientY));
- this.imageTranslationX = translation[0];
- this.imageTranslationY = translation[1];
- this.draw();
- } else {
- this.imageAnnotation.annotationHovering(this.getImageCords(e.clientX, e.clientY), this.scale);
- this.draw();
- }
-
- }
-
- imageMouseUp(e) {
- if (this.isLeftMouseDown) {
- this.isLeftMouseDown = false;
- switch (this.interactionMode) {
- case InteractionMode.imageAnnotate: {
- this.imageAnnotation.mouseUp(this.getImageCords(e.clientX, e.clientY), this.selectedLabel, this.labelCategory);
- if (!this.imageAnnotation.isPolygonLabeling()) {
- this.draw()
- }
- }
- break;
- }
- }
- if (this.isRightMouseDown) {
- this.isRightMouseDown = false;
- }
- }
-
- dblclick (e) {
- if (this.interactionMode = InteractionMode.imageAnnotate) {
- this.imageAnnotation.dblclick(this.getImageCords(e.clientX, e.clientY), this.selectedLabel, this.labelCategory);
- }
- }
-
- startDraw() {
- this.context.clearRect(0, 0, this.canvasWidth, this.canvasHeight);
-
- let newWidth = this.canvasWidth * this.scale;
- let newHeight = this.canvasHeight * this.scale;
-
- this.context.save();
-
- this.context.translate(-((newWidth - this.canvasWidth) / 2) + this.imageTranslationX,
- -((newHeight - this.canvasHeight) / 2) + this.imageTranslationY);
- this.context.scale(this.scale, this.scale);
-
- this.context.drawImage(this.image, this.canvasWidth / 2 - this.image.width / 2, this.canvasHeight / 2 - this.image.height / 2);
- }
-
- endDraw() {
- this.context.restore();
-
- this.context.beginPath();
- this.context.globalAlpha = 0.8;
- this.context.fillStyle = 'lightgrey';
- this.context.fillRect(0, 0, 50, 20);
- this.context.globalAlpha = 1;
- this.context.font = '12px Arial';
- this.context.fillStyle = 'black';
- this.context.fillText((Math.round(this.scale * 100) / 100).toFixed(2) + " x", 5,15);
- this.context.stroke();
- }
-
- draw() {
- this.startDraw();
- let imageXShift = (this.canvasWidth - this.image.width) / 2;
- let imageYShift =(this.canvasHeight - this.image.height) / 2;
- this.imageAnnotation.annotationDraw(imageXShift, imageYShift, this.scale, this.context);
- this.endDraw();
- }
-
- @HostListener('document:keydown', ['$event'])
- handleShortCuts(event: KeyboardEvent) {
- if (this.isHoverComponent) {
- if (event.code.toLowerCase().includes('digit')) {
- // Number
- let value = Number(event.key);
- if (value != 0 && value <= this.labels[this.labelCategory].length) {
- this.selectLabel(this.labels[this.labelCategory][value - 1]);
- }
- } else {
- let key = event.key;
- switch (key.toLowerCase()) {
- case 'q': this.previousImage();
- break;
- case 'e': this.nextImage();
- break;
- case 'w': this.imageTranslationY += 5; this.draw();
- break;
- case 'a': this.imageTranslationX += 5; this.draw();
- break;
- case 's': this.imageTranslationY -= 5; this.draw();
- break;
- case 'd': this.imageTranslationX -= 5; this.draw();
- break;
- case 'delete': this.imageAnnotation.deleteSelectedAnnotation();
- this.draw();
- }
- if (this.interactionMode == InteractionMode.imageAnnotate) {
- switch (key.toLowerCase()) {
- case 'r': this.imageAnnotation.setReactMode();
- break;
- case 'f': this.imageAnnotation.setPolygonMode();
- this.draw();
- }
- }
- }
- }
- }
-
- getColor(label) {
- return ColorUtil.getColor(label);
- }
-
- getCanvasCords(clientX, clientY): [any, any] {
- return [
- Math.floor(clientX - this.canvas.getBoundingClientRect().left),
- Math.floor(clientY - this.canvas.getBoundingClientRect().top),
- ]
- }
-
- getImageCords(clientX, clientY): [any, any] {
- return [
- Math.floor(((clientX - this.canvas.getBoundingClientRect().left) / this.scale) - ((this.canvasWidth / this.scale - this.image.width) / 2) - (this.imageTranslationX / this.scale)),
- Math.floor(((clientY - this.canvas.getBoundingClientRect().top) / this.scale) - ((this.canvasHeight / this.scale - this.image.height) / 2) - (this.imageTranslationY / this.scale)),
- ]
- }
-
- save() {
- let success: boolean;
-
- if (!this.imageAnnotation.saved) {
- success = this.imageAnnotation.save();
- }
- if (!this.imageClassification.saved) {
- success = this.imageClassification.save();
- }
- if (success) {
- this.openSnackBar('Saved');
- } else {
- this.openSnackBar('Error while saving');
- }
- }
-
- openSnackBar(message: string) {
- this.snackBar.open(message, '', {
- duration: 2000,
- verticalPosition: 'top',
- horizontalPosition: 'right'
- });
- }
-
- //UI Callbacks
-
- nextImage() {
- if (this.imagesSrcIndex < this.imagesSrcs.length - 1) {
- this.save();
- this.imagesSrcIndex++;
- this.changeImage(this.imagesSrcIndex);
- }
- }
-
- previousImage() {
- if (this.imagesSrcIndex > 0) {
- this.save();
- this.imagesSrcIndex--;
- this.changeImage(this.imagesSrcIndex);
- }
- }
-
- scroll(e) {
- this.scale += e.wheelDeltaY * (1/6000);
- this.draw();
- }
-
- zoomin()
- {
- this.scale += 0.05;
- this.draw();
- }
- zoomout()
- {
- this.scale -= 0.05;
- this.draw();
- }
-
- selectLabel(label) {
- this.selectedLabel = label;
- switch (this.interactionMode) {
- case InteractionMode.imageClassify: this.imageClassification.addClass(label);
- break;
- }
- }
-
- enterCanvas() {
- this.isHoverComponent = true;
- }
-
- leaveCanvas() {
- this.isHoverComponent = false;
- }
-
- getLabelById(id) {
- return this.imageAnnotation.getLabelById(id);
- }
-
- setImageViewInteractionMode() {
- this.save();
- this.interactionMode = InteractionMode.imageViewing;
- }
-
- setImageAnnotateInteractionMode() {
- this.save();
- this.interactionMode = InteractionMode.imageAnnotate;
- }
-
- setImageClassifyInteractionMode() {
- this.save();
- this.interactionMode = InteractionMode.imageClassify;
- }
-
- isImageViewingMode() {
- return this.interactionMode == InteractionMode.imageViewing;
- }
-
- isImageAnnotateMode() {
- return this.interactionMode == InteractionMode.imageAnnotate;
-
- }
-
- isImageClassifyMode() {
- return this.interactionMode == InteractionMode.imageClassify;
- }
-
- //annotations ui callbacks
-
- enterAnnotation(annotation) {
- annotation.isHovered = true;
- this.draw()
- }
-
- leaveAnnotation(annotation) {
- annotation.isHovered = false;
- this.draw()
- }
-
- deleteAnnotation(annotation) {
- this.imageAnnotation.deleteAnnotation(annotation);
- this.draw();
- }
-
- changeLabel(annonation, label, category) {
- this.imageAnnotation.changeLabel(annonation, label, category);
- this.draw();
- }
-
- //classification ui callbacks
-
- removeClass(clazz) {
- this.imageClassification.removeClass(clazz);
- }
-
-}
diff --git a/ui/src/app/data-explorer/data-explorer.component.html b/ui/src/app/data-explorer/data-explorer.component.html
index 778be74..0c8719e 100644
--- a/ui/src/app/data-explorer/data-explorer.component.html
+++ b/ui/src/app/data-explorer/data-explorer.component.html
@@ -25,8 +25,8 @@
</div>
<div class="container-fluid">
-
- <sp-image-labeler></sp-image-labeler>
+ <!--<sp-image-labeler></sp-image-labeler>-->
+ <sp-image></sp-image>