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

// 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,
},
};
}
}