Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Piotr | MS-96 | Can image suppored by multi labels #109

Merged
merged 5 commits into from
Aug 18, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added public/ico/camera.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified public/ico/tags.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions src/data/enums/ExportFormatType.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ export enum ExportFormatType {
YOLO = "YOLO",
COCO_JSON = "COCO_JSON",
CSV = "CSV",
JSON = "JSON",
VOC = "VOC",
VGG_JSON = "VGG_JSON"
}
6 changes: 3 additions & 3 deletions src/data/enums/PopupWindowType.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
export enum PopupWindowType {
LOAD_LABEL_NAMES = "LOAD_LABEL_NAMES",
UPDATE_LABEL_NAMES = "UPDATE_LABEL_NAMES",
UPDATE_LABEL = "UPDATE_LABEL",
SUGGEST_LABEL_NAMES = "SUGGEST_LABEL_NAMES",
LOAD_IMAGES = "LOAD_IMAGES",
IMPORT_IMAGES = "IMPORT_IMAGES",
LOAD_AI_MODEL = "LOAD_AI_MODEL",
EXPORT_LABELS = "EXPORT_LABELS",
EXPORT_ANNOTATIONS = "EXPORT_ANNOTATIONS",
INSERT_LABEL_NAMES = 'INSERT_LABEL_NAMES',
EXIT_PROJECT = 'EXIT_PROJECT',
LOADER = 'LOADER'
Expand Down
4 changes: 4 additions & 0 deletions src/data/export/TagExportFormatData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,9 @@ export const TagExportFormatData: IExportFormat[] = [
{
type: ExportFormatType.CSV,
label: "Single CSV file."
},
{
type: ExportFormatType.JSON,
label: "Single JSON file."
}
];
3 changes: 3 additions & 0 deletions src/logic/actions/LabelActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,9 @@ export class LabelActions {
} else {
return labelPolygon
}
}),
labelNameIds: imageData.labelNameIds.filter((labelNameId: string) => {
return !labelNamesIds.includes(labelNameId)
})
}
}
Expand Down
49 changes: 37 additions & 12 deletions src/logic/export/TagLabelsExport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import {ExportFormatType} from "../../data/enums/ExportFormatType";
import {LabelsSelector} from "../../store/selectors/LabelsSelector";
import {ImageData, LabelName} from "../../store/labels/types";
import {ExporterUtil} from "../../utils/ExporterUtil";
import {ImageRepository} from "../imageRepository/ImageRepository";
import {findLast} from "lodash";

export class TagLabelsExporter {
Expand All @@ -11,36 +10,62 @@ export class TagLabelsExporter {
case ExportFormatType.CSV:
TagLabelsExporter.exportAsCSV();
break;
case ExportFormatType.JSON:
TagLabelsExporter.exportAsJSON();
break;
default:
return;
}
}

private static exportAsCSV(): void {
const content: string = LabelsSelector.getImagesData()
.filter((imageData: ImageData) => {
return imageData.labelNameIds.length > 0
})
.map((imageData: ImageData) => {
return TagLabelsExporter.wrapLineLabelsIntoCSV(imageData)})
.filter((imageLabelData: string) => {
return !!imageLabelData})
return TagLabelsExporter.wrapLabelNamesIntoCSV(imageData)})
.join("\n");
const fileName: string = `${ExporterUtil.getExportFileName()}.csv`;
ExporterUtil.saveAs(content, fileName);
}

private static wrapLineLabelsIntoCSV(imageData: ImageData): string {
if (imageData.labelTagId === null || !imageData.loadStatus)
private static exportAsJSON(): void {
const contentObjects: object[] = LabelsSelector.getImagesData()
.filter((imageData: ImageData) => {
return imageData.labelNameIds.length > 0
})
.map((imageData: ImageData) => {
return {
"image": imageData.fileData.name,
"annotations": TagLabelsExporter.wrapLabelNamesIntoJSON(imageData)
}})
const content: string = JSON.stringify(contentObjects);
const fileName: string = `${ExporterUtil.getExportFileName()}.json`;
ExporterUtil.saveAs(content, fileName);
}

private static wrapLabelNamesIntoCSV(imageData: ImageData): string {
if (imageData.labelNameIds.length === 0 || !imageData.loadStatus)
return null;

const image: HTMLImageElement = ImageRepository.getById(imageData.id);
const labelNames: LabelName[] = LabelsSelector.getLabelNames();
const labelName: LabelName = findLast(labelNames, {id: imageData.labelTagId});
const labelFields = !!labelName ? [
labelName.name,
const annotations: string[] = imageData.labelNameIds.map((labelNameId: string) => {
return findLast(labelNames, {id: labelNameId}).name;
})
const labelFields = annotations.length !== 0 ? [
imageData.fileData.name,
image.width.toString(),
image.height.toString()
`"[${annotations.toString()}]"`
] : [];
return labelFields.join(",")
}

private static wrapLabelNamesIntoJSON(imageData: ImageData): string[] {
if (imageData.labelNameIds.length === 0 || !imageData.loadStatus)
return [];
const labelNames: LabelName[] = LabelsSelector.getLabelNames();
return imageData.labelNameIds.map((labelNameId: string) => {
return findLast(labelNames, {id: labelNameId}).name;
})
}
}
6 changes: 3 additions & 3 deletions src/logic/export/polygon/__tests__/VGGExporter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ describe("VGGExporter mapImageDataToVGG method", () => {
labelRects: [],
labelPolygons: [],
labelLines: [],
labelTagId: null,
labelNameIds: [],
fileData: {} as File,
isVisitedByObjectDetector: true,
isVisitedByPoseDetector: true
Expand Down Expand Up @@ -73,7 +73,7 @@ describe("VGGExporter mapImageDataToVGG method", () => {
}
],
labelLines: [],
labelTagId: null,
labelNameIds: [],
fileData: {} as File,
isVisitedByObjectDetector: true,
isVisitedByPoseDetector: true
Expand Down Expand Up @@ -145,7 +145,7 @@ describe("VGGExporter mapImageDataToVGG method", () => {
}
],
labelLines: [],
labelTagId: null,
labelNameIds: [],
fileData: {} as File,
isVisitedByObjectDetector: true,
isVisitedByPoseDetector: true
Expand Down
64 changes: 37 additions & 27 deletions src/logic/render/PrimaryEditorRenderEngine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {RenderEngineConfig} from "../../settings/RenderEngineConfig";
import {IPoint} from "../../interfaces/IPoint";
import {GeneralSelector} from "../../store/selectors/GeneralSelector";
import {ProjectType} from "../../data/enums/ProjectType";
import {PopupWindowType} from "../../data/enums/PopupWindowType";

export class PrimaryEditorRenderEngine extends BaseRenderEngine {
private config: RenderEngineConfig = new RenderEngineConfig();
Expand All @@ -31,40 +32,49 @@ export class PrimaryEditorRenderEngine extends BaseRenderEngine {

public render(data: EditorData): void {
this.drawImage(EditorModel.image, ViewPortActions.calculateViewPortContentImageRect());
this.renderCursor(data);
this.renderCrossHair(data);
}

public renderCursor(data: EditorData): void {
public renderCrossHair(data: EditorData): void {
if (!this.shouldRenderCrossHair(data)) return;

const mouse = RenderEngineUtil.setPointBetweenPixels(data.mousePositionOnViewPortContent);
const drawLine = (startPoint: IPoint, endPoint: IPoint) => {
DrawUtil.drawLine(this.canvas, startPoint, endPoint, this.config.crossHairLineColor, 1)
DrawUtil.drawLine(this.canvas, startPoint, endPoint, this.config.crossHairLineColor, 2)
}
drawLine(
{x: mouse.x, y: 0},
{x: mouse.x - 1, y: mouse.y - this.config.crossHairPadding}
)
drawLine(
{x: mouse.x, y: mouse.y + this.config.crossHairPadding},
{x: mouse.x - 1, y: data.viewPortContentSize.height}
)
drawLine(
{x: 0, y: mouse.y},
{x: mouse.x - this.config.crossHairPadding, y: mouse.y - 1}
)
drawLine(
{x: mouse.x + this.config.crossHairPadding, y: mouse.y},
{x: data.viewPortContentSize.width, y: mouse.y - 1}
)
}

const crossHairVisible = GeneralSelector.getCrossHairVisibleStatus();
const imageDragMode = GeneralSelector.getImageDragModeStatus();
public shouldRenderCrossHair(data: EditorData): boolean {
const isCrossHairVisible = GeneralSelector.getCrossHairVisibleStatus();
const isImageInDragMode = GeneralSelector.getImageDragModeStatus();
const projectType: ProjectType = GeneralSelector.getProjectType();

if (!this.canvas || !crossHairVisible || imageDragMode || projectType === ProjectType.IMAGE_RECOGNITION) return;

const activePopupType: PopupWindowType = GeneralSelector.getActivePopupType();
const isMouseOverCanvas: boolean = RenderEngineUtil.isMouseOverCanvas(data);
if (isMouseOverCanvas) {
const mouse = RenderEngineUtil.setPointBetweenPixels(data.mousePositionOnViewPortContent);
drawLine(
{x: mouse.x, y: 0},
{x: mouse.x - 1, y: mouse.y - this.config.crossHairPadding}
)
drawLine(
{x: mouse.x, y: mouse.y + this.config.crossHairPadding},
{x: mouse.x - 1, y: data.viewPortContentSize.height}
)
drawLine(
{x: 0, y: mouse.y},
{x: mouse.x - this.config.crossHairPadding, y: mouse.y - 1}
)
drawLine(
{x: mouse.x + this.config.crossHairPadding, y: mouse.y},
{x: data.viewPortContentSize.width, y: mouse.y - 1}
)
}

return [
!!this.canvas,
isCrossHairVisible,
!isImageInDragMode,
projectType !== ProjectType.IMAGE_RECOGNITION,
!activePopupType,
isMouseOverCanvas
].every(Boolean)
}

public drawImage(image: HTMLImageElement, imageRect: IRect) {
Expand Down
6 changes: 3 additions & 3 deletions src/settings/Settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,9 @@ export class Settings {
public static readonly RESIZE_HANDLE_HOVER_DIMENSION_PX = 16;

public static readonly CLOSEABLE_POPUPS: PopupWindowType[] = [
PopupWindowType.LOAD_IMAGES,
PopupWindowType.EXPORT_LABELS,
PopupWindowType.IMPORT_IMAGES,
PopupWindowType.EXPORT_ANNOTATIONS,
PopupWindowType.EXIT_PROJECT,
PopupWindowType.UPDATE_LABEL_NAMES
PopupWindowType.UPDATE_LABEL
];
}
2 changes: 1 addition & 1 deletion src/store/labels/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ export type ImageData = {
labelPoints: LabelPoint[];
labelLines: LabelLine[];
labelPolygons: LabelPolygon[];
labelTagId: string;
labelNameIds: string[];

// SSD
isVisitedByObjectDetector: boolean;
Expand Down
2 changes: 1 addition & 1 deletion src/utils/FileUtil.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export class FileUtil {
labelPoints: [],
labelLines: [],
labelPolygons: [],
labelTagId: null,
labelNameIds: [],
isVisitedByObjectDetector: false,
isVisitedByPoseDetector: false
}
Expand Down
2 changes: 1 addition & 1 deletion src/views/EditorView/EditorContainer/EditorContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ const EditorContainer: React.FC<IProps> = (
return <>
<VerticalEditorButton
label="Images"
image={"/ico/files.png"}
image={"/ico/camera.png"}
imageAlt={"images"}
onClick={leftSideBarButtonOnClick}
isActive={leftTabStatus}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ class ImagesList extends React.Component<IProps, IState> {
case LabelType.LINE:
return imageData.labelLines.length > 0
case LabelType.NAME:
return imageData.labelTagId !== null
return imageData.labelNameIds.length > 0
case LabelType.POINT:
return imageData.labelPoints
.filter((labelPoint: LabelPoint) => labelPoint.status === LabelStatus.ACCEPTED)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ class LabelInputField extends React.Component<IProps, IState> {

private openDropdown = () => {
if (LabelsSelector.getLabelNames().length === 0) {
this.props.updateActivePopupType(PopupWindowType.UPDATE_LABEL_NAMES);
this.props.updateActivePopupType(PopupWindowType.UPDATE_LABEL);
} else {
this.setState({isOpen: true});
window.addEventListener(EventType.MOUSE_DOWN, this.closeDropdown);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import Scrollbars from "react-custom-scrollbars";
import {updateImageDataById} from "../../../../store/labels/actionCreators";
import {AppState} from "../../../../store";
import {connect} from "react-redux";
import {remove} from "lodash";
import './TagLabelsList.scss';
import classNames from "classnames";
import {ImageButton} from "../../../Common/ImageButton/ImageButton";
Expand Down Expand Up @@ -37,15 +38,15 @@ const TagLabelsList: React.FC<IProps> = (
};

const onTagClick = (labelId: string) => {
if (imageData.labelTagId === labelId) {
if (imageData.labelNameIds.includes(labelId)) {
updateImageDataById(imageData.id, {
...imageData,
labelTagId: null
labelNameIds: remove(imageData.labelNameIds, (element: string) => element !== labelId)
})
} else {
updateImageDataById(imageData.id, {
...imageData,
labelTagId: labelId
labelNameIds: imageData.labelNameIds.concat(labelId)
})
}
}
Expand All @@ -54,13 +55,13 @@ const TagLabelsList: React.FC<IProps> = (
return classNames(
"TagItem",
{
"active": imageData.labelTagId === labelId
"active": imageData.labelNameIds.includes(labelId)
}
);
};

const addNewOnClick = () => {
updateActivePopupType(PopupWindowType.UPDATE_LABEL_NAMES)
updateActivePopupType(PopupWindowType.UPDATE_LABEL)
}

const getChildren = () => {
Expand Down
2 changes: 1 addition & 1 deletion src/views/EditorView/StateBar/StateBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ const StateBar: React.FC<IProps> = ({imagesData, activeLabelType}) => {
}, 0);

const tagLabeledImages = imagesData.reduce((currentCount: number, currentImage: ImageData) => {
return currentCount + (currentImage.labelTagId !== null ? 1 : 0);
return currentCount + (currentImage.labelNameIds.length !== 0 ? 1 : 0);
}, 0);

const getProgress = () => {
Expand Down
12 changes: 6 additions & 6 deletions src/views/EditorView/TopNavigationBar/TopNavigationBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,19 +62,19 @@ const TopNavigationBar: React.FC<IProps> = ({updateActivePopupType, updateProjec
</div>
<div className="NavigationBarGroupWrapper">
<UnderlineTextButton
label={"UPDATE LABELS NAMES"}
label={"LABELS"}
under={true}
onClick={() => updateActivePopupType(PopupWindowType.UPDATE_LABEL_NAMES)}
onClick={() => updateActivePopupType(PopupWindowType.UPDATE_LABEL)}
/>
<UnderlineTextButton
label={"MORE IMAGES"}
label={"IMAGES"}
under={true}
onClick={() => updateActivePopupType(PopupWindowType.LOAD_IMAGES)}
onClick={() => updateActivePopupType(PopupWindowType.IMPORT_IMAGES)}
/>
<UnderlineTextButton
label={"EXPORT LABELS"}
label={"EXPORT ANNOTATIONS"}
under={true}
onClick={() => updateActivePopupType(PopupWindowType.EXPORT_LABELS)}
onClick={() => updateActivePopupType(PopupWindowType.EXPORT_ANNOTATIONS)}
/>
<ImageButton
image={"img/github-logo.png"}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
width: 15px;
height: 15px;
filter: brightness(0) invert(1);
margin-right: 3px;
margin-right: 5px;
user-select: none;
}

Expand Down
2 changes: 1 addition & 1 deletion src/views/MainView/ImagesDropZone/ImagesDropZone.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ const ImagesDropZone: React.FC<IProps> = ({updateActiveImageIndex, addImageData,
alt={"upload"}
src={"img/box-opened.png"}
/>
<p className="extraBold">Drop some images</p>
<p className="extraBold">Drop images</p>
<p>or</p>
<p className="extraBold">Click here to select them</p>
</>;
Expand Down
Loading