ppu-ocv 1.2.2 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/contours.d.ts +5 -0
- package/contours.js +1 -1
- package/image-processor.d.ts +14 -1
- package/image-processor.js +1 -1
- package/index.d.ts +1 -1
- package/operations/convert.d.ts +12 -0
- package/operations/convert.js +1 -0
- package/operations/rotate.d.ts +14 -0
- package/operations/rotate.js +1 -0
- package/package.json +1 -1
- package/pipeline/index.d.ts +4 -0
- package/pipeline/index.js +1 -1
package/README.md
CHANGED
|
@@ -119,6 +119,7 @@ To avoid bloat, we only ship essential operations for chaining. Currently shippe
|
|
|
119
119
|
| **warp** | – | Geometric transform; can be applied at any point. |
|
|
120
120
|
| **resize** | – | Also independent; purely geometry. |
|
|
121
121
|
| **border** | – | Independent; purely geometry. |
|
|
122
|
+
| **rotate** | – | Independent. |
|
|
122
123
|
|
|
123
124
|
## Extending operations
|
|
124
125
|
|
package/contours.d.ts
CHANGED
|
@@ -25,6 +25,11 @@ export declare class Contours {
|
|
|
25
25
|
* @returns The number of contours found in the image (cv.MatVector).
|
|
26
26
|
*/
|
|
27
27
|
getAll(): cv.MatVector;
|
|
28
|
+
/**
|
|
29
|
+
* Get the size of the contours
|
|
30
|
+
* @returns Size of the contours
|
|
31
|
+
*/
|
|
32
|
+
getSize(): number;
|
|
28
33
|
/**
|
|
29
34
|
* Get contour at a specific index.
|
|
30
35
|
* @param index - The index of the contour to get.
|
package/contours.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export class Contours{contours;constructor(img,options={}){let opts={...defaultOptions(),...options};if(img instanceof cv.Mat){let contours=new cv.MatVector;let hierarchy=new cv.Mat;try{cv.findContours(img,contours,hierarchy,opts.mode,opts.method)}catch(error){throw error}hierarchy.delete();this.contours=contours}else{throw new Error("Invalid img type. Must be cv.Mat.")}}getAll(){return this.contours}getFromIndex(index){if(index<this.contours.size()){return this.contours.get(index)}return new cv.Mat}getRect(contour){return cv.boundingRect(contour)}iterate(callback){for(let i=0,len=this.contours.size();i<len;i++){let contour=this.contours.get(i);callback(contour)}return this}getLargestContourArea(){let maxArea=0;let largestContour=null;this.iterate((contour)=>{let area=cv.contourArea(contour);if(area>maxArea){maxArea=area;largestContour=contour}});return largestContour}getCornerPoints(options){const{canvas,contour=this.getLargestContourArea()}=options;let bbox={x0:0,y0:0,x1:canvas.width,y1:canvas.height};if(!contour){return{points:{topLeft:{x:bbox.x0,y:bbox.y0},topRight:{x:bbox.x1,y:bbox.y0},bottomLeft:{x:bbox.x0,y:bbox.y1},bottomRight:{x:bbox.x1,y:bbox.y1}},bbox}}let rect=cv.minAreaRect(contour);let vertices=cv.RotatedRect.points(rect);let points={topLeft:{x:0,y:0},topRight:{x:0,y:0},bottomRight:{x:0,y:0},bottomLeft:{x:0,y:0}};let sums=vertices.map((pt)=>pt.x+pt.y);let diffs=vertices.map((pt)=>pt.y-pt.x);let topLeftIdx=sums.indexOf(Math.min(...sums));let topRightIdx=diffs.indexOf(Math.min(...diffs));let bottomRightIdx=sums.indexOf(Math.max(...sums));let bottomLeftIdx=diffs.indexOf(Math.max(...diffs));if(!vertices[topLeftIdx]||!vertices[topRightIdx]||!vertices[bottomRightIdx]||!vertices[bottomLeftIdx]){return{points:{topLeft:{x:bbox.x0,y:bbox.y0},topRight:{x:bbox.x1,y:bbox.y0},bottomLeft:{x:bbox.x0,y:bbox.y1},bottomRight:{x:bbox.x1,y:bbox.y1}},bbox}}points.topLeft={x:vertices[topLeftIdx].x,y:vertices[topLeftIdx].y};points.topRight={x:vertices[topRightIdx].x,y:vertices[topRightIdx].y};points.bottomRight={x:vertices[bottomRightIdx].x,y:vertices[bottomRightIdx].y};points.bottomLeft={x:vertices[bottomLeftIdx].x,y:vertices[bottomLeftIdx].y};contour.delete();let ensureInBounds=(p)=>{p.x=Math.max(0,Math.min(canvas.width,p.x));p.y=Math.max(0,Math.min(canvas.height,p.y));return p};points.topLeft=ensureInBounds(points.topLeft);points.topRight=ensureInBounds(points.topRight);points.bottomLeft=ensureInBounds(points.bottomLeft);points.bottomRight=ensureInBounds(points.bottomRight);return{points,bbox}}getApproximateRectangleContour(options){const{threshold=0.02,contour=this.getLargestContourArea()}=options??{};if(!contour)return;let epsilon=threshold*cv.arcLength(contour,true);let approxContour=new cv.Mat;cv.approxPolyDP(contour,approxContour,epsilon,true);contour.delete();return approxContour}destroy(){try{this.contours.delete()}catch(error){}}}import{cv}from"./index";function defaultOptions(){return{mode:cv.RETR_EXTERNAL,method:cv.CHAIN_APPROX_SIMPLE}}
|
|
1
|
+
export class Contours{contours;constructor(img,options={}){let opts={...defaultOptions(),...options};if(img instanceof cv.Mat){let contours=new cv.MatVector;let hierarchy=new cv.Mat;try{cv.findContours(img,contours,hierarchy,opts.mode,opts.method)}catch(error){throw error}hierarchy.delete();this.contours=contours}else{throw new Error("Invalid img type. Must be cv.Mat.")}}getAll(){return this.contours}getSize(){return this.contours.size()}getFromIndex(index){if(index<this.contours.size()){return this.contours.get(index)}return new cv.Mat}getRect(contour){return cv.boundingRect(contour)}iterate(callback){for(let i=0,len=this.contours.size();i<len;i++){let contour=this.contours.get(i);callback(contour)}return this}getLargestContourArea(){let maxArea=0;let largestContour=null;this.iterate((contour)=>{let area=cv.contourArea(contour);if(area>maxArea){maxArea=area;largestContour=contour}});return largestContour}getCornerPoints(options){const{canvas,contour=this.getLargestContourArea()}=options;let bbox={x0:0,y0:0,x1:canvas.width,y1:canvas.height};if(!contour){return{points:{topLeft:{x:bbox.x0,y:bbox.y0},topRight:{x:bbox.x1,y:bbox.y0},bottomLeft:{x:bbox.x0,y:bbox.y1},bottomRight:{x:bbox.x1,y:bbox.y1}},bbox}}let rect=cv.minAreaRect(contour);let vertices=cv.RotatedRect.points(rect);let points={topLeft:{x:0,y:0},topRight:{x:0,y:0},bottomRight:{x:0,y:0},bottomLeft:{x:0,y:0}};let sums=vertices.map((pt)=>pt.x+pt.y);let diffs=vertices.map((pt)=>pt.y-pt.x);let topLeftIdx=sums.indexOf(Math.min(...sums));let topRightIdx=diffs.indexOf(Math.min(...diffs));let bottomRightIdx=sums.indexOf(Math.max(...sums));let bottomLeftIdx=diffs.indexOf(Math.max(...diffs));if(!vertices[topLeftIdx]||!vertices[topRightIdx]||!vertices[bottomRightIdx]||!vertices[bottomLeftIdx]){return{points:{topLeft:{x:bbox.x0,y:bbox.y0},topRight:{x:bbox.x1,y:bbox.y0},bottomLeft:{x:bbox.x0,y:bbox.y1},bottomRight:{x:bbox.x1,y:bbox.y1}},bbox}}points.topLeft={x:vertices[topLeftIdx].x,y:vertices[topLeftIdx].y};points.topRight={x:vertices[topRightIdx].x,y:vertices[topRightIdx].y};points.bottomRight={x:vertices[bottomRightIdx].x,y:vertices[bottomRightIdx].y};points.bottomLeft={x:vertices[bottomLeftIdx].x,y:vertices[bottomLeftIdx].y};contour.delete();let ensureInBounds=(p)=>{p.x=Math.max(0,Math.min(canvas.width,p.x));p.y=Math.max(0,Math.min(canvas.height,p.y));return p};points.topLeft=ensureInBounds(points.topLeft);points.topRight=ensureInBounds(points.topRight);points.bottomLeft=ensureInBounds(points.bottomLeft);points.bottomRight=ensureInBounds(points.bottomRight);return{points,bbox}}getApproximateRectangleContour(options){const{threshold=0.02,contour=this.getLargestContourArea()}=options??{};if(!contour)return;let epsilon=threshold*cv.arcLength(contour,true);let approxContour=new cv.Mat;cv.approxPolyDP(contour,approxContour,epsilon,true);contour.delete();return approxContour}destroy(){try{this.contours.delete()}catch(error){}}}import{cv}from"./index";function defaultOptions(){return{mode:cv.RETR_EXTERNAL,method:cv.CHAIN_APPROX_SIMPLE}}
|
package/image-processor.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Canvas, cv } from "./index";
|
|
2
|
-
import type { AdaptiveThresholdOptions, BlurOptions, BorderOptions, CannyOptions, DilateOptions, ErodeOptions, GrayscaleOptions, InvertOptions, MorphologicalGradientOptions, OperationName, OperationOptions, RequiredOptions, ResizeOptions, ThresholdOptions, WarpOptions } from "./index";
|
|
2
|
+
import type { AdaptiveThresholdOptions, BlurOptions, BorderOptions, CannyOptions, DilateOptions, ErodeOptions, GrayscaleOptions, InvertOptions, MorphologicalGradientOptions, OperationName, OperationOptions, RequiredOptions, ResizeOptions, RotateOptions, ThresholdOptions, WarpOptions } from "./index";
|
|
3
|
+
import type { ConvertOptions } from "./pipeline";
|
|
3
4
|
type NameWithRequiredOptions = {
|
|
4
5
|
[N in OperationName]: OperationOptions<N> extends RequiredOptions ? N : never;
|
|
5
6
|
}[OperationName];
|
|
@@ -100,6 +101,18 @@ export declare class ImageProcessor {
|
|
|
100
101
|
* @param options Warp configuration options
|
|
101
102
|
*/
|
|
102
103
|
warp(options: WarpOptions): this;
|
|
104
|
+
/**
|
|
105
|
+
* Rotate image by a given angle
|
|
106
|
+
* @description Usage order: independent
|
|
107
|
+
* @param options Rotate configuration options
|
|
108
|
+
*/
|
|
109
|
+
rotate(options: RotateOptions): this;
|
|
110
|
+
/**
|
|
111
|
+
* Convert image matrix into new matrix type
|
|
112
|
+
* @description Usage order: independent
|
|
113
|
+
* @param options Convert configuration options
|
|
114
|
+
*/
|
|
115
|
+
convert(options: ConvertOptions): this;
|
|
103
116
|
/**
|
|
104
117
|
* Destroy the image (cv.Mat) stored in image processor state
|
|
105
118
|
* @kind non-chainable
|
package/image-processor.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export class ImageProcessor{img;width;height;constructor(source){if(source instanceof Canvas){let ctx=source.getContext("2d");let imageData=ctx.getImageData(0,0,source.width,source.height);this.img=cv.matFromImageData(imageData);this.width=source.width;this.height=source.height}else if(source instanceof cv.Mat){this.img=source;this.width=source.cols;this.height=source.rows}else{throw new Error("Invalid source type. Must be either Canvas or cv.Mat.")}}static async prepareCanvas(file){let img=await loadImage(file);let canvas=createCanvas(img.width,img.height);let ctx=canvas.getContext("2d");ctx.drawImage(img,0,0);return canvas}static async initRuntime(){return new Promise((res)=>{if(cv&&cv.Mat){res()}else{cv["onRuntimeInitialized"]=()=>{res()}}})}execute(operationName,options){if(!registry.hasOperation(operationName)){throw new Error(`Operation "${operationName}" not found`)}try{let result=executeOperation(operationName,this.img,options);this.img=result.img;this.width=result.width;this.height=result.height}catch(error){console.error(`Error executing operation "${operationName}":`,error);throw error}return this}grayscale(options={}){return this.execute("grayscale",options)}invert(options={}){return this.execute("invert",options)}border(options={}){return this.execute("border",options)}blur(options={}){return this.execute("blur",options)}threshold(options={}){return this.execute("threshold",options)}adaptiveThreshold(options={}){return this.execute("adaptiveThreshold",options)}canny(options={}){return this.execute("canny",options)}morphologicalGradient(options={}){return this.execute("morphologicalGradient",options)}erode(options={}){return this.execute("erode",options)}dilate(options={}){return this.execute("dilate",options)}resize(options){return this.execute("resize",options)}warp(options){return this.execute("warp",options)}destroy(){this.img.delete()}toMat(){return this.img}toCanvas(){let canvas=createCanvas(this.width,this.height);let ctx=canvas.getContext("2d");let imgData=ctx.createImageData(this.width,this.height);if(this.img.channels()===1){let data=imgData.data;let gray=new Uint8Array(this.img.data);for(let i=0;i<gray.length;i++){data[i*4]=gray[i];data[i*4+1]=gray[i];data[i*4+2]=gray[i];data[i*4+3]=255}}else{imgData.data.set(new Uint8ClampedArray(this.img.data))}ctx.putImageData(imgData,0,0);return canvas}}import{Canvas,createCanvas,cv,loadImage}from"./index";import{executeOperation,registry}from"./index";
|
|
1
|
+
export class ImageProcessor{img;width;height;constructor(source){if(source instanceof Canvas){let ctx=source.getContext("2d");let imageData=ctx.getImageData(0,0,source.width,source.height);this.img=cv.matFromImageData(imageData);this.width=source.width;this.height=source.height}else if(source instanceof cv.Mat){this.img=source;this.width=source.cols;this.height=source.rows}else{throw new Error("Invalid source type. Must be either Canvas or cv.Mat.")}}static async prepareCanvas(file){let img=await loadImage(file);let canvas=createCanvas(img.width,img.height);let ctx=canvas.getContext("2d");ctx.drawImage(img,0,0);return canvas}static async initRuntime(){return new Promise((res)=>{if(cv&&cv.Mat){res()}else{cv["onRuntimeInitialized"]=()=>{res()}}})}execute(operationName,options){if(!registry.hasOperation(operationName)){throw new Error(`Operation "${operationName}" not found`)}try{let result=executeOperation(operationName,this.img,options);this.img=result.img;this.width=result.width;this.height=result.height}catch(error){console.error(`Error executing operation "${operationName}":`,error);throw error}return this}grayscale(options={}){return this.execute("grayscale",options)}invert(options={}){return this.execute("invert",options)}border(options={}){return this.execute("border",options)}blur(options={}){return this.execute("blur",options)}threshold(options={}){return this.execute("threshold",options)}adaptiveThreshold(options={}){return this.execute("adaptiveThreshold",options)}canny(options={}){return this.execute("canny",options)}morphologicalGradient(options={}){return this.execute("morphologicalGradient",options)}erode(options={}){return this.execute("erode",options)}dilate(options={}){return this.execute("dilate",options)}resize(options){return this.execute("resize",options)}warp(options){return this.execute("warp",options)}rotate(options){return this.execute("rotate",options)}convert(options){return this.execute("convert",options)}destroy(){this.img.delete()}toMat(){return this.img}toCanvas(){let canvas=createCanvas(this.width,this.height);let ctx=canvas.getContext("2d");let imgData=ctx.createImageData(this.width,this.height);if(this.img.channels()===1){let data=imgData.data;let gray=new Uint8Array(this.img.data);for(let i=0;i<gray.length;i++){data[i*4]=gray[i];data[i*4+1]=gray[i];data[i*4+2]=gray[i];data[i*4+3]=255}}else{imgData.data.set(new Uint8ClampedArray(this.img.data))}ctx.putImageData(imgData,0,0);return canvas}}import{Canvas,createCanvas,cv,loadImage}from"./index";import{executeOperation,registry}from"./index";
|
package/index.d.ts
CHANGED
|
@@ -8,4 +8,4 @@ export { CanvasToolkit } from "./canvas-toolkit";
|
|
|
8
8
|
export { Contours } from "./contours";
|
|
9
9
|
export { calculateMeanGrayscaleValue, calculateMeanNormalizedLabLightness, type CalculateMeanLightnessOptions, } from "./image-analysis";
|
|
10
10
|
export { ImageProcessor } from "./image-processor";
|
|
11
|
-
export type { AdaptiveThresholdOptions, BlurOptions, BorderOptions, CannyOptions, DilateOptions, ErodeOptions, GrayscaleOptions, InvertOptions, MorphologicalGradientOptions, OperationFunction, OperationName, OperationOptions, OperationResult, PartialOptions, RegisteredOperations, RequiredOptions, ResizeOptions, ThresholdOptions, WarpOptions, } from "./pipeline";
|
|
11
|
+
export type { AdaptiveThresholdOptions, BlurOptions, BorderOptions, CannyOptions, DilateOptions, ErodeOptions, GrayscaleOptions, InvertOptions, MorphologicalGradientOptions, OperationFunction, OperationName, OperationOptions, OperationResult, PartialOptions, RegisteredOperations, RequiredOptions, ResizeOptions, RotateOptions, ThresholdOptions, WarpOptions, } from "./pipeline";
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { OperationResult, RequiredOptions } from "../index";
|
|
2
|
+
import { cv } from "../index";
|
|
3
|
+
declare module "../index" {
|
|
4
|
+
interface RegisteredOperations {
|
|
5
|
+
convert: ConvertOptions;
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
export interface ConvertOptions extends RequiredOptions {
|
|
9
|
+
/** Desired matrix type (cv.CV_...) if negative, it will be the same as input */
|
|
10
|
+
rtype: number;
|
|
11
|
+
}
|
|
12
|
+
export declare function convert(img: cv.Mat, options: ConvertOptions): OperationResult;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{cv,registry}from"../index";export function convert(img,options){if(options.rtype===undefined){throw new Error("Invalid options: rtype is required")}let imgConvert=new cv.Mat;img.convertTo(imgConvert,options.rtype);img.delete();return{img:imgConvert,width:imgConvert.cols,height:imgConvert.rows}}registry.register("convert",convert);
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { OperationResult, RequiredOptions } from "../index";
|
|
2
|
+
import { cv } from "../index";
|
|
3
|
+
declare module "../index" {
|
|
4
|
+
interface RegisteredOperations {
|
|
5
|
+
rotate: RotateOptions;
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
export interface RotateOptions extends RequiredOptions {
|
|
9
|
+
/** Angle of rotation in degrees (positive for counter-clockwise) */
|
|
10
|
+
angle: number;
|
|
11
|
+
/** Optional center of rotation. Defaults to the image center. */
|
|
12
|
+
center?: cv.Point;
|
|
13
|
+
}
|
|
14
|
+
export declare function rotate(img: cv.Mat, options: RotateOptions): OperationResult;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{cv,registry}from"../index";export function rotate(img,options){let center=options.center||new cv.Point(img.cols/2,img.rows/2);let M=cv.getRotationMatrix2D(center,options.angle,1);let dsize=new cv.Size(img.cols,img.rows);let rotatedImg=new cv.Mat;cv.warpAffine(img,rotatedImg,M,dsize,cv.INTER_LINEAR,cv.BORDER_CONSTANT,new cv.Scalar);img.delete();M.delete();return{img:rotatedImg,width:rotatedImg.cols,height:rotatedImg.rows}}registry.register("rotate",rotate);
|
package/package.json
CHANGED
package/pipeline/index.d.ts
CHANGED
|
@@ -4,23 +4,27 @@ import "../operations/adaptive-threshold";
|
|
|
4
4
|
import "../operations/blur";
|
|
5
5
|
import "../operations/border";
|
|
6
6
|
import "../operations/canny";
|
|
7
|
+
import "../operations/convert";
|
|
7
8
|
import "../operations/dilate";
|
|
8
9
|
import "../operations/erode";
|
|
9
10
|
import "../operations/grayscale";
|
|
10
11
|
import "../operations/invert";
|
|
11
12
|
import "../operations/morphological-gradient";
|
|
12
13
|
import "../operations/resize";
|
|
14
|
+
import "../operations/rotate";
|
|
13
15
|
import "../operations/threshold";
|
|
14
16
|
import "../operations/warp";
|
|
15
17
|
export type { AdaptiveThresholdOptions } from "../operations/adaptive-threshold";
|
|
16
18
|
export type { BlurOptions } from "../operations/blur";
|
|
17
19
|
export type { BorderOptions } from "../operations/border";
|
|
18
20
|
export type { CannyOptions } from "../operations/canny";
|
|
21
|
+
export type { ConvertOptions } from "../operations/convert";
|
|
19
22
|
export type { DilateOptions } from "../operations/dilate";
|
|
20
23
|
export type { ErodeOptions } from "../operations/erode";
|
|
21
24
|
export type { GrayscaleOptions } from "../operations/grayscale";
|
|
22
25
|
export type { InvertOptions } from "../operations/invert";
|
|
23
26
|
export type { MorphologicalGradientOptions } from "../operations/morphological-gradient";
|
|
24
27
|
export type { ResizeOptions } from "../operations/resize";
|
|
28
|
+
export type { RotateOptions } from "../operations/rotate";
|
|
25
29
|
export type { ThresholdOptions } from "../operations/threshold";
|
|
26
30
|
export type { WarpOptions } from "../operations/warp";
|
package/pipeline/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export{executeOperation,OperationRegistry,registry}from"./registry";import"../operations/adaptive-threshold";import"../operations/blur";import"../operations/border";import"../operations/canny";import"../operations/dilate";import"../operations/erode";import"../operations/grayscale";import"../operations/invert";import"../operations/morphological-gradient";import"../operations/resize";import"../operations/threshold";import"../operations/warp";
|
|
1
|
+
export{executeOperation,OperationRegistry,registry}from"./registry";import"../operations/adaptive-threshold";import"../operations/blur";import"../operations/border";import"../operations/canny";import"../operations/convert";import"../operations/dilate";import"../operations/erode";import"../operations/grayscale";import"../operations/invert";import"../operations/morphological-gradient";import"../operations/resize";import"../operations/rotate";import"../operations/threshold";import"../operations/warp";
|