You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
186 lines
6.0 KiB
TypeScript
186 lines
6.0 KiB
TypeScript
// Copyright (C) 2021 Intel Corporation
|
|
//
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
import { numberArrayToPoints, pointsToNumberArray, Point } from '../math';
|
|
|
|
export interface IntelligentScissorsParams {
|
|
shape: {
|
|
shapeType: 'polygon' | 'polyline';
|
|
};
|
|
canvas: {
|
|
shapeType: 'points';
|
|
enableThreshold: boolean;
|
|
enableSliding: boolean;
|
|
allowRemoveOnlyLast: boolean;
|
|
minPosVertices: number;
|
|
onChangeToolsBlockerState: (event:string)=>void;
|
|
};
|
|
}
|
|
|
|
export interface IntelligentScissors {
|
|
reset(): void;
|
|
run(points: number[], image: ImageData, offsetX: number, offsetY: number): number[];
|
|
params: IntelligentScissorsParams;
|
|
switchBlockMode(mode?:boolean):void;
|
|
}
|
|
|
|
function applyOffset(points: Point[], offsetX: number, offsetY: number): Point[] {
|
|
return points.map(
|
|
(point: Point): Point => ({
|
|
x: point.x - offsetX,
|
|
y: point.y - offsetY,
|
|
}),
|
|
);
|
|
}
|
|
|
|
export default class IntelligentScissorsImplementation implements IntelligentScissors {
|
|
private cv: any;
|
|
private onChangeToolsBlockerState: (event:string)=>void;
|
|
private scissors: {
|
|
tool: any;
|
|
state: {
|
|
path: number[];
|
|
anchors: Record<
|
|
number,
|
|
{
|
|
point: Point;
|
|
start: number;
|
|
}
|
|
>; // point index : start index in path
|
|
image: any | null;
|
|
blocked: boolean;
|
|
};
|
|
};
|
|
|
|
public constructor(cv: any, onChangeToolsBlockerState:(event:string)=>void) {
|
|
this.cv = cv;
|
|
this.onChangeToolsBlockerState = onChangeToolsBlockerState;
|
|
this.reset();
|
|
}
|
|
|
|
public switchBlockMode(mode:boolean): void {
|
|
this.scissors.state.blocked = mode;
|
|
}
|
|
|
|
public reset(): void {
|
|
if (this.scissors && this.scissors.tool) {
|
|
this.scissors.tool.delete();
|
|
}
|
|
|
|
this.scissors = {
|
|
// eslint-disable-next-line new-cap
|
|
tool: new this.cv.segmentation_IntelligentScissorsMB(),
|
|
state: {
|
|
path: [],
|
|
anchors: {},
|
|
image: null,
|
|
blocked: false,
|
|
},
|
|
};
|
|
|
|
this.scissors.tool.setEdgeFeatureCannyParameters(32, 100);
|
|
this.scissors.tool.setGradientMagnitudeMaxLimit(200);
|
|
}
|
|
|
|
public run(coordinates: number[], image: ImageData, offsetX: number, offsetY: number): number[] {
|
|
if (!Array.isArray(coordinates)) {
|
|
throw new Error('Coordinates is expected to be an array');
|
|
}
|
|
if (!coordinates.length) {
|
|
throw new Error('At least one point is expected');
|
|
}
|
|
if (!(image instanceof ImageData)) {
|
|
throw new Error('Image is expected to be an instance of ImageData');
|
|
}
|
|
|
|
const { cv, scissors } = this;
|
|
const { tool, state } = scissors;
|
|
|
|
const points = applyOffset(numberArrayToPoints(coordinates), offsetX, offsetY);
|
|
if (points.length > 1) {
|
|
let matImage = null;
|
|
const contour = new cv.Mat();
|
|
|
|
try {
|
|
const [prev, cur] = points.slice(-2);
|
|
const { x: prevX, y: prevY } = prev;
|
|
const { x: curX, y: curY } = cur;
|
|
|
|
const latestPointRemoved = points.length < Object.keys(state.anchors).length;
|
|
const latestPointReplaced = points.length === Object.keys(state.anchors).length;
|
|
|
|
if (latestPointRemoved) {
|
|
for (const i of Object.keys(state.anchors).sort((a, b) => +b - +a)) {
|
|
if (+i >= points.length) {
|
|
state.path = state.path.slice(0, state.anchors[points.length].start);
|
|
delete state.anchors[+i];
|
|
}
|
|
}
|
|
return [...state.path];
|
|
}
|
|
|
|
matImage = cv.matFromImageData(image);
|
|
|
|
if (latestPointReplaced) {
|
|
state.path = state.path.slice(0, state.anchors[points.length - 1].start);
|
|
delete state.anchors[points.length - 1];
|
|
}
|
|
const pathSegment = [];
|
|
if (!state.blocked) {
|
|
tool.applyImage(matImage);
|
|
tool.buildMap(new cv.Point(prevX, prevY));
|
|
tool.getContour(new cv.Point(curX, curY), contour);
|
|
|
|
for (let row = 0; row < contour.rows; row++) {
|
|
pathSegment.push(contour.intAt(row, 0) + offsetX, contour.intAt(row, 1) + offsetY);
|
|
}
|
|
} else {
|
|
pathSegment.push(curX + offsetX, curY + offsetY);
|
|
}
|
|
state.anchors[points.length - 1] = {
|
|
point: cur,
|
|
start: state.path.length,
|
|
};
|
|
state.path.push(...pathSegment);
|
|
} finally {
|
|
if (matImage) {
|
|
matImage.delete();
|
|
}
|
|
|
|
contour.delete();
|
|
}
|
|
} else {
|
|
state.path = [];
|
|
state.path.push(...pointsToNumberArray(applyOffset(points.slice(-1), -offsetX, -offsetY)));
|
|
state.anchors[0] = {
|
|
point: points[0],
|
|
start: 0,
|
|
};
|
|
}
|
|
return [...state.path];
|
|
}
|
|
|
|
// eslint-disable-next-line class-methods-use-this
|
|
public get type(): string {
|
|
return 'opencv_intelligent_scissors';
|
|
}
|
|
|
|
// eslint-disable-next-line class-methods-use-this
|
|
public get params(): IntelligentScissorsParams {
|
|
return {
|
|
shape: {
|
|
shapeType: 'polygon',
|
|
},
|
|
canvas: {
|
|
shapeType: 'points',
|
|
enableThreshold: true,
|
|
enableSliding: true,
|
|
allowRemoveOnlyLast: true,
|
|
minPosVertices: 1,
|
|
onChangeToolsBlockerState: this.onChangeToolsBlockerState,
|
|
},
|
|
};
|
|
}
|
|
}
|