@meta-sam/graphics 0.1.5
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/LICENSE +61 -0
- package/README.md +490 -0
- package/dist/contour.d.ts +34 -0
- package/dist/contour.js +230 -0
- package/dist/errors.d.ts +24 -0
- package/dist/errors.js +45 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +5 -0
- package/dist/renderer.d.ts +111 -0
- package/dist/renderer.js +649 -0
- package/package.json +43 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
SAM License
|
|
2
|
+
Last Updated: November 19, 2025
|
|
3
|
+
|
|
4
|
+
“Agreement” means the terms and conditions for use, reproduction, distribution and modification of the SAM Materials set forth herein.
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
“SAM Materials” means, collectively, Documentation and the models, software and algorithms, including machine-learning model code, trained model weights, inference-enabling code, training-enabling code, fine-tuning enabling code, and other elements of the foregoing distributed by Meta and made available under this Agreement.
|
|
8
|
+
|
|
9
|
+
“Documentation” means the specifications, manuals and documentation accompanying
|
|
10
|
+
SAM Materials distributed by Meta.
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
“Licensee” or “you” means you, or your employer or any other person or entity (if you are entering into this Agreement on such person or entity’s behalf), of the age required under applicable laws, rules or regulations to provide legal consent and that has legal authority to bind your employer or such other person or entity if you are entering in this Agreement on their behalf.
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
“Meta” or “we” means Meta Platforms Ireland Limited (if you are located in or, if you are an entity, your principal place of business is in the EEA or Switzerland) or Meta Platforms, Inc. (if you are located outside of the EEA or Switzerland).
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
“Sanctions” means any economic or trade sanctions or restrictions administered or enforced by the United States (including the Office of Foreign Assets Control of the U.S. Department of the Treasury (“OFAC”), the U.S. Department of State and the U.S. Department of Commerce), the United Nations, the European Union, or the United Kingdom.
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
“Trade Controls” means any of the following: Sanctions and applicable export and import controls.
|
|
23
|
+
|
|
24
|
+
By using or distributing any portion or element of the SAM Materials, you agree to be bound by this Agreement.
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
1. License Rights and Redistribution.
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
a. Grant of Rights. You are granted a non-exclusive, worldwide, non-transferable and royalty-free limited license under Meta’s intellectual property or other rights owned by Meta embodied in the SAM Materials to use, reproduce, distribute, copy, create derivative works of, and make modifications to the SAM Materials.
|
|
31
|
+
|
|
32
|
+
b. Redistribution and Use.
|
|
33
|
+
i. Distribution of SAM Materials, and any derivative works thereof, are subject to the terms of this Agreement. If you distribute or make the SAM Materials, or any derivative works thereof, available to a third party, you may only do so under the terms of this Agreement and you shall provide a copy of this Agreement with any such SAM Materials.
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
ii. If you submit for publication the results of research you perform on, using, or otherwise in connection with SAM Materials, you must acknowledge the use of SAM Materials in your publication.
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
iii. Your use of the SAM Materials must comply with applicable laws and regulations, including Trade Control Laws and applicable privacy and data protection laws.
|
|
40
|
+
iv. Your use of the SAM Materials will not involve or encourage others to reverse engineer, decompile or discover the underlying components of the SAM Materials.
|
|
41
|
+
v. You are not the target of Trade Controls and your use of SAM Materials must comply with Trade Controls. You agree not to use, or permit others to use, SAM Materials for any activities subject to the International Traffic in Arms Regulations (ITAR) or end uses prohibited by Trade Controls, including those related to military or warfare purposes, nuclear industries or applications, espionage, or the development or use of guns or illegal weapons.
|
|
42
|
+
2. User Support. Your use of the SAM Materials is done at your own discretion; Meta does not process any information nor provide any service in relation to such use. Meta is under no obligation to provide any support services for the SAM Materials. Any support provided is “as is”, “with all faults”, and without warranty of any kind.
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
3. Disclaimer of Warranty. UNLESS REQUIRED BY APPLICABLE LAW, THE SAM MATERIALS AND ANY OUTPUT AND RESULTS THEREFROM ARE PROVIDED ON AN “AS IS” BASIS, WITHOUT WARRANTIES OF ANY KIND, AND META DISCLAIMS ALL WARRANTIES OF ANY KIND, BOTH EXPRESS AND IMPLIED, INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE. YOU ARE SOLELY RESPONSIBLE FOR DETERMINING THE APPROPRIATENESS OF USING OR REDISTRIBUTING THE SAM MATERIALS AND ASSUME ANY RISKS ASSOCIATED WITH YOUR USE OF THE SAM MATERIALS AND ANY OUTPUT AND RESULTS.
|
|
46
|
+
|
|
47
|
+
4. Limitation of Liability. IN NO EVENT WILL META OR ITS AFFILIATES BE LIABLE UNDER ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, TORT, NEGLIGENCE, PRODUCTS LIABILITY, OR OTHERWISE, ARISING OUT OF THIS AGREEMENT, FOR ANY LOST PROFITS OR ANY DIRECT OR INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL, EXEMPLARY OR PUNITIVE DAMAGES, EVEN IF META OR ITS AFFILIATES HAVE BEEN ADVISED OF THE POSSIBILITY OF ANY OF THE FOREGOING.
|
|
48
|
+
|
|
49
|
+
5. Intellectual Property.
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
a. Subject to Meta’s ownership of SAM Materials and derivatives made by or for Meta, with respect to any derivative works and modifications of the SAM Materials that are made by you, as between you and Meta, you are and will be the owner of such derivative works and modifications.
|
|
53
|
+
|
|
54
|
+
b. If you institute litigation or other proceedings against Meta or any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the SAM Materials, outputs or results, or any portion of any of the foregoing, constitutes infringement of intellectual property or other rights owned or licensable by you, then any licenses granted to you under this Agreement shall terminate as of the date such litigation or claim is filed or instituted. You will indemnify and hold harmless Meta from and against any claim by any third party arising out of or related to your use or distribution of the SAM Materials.
|
|
55
|
+
|
|
56
|
+
6. Term and Termination. The term of this Agreement will commence upon your acceptance of this Agreement or access to the SAM Materials and will continue in full force and effect until terminated in accordance with the terms and conditions herein. Meta may terminate this Agreement if you are in breach of any term or condition of this Agreement. Upon termination of this Agreement, you shall delete and cease use of the SAM Materials. Sections 3, 4 and 7 shall survive the termination of this Agreement.
|
|
57
|
+
|
|
58
|
+
7. Governing Law and Jurisdiction. This Agreement will be governed and construed under the laws of the State of California without regard to choice of law principles, and the UN Convention on Contracts for the International Sale of Goods does not apply to this Agreement. The courts of California shall have exclusive jurisdiction of any dispute arising out of this Agreement.
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
8. Modifications and Amendments. Meta may modify this Agreement from time to time; provided that they are similar in spirit to the current version of the Agreement, but may differ in detail to address new problems or concerns. All such changes will be effective immediately. Your continued use of the SAM Materials after any modification to this Agreement constitutes your agreement to such modification. Except as provided in this Agreement, no modification or addition to any provision of this Agreement will be binding unless it is in writing and signed by an authorized representative of both you and Meta.
|
package/README.md
ADDED
|
@@ -0,0 +1,490 @@
|
|
|
1
|
+
# @meta-sam/graphics
|
|
2
|
+
|
|
3
|
+
`@meta-sam/graphics` is an ESM-only, framework-neutral Canvas 2D renderer for
|
|
4
|
+
SAM 3 segmentation results. It retains complete masks and boxes from
|
|
5
|
+
`@meta-sam/parser`, traces masks during asynchronous updates, and creates browser
|
|
6
|
+
`Path2D` objects lazily while rendering.
|
|
7
|
+
|
|
8
|
+
## Installation
|
|
9
|
+
|
|
10
|
+
```sh
|
|
11
|
+
npm install @meta-sam/graphics @meta-sam/parser @meta-sam/video
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## Image quick start
|
|
15
|
+
|
|
16
|
+
The renderer accepts image or video results and snapshots from `@meta-sam/parser`.
|
|
17
|
+
Call and await `update()` before `render()`; rendering is synchronous and sees only
|
|
18
|
+
the last successfully committed update.
|
|
19
|
+
|
|
20
|
+
<!-- readme-example -->
|
|
21
|
+
|
|
22
|
+
```ts
|
|
23
|
+
import { SegmentationRenderer } from '@meta-sam/graphics';
|
|
24
|
+
import type { ImageSegmentationResult } from '@meta-sam/parser';
|
|
25
|
+
|
|
26
|
+
const result: ImageSegmentationResult = {
|
|
27
|
+
media: 'image',
|
|
28
|
+
revision: 1,
|
|
29
|
+
records: [
|
|
30
|
+
{
|
|
31
|
+
kind: 'mask',
|
|
32
|
+
order: 0,
|
|
33
|
+
objectId: 'bus',
|
|
34
|
+
identity: 'image:*:bus',
|
|
35
|
+
revision: 1,
|
|
36
|
+
mask: {
|
|
37
|
+
encoding: 'one_bit',
|
|
38
|
+
width: 5,
|
|
39
|
+
height: 5,
|
|
40
|
+
payload: '!!!!!(QO(0lu8?',
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
kind: 'box',
|
|
45
|
+
order: 1,
|
|
46
|
+
objectId: 'bus',
|
|
47
|
+
left: 1,
|
|
48
|
+
top: 1,
|
|
49
|
+
right: 4,
|
|
50
|
+
bottom: 4,
|
|
51
|
+
},
|
|
52
|
+
],
|
|
53
|
+
diagnostics: [],
|
|
54
|
+
rawOutput: '',
|
|
55
|
+
outcome: { status: 'completed' },
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const canvas = document.querySelector<HTMLCanvasElement>('#overlay');
|
|
59
|
+
if (canvas === null) throw new Error('Missing overlay canvas.');
|
|
60
|
+
|
|
61
|
+
const context = canvas.getContext('2d');
|
|
62
|
+
if (context === null) throw new Error('Canvas 2D is unavailable.');
|
|
63
|
+
|
|
64
|
+
const renderer = new SegmentationRenderer();
|
|
65
|
+
await renderer.update(result);
|
|
66
|
+
renderer.render(context, {
|
|
67
|
+
media: 'image',
|
|
68
|
+
source: { x: 0, y: 0, width: 5, height: 5 },
|
|
69
|
+
target: { x: 0, y: 0, width: canvas.width, height: canvas.height },
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
// Remove retained overlays while keeping the renderer reusable.
|
|
73
|
+
renderer.clear();
|
|
74
|
+
// Permanently release the renderer when ownership ends.
|
|
75
|
+
renderer.dispose();
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
The mask payload in this example is one complete, canonical `one_bit` payload. A
|
|
79
|
+
real application normally passes parser snapshots to `update()` as they arrive and
|
|
80
|
+
then passes the final result when parsing completes.
|
|
81
|
+
|
|
82
|
+
## Video and frame selection
|
|
83
|
+
|
|
84
|
+
Use video results with `media: 'video'` and select the frame to draw with
|
|
85
|
+
`frameIndex`:
|
|
86
|
+
|
|
87
|
+
```ts
|
|
88
|
+
await renderer.update(videoResult);
|
|
89
|
+
renderer.render(context, {
|
|
90
|
+
media: 'video',
|
|
91
|
+
frameIndex: 42,
|
|
92
|
+
source: { x: 0, y: 0, width: video.videoWidth, height: video.videoHeight },
|
|
93
|
+
target: { x: 0, y: 0, width: canvas.width, height: canvas.height },
|
|
94
|
+
});
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
`frameIndex` must be a non-negative safe integer. Records with the same frame index
|
|
98
|
+
are drawn; records without a frame reference are global and are drawn on every
|
|
99
|
+
video frame. The retained result media must match the render options.
|
|
100
|
+
|
|
101
|
+
## Same-canvas decoded video composition
|
|
102
|
+
|
|
103
|
+
`renderVideoFrame()` is a small structural adapter for Canvas media players. Its
|
|
104
|
+
context matches the frame, composition/fallback canvases and contexts, frame-index,
|
|
105
|
+
and abort fields supplied by `@meta-sam/video`'s `MediaPlayerRenderContext`, but
|
|
106
|
+
graphics does not import or depend on the video package. It clears the isolated
|
|
107
|
+
composition canvas, draws the decoded frame first, then draws the exact frame-indexed
|
|
108
|
+
SAM overlay. When the media player supplies `fallbackCanvas`/`fallbackCtx`, the helper
|
|
109
|
+
also draws the same decoded frame with the same fit and DPR but without overlays, ready
|
|
110
|
+
for a missed final-frame deadline.
|
|
111
|
+
|
|
112
|
+
<!-- readme-example -->
|
|
113
|
+
|
|
114
|
+
```ts
|
|
115
|
+
import {
|
|
116
|
+
SegmentationRenderer,
|
|
117
|
+
type VideoFrameCompositionOptions,
|
|
118
|
+
} from '@meta-sam/graphics';
|
|
119
|
+
import type { VideoSegmentationResult } from '@meta-sam/parser';
|
|
120
|
+
import type { IMediaPlayer } from '@meta-sam/video';
|
|
121
|
+
|
|
122
|
+
declare const player: IMediaPlayer;
|
|
123
|
+
declare const result: VideoSegmentationResult;
|
|
124
|
+
|
|
125
|
+
const renderer = new SegmentationRenderer();
|
|
126
|
+
await renderer.update(result);
|
|
127
|
+
|
|
128
|
+
const hiddenIds = new Set<string>();
|
|
129
|
+
let pixelRatio = window.devicePixelRatio || 1;
|
|
130
|
+
const composition: VideoFrameCompositionOptions = {
|
|
131
|
+
fit: 'contain',
|
|
132
|
+
devicePixelRatio: () => pixelRatio,
|
|
133
|
+
hiddenIds,
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
player.setCustomRender((context) => {
|
|
137
|
+
renderer.renderVideoFrame(context, composition);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
// The visible canvas backing store owns resize policy. After changing its width or
|
|
141
|
+
// height, update pixelRatio if needed and re-render the retained decoded frame.
|
|
142
|
+
pixelRatio = 2;
|
|
143
|
+
await player.forceRender();
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
`fit` may be `contain` (the default), `cover`, or `fill`. The helper uses the decoded
|
|
147
|
+
frame dimensions as `source`, derives a logical display rectangle from
|
|
148
|
+
`composition.canvas.width / devicePixelRatio` and height, and recomputes `target` on
|
|
149
|
+
every render. Set the visible output canvas backing width and height to the rounded
|
|
150
|
+
logical size times the same DPR before opening or forcing a render. This makes resize
|
|
151
|
+
behavior explicit and also works for offscreen canvases, where CSS dimensions and the
|
|
152
|
+
global device pixel ratio are unavailable.
|
|
153
|
+
|
|
154
|
+
The helper checks an already-aborted signal before touching the composition canvas and
|
|
155
|
+
returns `false`; otherwise it returns `true` after synchronous composition. The media
|
|
156
|
+
player's isolated canvas and generation fence prevent a render aborted during later
|
|
157
|
+
asynchronous custom work from reaching the visible canvas. `hiddenIds` contains object
|
|
158
|
+
IDs, not mask identities. Callers still own and await `update()` separately, so a render
|
|
159
|
+
while an update is pending sees the prior committed segmentation state.
|
|
160
|
+
|
|
161
|
+
The lower-level `render()` API remains available for overlay-only canvases and custom
|
|
162
|
+
source/target rectangles.
|
|
163
|
+
|
|
164
|
+
## Results, records, and updates
|
|
165
|
+
|
|
166
|
+
Parser snapshots and final results are **cumulative views**, not deltas. Pass the
|
|
167
|
+
complete current view to every `update()` call.
|
|
168
|
+
|
|
169
|
+
- `update()` calls are serialized in invocation order. Always await the update that
|
|
170
|
+
must be visible before calling `render()`.
|
|
171
|
+
- Masks and boxes are retained. Text and point records are accepted but are not
|
|
172
|
+
drawn.
|
|
173
|
+
- Mask records are collapsed by `identity`; the greatest per-mask `revision` wins.
|
|
174
|
+
At the same revision, conflicting mask data is rejected.
|
|
175
|
+
- Boxes are collapsed by frame and `objectId`; the last box in the accepted view
|
|
176
|
+
wins.
|
|
177
|
+
- Masks are always drawn before boxes, regardless of record order. An all-zero mask
|
|
178
|
+
has no path and is skipped.
|
|
179
|
+
- For the same media, a snapshot revision older than the committed snapshot is
|
|
180
|
+
ignored. At an accepted equal or newer snapshot revision, identities omitted from
|
|
181
|
+
the cumulative view are removed. A present mask with an older per-mask revision
|
|
182
|
+
keeps its retained geometry.
|
|
183
|
+
- Changing media starts a fresh retained view even without `{ reset: true }`.
|
|
184
|
+
|
|
185
|
+
All records in an input count toward resource limits before masks and boxes are
|
|
186
|
+
collapsed.
|
|
187
|
+
|
|
188
|
+
## Complete mask payloads
|
|
189
|
+
|
|
190
|
+
Graphics accepts only complete masks with this shape:
|
|
191
|
+
|
|
192
|
+
```ts
|
|
193
|
+
{
|
|
194
|
+
encoding: 'one_bit',
|
|
195
|
+
width: 5,
|
|
196
|
+
height: 5,
|
|
197
|
+
payload: '!!!!!(QO(0lu8?'
|
|
198
|
+
}
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
`width` and `height` must be positive safe integers, their area must be within the
|
|
202
|
+
configured and codec limits, and `payload` must be within the configured and codec
|
|
203
|
+
length limits. During `update()`, the renderer strictly decodes the entire payload,
|
|
204
|
+
verifies decoder finalization, verifies that the representation is canonical, and
|
|
205
|
+
traces the resulting binary raster. Partial, truncated, trailing, invalid, and
|
|
206
|
+
noncanonical payloads reject the update. Unsupported encodings reject the update.
|
|
207
|
+
|
|
208
|
+
Use `@meta-sam/parser` to produce complete mask records. Graphics does not accept
|
|
209
|
+
streaming mask fragments.
|
|
210
|
+
|
|
211
|
+
## Source and target rectangles
|
|
212
|
+
|
|
213
|
+
`source` selects the coordinate-space rectangle to map, and `target` is the canvas
|
|
214
|
+
rectangle to fill. Both require finite coordinates and strictly positive width and
|
|
215
|
+
height; origins may be negative. Graphics clips to `target` and applies this mapping:
|
|
216
|
+
|
|
217
|
+
```text
|
|
218
|
+
scaleX = target.width / source.width
|
|
219
|
+
scaleY = target.height / source.height
|
|
220
|
+
offsetX = target.x - source.x * scaleX
|
|
221
|
+
offsetY = target.y - source.y * scaleY
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
The axes scale independently; the renderer does not choose a contain or cover fit.
|
|
225
|
+
Use a `source` rectangle in the same coordinate space as mask pixels and box edges.
|
|
226
|
+
Canvas state is enclosed by `save()` and `restore()`, including when drawing throws.
|
|
227
|
+
|
|
228
|
+
## Visibility and styling
|
|
229
|
+
|
|
230
|
+
Hide an object without changing retained state by passing its `objectId` in an array
|
|
231
|
+
or `ReadonlySet`:
|
|
232
|
+
|
|
233
|
+
```ts
|
|
234
|
+
renderer.render(context, {
|
|
235
|
+
media: 'image',
|
|
236
|
+
source,
|
|
237
|
+
target,
|
|
238
|
+
hiddenIds: new Set(['background', 'person-2']),
|
|
239
|
+
});
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
A hidden object contributes neither its mask nor its box, and its mask `Path2D` is
|
|
243
|
+
not created. Colors and geometry are intentionally fixed; fill and outline opacity
|
|
244
|
+
are configurable:
|
|
245
|
+
|
|
246
|
+
- Each `objectId` hashes deterministically to one of eight colors.
|
|
247
|
+
- Every mask is traced once into a marching-squares contour: its vertices are the
|
|
248
|
+
midpoints of the edges between neighbouring pixel centers, so a straight
|
|
249
|
+
boundary follows the pixel edge while corners and diagonals are cut at 45°
|
|
250
|
+
instead of stepping. One closed subpath is emitted per region, holes included.
|
|
251
|
+
- Each closed polygon is then decimated and smoothed. Decimation drops every
|
|
252
|
+
vertex the boundary passes straight through, so a straight run — or one 45°
|
|
253
|
+
diagonal — collapses to its two endpoints. The result is emitted as a uniform
|
|
254
|
+
quadratic B-spline: for the polygon `p₀…pₙ₋₁` the subpath is
|
|
255
|
+
`M m₀ Q p₁ m₁ Q p₂ m₂ … Q p₀ m₀ Z`, where `mᵢ` is the midpoint of `pᵢpᵢ₊₁`.
|
|
256
|
+
The curve therefore runs through every edge midpoint and takes each vertex as
|
|
257
|
+
a control point rather than passing through it. A 1-pixel staircase — what a
|
|
258
|
+
mask at native video resolution actually is — becomes a curve within about a
|
|
259
|
+
tenth of a pixel of the line it approximates instead of a visible step; a long
|
|
260
|
+
edge keeps its own tangent at its midpoint, so decimated straight runs stay
|
|
261
|
+
straight; and a genuine right-angle corner, which marching squares has already
|
|
262
|
+
bevelled at 45°, rounds by under half a source pixel. Coordinates are emitted
|
|
263
|
+
with at most one decimal, exact for the half-pixel vertex lattice and within
|
|
264
|
+
0.05 pixels for the quarter-pixel midpoints.
|
|
265
|
+
- A contour of at most four vertices — a lone pixel, a two-pixel sliver — keeps
|
|
266
|
+
its straight segments, so a single pixel still renders as a full half-pixel
|
|
267
|
+
diamond instead of being smoothed inward.
|
|
268
|
+
- That single path is both filled and stroked, so the body and the edge can never
|
|
269
|
+
disagree: filled in the object color with the `evenodd` rule, then stroked in the
|
|
270
|
+
same color with round joins and caps. Fill opacity defaults to `0.35`; contour
|
|
271
|
+
opacity defaults to `0.8`. The path is cached per mask and counts toward the cache
|
|
272
|
+
and `maxPathComplexity` limits, which measure the emitted path string; smoothing
|
|
273
|
+
roughly doubles that string for the same polygon, since a curve carries a control
|
|
274
|
+
point as well as an endpoint.
|
|
275
|
+
- The contour width defaults to `0.003 × min(source.width, source.height)` — about
|
|
276
|
+
2.2 pixels for 720p media — expressed in source pixels, so it scales with the
|
|
277
|
+
source-to-target transform and tracks the media resolution rather than a fixed
|
|
278
|
+
pixel count. Turn the contour off with `maskOutline: false`, or configure its
|
|
279
|
+
source-pixel weight and opacity with `maskOutline: { width: 2, opacity: 0.9 }`.
|
|
280
|
+
- Boxes use the same color at `globalAlpha = 1` and a source-coordinate line width
|
|
281
|
+
of `2 / max(abs(scaleX), abs(scaleY))`.
|
|
282
|
+
- Masks render before boxes.
|
|
283
|
+
|
|
284
|
+
The renderer does not mutate records. Object colors, contour geometry, box styling,
|
|
285
|
+
draw order, and source transforms are not configurable.
|
|
286
|
+
|
|
287
|
+
## Lifecycle and transactional behavior
|
|
288
|
+
|
|
289
|
+
State changes are transactional. Mask validation, decoding, tracing, and aggregate
|
|
290
|
+
limit checks complete before a candidate state commits. If an update rejects, the
|
|
291
|
+
previous retained state and path cache remain available.
|
|
292
|
+
|
|
293
|
+
- `update(result, { reset: true })` ignores retained media and revisions. The old
|
|
294
|
+
state and cache are dropped only after the replacement commits successfully.
|
|
295
|
+
- `clear()` removes committed state and cached `Path2D` objects while keeping the
|
|
296
|
+
renderer reusable. It also fences already queued stale updates before they decode
|
|
297
|
+
or commit.
|
|
298
|
+
- `dispose()` is idempotent, clears state and cache, and permanently fences queued
|
|
299
|
+
work. Later `render()` calls throw `RendererDisposedError`; later `update()` calls
|
|
300
|
+
reject with it. `clear()` after disposal is a no-op.
|
|
301
|
+
|
|
302
|
+
`render()` never waits for a pending update. It draws the previous committed state
|
|
303
|
+
until that update resolves.
|
|
304
|
+
|
|
305
|
+
## Browser requirements
|
|
306
|
+
|
|
307
|
+
Importing the package and calling `update()` do not access `Path2D`. Rendering
|
|
308
|
+
requires a Canvas 2D context and a browser-compatible `globalThis.Path2D` constructor.
|
|
309
|
+
If `Path2D` is absent, `render()` throws `Path2DUnavailableError` before mutating the
|
|
310
|
+
canvas context. Mask `Path2D` objects are created lazily and retained in a bounded
|
|
311
|
+
least-recently-used cache; each render also creates an uncached path for target
|
|
312
|
+
clipping.
|
|
313
|
+
|
|
314
|
+
## Configuration
|
|
315
|
+
|
|
316
|
+
<!-- readme-example -->
|
|
317
|
+
|
|
318
|
+
```ts
|
|
319
|
+
import {
|
|
320
|
+
SegmentationRenderer,
|
|
321
|
+
type SegmentationRendererOptions,
|
|
322
|
+
} from '@meta-sam/graphics';
|
|
323
|
+
|
|
324
|
+
const options: SegmentationRendererOptions = {
|
|
325
|
+
maskFillOpacity: 0.5,
|
|
326
|
+
maskOutline: { width: 2, opacity: 0.9 },
|
|
327
|
+
maxCachedPaths: 128,
|
|
328
|
+
maxCachedComplexity: 250_000,
|
|
329
|
+
maxRecords: 20_000,
|
|
330
|
+
maxMasks: 4_096,
|
|
331
|
+
maxBoxes: 8_192,
|
|
332
|
+
maxMaskArea: 16_777_216,
|
|
333
|
+
maxMaskPayloadLength: 2_000_000,
|
|
334
|
+
maxPathComplexity: 250_000,
|
|
335
|
+
maxRetainedComplexity: 1_000_000,
|
|
336
|
+
};
|
|
337
|
+
const renderer = new SegmentationRenderer(options);
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
| Option | Default |
|
|
341
|
+
| ----------------------- | ------------------------------------------ |
|
|
342
|
+
| `maskFillOpacity` | `0.35` |
|
|
343
|
+
| `maskOutline` | `true` |
|
|
344
|
+
| `maskOutline.width` | `0.003 × min(source.width, source.height)` |
|
|
345
|
+
| `maskOutline.opacity` | `0.8` |
|
|
346
|
+
| `maxCachedPaths` | `128` |
|
|
347
|
+
| `maxCachedComplexity` | `250_000` |
|
|
348
|
+
| `maxRecords` | `20_000` |
|
|
349
|
+
| `maxMasks` | `4_096` |
|
|
350
|
+
| `maxBoxes` | `8_192` |
|
|
351
|
+
| `maxMaskArea` | `16_777_216` |
|
|
352
|
+
| `maxMaskPayloadLength` | `2_000_000` |
|
|
353
|
+
| `maxPathComplexity` | `250_000` |
|
|
354
|
+
| `maxRetainedComplexity` | `1_000_000` |
|
|
355
|
+
|
|
356
|
+
`maskFillOpacity` and `maskOutline.opacity` must be finite numbers in the inclusive
|
|
357
|
+
range from `0` through `1`. An outline with opacity `0` remains enabled and is still
|
|
358
|
+
stroked. `maskOutline` also accepts `true`, `false`, or `{ width }`; width is in source
|
|
359
|
+
pixels and must be finite and greater than zero. Every resource override must be a
|
|
360
|
+
positive safe integer. Invalid constructor options throw `TypeError`. Constructor
|
|
361
|
+
settings are resolved once, so later mutation of an options object has no effect.
|
|
362
|
+
Cache limits evict least-recently-used paths. Other resource limits reject an update
|
|
363
|
+
before it commits.
|
|
364
|
+
|
|
365
|
+
## Public API
|
|
366
|
+
|
|
367
|
+
Only the package root is public; deep imports are not supported.
|
|
368
|
+
|
|
369
|
+
### Runtime exports
|
|
370
|
+
|
|
371
|
+
| Export | Purpose |
|
|
372
|
+
| -------------------------------- | ----------------------------------------------------------- |
|
|
373
|
+
| `SegmentationRenderer` | Retain parser views and render masks and boxes. |
|
|
374
|
+
| `SegmentationGraphicsError` | Base class for package-specific errors. |
|
|
375
|
+
| `UnsupportedMaskEncodingError` | Reject a mask encoding other than `one_bit`. |
|
|
376
|
+
| `InvalidMaskPayloadError` | Reject invalid mask data or a conflicting mask revision. |
|
|
377
|
+
| `SegmentationResourceLimitError` | Reject an update that exceeds a configured resource limit. |
|
|
378
|
+
| `InvalidRenderOptionsError` | Reject invalid result, frame, rectangle, or transform data. |
|
|
379
|
+
| `RendererDisposedError` | Reject operations after permanent disposal. |
|
|
380
|
+
| `Path2DUnavailableError` | Report a missing browser `Path2D` implementation. |
|
|
381
|
+
|
|
382
|
+
### Type exports
|
|
383
|
+
|
|
384
|
+
- `Rectangle`
|
|
385
|
+
- `SegmentationCanvasContext`
|
|
386
|
+
- `MaskOutlineOptions`
|
|
387
|
+
- `SegmentationRendererOptions`
|
|
388
|
+
- `SegmentationUpdateOptions`
|
|
389
|
+
- `ImageRenderOptions`
|
|
390
|
+
- `VideoRenderOptions`
|
|
391
|
+
- `SegmentationRenderOptions`
|
|
392
|
+
- `VideoFrameCompositionContext`
|
|
393
|
+
- `VideoFrameCompositionOptions`
|
|
394
|
+
- `VideoFrameFit`
|
|
395
|
+
|
|
396
|
+
Opacity configuration is declared on the renderer options:
|
|
397
|
+
|
|
398
|
+
```ts
|
|
399
|
+
interface MaskOutlineOptions {
|
|
400
|
+
readonly width?: number;
|
|
401
|
+
/** Contour opacity. Defaults to 0.8. */
|
|
402
|
+
readonly opacity?: number;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
interface SegmentationRendererOptions {
|
|
406
|
+
/** Mask fill opacity. Defaults to 0.35. */
|
|
407
|
+
readonly maskFillOpacity?: number;
|
|
408
|
+
readonly maskOutline?: boolean | MaskOutlineOptions;
|
|
409
|
+
// Resource limit options are unchanged.
|
|
410
|
+
}
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
The primary signatures are:
|
|
414
|
+
|
|
415
|
+
```ts
|
|
416
|
+
class SegmentationRenderer {
|
|
417
|
+
constructor(options?: SegmentationRendererOptions);
|
|
418
|
+
update(
|
|
419
|
+
result: SegmentationResult | SegmentationSnapshot,
|
|
420
|
+
options?: SegmentationUpdateOptions,
|
|
421
|
+
): Promise<void>;
|
|
422
|
+
render(context: SegmentationCanvasContext, options: SegmentationRenderOptions): void;
|
|
423
|
+
renderVideoFrame(
|
|
424
|
+
context: VideoFrameCompositionContext,
|
|
425
|
+
options?: VideoFrameCompositionOptions,
|
|
426
|
+
): boolean;
|
|
427
|
+
clear(): void;
|
|
428
|
+
dispose(): void;
|
|
429
|
+
}
|
|
430
|
+
```
|
|
431
|
+
|
|
432
|
+
`SegmentationResult` and `SegmentationSnapshot` are public types from
|
|
433
|
+
`@meta-sam/parser`.
|
|
434
|
+
|
|
435
|
+
## Errors
|
|
436
|
+
|
|
437
|
+
All package-specific errors extend `SegmentationGraphicsError` and expose a stable
|
|
438
|
+
`code`. Handle expected categories with `instanceof`:
|
|
439
|
+
|
|
440
|
+
```ts
|
|
441
|
+
import { SegmentationGraphicsError, SegmentationRenderer } from '@meta-sam/graphics';
|
|
442
|
+
|
|
443
|
+
const renderer = new SegmentationRenderer();
|
|
444
|
+
|
|
445
|
+
try {
|
|
446
|
+
await renderer.update(result);
|
|
447
|
+
} catch (error) {
|
|
448
|
+
if (error instanceof SegmentationGraphicsError) {
|
|
449
|
+
console.error(error.code, error.message, error.cause);
|
|
450
|
+
} else {
|
|
451
|
+
throw error;
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
```
|
|
455
|
+
|
|
456
|
+
| Error | `code` |
|
|
457
|
+
| -------------------------------- | ------------------------ |
|
|
458
|
+
| `UnsupportedMaskEncodingError` | `unsupported_encoding` |
|
|
459
|
+
| `InvalidMaskPayloadError` | `invalid_mask_payload` |
|
|
460
|
+
| `SegmentationResourceLimitError` | `resource_limit` |
|
|
461
|
+
| `InvalidRenderOptionsError` | `invalid_render_options` |
|
|
462
|
+
| `RendererDisposedError` | `renderer_disposed` |
|
|
463
|
+
| `Path2DUnavailableError` | `path2d_unavailable` |
|
|
464
|
+
|
|
465
|
+
Invalid constructor limits throw ordinary `TypeError`. Update failures reject the
|
|
466
|
+
returned promise; render failures throw synchronously.
|
|
467
|
+
|
|
468
|
+
## Runtime and compatibility
|
|
469
|
+
|
|
470
|
+
- ESM only. Use `import`; there is no CommonJS export.
|
|
471
|
+
- Supported Node.js versions are `^20.17.0 || >=22.9.0`.
|
|
472
|
+
- The package targets ES2022 and declares `@meta-sam/parser` as its only runtime
|
|
473
|
+
dependency.
|
|
474
|
+
- Importing and updating are safe without browser globals. Rendering requires Canvas
|
|
475
|
+
2D and `Path2D`.
|
|
476
|
+
- The package is side-effect-free and publishes only `dist` and this README.
|
|
477
|
+
|
|
478
|
+
## Related packages
|
|
479
|
+
|
|
480
|
+
| Package | Role |
|
|
481
|
+
| -------------------- | ------------------------------------------------------------------------- |
|
|
482
|
+
| `@meta-sam/parser` | Parse structural response events into segmentation snapshots and results. |
|
|
483
|
+
| `@meta-sam/graphics` | Retain mask paths and render Canvas 2D overlays. |
|
|
484
|
+
| `@meta-sam/video` | Decode media into Canvas with packet-exact frame metadata and audio. |
|
|
485
|
+
| `@meta-sam/react` | Provide React bindings over the video and graphics packages. |
|
|
486
|
+
|
|
487
|
+
## License
|
|
488
|
+
|
|
489
|
+
The source is licensed under the SAM License. See `LICENSE` in this package or the
|
|
490
|
+
repository root for the license text.
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export interface TracedContour {
|
|
2
|
+
readonly d: string;
|
|
3
|
+
readonly complexity: number;
|
|
4
|
+
}
|
|
5
|
+
export interface TraceContourOptions {
|
|
6
|
+
/**
|
|
7
|
+
* Replace each closed polygon with a uniform quadratic B-spline through its
|
|
8
|
+
* edge midpoints. Defaults to enabled; pass `false` for the raw polygons.
|
|
9
|
+
*/
|
|
10
|
+
readonly smooth?: boolean;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Traces the contour of a binary raster as closed polygons with marching
|
|
14
|
+
* squares, then smooths each polygon.
|
|
15
|
+
*
|
|
16
|
+
* Contour vertices are the midpoints of the edges between neighbouring pixel
|
|
17
|
+
* centers, so a boundary that runs straight follows the pixel edge exactly
|
|
18
|
+
* while a corner or a diagonal is cut at 45° instead of stepping. Every
|
|
19
|
+
* segment is emitted with the filled region on its right, which makes outer
|
|
20
|
+
* contours wind opposite to the holes they enclose; the caller fills the
|
|
21
|
+
* result with `evenodd` and strokes the same path.
|
|
22
|
+
*
|
|
23
|
+
* The two saddle cases (a filled diagonal pair) are both resolved as a filled
|
|
24
|
+
* center, matching the eight-connected reading of the raster. That choice is
|
|
25
|
+
* what makes every crossing the endpoint of exactly one segment, so chaining
|
|
26
|
+
* the segments is a walk rather than a search.
|
|
27
|
+
*
|
|
28
|
+
* A polygon is then decimated — a vertex the contour passes straight through
|
|
29
|
+
* is dropped, collapsing a straight run to its two endpoints — and emitted as
|
|
30
|
+
* a quadratic B-spline through the edge midpoints, which turns the 1-pixel
|
|
31
|
+
* staircase of a native-resolution mask into a smooth boundary. Contours of at
|
|
32
|
+
* most `SMALL_CONTOUR_VERTICES` vertices keep their straight segments.
|
|
33
|
+
*/
|
|
34
|
+
export declare function traceContour(raster: Uint8Array, width: number, height: number, limit: number, options?: TraceContourOptions): TracedContour;
|