seg-cam 0.4.8 → 1.0.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 +13 -3
- package/dist/index.d.mts +47 -36
- package/dist/index.d.ts +47 -36
- package/dist/index.js +1 -1
- package/dist/index.mjs +1 -1
- package/dist/jersey-detector-worker.js +6669 -0
- package/package.json +4 -3
- package/public/jersey-detector-worker.js +2652 -2611
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
An easy-to-use drop-in library that wraps around TensorFlow.js BodyPix. Using it is as simple as passing a video reference to the `JerseyDetector` component.
|
|
4
4
|
|
|
5
|
-
`seg-cam` was
|
|
5
|
+
`seg-cam` was created for real-time video processing—optimized for high-performance jersey and person segmentation in browser environments.
|
|
6
6
|
|
|
7
7
|
## Features
|
|
8
8
|
|
|
@@ -82,10 +82,10 @@ export function CameraApp() {
|
|
|
82
82
|
| `threshold` | `number` | `0.5` | Confidence threshold for detection (0 to 1). |
|
|
83
83
|
| `targetPartId` | `number` | `12` | BodyPix Part ID to isolate (12 = Torso). |
|
|
84
84
|
| `imagesToReturn` | `'mask' \| 'mask_with_image' \| 'none'` | `'mask'` | Controls output: 'mask' (overlay only), 'mask_with_image' (overlay + frame), 'none' (data only). |
|
|
85
|
-
| `onStatsUpdate`| `(stats: DetectionStats) => void` | - | Callback triggered
|
|
85
|
+
| `onStatsUpdate`| `(stats: DetectionStats) => void` | - | Throttled (500ms). Callback triggered with detection metrics. |
|
|
86
86
|
| `onWorkerReady`| `(ready: boolean) => void` | - | Callback triggered when the TFJS model is loaded. |
|
|
87
87
|
| `onWorkerError`| `(error: string \| null) => void` | - | Callback triggered if the worker fails to initialize. |
|
|
88
|
-
| `onSegmentedImage`| `(images: ImageBitmap[]) => void`| - |
|
|
88
|
+
| `onSegmentedImage`| `(images: ImageBitmap[], bboxes?: BoundingBox[]) => void`| - | Throttled (500ms). Returns individual bitmaps and bounding boxes of detected objects (tlbr format). |
|
|
89
89
|
| `bodyPixArchitecture`| `'MobileNetV1' \| 'ResNet50'` | `'MobileNetV1'` | Performance vs Accuracy trade-off. |
|
|
90
90
|
| `verbose` | `boolean` | `false` | Enable detailed console logging. |
|
|
91
91
|
|
|
@@ -136,6 +136,16 @@ const currentStats = detectorRef.current?.stats;
|
|
|
136
136
|
- `'none'`: Skips bitmap generation entirely. Use this if you only need `onSegmentedImage` or stats, improving performance.
|
|
137
137
|
- **`threshold`**: (`number`, 0.0-1.0, Default `0.5`) The minimum confidence required for a person pixel to be considered valid.
|
|
138
138
|
|
|
139
|
+
## Performance Tips
|
|
140
|
+
|
|
141
|
+
To get the most out of `seg-cam` v1.0.0:
|
|
142
|
+
|
|
143
|
+
1. **Use `imagesToReturn="none"`** if you only need the data (stats, segmented images, or bboxes). This bypasses the creation of the full-screen overlay bitmap.
|
|
144
|
+
2. **Optimize your `videoRef` dimensions**: Processing smaller video streams (e.g., 640x480) is significantly faster than 1080p.
|
|
145
|
+
3. **Architecture Choice**: `MobileNetV1` with a `multiplier` of `0.75` or `0.5` is the sweet spot for mobile devices.
|
|
146
|
+
4. **Quantization**: Stick to `bodyPixQuantBytes={2}` for a good balance between model size and accuracy.
|
|
147
|
+
5. **Notification Throttling**: By default, `onStatsUpdate` and `onSegmentedImage` are throttled to **500ms** to ensure the main thread remains responsive. They will still fire immediately if the number of detected jerseys changes.
|
|
148
|
+
|
|
139
149
|
## Acknowledgements
|
|
140
150
|
|
|
141
151
|
This project is powered by **TensorFlow.js** and the **BodyPix** model.
|
package/dist/index.d.mts
CHANGED
|
@@ -33,6 +33,7 @@ declare const DEFAULT_CONFIG: {
|
|
|
33
33
|
TARGET_PART_ID: number;
|
|
34
34
|
IMAGES_TO_RETURN: ImagesToReturn;
|
|
35
35
|
VERBOSE: boolean;
|
|
36
|
+
MIN_DETECTION_AREA: number;
|
|
36
37
|
};
|
|
37
38
|
|
|
38
39
|
/**
|
|
@@ -49,36 +50,6 @@ interface DetectionStats {
|
|
|
49
50
|
fps: number;
|
|
50
51
|
}
|
|
51
52
|
|
|
52
|
-
interface JerseyDetectorHandle {
|
|
53
|
-
isReady: boolean;
|
|
54
|
-
stats: DetectionStats;
|
|
55
|
-
lastError: string | null;
|
|
56
|
-
capture: (format?: "image/jpeg" | "image/png", quality?: number) => string;
|
|
57
|
-
}
|
|
58
|
-
interface JerseyDetectorProps {
|
|
59
|
-
videoRef: React.RefObject<HTMLVideoElement>;
|
|
60
|
-
onWorkerReady?: (ready: boolean) => void;
|
|
61
|
-
onWorkerError?: (error: string | null) => void;
|
|
62
|
-
onStatsUpdate?: (stats: DetectionStats) => void;
|
|
63
|
-
onSegmentedImage?: (segmentedImages: ImageBitmap[]) => void;
|
|
64
|
-
task?: TaskType;
|
|
65
|
-
model?: ModelType;
|
|
66
|
-
verbose?: boolean;
|
|
67
|
-
bodyPixArchitecture?: "MobileNetV1" | "ResNet50";
|
|
68
|
-
bodyPixMultiplier?: number;
|
|
69
|
-
bodyPixQuantBytes?: number;
|
|
70
|
-
bodyPixStride?: number;
|
|
71
|
-
multiSegmentation?: boolean;
|
|
72
|
-
segmentBodyParts?: boolean;
|
|
73
|
-
backgroundShade?: number;
|
|
74
|
-
shirtShade?: number;
|
|
75
|
-
threshold?: number;
|
|
76
|
-
targetPartId?: number;
|
|
77
|
-
imagesToReturn?: ImagesToReturn;
|
|
78
|
-
workerUrl?: string;
|
|
79
|
-
}
|
|
80
|
-
declare const JerseyDetector: react.ForwardRefExoticComponent<JerseyDetectorProps & react.RefAttributes<JerseyDetectorHandle>>;
|
|
81
|
-
|
|
82
53
|
type WorkerConfig = {
|
|
83
54
|
task: TaskType;
|
|
84
55
|
model: ModelType;
|
|
@@ -94,24 +65,36 @@ type WorkerConfig = {
|
|
|
94
65
|
threshold?: number;
|
|
95
66
|
targetPartId?: number;
|
|
96
67
|
imagesToReturn?: ImagesToReturn;
|
|
68
|
+
minDetectionArea?: number;
|
|
69
|
+
};
|
|
70
|
+
type BoundingBox = {
|
|
71
|
+
top: number;
|
|
72
|
+
left: number;
|
|
73
|
+
bottom: number;
|
|
74
|
+
right: number;
|
|
97
75
|
};
|
|
98
|
-
type ImageDimensions
|
|
76
|
+
type ImageDimensions = {
|
|
99
77
|
width: number;
|
|
100
78
|
height: number;
|
|
101
79
|
};
|
|
102
80
|
type WorkerInitMessage = {
|
|
103
81
|
type: "init";
|
|
82
|
+
requestId?: string;
|
|
104
83
|
isProduction: boolean;
|
|
105
84
|
config: WorkerConfig;
|
|
106
85
|
};
|
|
107
86
|
type WorkerDetectMessage = {
|
|
87
|
+
requestId: string;
|
|
108
88
|
type: "detect";
|
|
109
|
-
dimensions: ImageDimensions
|
|
89
|
+
dimensions: ImageDimensions;
|
|
110
90
|
threshold?: number;
|
|
111
91
|
bitmap: ImageBitmap;
|
|
112
92
|
};
|
|
113
93
|
type WorkerReadyMessage = {
|
|
114
94
|
type: "ready";
|
|
95
|
+
config: WorkerConfig;
|
|
96
|
+
device: string;
|
|
97
|
+
dtype: string;
|
|
115
98
|
};
|
|
116
99
|
type WorkerProgressMessage = {
|
|
117
100
|
type: "loading_progress";
|
|
@@ -127,6 +110,7 @@ type WorkerDetectResponseMessage = {
|
|
|
127
110
|
bitmap?: ImageBitmap;
|
|
128
111
|
stats: DetectionStats;
|
|
129
112
|
segmentedImages?: ImageBitmap[];
|
|
113
|
+
bboxes?: BoundingBox[];
|
|
130
114
|
};
|
|
131
115
|
type WorkerDebugMessage = {
|
|
132
116
|
type: "debug";
|
|
@@ -135,10 +119,37 @@ type WorkerDebugMessage = {
|
|
|
135
119
|
type WorkerPostMessage = WorkerInitMessage | WorkerDetectMessage;
|
|
136
120
|
type WorkerResponseMessage = WorkerReadyMessage | WorkerProgressMessage | WorkerErrorMessage | WorkerDetectResponseMessage | WorkerDetectMessage;
|
|
137
121
|
|
|
138
|
-
interface
|
|
139
|
-
|
|
140
|
-
|
|
122
|
+
interface JerseyDetectorHandle {
|
|
123
|
+
isReady: boolean;
|
|
124
|
+
stats: DetectionStats;
|
|
125
|
+
lastError: string | null;
|
|
126
|
+
capture: (format?: "image/jpeg" | "image/png", quality?: number) => string;
|
|
127
|
+
}
|
|
128
|
+
interface JerseyDetectorProps {
|
|
129
|
+
videoRef: React.RefObject<HTMLVideoElement>;
|
|
130
|
+
onWorkerReady?: (ready: boolean) => void;
|
|
131
|
+
onWorkerError?: (error: string | null) => void;
|
|
132
|
+
onStatsUpdate?: (stats: DetectionStats) => void;
|
|
133
|
+
onSegmentedImage?: (segmentedImages: ImageBitmap[], bboxes?: BoundingBox[]) => void;
|
|
134
|
+
task?: TaskType;
|
|
135
|
+
model?: ModelType;
|
|
136
|
+
verbose?: boolean;
|
|
137
|
+
bodyPixArchitecture?: "MobileNetV1" | "ResNet50";
|
|
138
|
+
bodyPixMultiplier?: number;
|
|
139
|
+
bodyPixQuantBytes?: number;
|
|
140
|
+
bodyPixStride?: number;
|
|
141
|
+
multiSegmentation?: boolean;
|
|
142
|
+
segmentBodyParts?: boolean;
|
|
143
|
+
backgroundShade?: number;
|
|
144
|
+
shirtShade?: number;
|
|
145
|
+
threshold?: number;
|
|
146
|
+
targetPartId?: number;
|
|
147
|
+
imagesToReturn?: ImagesToReturn;
|
|
148
|
+
minDetectionArea?: number;
|
|
149
|
+
workerUrl?: string;
|
|
141
150
|
}
|
|
151
|
+
declare const JerseyDetector: react.ForwardRefExoticComponent<JerseyDetectorProps & react.RefAttributes<JerseyDetectorHandle>>;
|
|
152
|
+
|
|
142
153
|
/**
|
|
143
154
|
* Simplified hook for jersey detection worker
|
|
144
155
|
* Manages worker lifecycle, state, and detection requests
|
|
@@ -150,4 +161,4 @@ declare function useJerseyWorker(config: WorkerConfig, workerUrlProp?: string):
|
|
|
150
161
|
detect: (bitmap: ImageBitmap, dimensions: ImageDimensions, threshold?: number) => Promise<any>;
|
|
151
162
|
};
|
|
152
163
|
|
|
153
|
-
export { DEFAULT_CONFIG, type DetectionStats, type ImageDimensions
|
|
164
|
+
export { type BoundingBox, DEFAULT_CONFIG, type DetectionStats, type ImageDimensions, type ImageSegmentationType, type ImagesToReturn, JerseyDetector, type JerseyDetectorHandle, type JerseyDetectorProps, type ModelType, type ObjectDetectionType, type TaskType, type WorkerConfig, type WorkerDebugMessage, type WorkerDetectMessage, type WorkerDetectResponseMessage, type WorkerErrorMessage, type WorkerInitMessage, type WorkerPostMessage, type WorkerProgressMessage, type WorkerReadyMessage, type WorkerResponseMessage, useJerseyWorker };
|
package/dist/index.d.ts
CHANGED
|
@@ -33,6 +33,7 @@ declare const DEFAULT_CONFIG: {
|
|
|
33
33
|
TARGET_PART_ID: number;
|
|
34
34
|
IMAGES_TO_RETURN: ImagesToReturn;
|
|
35
35
|
VERBOSE: boolean;
|
|
36
|
+
MIN_DETECTION_AREA: number;
|
|
36
37
|
};
|
|
37
38
|
|
|
38
39
|
/**
|
|
@@ -49,36 +50,6 @@ interface DetectionStats {
|
|
|
49
50
|
fps: number;
|
|
50
51
|
}
|
|
51
52
|
|
|
52
|
-
interface JerseyDetectorHandle {
|
|
53
|
-
isReady: boolean;
|
|
54
|
-
stats: DetectionStats;
|
|
55
|
-
lastError: string | null;
|
|
56
|
-
capture: (format?: "image/jpeg" | "image/png", quality?: number) => string;
|
|
57
|
-
}
|
|
58
|
-
interface JerseyDetectorProps {
|
|
59
|
-
videoRef: React.RefObject<HTMLVideoElement>;
|
|
60
|
-
onWorkerReady?: (ready: boolean) => void;
|
|
61
|
-
onWorkerError?: (error: string | null) => void;
|
|
62
|
-
onStatsUpdate?: (stats: DetectionStats) => void;
|
|
63
|
-
onSegmentedImage?: (segmentedImages: ImageBitmap[]) => void;
|
|
64
|
-
task?: TaskType;
|
|
65
|
-
model?: ModelType;
|
|
66
|
-
verbose?: boolean;
|
|
67
|
-
bodyPixArchitecture?: "MobileNetV1" | "ResNet50";
|
|
68
|
-
bodyPixMultiplier?: number;
|
|
69
|
-
bodyPixQuantBytes?: number;
|
|
70
|
-
bodyPixStride?: number;
|
|
71
|
-
multiSegmentation?: boolean;
|
|
72
|
-
segmentBodyParts?: boolean;
|
|
73
|
-
backgroundShade?: number;
|
|
74
|
-
shirtShade?: number;
|
|
75
|
-
threshold?: number;
|
|
76
|
-
targetPartId?: number;
|
|
77
|
-
imagesToReturn?: ImagesToReturn;
|
|
78
|
-
workerUrl?: string;
|
|
79
|
-
}
|
|
80
|
-
declare const JerseyDetector: react.ForwardRefExoticComponent<JerseyDetectorProps & react.RefAttributes<JerseyDetectorHandle>>;
|
|
81
|
-
|
|
82
53
|
type WorkerConfig = {
|
|
83
54
|
task: TaskType;
|
|
84
55
|
model: ModelType;
|
|
@@ -94,24 +65,36 @@ type WorkerConfig = {
|
|
|
94
65
|
threshold?: number;
|
|
95
66
|
targetPartId?: number;
|
|
96
67
|
imagesToReturn?: ImagesToReturn;
|
|
68
|
+
minDetectionArea?: number;
|
|
69
|
+
};
|
|
70
|
+
type BoundingBox = {
|
|
71
|
+
top: number;
|
|
72
|
+
left: number;
|
|
73
|
+
bottom: number;
|
|
74
|
+
right: number;
|
|
97
75
|
};
|
|
98
|
-
type ImageDimensions
|
|
76
|
+
type ImageDimensions = {
|
|
99
77
|
width: number;
|
|
100
78
|
height: number;
|
|
101
79
|
};
|
|
102
80
|
type WorkerInitMessage = {
|
|
103
81
|
type: "init";
|
|
82
|
+
requestId?: string;
|
|
104
83
|
isProduction: boolean;
|
|
105
84
|
config: WorkerConfig;
|
|
106
85
|
};
|
|
107
86
|
type WorkerDetectMessage = {
|
|
87
|
+
requestId: string;
|
|
108
88
|
type: "detect";
|
|
109
|
-
dimensions: ImageDimensions
|
|
89
|
+
dimensions: ImageDimensions;
|
|
110
90
|
threshold?: number;
|
|
111
91
|
bitmap: ImageBitmap;
|
|
112
92
|
};
|
|
113
93
|
type WorkerReadyMessage = {
|
|
114
94
|
type: "ready";
|
|
95
|
+
config: WorkerConfig;
|
|
96
|
+
device: string;
|
|
97
|
+
dtype: string;
|
|
115
98
|
};
|
|
116
99
|
type WorkerProgressMessage = {
|
|
117
100
|
type: "loading_progress";
|
|
@@ -127,6 +110,7 @@ type WorkerDetectResponseMessage = {
|
|
|
127
110
|
bitmap?: ImageBitmap;
|
|
128
111
|
stats: DetectionStats;
|
|
129
112
|
segmentedImages?: ImageBitmap[];
|
|
113
|
+
bboxes?: BoundingBox[];
|
|
130
114
|
};
|
|
131
115
|
type WorkerDebugMessage = {
|
|
132
116
|
type: "debug";
|
|
@@ -135,10 +119,37 @@ type WorkerDebugMessage = {
|
|
|
135
119
|
type WorkerPostMessage = WorkerInitMessage | WorkerDetectMessage;
|
|
136
120
|
type WorkerResponseMessage = WorkerReadyMessage | WorkerProgressMessage | WorkerErrorMessage | WorkerDetectResponseMessage | WorkerDetectMessage;
|
|
137
121
|
|
|
138
|
-
interface
|
|
139
|
-
|
|
140
|
-
|
|
122
|
+
interface JerseyDetectorHandle {
|
|
123
|
+
isReady: boolean;
|
|
124
|
+
stats: DetectionStats;
|
|
125
|
+
lastError: string | null;
|
|
126
|
+
capture: (format?: "image/jpeg" | "image/png", quality?: number) => string;
|
|
127
|
+
}
|
|
128
|
+
interface JerseyDetectorProps {
|
|
129
|
+
videoRef: React.RefObject<HTMLVideoElement>;
|
|
130
|
+
onWorkerReady?: (ready: boolean) => void;
|
|
131
|
+
onWorkerError?: (error: string | null) => void;
|
|
132
|
+
onStatsUpdate?: (stats: DetectionStats) => void;
|
|
133
|
+
onSegmentedImage?: (segmentedImages: ImageBitmap[], bboxes?: BoundingBox[]) => void;
|
|
134
|
+
task?: TaskType;
|
|
135
|
+
model?: ModelType;
|
|
136
|
+
verbose?: boolean;
|
|
137
|
+
bodyPixArchitecture?: "MobileNetV1" | "ResNet50";
|
|
138
|
+
bodyPixMultiplier?: number;
|
|
139
|
+
bodyPixQuantBytes?: number;
|
|
140
|
+
bodyPixStride?: number;
|
|
141
|
+
multiSegmentation?: boolean;
|
|
142
|
+
segmentBodyParts?: boolean;
|
|
143
|
+
backgroundShade?: number;
|
|
144
|
+
shirtShade?: number;
|
|
145
|
+
threshold?: number;
|
|
146
|
+
targetPartId?: number;
|
|
147
|
+
imagesToReturn?: ImagesToReturn;
|
|
148
|
+
minDetectionArea?: number;
|
|
149
|
+
workerUrl?: string;
|
|
141
150
|
}
|
|
151
|
+
declare const JerseyDetector: react.ForwardRefExoticComponent<JerseyDetectorProps & react.RefAttributes<JerseyDetectorHandle>>;
|
|
152
|
+
|
|
142
153
|
/**
|
|
143
154
|
* Simplified hook for jersey detection worker
|
|
144
155
|
* Manages worker lifecycle, state, and detection requests
|
|
@@ -150,4 +161,4 @@ declare function useJerseyWorker(config: WorkerConfig, workerUrlProp?: string):
|
|
|
150
161
|
detect: (bitmap: ImageBitmap, dimensions: ImageDimensions, threshold?: number) => Promise<any>;
|
|
151
162
|
};
|
|
152
163
|
|
|
153
|
-
export { DEFAULT_CONFIG, type DetectionStats, type ImageDimensions
|
|
164
|
+
export { type BoundingBox, DEFAULT_CONFIG, type DetectionStats, type ImageDimensions, type ImageSegmentationType, type ImagesToReturn, JerseyDetector, type JerseyDetectorHandle, type JerseyDetectorProps, type ModelType, type ObjectDetectionType, type TaskType, type WorkerConfig, type WorkerDebugMessage, type WorkerDetectMessage, type WorkerDetectResponseMessage, type WorkerErrorMessage, type WorkerInitMessage, type WorkerPostMessage, type WorkerProgressMessage, type WorkerReadyMessage, type WorkerResponseMessage, useJerseyWorker };
|
package/dist/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var
|
|
1
|
+
"use strict";var ke=Object.create;var $=Object.defineProperty;var be=Object.getOwnPropertyDescriptor;var we=Object.getOwnPropertyNames;var Re=Object.getPrototypeOf,ve=Object.prototype.hasOwnProperty;var _e=(e,o)=>{for(var c in o)$(e,c,{get:o[c],enumerable:!0})},de=(e,o,c,T)=>{if(o&&typeof o=="object"||typeof o=="function")for(let s of we(o))!ve.call(e,s)&&s!==c&&$(e,s,{get:()=>o[s],enumerable:!(T=be(o,s))||T.enumerable});return e};var Me=(e,o,c)=>(c=e!=null?ke(Re(e)):{},de(o||!e||!e.__esModule?$(c,"default",{value:e,enumerable:!0}):c,e)),Ce=e=>de($({},"__esModule",{value:!0}),e);var S=(e,o,c)=>new Promise((T,s)=>{var O=f=>{try{b(c.next(f))}catch(w){s(w)}},I=f=>{try{b(c.throw(f))}catch(w){s(w)}},b=f=>f.done?T(f.value):Promise.resolve(f.value).then(O,I);b((c=c.apply(e,o)).next())});var Se={};_e(Se,{DEFAULT_CONFIG:()=>d,JerseyDetector:()=>te,useJerseyWorker:()=>K});module.exports=Ce(Se);var n=require("react");var p=require("react");var y=null,C=!1,F=null,ee=0,re=null,D=new Map,We=0,z=new Map;function K(e,o){let c=(0,p.useRef)(!1),[T,s]=(0,p.useState)(!1),[O,I]=(0,p.useState)(null),b=(0,p.useRef)(!0),f=(0,p.useRef)(!1),w=(0,p.useRef)(`instance_${Math.random().toString(36).substr(2,9)}`),A=(0,p.useRef)(0);(0,p.useEffect)(()=>{var _;if(!e)return;b.current=!0,A.current++;let v=A.current,W=w.current,N=(_=z.get(W))!=null?_:0;return v>N&&(ee++,z.set(W,v)),S(null,null,function*(){var h;if(!y||!xe(re,e)){y&&(y.terminate(),y=null,C=!1,console.log("[v0 useJerseyWorker] Worker terminated because of change in config"));try{f.current=(h=e.verbose)!=null?h:f.current;let m=o||"/jersey-detector-worker.js";y=new Worker(m),re=e,C=!1,F=null,s(!1),I(null),y.onmessage=g=>{let a=g.data;switch(a.type){case"ready":C=!0,s(!0),f.current&&console.log("[v0 useJerseyWorker] Worker Ready",{device:a.device,dtype:a.dtype,config:a.config});break;case"loading_progress":f.current&&console.log(`[v0 useJerseyWorker] Loaded ${a.progress}% of ${a.file}`);break;case"debug":f.current&&console.log(a.debug_message);break;case"error":let J=a.error_message||"Unknown worker error";console.error("[v0 useJerseyWorker] Worker sent error message:",J),C=!1,F=J,s(!1),I(J);break;case"detect_response":if(a.requestId&&D.has(a.requestId)){let k=D.get(a.requestId);k&&(k.resolve(a),D.delete(a.requestId))}else if(D.size>0){let k=D.entries().next();if(!k.done){let[x,P]=k.value;P.resolve(a),D.delete(x)}}break}},y.onerror=g=>{let a=g.message||"Worker initialization error";console.error("[useJerseyWorker] Worker error:",a),C=!1,F=a,s(!1),I(a)},y.postMessage({type:"init",config:e})}catch(m){let g=(m==null?void 0:m.message)||String(m);console.error("[useJerseyWorker] Failed to create worker:",g),s(!1),I(g)}}else s(C),I(F)}),()=>{b.current=!1;let h=w.current,m=z.get(h),g=A.current;m===g&&(ee--,z.delete(h),ee<=0&&y&&(y.terminate(),y=null,C=!1,F=null,re=null,D.clear()))}},[]);let V=(0,p.useCallback)((v,W,N=.5)=>S(null,null,function*(){if(!y||!C)throw new Error("Worker not ready");if(c.current)return null;let R=`req_${++We}_${Date.now()}`;return new Promise((_,h)=>{D.set(R,{resolve:_,reject:h});try{c.current=!0,y.postMessage({type:"detect",requestId:R,dimensions:W,bitmap:v,threshold:N},[v])}catch(m){D.delete(R);let g=`Failed to send detect: ${m==null?void 0:m.message}`;h(new Error(g))}finally{c.current=!1}setTimeout(()=>{D.has(R)&&(D.delete(R),h(new Error("Detection timeout")))},1e4)})}),[T]);return{isReady:T,workerReady:T,lastError:O,detect:V}}function xe(e,o){return e?JSON.stringify(e)===JSON.stringify(o):!1}var d={TASK:"image-segmentation",MODEL:"BodyPix",BODYPIX_ARCHITECTURE:"MobileNetV1",BODYPIX_MULTIPLIER:.75,BODYPIX_QUANT_BYTES:2,BODYPIX_STRIDE:16,MULTI_SEGMENTATION:!0,SEGMENT_BODY_PARTS:!0,BACKGROUND_SHADE:0,SHIRT_SHADE:170,DETECTION_THRESHOLD:.5,TARGET_PART_ID:12,IMAGES_TO_RETURN:"mask",VERBOSE:!1,MIN_DETECTION_AREA:1e4};var fe=Me(require("p-limit"));var me=require("react/jsx-runtime"),te=(0,n.forwardRef)(({videoRef:e,onWorkerReady:o,onWorkerError:c,onStatsUpdate:T,onSegmentedImage:s,task:O=d.TASK,model:I=d.MODEL,verbose:b=d.VERBOSE,bodyPixArchitecture:f=d.BODYPIX_ARCHITECTURE,bodyPixMultiplier:w=d.BODYPIX_MULTIPLIER,bodyPixQuantBytes:A=d.BODYPIX_QUANT_BYTES,bodyPixStride:V=d.BODYPIX_STRIDE,multiSegmentation:v=d.MULTI_SEGMENTATION,segmentBodyParts:W=d.SEGMENT_BODY_PARTS,backgroundShade:N=d.BACKGROUND_SHADE,shirtShade:R=d.SHIRT_SHADE,threshold:_=d.DETECTION_THRESHOLD,targetPartId:h=d.TARGET_PART_ID,imagesToReturn:m=d.IMAGES_TO_RETURN,minDetectionArea:g=d.MIN_DETECTION_AREA,workerUrl:a},J)=>{let k=(0,n.useRef)(null),x=(0,n.useRef)(null),P=(0,n.useRef)(!1),j=(0,n.useRef)([]),ge=(0,n.useRef)(null),L=(0,n.useRef)(!1),Y=(0,n.useRef)(void 0);Y.current=c;let Q=(0,n.useRef)({jerseyCount:0,confidence:0,fps:0,processingTime:0}),ne=(0,n.useRef)(0),ye=500,oe=5,se=(0,n.useMemo)(()=>(0,fe.default)(oe),[]),Z=(0,n.useMemo)(()=>({task:O,model:I,verbose:b,bodyPixArchitecture:f,bodyPixMultiplier:w,bodyPixQuantBytes:A,bodyPixStride:V,multiSegmentation:v,segmentBodyParts:W,backgroundShade:N,shirtShade:R,threshold:_,targetPartId:h,imagesToReturn:m,minDetectionArea:g}),[O,I,b,f,w,A,V,v,W,N,R,_,h,m,g]),pe=()=>{var r,i;return{width:((r=e.current)==null?void 0:r.videoWidth)||0,height:((i=e.current)==null?void 0:i.videoHeight)||0}},{isReady:B,workerReady:M,lastError:U,detect:he}=K(Z,a);(0,n.useEffect)(()=>{M&&console.log("[JerseyDetector] Worker Ready with config:",Z),o==null||o(M)},[M,o,Z]),(0,n.useEffect)(()=>{var r;U&&(L.current=!0,(r=Y.current)==null||r.call(Y,U))},[U]);let Te=(0,n.useCallback)((r,i)=>{if(se.activeCount>=oe){r.forEach(t=>{var u;return(u=t==null?void 0:t.close)==null?void 0:u.call(t)}),console.warn("[JerseyDetector] Dropped new segmented images \u2014 max 5 classifications running");return}se(()=>S(null,null,function*(){try{s==null||s(r,i)}catch(t){console.error("[JerseyDetector] Classification error:",t)}finally{r.forEach(t=>{var u;return(u=t==null?void 0:t.close)==null?void 0:u.call(t)})}}))},[s]),Ee=()=>S(null,null,function*(){var H,ie,ae;let r=e.current,i=k.current;if(L.current||!B||!M||!r||!i||P.current||r.readyState<2||r.paused||r.ended||r.videoWidth===0||r.videoHeight===0)return;let t=i.getContext("2d");if(!t||i.width<=0||i.height<=0)return;let u=null,l=null;try{P.current=!0;let G=r.videoWidth,q=r.videoHeight;ge.current={width:G,height:q},u=yield createImageBitmap(r);let De=pe(),E=yield he(u,De,_);if((E==null?void 0:E.type)==="detect_response"){E.bitmap&&m!=="none"&&(l=E.bitmap,(i.width!==G||i.height!==q)&&(i.width=G,i.height=q),t.clearRect(0,0,G,q),t.drawImage(l,0,0),console.log(`[JerseyDetector] Bitmap created with dims ${l.width}x${l.height}`),l.close()),j.current.push(Date.now()),j.current=j.current.filter(Ie=>Date.now()-Ie<1e3);let X={jerseyCount:((H=E.stats)==null?void 0:H.jerseyCount)||0,confidence:((ie=E.stats)==null?void 0:ie.confidence)||0,fps:j.current.length,processingTime:((ae=E.stats)==null?void 0:ae.processingTime)||0},ce=Q.current;Q.current=X;let ue=Date.now(),le=ue-ne.current>ye||X.jerseyCount!==ce.jerseyCount||X.confidence!==ce.confidence;le&&T&&(T(X),ne.current=ue),le&&E.segmentedImages&&s&&Te(E.segmentedImages,E.bboxes)}}catch(G){L.current=!0}finally{u==null||u.close(),l==null||l.close(),P.current=!1}});return(0,n.useImperativeHandle)(J,()=>({get isReady(){return B},get lastError(){return U},get stats(){return Q.current},capture:(r="image/jpeg",i=.92)=>{let t=e.current,u=k.current;if(!t||t.videoWidth<=0||t.videoHeight<=0)throw new Error("Video not ready for capture");let l=document.createElement("canvas");l.width=t.videoWidth,l.height=t.videoHeight;let H=l.getContext("2d");if(!H)throw new Error("Failed to get context for capture canvas");return H.drawImage(t,0,0,l.width,l.height),u&&u.width>0&&u.height>0?H.drawImage(u,0,0,l.width,l.height):console.warn("[capture] Mask canvas not available \u2014 returning raw video frame"),l.toDataURL(r,i)}}),[B,U,M]),(0,n.useEffect)(()=>{if(!M||!B||L.current)return;let r=e.current,i=k.current;if(!r||!i||r.videoWidth===0||r.videoHeight===0){let u=setTimeout(()=>{x.current=requestAnimationFrame(()=>{})},100);return()=>clearTimeout(u)}(i.width!==r.videoWidth||i.height!==r.videoHeight)&&(i.width=r.videoWidth,i.height=r.videoHeight);let t=()=>S(null,null,function*(){!M||!B||L.current||(yield Ee(),x.current=requestAnimationFrame(t))});return x.current=requestAnimationFrame(t),()=>{x.current&&cancelAnimationFrame(x.current)}},[M,B]),(0,me.jsx)("canvas",{ref:k,className:"absolute inset-0 pointer-events-none",style:{zIndex:10,width:"100%",height:"100%"}})});te.displayName="JerseyDetector";0&&(module.exports={DEFAULT_CONFIG,JerseyDetector,useJerseyWorker});
|
package/dist/index.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
var
|
|
1
|
+
var x=(i,p,h)=>new Promise((w,l)=>{var S=a=>{try{D(h.next(a))}catch(I){l(I)}},T=a=>{try{D(h.throw(a))}catch(I){l(I)}},D=a=>a.done?w(a.value):Promise.resolve(a.value).then(S,T);D((h=h.apply(i,p)).next())});import{forwardRef as Re,useImperativeHandle as ve,useRef as b,useEffect as re,useMemo as de,useCallback as _e}from"react";import{useEffect as Ie,useState as le,useCallback as ke,useRef as G}from"react";var f=null,M=!1,F=null,Q=0,Z=null,y=new Map,be=0,$=new Map;function ee(i,p){let h=G(!1),[w,l]=le(!1),[S,T]=le(null),D=G(!0),a=G(!1),I=G(`instance_${Math.random().toString(36).substr(2,9)}`),O=G(0);Ie(()=>{var v;if(!i)return;D.current=!0,O.current++;let R=O.current,C=I.current,A=(v=$.get(C))!=null?v:0;return R>A&&(Q++,$.set(C,R)),x(null,null,function*(){var m;if(!f||!we(Z,i)){f&&(f.terminate(),f=null,M=!1,console.log("[v0 useJerseyWorker] Worker terminated because of change in config"));try{a.current=(m=i.verbose)!=null?m:a.current;let c=p||"/jersey-detector-worker.js";f=new Worker(c),Z=i,M=!1,F=null,l(!1),T(null),f.onmessage=d=>{let n=d.data;switch(n.type){case"ready":M=!0,l(!0),a.current&&console.log("[v0 useJerseyWorker] Worker Ready",{device:n.device,dtype:n.dtype,config:n.config});break;case"loading_progress":a.current&&console.log(`[v0 useJerseyWorker] Loaded ${n.progress}% of ${n.file}`);break;case"debug":a.current&&console.log(n.debug_message);break;case"error":let H=n.error_message||"Unknown worker error";console.error("[v0 useJerseyWorker] Worker sent error message:",H),M=!1,F=H,l(!1),T(H);break;case"detect_response":if(n.requestId&&y.has(n.requestId)){let E=y.get(n.requestId);E&&(E.resolve(n),y.delete(n.requestId))}else if(y.size>0){let E=y.entries().next();if(!E.done){let[W,J]=E.value;J.resolve(n),y.delete(W)}}break}},f.onerror=d=>{let n=d.message||"Worker initialization error";console.error("[useJerseyWorker] Worker error:",n),M=!1,F=n,l(!1),T(n)},f.postMessage({type:"init",config:i})}catch(c){let d=(c==null?void 0:c.message)||String(c);console.error("[useJerseyWorker] Failed to create worker:",d),l(!1),T(d)}}else l(M),T(F)}),()=>{D.current=!1;let m=I.current,c=$.get(m),d=O.current;c===d&&(Q--,$.delete(m),Q<=0&&f&&(f.terminate(),f=null,M=!1,F=null,Z=null,y.clear()))}},[]);let V=ke((R,C,A=.5)=>x(null,null,function*(){if(!f||!M)throw new Error("Worker not ready");if(h.current)return null;let k=`req_${++be}_${Date.now()}`;return new Promise((v,m)=>{y.set(k,{resolve:v,reject:m});try{h.current=!0,f.postMessage({type:"detect",requestId:k,dimensions:C,bitmap:R,threshold:A},[R])}catch(c){y.delete(k);let d=`Failed to send detect: ${c==null?void 0:c.message}`;m(new Error(d))}finally{h.current=!1}setTimeout(()=>{y.has(k)&&(y.delete(k),m(new Error("Detection timeout")))},1e4)})}),[w]);return{isReady:w,workerReady:w,lastError:S,detect:V}}function we(i,p){return i?JSON.stringify(i)===JSON.stringify(p):!1}var u={TASK:"image-segmentation",MODEL:"BodyPix",BODYPIX_ARCHITECTURE:"MobileNetV1",BODYPIX_MULTIPLIER:.75,BODYPIX_QUANT_BYTES:2,BODYPIX_STRIDE:16,MULTI_SEGMENTATION:!0,SEGMENT_BODY_PARTS:!0,BACKGROUND_SHADE:0,SHIRT_SHADE:170,DETECTION_THRESHOLD:.5,TARGET_PART_ID:12,IMAGES_TO_RETURN:"mask",VERBOSE:!1,MIN_DETECTION_AREA:1e4};import Me from"p-limit";import{jsx as Ce}from"react/jsx-runtime";var fe=Re(({videoRef:i,onWorkerReady:p,onWorkerError:h,onStatsUpdate:w,onSegmentedImage:l,task:S=u.TASK,model:T=u.MODEL,verbose:D=u.VERBOSE,bodyPixArchitecture:a=u.BODYPIX_ARCHITECTURE,bodyPixMultiplier:I=u.BODYPIX_MULTIPLIER,bodyPixQuantBytes:O=u.BODYPIX_QUANT_BYTES,bodyPixStride:V=u.BODYPIX_STRIDE,multiSegmentation:R=u.MULTI_SEGMENTATION,segmentBodyParts:C=u.SEGMENT_BODY_PARTS,backgroundShade:A=u.BACKGROUND_SHADE,shirtShade:k=u.SHIRT_SHADE,threshold:v=u.DETECTION_THRESHOLD,targetPartId:m=u.TARGET_PART_ID,imagesToReturn:c=u.IMAGES_TO_RETURN,minDetectionArea:d=u.MIN_DETECTION_AREA,workerUrl:n},H)=>{let E=b(null),W=b(null),J=b(!1),j=b([]),me=b(null),P=b(!1),Y=b(void 0);Y.current=h;let z=b({jerseyCount:0,confidence:0,fps:0,processingTime:0}),te=b(0),ge=500,ne=5,oe=de(()=>Me(ne),[]),K=de(()=>({task:S,model:T,verbose:D,bodyPixArchitecture:a,bodyPixMultiplier:I,bodyPixQuantBytes:O,bodyPixStride:V,multiSegmentation:R,segmentBodyParts:C,backgroundShade:A,shirtShade:k,threshold:v,targetPartId:m,imagesToReturn:c,minDetectionArea:d}),[S,T,D,a,I,O,V,R,C,A,k,v,m,c,d]),ye=()=>{var e,t;return{width:((e=i.current)==null?void 0:e.videoWidth)||0,height:((t=i.current)==null?void 0:t.videoHeight)||0}},{isReady:N,workerReady:_,lastError:L,detect:pe}=ee(K,n);re(()=>{_&&console.log("[JerseyDetector] Worker Ready with config:",K),p==null||p(_)},[_,p,K]),re(()=>{var e;L&&(P.current=!0,(e=Y.current)==null||e.call(Y,L))},[L]);let he=_e((e,t)=>{if(oe.activeCount>=ne){e.forEach(r=>{var o;return(o=r==null?void 0:r.close)==null?void 0:o.call(r)}),console.warn("[JerseyDetector] Dropped new segmented images \u2014 max 5 classifications running");return}oe(()=>x(null,null,function*(){try{l==null||l(e,t)}catch(r){console.error("[JerseyDetector] Classification error:",r)}finally{e.forEach(r=>{var o;return(o=r==null?void 0:r.close)==null?void 0:o.call(r)})}}))},[l]),Te=()=>x(null,null,function*(){var B,se,ie;let e=i.current,t=E.current;if(P.current||!N||!_||!e||!t||J.current||e.readyState<2||e.paused||e.ended||e.videoWidth===0||e.videoHeight===0)return;let r=t.getContext("2d");if(!r||t.width<=0||t.height<=0)return;let o=null,s=null;try{J.current=!0;let U=e.videoWidth,q=e.videoHeight;me.current={width:U,height:q},o=yield createImageBitmap(e);let Ee=ye(),g=yield pe(o,Ee,v);if((g==null?void 0:g.type)==="detect_response"){g.bitmap&&c!=="none"&&(s=g.bitmap,(t.width!==U||t.height!==q)&&(t.width=U,t.height=q),r.clearRect(0,0,U,q),r.drawImage(s,0,0),console.log(`[JerseyDetector] Bitmap created with dims ${s.width}x${s.height}`),s.close()),j.current.push(Date.now()),j.current=j.current.filter(De=>Date.now()-De<1e3);let X={jerseyCount:((B=g.stats)==null?void 0:B.jerseyCount)||0,confidence:((se=g.stats)==null?void 0:se.confidence)||0,fps:j.current.length,processingTime:((ie=g.stats)==null?void 0:ie.processingTime)||0},ae=z.current;z.current=X;let ce=Date.now(),ue=ce-te.current>ge||X.jerseyCount!==ae.jerseyCount||X.confidence!==ae.confidence;ue&&w&&(w(X),te.current=ce),ue&&g.segmentedImages&&l&&he(g.segmentedImages,g.bboxes)}}catch(U){P.current=!0}finally{o==null||o.close(),s==null||s.close(),J.current=!1}});return ve(H,()=>({get isReady(){return N},get lastError(){return L},get stats(){return z.current},capture:(e="image/jpeg",t=.92)=>{let r=i.current,o=E.current;if(!r||r.videoWidth<=0||r.videoHeight<=0)throw new Error("Video not ready for capture");let s=document.createElement("canvas");s.width=r.videoWidth,s.height=r.videoHeight;let B=s.getContext("2d");if(!B)throw new Error("Failed to get context for capture canvas");return B.drawImage(r,0,0,s.width,s.height),o&&o.width>0&&o.height>0?B.drawImage(o,0,0,s.width,s.height):console.warn("[capture] Mask canvas not available \u2014 returning raw video frame"),s.toDataURL(e,t)}}),[N,L,_]),re(()=>{if(!_||!N||P.current)return;let e=i.current,t=E.current;if(!e||!t||e.videoWidth===0||e.videoHeight===0){let o=setTimeout(()=>{W.current=requestAnimationFrame(()=>{})},100);return()=>clearTimeout(o)}(t.width!==e.videoWidth||t.height!==e.videoHeight)&&(t.width=e.videoWidth,t.height=e.videoHeight);let r=()=>x(null,null,function*(){!_||!N||P.current||(yield Te(),W.current=requestAnimationFrame(r))});return W.current=requestAnimationFrame(r),()=>{W.current&&cancelAnimationFrame(W.current)}},[_,N]),Ce("canvas",{ref:E,className:"absolute inset-0 pointer-events-none",style:{zIndex:10,width:"100%",height:"100%"}})});fe.displayName="JerseyDetector";export{u as DEFAULT_CONFIG,fe as JerseyDetector,ee as useJerseyWorker};
|