@livemira/target-rating 0.0.0-stage → 0.6.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/CHANGELOG.md +240 -0
- package/LICENSE +201 -0
- package/NOTICE +18 -0
- package/README.md +88 -2
- package/README.zh-CN.md +118 -0
- package/dist/livemira-target-rating-worker.min.js +1 -0
- package/dist/livemira-target-rating.d.ts +105 -0
- package/dist/livemira-target-rating.min.mjs +1 -0
- package/package.json +40 -4
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes are listed here, following [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). Versions
|
|
4
|
+
follow [Semantic Versioning](https://semver.org/).
|
|
5
|
+
|
|
6
|
+
## [Unreleased]
|
|
7
|
+
|
|
8
|
+
## [0.6.0] — 2026-11-06
|
|
9
|
+
|
|
10
|
+
The camera session learns to wait: a hidden page suspends the run instead of ending it, the videos of a run are kept
|
|
11
|
+
for the run, the wait for the camera permission no longer shows a made-up percentage, and the camera is released
|
|
12
|
+
where it used to be left on. The recognition and tracking algorithms are unchanged.
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
|
|
16
|
+
- `suspend()`, `resume()` and `isSuspended` on the engine, for a hidden page: videos pause (no sound), recognition and
|
|
17
|
+
drawing idle, the camera track is switched off (`enabled = false`) and targets in view are reported lost with the new
|
|
18
|
+
reason `'suspended'` (`targetLost`). The camera, the compiled targets and the loaded videos stay. Idempotent; called
|
|
19
|
+
before the camera is open, it applies once the camera is; `stop()` ends it. The recognition Worker switches off the
|
|
20
|
+
track it owns and releases incoming frames unprocessed while suspended (new `suspend` message to the Worker).
|
|
21
|
+
- `media.selectTarget(targetId | null)` on the engine: optional exclusive playback. Until it is first called every
|
|
22
|
+
target in view plays its video, as before; after it only the selected target's video plays or is drawn, the others
|
|
23
|
+
stay paused with their source kept, and `setMuted` and `retry` act on the selected one.
|
|
24
|
+
- `suspend()` and `resume()` on the camera session (`@livemira/ui`), the `isSuspended` state field, and the same two
|
|
25
|
+
optional members on `ArSessionLike`. `suspend()` returns `false` when it cannot (no run, or an engine module without
|
|
26
|
+
`suspend`).
|
|
27
|
+
- A short hint at the top of the camera view, positioned to reduce overlap with the browser's own permission prompt, while the camera is not yet
|
|
28
|
+
open.
|
|
29
|
+
|
|
30
|
+
### Changed
|
|
31
|
+
|
|
32
|
+
- Videos are kept per run. A photo's video is requested the first time the photo is recognised and kept until the run
|
|
33
|
+
ends: losing and finding the photo again, or switching between photos, never requests it twice, and other photos'
|
|
34
|
+
downloads are not interrupted. `stop()` (exit and `dispose()` too, also while a prompt, the preparation or a
|
|
35
|
+
download is pending) aborts every download, releases the videos and ignores late results.
|
|
36
|
+
- One video plays at a time: the session selects the recognised photo with `media.selectTarget`, so videos kept for
|
|
37
|
+
other photos stay paused. Without that engine member the UI falls back to the old behaviour.
|
|
38
|
+
- A hidden page, by default, suspends the run and resumes it when the page is visible again (`pageshow` too). An
|
|
39
|
+
engine module without `suspend` still gets the old behaviour (the run stops). `isStoppedWhenHidden` is now unset by
|
|
40
|
+
default: `true` stops the run as 0.5 did, `false` leaves it alone. **Behaviour change** for hosts that relied on the
|
|
41
|
+
hidden page ending the run without setting the option.
|
|
42
|
+
- `readiness` is the share of photos prepared (0..1) and `null` until the engine is loaded. Waiting for the camera
|
|
43
|
+
permission is no longer part of it and no longer sits at a made-up percentage: the loader card's ring fills only for
|
|
44
|
+
measured work (photos prepared) and turns for every other wait. The percentage text inside the ring is gone.
|
|
45
|
+
- Wording (both languages): the camera hint is "Allow camera access", the video status "Preparing video…" and "Loading
|
|
46
|
+
video N%", and the lost notice "Photo out of view". Hosts that match these texts (tests, `messages` overrides that
|
|
47
|
+
copy them) need to follow.
|
|
48
|
+
- The documentation set describes the target rating as an npm package (`@livemira/target-rating`) next to its offline
|
|
49
|
+
tarball.
|
|
50
|
+
- Release tooling: the integration guide and the release notes template live in `release-src/`, the changelog and
|
|
51
|
+
NOTICE at the repository root, the guides only in `documentation/`; the release builds the target rating into the
|
|
52
|
+
demo's folder too and checks every relative link of the finished release. The demo's sample media are read from the
|
|
53
|
+
0.5.1 release directory (`DEMO_ASSETS` in `tools/bundle.ts`).
|
|
54
|
+
|
|
55
|
+
### Fixed
|
|
56
|
+
|
|
57
|
+
- Camera privacy: a camera granted after the run had already been stopped (stopped while the permission prompt was
|
|
58
|
+
open) is released at once; before, the stream stayed open with nothing left to close it.
|
|
59
|
+
- Camera privacy: a hidden page no longer keeps the camera track live. Suspending switches off every track of the run,
|
|
60
|
+
including the one the recognition Worker owns, and frames that arrive meanwhile are dropped, not processed.
|
|
61
|
+
- A page that is already hidden when the camera view is mounted is suspended too (browsers fire no `visibilitychange`
|
|
62
|
+
for that initial state).
|
|
63
|
+
- A video that failed to load while its photo was out of view is reported when the photo is next found, and one photo's
|
|
64
|
+
video notice no longer shows on another.
|
|
65
|
+
|
|
66
|
+
### Limits
|
|
67
|
+
|
|
68
|
+
- Suspending switches a track off; it does not close the camera. Whether a browser's camera indicator goes off with it
|
|
69
|
+
depends on the browser: call `stop()` to release the camera.
|
|
70
|
+
- The hidden-page and permission-wait behaviour is covered by unit and integration tests and was accepted on phones by
|
|
71
|
+
the publisher. No wider device matrix was run for this release; how browsers treat a disabled camera track differs.
|
|
72
|
+
|
|
73
|
+
## [0.5.1] — 2026-11-04
|
|
74
|
+
|
|
75
|
+
The camera UI learns two things at runtime: a language button and a relocated sound button. The demo is now in
|
|
76
|
+
English by default and starts scanning on open.
|
|
77
|
+
|
|
78
|
+
### Added
|
|
79
|
+
|
|
80
|
+
- A language button in the camera view (top right): switches the interface between English and Simplified Chinese
|
|
81
|
+
live, without restarting the session. It shows the language it switches to ("中" on an English interface, "EN" on
|
|
82
|
+
a Chinese one), and the label carries its own `lang` so screen readers pronounce it right. The root element's
|
|
83
|
+
`lang` follows the active language.
|
|
84
|
+
- `update({ locale })` on the view: the host page can change the language itself (the button does exactly this).
|
|
85
|
+
|
|
86
|
+
### Changed
|
|
87
|
+
|
|
88
|
+
- The sound button moved from the top right to the bottom right (clear of the bottom controls; safe-area aware).
|
|
89
|
+
- The demo starts the camera as soon as the page opens (no start button, no settings), is in English by default
|
|
90
|
+
(`?lang=zh-CN` for Chinese), and its target page shows each image's tracking rating as stars beside the pager
|
|
91
|
+
(half stars included).
|
|
92
|
+
|
|
93
|
+
## [0.5.0] — 2026-10-26
|
|
94
|
+
|
|
95
|
+
The 0.5.0 release: the engine, camera UI and rating of 0.5.0-rc.2 (accepted on phones inside LiveMorae), with the
|
|
96
|
+
rating made a module of its own and the engine reduced to its core.
|
|
97
|
+
|
|
98
|
+
### Added
|
|
99
|
+
|
|
100
|
+
- `@livemira/target-rating` (`files/livemira-target-rating.min.mjs`, with its Worker file and declarations): the
|
|
101
|
+
target rating as a module of its own, for pages that choose pictures without the engine (a work editor).
|
|
102
|
+
`rateTargetImage(blob)` decodes and compiles the picture as the engine's `compileTargetImage` would, in the module's
|
|
103
|
+
own Worker, and rates it; `rateTarget(bundle | bytes)` rates a compiled target. Errors are `RatingError`s
|
|
104
|
+
(`UNSUITABLE_TARGET` for a picture the engine would refuse). Same ratings as rc.2. The release manifest lists the
|
|
105
|
+
engine's and the rating's build ids apart (`modules`): a change to one leaves the other's files as they are.
|
|
106
|
+
|
|
107
|
+
### Removed
|
|
108
|
+
|
|
109
|
+
- `rateTarget` and its types are no longer in the engine (`@livemira/engine`); use `@livemira/target-rating`.
|
|
110
|
+
|
|
111
|
+
### Changed
|
|
112
|
+
|
|
113
|
+
- The engine is 7 KB smaller (123 KB, was 130 KB): besides the rating, device-test diagnostics (frame strips, per-frame traces,
|
|
114
|
+
orientation thumbnails) are compiled out of releases, and the vision Worker no longer estimates a 6-DoF pose every
|
|
115
|
+
frame (nothing used it). Recognition, tracking and presentation are unchanged.
|
|
116
|
+
- The demo's rating page uses the rating module; the engine is not loaded there.
|
|
117
|
+
- The engine and the camera UI are on npm (`@livemira/engine`, `@livemira/ui`). Their packages hold the minified
|
|
118
|
+
modules, the engine's Worker file, the declarations and the guides (links pointing to the release on GitHub), with
|
|
119
|
+
repository metadata; the readable builds stay in the release's `files/`. The target rating is an offline package in
|
|
120
|
+
the release's `npm/` only.
|
|
121
|
+
|
|
122
|
+
## [0.5.0-rc.2] — 2026-10-25
|
|
123
|
+
|
|
124
|
+
Second release candidate of 0.5.0: small photos kept and found again, recognition on the camera's own size by
|
|
125
|
+
default (both accepted on a phone with the ⑩ comparison), and target rating. For the acceptance inside LiveMorae;
|
|
126
|
+
0.5.0 will differ only in its version.
|
|
127
|
+
|
|
128
|
+
### Added
|
|
129
|
+
|
|
130
|
+
- `rateTarget(bundle)`: target rating in stars (0.5–5) from the compiled target and the engine's own detector on
|
|
131
|
+
synthetic views, with four factors (detail, spread, distinctiveness, recognition), five recognition tests (size,
|
|
132
|
+
tilt, motion blur, dim light, partly covered; each a ladder from easy to hard, each view tried twice and counted by
|
|
133
|
+
its inlier margin), advice codes and the counted points. Detail crowded in one place caps the stars. Weights
|
|
134
|
+
calibrated on 28 public pictures (the set 0.4.1's demo pictures come from) and the LiveMorae demo photo against
|
|
135
|
+
their measured difficulty in the bench's scene (bench/src/measure-targets.ts; rank correlation ~0.6). The stars sit
|
|
136
|
+
between a first, lenient version (most photos 5) and a strict one (most 3–4), fitted to their midpoint: those
|
|
137
|
+
pictures average 4 (2 rate 5, 12 rate 4.5, 7 rate 4, 3–4 rate 3.5, 3–4 rate 3 or less); 0.4.1's samples rate 4.5
|
|
138
|
+
(bird), 4.5 (forest), 3–3.5 (mountain), 2.5 (ship), the LiveMorae demo photo 4. Pictures with detail kept in a
|
|
139
|
+
quarter or the centre rate 0.5, in a half 1.5, all but a quarter 3; low-texture 0.5, a repeating tile 1. The demo gains `rate.html`: a picture or a video frame
|
|
140
|
+
(first, last or any), its rating drawn over it with the five tests, what the stars mean, and the ratings of the 0.4.1
|
|
141
|
+
samples next to 0.4.1's own scores and their measured difficulty.
|
|
142
|
+
- The demo's `targets.html` shows one picture per page, as large as the page allows, turned by swiping, the arrows or
|
|
143
|
+
the arrow keys (`#2` opens a page); printed, one picture per sheet.
|
|
144
|
+
- `camera.processingScale` (1, 0.75, 0.5; default 1; in `@livemira/ui` through `engineOptions.camera`): recognition and tracking work on the camera's own size by
|
|
145
|
+
default, no longer on a fixed 640 px long edge (0.5 of a 720p camera). In simulation (a 1280 × 720 camera, the
|
|
146
|
+
LiveMorae demo photo) the reliable range starts at about 25% of the frame's short edge instead of 40%. On an iPhone
|
|
147
|
+
(⑩, 2.5 minutes each) the own size lost the photo 0 times, 0.5 six times, for 3.4–4.6 ms per frame instead of
|
|
148
|
+
1.7–2.7 ms at 30 fps. A quarter size recognised no photo in simulation and is not offered.
|
|
149
|
+
|
|
150
|
+
### Changed
|
|
151
|
+
|
|
152
|
+
- The detector takes features from a whole frame at the density of 700 per 640 × 360 (never fewer than 700), so a
|
|
153
|
+
larger processed picture is not searched more thinly.
|
|
154
|
+
- The luma resample of integer ratios (the camera's size, half, quarter) averages and turns by index steps instead of
|
|
155
|
+
interpolating; RGB frames reuse their gray buffer.
|
|
156
|
+
|
|
157
|
+
### Fixed
|
|
158
|
+
|
|
159
|
+
- Photos small on screen were lost more often and came back slowly (found with the ⑩ comparison on a phone: below
|
|
160
|
+
about half of the frame's short edge). After a short tracking failure (a jerk, a refocus) the engine searched the
|
|
161
|
+
whole frame at its own size, where a photo under about 140 px long edge cannot be recognised. It now searches
|
|
162
|
+
around the photo's last place for up to 2 s (the region growing with time; the whole frame every third frame),
|
|
163
|
+
and enlarges a small photo's region up to 3× before taking its features; the coasting search and the periodic
|
|
164
|
+
relocalisation enlarge it too. In simulation the detector finds 80–120 px photos in 85–100% of frames instead of
|
|
165
|
+
0–25%.
|
|
166
|
+
|
|
167
|
+
## [0.5.0-rc.1] — 2026-10-23
|
|
168
|
+
|
|
169
|
+
Release candidate of 0.5.0: the code of 0.5.0-preview.3 (accepted on a phone with the test page), for the acceptance
|
|
170
|
+
inside LiveMorae. 0.5.0 will differ only in its version.
|
|
171
|
+
|
|
172
|
+
### Changed
|
|
173
|
+
|
|
174
|
+
- The guides and release notes no longer describe a preview; the release manifest marks neither `stable` nor
|
|
175
|
+
`preview`.
|
|
176
|
+
- The upgrade notes also list the version assertion in LiveMorae's `tests/scanTargets.test.ts` and the documents that
|
|
177
|
+
name the current version.
|
|
178
|
+
|
|
179
|
+
## [0.5.0-preview.3] — 2026-10-23
|
|
180
|
+
|
|
181
|
+
### Changed
|
|
182
|
+
|
|
183
|
+
- Videos stream: a video URL goes to the browser's player, which fetches it in ranges and starts as soon as enough has
|
|
184
|
+
arrived (it used to be downloaded whole first). Camera UI: `video.isStreamed`, default true, or false when the host
|
|
185
|
+
gives its own `loadMedia` (media behind header authentication keeps being downloaded whole). A streamed video that
|
|
186
|
+
cannot be fetched is a `video-load` error the host may classify.
|
|
187
|
+
- The loading look follows the video: right on recognition a see-through dark region in the video's shape (the
|
|
188
|
+
photo's until the browser knows the video's size, then easing to it) with the spinner in its centre; the reveal
|
|
189
|
+
opens that region from its centre once the video can play, its wavefront kept inside it.
|
|
190
|
+
- The engine asks a recognised photo's video to play at once (some browsers load a video only when asked to play).
|
|
191
|
+
|
|
192
|
+
### Added
|
|
193
|
+
|
|
194
|
+
- The spinner shows again over a playing video that runs short of data for more than 0.4 s (the browser's `waiting`).
|
|
195
|
+
|
|
196
|
+
## [0.5.0-preview.2] — 2026-10-23
|
|
197
|
+
|
|
198
|
+
### Fixed
|
|
199
|
+
|
|
200
|
+
- The camera UI's default `loadMedia` sends cookies to the page's own origin (never to another origin). With
|
|
201
|
+
`credentials: 'omit'` a page behind a login or an access cookie could not load its own photos and videos, and the
|
|
202
|
+
run ended with "the photos could not be loaded" (found in the first phone test).
|
|
203
|
+
|
|
204
|
+
## [0.5.0-preview.1] — 2026-10-23
|
|
205
|
+
|
|
206
|
+
A preview of the next-generation engine for LiveMorae's internal testing. The engine is new; the camera UI keeps the
|
|
207
|
+
0.4.1 API.
|
|
208
|
+
|
|
209
|
+
### Added
|
|
210
|
+
|
|
211
|
+
- Engine profiles: `'swift'` (default), `'hawk'` and `'wren'`, with fallback by device capability and Hawk stepping
|
|
212
|
+
down to Swift at run time. Devices without WebGL2 and `VideoFrame` are refused with `UNSUPPORTED_DEVICE`.
|
|
213
|
+
- Frame-locked composition: the camera picture and everything drawn on a photo come from the same processed frame in
|
|
214
|
+
one WebGL2 canvas, so videos do not drift against their photos.
|
|
215
|
+
- On the photo: recognition glow, loading veil with a progress ring (`setTargetProgress`), a play mark when playback
|
|
216
|
+
needs a tap, and the centre-out reveal with a light wavefront; perspective-correct and anti-aliased; a 0.2 s fade
|
|
217
|
+
with reduced motion.
|
|
218
|
+
- `setTargets` while running (loaded in the background, `targetsReady`), `probeDevice()`, `parseTarget` for stored
|
|
219
|
+
targets, the Worker file for strict CSPs (`workerUrl`).
|
|
220
|
+
- Camera UI: a loader card with real progress, a scanning guide, recognition vibration (`isHapticEnabled`), download
|
|
221
|
+
progress through `loadMedia`'s `onProgress`, state fields `readiness`, `isCameraOn`, `videoProgress`.
|
|
222
|
+
|
|
223
|
+
### Changed
|
|
224
|
+
|
|
225
|
+
- Recognition and tracking: C1 oriented multi-scale binary features with view synthesis, ESM fast loop, corner-space
|
|
226
|
+
Kalman filter, concurrent relocalisation, WebAssembly SIMD kernels (see the design document's measured results).
|
|
227
|
+
- Photos compile in a pool of up to three short-lived Workers, at the same time as the camera opens.
|
|
228
|
+
- Target format v2 (`livemira-target/2`, SHA-256, descriptor version); 0.4 targets must be compiled again.
|
|
229
|
+
- Engine API: new events (`targetFound`, `targetLost`, `media`, `viewport`, `ended`, …) and error codes; see the
|
|
230
|
+
integration guide.
|
|
231
|
+
|
|
232
|
+
### Removed
|
|
233
|
+
|
|
234
|
+
- 0.4 engine options without a counterpart (`processingLongEdge`, `capture`, `timestamps`, `maxPredictionMs`,
|
|
235
|
+
`adapters`, `extensions`, `diagnostics`) and the optional extensions (tracking visuals, recognition diagnostics,
|
|
236
|
+
target quality).
|
|
237
|
+
|
|
238
|
+
## [0.4.1] — 2026-10-18
|
|
239
|
+
|
|
240
|
+
See the 0.4.1 release.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
Apache License
|
|
2
|
+
Version 2.0, January 2004
|
|
3
|
+
http://www.apache.org/licenses/
|
|
4
|
+
|
|
5
|
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
6
|
+
|
|
7
|
+
1. Definitions.
|
|
8
|
+
|
|
9
|
+
"License" shall mean the terms and conditions for use, reproduction,
|
|
10
|
+
and distribution as defined by Sections 1 through 9 of this document.
|
|
11
|
+
|
|
12
|
+
"Licensor" shall mean the copyright owner or entity authorized by
|
|
13
|
+
the copyright owner that is granting the License.
|
|
14
|
+
|
|
15
|
+
"Legal Entity" shall mean the union of the acting entity and all
|
|
16
|
+
other entities that control, are controlled by, or are under common
|
|
17
|
+
control with that entity. For the purposes of this definition,
|
|
18
|
+
"control" means (i) the power, direct or indirect, to cause the
|
|
19
|
+
direction or management of such entity, whether by contract or
|
|
20
|
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
21
|
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
22
|
+
|
|
23
|
+
"You" (or "Your") shall mean an individual or Legal Entity
|
|
24
|
+
exercising permissions granted by this License.
|
|
25
|
+
|
|
26
|
+
"Source" form shall mean the preferred form for making modifications,
|
|
27
|
+
including but not limited to software source code, documentation
|
|
28
|
+
source, and configuration files.
|
|
29
|
+
|
|
30
|
+
"Object" form shall mean any form resulting from mechanical
|
|
31
|
+
transformation or translation of a Source form, including but
|
|
32
|
+
not limited to compiled object code, generated documentation,
|
|
33
|
+
and conversions to other media types.
|
|
34
|
+
|
|
35
|
+
"Work" shall mean the work of authorship, whether in Source or
|
|
36
|
+
Object form, made available under the License, as indicated by a
|
|
37
|
+
copyright notice that is included in or attached to the work
|
|
38
|
+
(an example is provided in the Appendix below).
|
|
39
|
+
|
|
40
|
+
"Derivative Works" shall mean any work, whether in Source or Object
|
|
41
|
+
form, that is based on (or derived from) the Work and for which the
|
|
42
|
+
editorial revisions, annotations, elaborations, or other modifications
|
|
43
|
+
represent, as a whole, an original work of authorship. For the purposes
|
|
44
|
+
of this License, Derivative Works shall not include works that remain
|
|
45
|
+
separable from, or merely link (or bind by name) to the interfaces of,
|
|
46
|
+
the Work and Derivative Works thereof.
|
|
47
|
+
|
|
48
|
+
"Contribution" shall mean any work of authorship, including
|
|
49
|
+
the original version of the Work and any modifications or additions
|
|
50
|
+
to that Work or Derivative Works thereof, that is intentionally
|
|
51
|
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
52
|
+
or by an individual or Legal Entity authorized to submit on behalf of
|
|
53
|
+
the copyright owner. For the purposes of this definition, "submitted"
|
|
54
|
+
means any form of electronic, verbal, or written communication sent
|
|
55
|
+
to the Licensor or its representatives, including but not limited to
|
|
56
|
+
communication on electronic mailing lists, source code control systems,
|
|
57
|
+
and issue tracking systems that are managed by, or on behalf of, the
|
|
58
|
+
Licensor for the purpose of discussing and improving the Work, but
|
|
59
|
+
excluding communication that is conspicuously marked or otherwise
|
|
60
|
+
designated in writing by the copyright owner as "Not a Contribution."
|
|
61
|
+
|
|
62
|
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
63
|
+
on behalf of whom a Contribution has been received by Licensor and
|
|
64
|
+
subsequently incorporated within the Work.
|
|
65
|
+
|
|
66
|
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
67
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
68
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
69
|
+
copyright license to reproduce, prepare Derivative Works of,
|
|
70
|
+
publicly display, publicly perform, sublicense, and distribute the
|
|
71
|
+
Work and such Derivative Works in Source or Object form.
|
|
72
|
+
|
|
73
|
+
3. Grant of Patent License. Subject to the terms and conditions of
|
|
74
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
75
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
76
|
+
(except as stated in this section) patent license to make, have made,
|
|
77
|
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
78
|
+
where such license applies only to those patent claims licensable
|
|
79
|
+
by such Contributor that are necessarily infringed by their
|
|
80
|
+
Contribution(s) alone or by combination of their Contribution(s)
|
|
81
|
+
with the Work to which such Contribution(s) was submitted. If You
|
|
82
|
+
institute patent litigation against any entity (including a
|
|
83
|
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
84
|
+
or a Contribution incorporated within the Work constitutes direct
|
|
85
|
+
or contributory patent infringement, then any patent licenses
|
|
86
|
+
granted to You under this License for that Work shall terminate
|
|
87
|
+
as of the date such litigation is filed.
|
|
88
|
+
|
|
89
|
+
4. Redistribution. You may reproduce and distribute copies of the
|
|
90
|
+
Work or Derivative Works thereof in any medium, with or without
|
|
91
|
+
modifications, and in Source or Object form, provided that You
|
|
92
|
+
meet the following conditions:
|
|
93
|
+
|
|
94
|
+
(a) You must give any other recipients of the Work or
|
|
95
|
+
Derivative Works a copy of this License; and
|
|
96
|
+
|
|
97
|
+
(b) You must cause any modified files to carry prominent notices
|
|
98
|
+
stating that You changed the files; and
|
|
99
|
+
|
|
100
|
+
(c) You must retain, in the Source form of any Derivative Works
|
|
101
|
+
that You distribute, all copyright, patent, trademark, and
|
|
102
|
+
attribution notices from the Source form of the Work,
|
|
103
|
+
excluding those notices that do not pertain to any part of
|
|
104
|
+
the Derivative Works; and
|
|
105
|
+
|
|
106
|
+
(d) If the Work includes a "NOTICE" text file as part of its
|
|
107
|
+
distribution, then any Derivative Works that You distribute must
|
|
108
|
+
include a readable copy of the attribution notices contained
|
|
109
|
+
within such NOTICE file, excluding those notices that do not
|
|
110
|
+
pertain to any part of the Derivative Works, in at least one
|
|
111
|
+
of the following places: within a NOTICE text file distributed
|
|
112
|
+
as part of the Derivative Works; within the Source form or
|
|
113
|
+
documentation, if provided along with the Derivative Works; or,
|
|
114
|
+
within a display generated by the Derivative Works, if and
|
|
115
|
+
wherever such third-party notices normally appear. The contents
|
|
116
|
+
of the NOTICE file are for informational purposes only and
|
|
117
|
+
do not modify the License. You may add Your own attribution
|
|
118
|
+
notices within Derivative Works that You distribute, alongside
|
|
119
|
+
or as an addendum to the NOTICE text from the Work, provided
|
|
120
|
+
that such additional attribution notices cannot be construed
|
|
121
|
+
as modifying the License.
|
|
122
|
+
|
|
123
|
+
You may add Your own copyright statement to Your modifications and
|
|
124
|
+
may provide additional or different license terms and conditions
|
|
125
|
+
for use, reproduction, or distribution of Your modifications, or
|
|
126
|
+
for any such Derivative Works as a whole, provided Your use,
|
|
127
|
+
reproduction, and distribution of the Work otherwise complies with
|
|
128
|
+
the conditions stated in this License.
|
|
129
|
+
|
|
130
|
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
131
|
+
any Contribution intentionally submitted for inclusion in the Work
|
|
132
|
+
by You to the Licensor shall be under the terms and conditions of
|
|
133
|
+
this License, without any additional terms or conditions.
|
|
134
|
+
Notwithstanding the above, nothing herein shall supersede or modify
|
|
135
|
+
the terms of any separate license agreement you may have executed
|
|
136
|
+
with Licensor regarding such Contributions.
|
|
137
|
+
|
|
138
|
+
6. Trademarks. This License does not grant permission to use the trade
|
|
139
|
+
names, trademarks, service marks, or product names of the Licensor,
|
|
140
|
+
except as required for reasonable and customary use in describing the
|
|
141
|
+
origin of the Work and reproducing the content of the NOTICE file.
|
|
142
|
+
|
|
143
|
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
144
|
+
agreed to in writing, Licensor provides the Work (and each
|
|
145
|
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
146
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
147
|
+
implied, including, without limitation, any warranties or conditions
|
|
148
|
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
149
|
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
150
|
+
appropriateness of using or redistributing the Work and assume any
|
|
151
|
+
risks associated with Your exercise of permissions under this License.
|
|
152
|
+
|
|
153
|
+
8. Limitation of Liability. In no event and under no legal theory,
|
|
154
|
+
whether in tort (including negligence), contract, or otherwise,
|
|
155
|
+
unless required by applicable law (such as deliberate and grossly
|
|
156
|
+
negligent acts) or agreed to in writing, shall any Contributor be
|
|
157
|
+
liable to You for damages, including any direct, indirect, special,
|
|
158
|
+
incidental, or consequential damages of any character arising as a
|
|
159
|
+
result of this License or out of the use or inability to use the
|
|
160
|
+
Work (including but not limited to damages for loss of goodwill,
|
|
161
|
+
work stoppage, computer failure or malfunction, or any and all
|
|
162
|
+
other commercial damages or losses), even if such Contributor
|
|
163
|
+
has been advised of the possibility of such damages.
|
|
164
|
+
|
|
165
|
+
9. Accepting Warranty or Additional Liability. While redistributing
|
|
166
|
+
the Work or Derivative Works thereof, You may choose to offer,
|
|
167
|
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
168
|
+
or other liability obligations and/or rights consistent with this
|
|
169
|
+
License. However, in accepting such obligations, You may act only
|
|
170
|
+
on Your own behalf and on Your sole responsibility, not on behalf
|
|
171
|
+
of any other Contributor, and only if You agree to indemnify,
|
|
172
|
+
defend, and hold each Contributor harmless for any liability
|
|
173
|
+
incurred by, or claims asserted against, such Contributor by reason
|
|
174
|
+
of your accepting any such warranty or additional liability.
|
|
175
|
+
|
|
176
|
+
END OF TERMS AND CONDITIONS
|
|
177
|
+
|
|
178
|
+
APPENDIX: How to apply the Apache License to your work.
|
|
179
|
+
|
|
180
|
+
To apply the Apache License to your work, attach the following
|
|
181
|
+
boilerplate notice, with the fields enclosed by brackets "[]"
|
|
182
|
+
replaced with your own identifying information. (Don't include
|
|
183
|
+
the brackets!) The text should be enclosed in the appropriate
|
|
184
|
+
comment syntax for the file format. We also recommend that a
|
|
185
|
+
file or class name and description of purpose be included on the
|
|
186
|
+
same "printed page" as the copyright notice for easier
|
|
187
|
+
identification within third-party archives.
|
|
188
|
+
|
|
189
|
+
Copyright 2026 Andy (@andy-mira)
|
|
190
|
+
|
|
191
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
192
|
+
you may not use this file except in compliance with the License.
|
|
193
|
+
You may obtain a copy of the License at
|
|
194
|
+
|
|
195
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
196
|
+
|
|
197
|
+
Unless required by applicable law or agreed to in writing, software
|
|
198
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
199
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
200
|
+
See the License for the specific language governing permissions and
|
|
201
|
+
limitations under the License.
|
package/NOTICE
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
LiveMira
|
|
2
|
+
Copyright 2026 Andy
|
|
3
|
+
|
|
4
|
+
This product includes software developed by Andy.
|
|
5
|
+
|
|
6
|
+
All LiveMira source code, including the Rust sources of the WebAssembly SIMD
|
|
7
|
+
kernels (kernels/vision) and the modules generated from them, build tools and
|
|
8
|
+
documentation, is licensed under the Apache License, Version 2.0. See the
|
|
9
|
+
LICENSE file.
|
|
10
|
+
|
|
11
|
+
The WebAssembly module is compiled from those sources with the Rust toolchain,
|
|
12
|
+
a development tool that is not redistributed. No third-party code is bundled
|
|
13
|
+
into the engine or the camera UI.
|
|
14
|
+
|
|
15
|
+
The sample photographs of the demo (demo/assets) are public-domain (CC0 1.0)
|
|
16
|
+
works by their authors; see demo/assets/CREDITS.md. They are not covered by the
|
|
17
|
+
Apache License of this project. The demo video is an original work of this
|
|
18
|
+
project (Apache-2.0).
|
package/README.md
CHANGED
|
@@ -1,3 +1,89 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @livemira/target-rating — target rating
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[English](https://github.com/andy-mira/livemira/blob/v0.6.0/release/0.6.0/documentation/target-rating.md) | [简体中文](https://github.com/andy-mira/livemira/blob/v0.6.0/release/0.6.0/documentation/target-rating.zh-CN.md)
|
|
4
|
+
|
|
5
|
+
> **npm:** `npm install @livemira/target-rating`. The files listed below as `files/livemira-target-rating*` are under `dist/` here (`import { rateTargetImage } from '@livemira/target-rating'`; the Worker file is `@livemira/target-rating/worker`). It needs no engine. The same package is in the [release](https://github.com/andy-mira/livemira/tree/v0.6.0/release/0.6.0) as an offline tarball.
|
|
6
|
+
|
|
7
|
+
Rates a picture for scanning (0.5–5 stars), so a page where the user picks the target picture (an uploaded photo, or
|
|
8
|
+
the first, last or any frame of a video) can suggest a better one.
|
|
9
|
+
|
|
10
|
+
The rating is a separate module and **needs no engine**: a page that never opens the camera, such as a work editor,
|
|
11
|
+
loads this one file. In its own Worker it decodes, scales and compiles the picture as the engine's
|
|
12
|
+
`compileTargetImage` does, then runs the engine's own detector on synthetic views, so the rating is of exactly the
|
|
13
|
+
target the engine would track.
|
|
14
|
+
|
|
15
|
+
## Files
|
|
16
|
+
|
|
17
|
+
| File | Purpose |
|
|
18
|
+
| -------------------------------------------- | ----------------------------------------------------------------------------- |
|
|
19
|
+
| `files/livemira-target-rating.min.mjs` | One ES module, Worker embedded (about 47 KB). |
|
|
20
|
+
| `files/livemira-target-rating.mjs` | The same module, readable. |
|
|
21
|
+
| `files/livemira-target-rating-worker.min.js` | The rating Worker script (classic), for pages whose CSP forbids `blob:`. |
|
|
22
|
+
| `files/livemira-target-rating.d.ts` | Declarations. |
|
|
23
|
+
| `npm/livemira-target-rating-*.tgz` | The npm package `@livemira/target-rating` as a tarball, for offline installs. |
|
|
24
|
+
|
|
25
|
+
Importing the module creates nothing; the first rating starts its Worker, which closes after 4 s idle. One picture is
|
|
26
|
+
rated at a time; the rest wait in order.
|
|
27
|
+
|
|
28
|
+
## Use
|
|
29
|
+
|
|
30
|
+
```js
|
|
31
|
+
import { rateTargetImage, RatingError } from './livemira-target-rating.min.mjs';
|
|
32
|
+
|
|
33
|
+
try {
|
|
34
|
+
const rating = await rateTargetImage(photoFile, { signal });
|
|
35
|
+
showStars(rating.stars); // 0.5…5 in halves
|
|
36
|
+
if (rating.stars < 3) suggestAnotherFrame(rating.advice);
|
|
37
|
+
} catch (error) {
|
|
38
|
+
if (error instanceof RatingError && error.code === 'UNSUITABLE_TARGET')
|
|
39
|
+
showCannotUse(); // the engine would refuse it
|
|
40
|
+
else if (error?.code !== 'ABORTED') throw error;
|
|
41
|
+
}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
- `rateTargetImage(image, { signal?, workerUrl?, maxEdgePx = 640 })`: a picture file (JPEG, PNG, WebP, …; its own
|
|
45
|
+
orientation applied). Keep `maxEdgePx` equal to the engine's `compileTargetImage`.
|
|
46
|
+
- `rateTarget(target, { signal?, workerUrl? })`: a compiled target, the engine's bundle (`compileTargetImage`,
|
|
47
|
+
`parseTarget`) or its `bytes`.
|
|
48
|
+
- Abort the previous rating with `signal` when the picture changes. About 0.5 s per picture on a desktop, 1–2 s on a
|
|
49
|
+
phone.
|
|
50
|
+
- `RatingError.code`: `UNSUITABLE_TARGET` (too small, or under 48 recognition points: the engine would refuse it),
|
|
51
|
+
`DECODE_FAILED`, `INVALID_TARGET`, `INVALID_INPUT`, `WORKER_FAILED`, `ABORTED`.
|
|
52
|
+
|
|
53
|
+
## Result
|
|
54
|
+
|
|
55
|
+
`{ stars, score, factors: { detail, spread, distinctiveness, recognition }, metrics, advice, points, width, height }`.
|
|
56
|
+
5 stars: near the best on every count; 4–4.5 good; 3–3.5 usable (steady when frontal and near, loses the picture when
|
|
57
|
+
small, tilted or shaken); 2–2.5 better replaced; below, not recommended. Suggest another picture below 3 stars.
|
|
58
|
+
|
|
59
|
+
Factors (0..1): `detail` (points found in at least 5 of the compiler's 14 views), `spread` (those points over the
|
|
60
|
+
picture: grid cells, evenness, the weakest quarter), `distinctiveness` (no look-alikes far apart), `recognition` (the
|
|
61
|
+
detector on synthetic views, five tests from easy to hard: smaller, tilted, motion-blurred, dimmer, partly covered;
|
|
62
|
+
each view tried twice and counted by its margin; per test in `metrics.tests`). Score = 100 × (0.15 detail + 0.4 spread
|
|
63
|
+
|
|
64
|
+
- 0.05 distinctiveness + 0.4 recognition); stars = 0.5 + 4.5 × the score's place between 15 and 95, capped at
|
|
65
|
+
1 + 4 × min(spread, recognition)^0.4: detail crowded in one place, or hardly found in the views, cannot rate high.
|
|
66
|
+
|
|
67
|
+
`advice`, most important first: `HARD_TO_FIND`, `CONCENTRATED`, `WEAK_QUARTER` (`metrics.weakestQuarter`),
|
|
68
|
+
`FEW_DETAILS`, `REPETITIVE`, and `FAILS_SMALL`, `FAILS_TILTED`, `FAILS_BLURRED`, `FAILS_DIM`, `FAILS_COVERED` (that
|
|
69
|
+
test clearly weaker than for most photos). `points` are the counted points in target pixels (`width` × `height`), for
|
|
70
|
+
drawing the spread over the picture.
|
|
71
|
+
|
|
72
|
+
Calibrated on 28 public pictures (the set 0.4.1's samples come from) and the LiveMorae demo photo against their
|
|
73
|
+
measured difficulty in simulation (rank correlation ~0.6); the stars were tuned in two rounds of use on phones,
|
|
74
|
+
between a lenient and a strict version (those pictures average 4). The demo's `rate.html` is a complete rating page.
|
|
75
|
+
|
|
76
|
+
## CSP and browsers
|
|
77
|
+
|
|
78
|
+
The Worker starts from a `blob:` URL: allow `worker-src blob:`, or serve `livemira-target-rating-worker.min.js` and
|
|
79
|
+
pass `workerUrl`. No WebAssembly, no network requests. Needs Workers and `createImageBitmap`; pictures are decoded in
|
|
80
|
+
the Worker with `OffscreenCanvas` 2D, else on the page.
|
|
81
|
+
|
|
82
|
+
## Version
|
|
83
|
+
|
|
84
|
+
Built apart from the engine (its own build id; `release-manifest.json` lists both under `modules`): a change to the
|
|
85
|
+
rating leaves the engine's files as they are. `RATING_VERSION` and `ratingBuildInfo` give the version and build id.
|
|
86
|
+
|
|
87
|
+
## License
|
|
88
|
+
|
|
89
|
+
Apache-2.0. See [LICENSE](https://github.com/andy-mira/livemira/blob/v0.6.0/release/0.6.0/LICENSE) and [NOTICE](https://github.com/andy-mira/livemira/blob/v0.6.0/release/0.6.0/NOTICE).
|
package/README.zh-CN.md
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# @livemira/target-rating —— 目标图评分
|
|
2
|
+
|
|
3
|
+
[English](https://github.com/andy-mira/livemira/blob/v0.6.0/release/0.6.0/documentation/target-rating.md) | [简体中文](https://github.com/andy-mira/livemira/blob/v0.6.0/release/0.6.0/documentation/target-rating.zh-CN.md)
|
|
4
|
+
|
|
5
|
+
> **npm:** `npm install @livemira/target-rating`。下文中 `files/livemira-target-rating*` 在本包的 `dist/` 下(`import { rateTargetImage } from '@livemira/target-rating'`,Worker 文件为 `@livemira/target-rating/worker`)。不需要引擎。同一个包也以离线 tarball 形式在[发布包](https://github.com/andy-mira/livemira/tree/v0.6.0/release/0.6.0)中。
|
|
6
|
+
|
|
7
|
+
给扫描用的照片打分(0.5–5 星),在用户选目标图时(上传的图片,或视频的首帧、尾帧、任意一帧)提示他换一张更好的。
|
|
8
|
+
|
|
9
|
+
评分是独立模块,**不需要引擎**:作品编辑页这类页面不打开相机,只加载这一个文件即可。它在自己的 Worker 里按引擎 `compileTargetImage` 的方式解码、缩放、编译图片,再用引擎自己的检测器在合成画面上实测,所以分数针对的正是引擎将要跟踪的那份目标。
|
|
10
|
+
|
|
11
|
+
## 文件
|
|
12
|
+
|
|
13
|
+
| 文件 | 用途 |
|
|
14
|
+
| -------------------------------------------- | ------------------------------------------------------------------------------------------------------------- |
|
|
15
|
+
| `files/livemira-target-rating.min.mjs` | 单个 ES 模块,Worker 已内嵌(约 47 KB)。 |
|
|
16
|
+
| `files/livemira-target-rating.mjs` | 同一模块的可读版本。 |
|
|
17
|
+
| `files/livemira-target-rating-worker.min.js` | 评分的 Worker 脚本(经典脚本),供 CSP 禁止 `blob:` Worker 的页面使用。 |
|
|
18
|
+
| `files/livemira-target-rating.d.ts` | 类型声明。 |
|
|
19
|
+
| `npm/livemira-target-rating-*.tgz` | npm 包 `@livemira/target-rating` 的 tarball,用于离线安装(也可直接 `npm install @livemira/target-rating`)。 |
|
|
20
|
+
|
|
21
|
+
导入模块不会创建任何东西;第一次评分时才启动 Worker,空闲 4 秒后关闭。同一时间只评一张,其余排队。
|
|
22
|
+
|
|
23
|
+
## 用法
|
|
24
|
+
|
|
25
|
+
```js
|
|
26
|
+
import { rateTargetImage, RatingError } from './livemira-target-rating.min.mjs';
|
|
27
|
+
|
|
28
|
+
const controller = new AbortController();
|
|
29
|
+
try {
|
|
30
|
+
const rating = await rateTargetImage(photoFile, { signal: controller.signal });
|
|
31
|
+
showStars(rating.stars); // 0.5…5,半星
|
|
32
|
+
if (rating.stars < 3) suggestAnotherFrame(rating.advice);
|
|
33
|
+
} catch (error) {
|
|
34
|
+
if (error instanceof RatingError && error.code === 'UNSUITABLE_TARGET')
|
|
35
|
+
showCannotUse(); // 引擎不会接受这张图
|
|
36
|
+
else if (error?.code !== 'ABORTED') throw error;
|
|
37
|
+
}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
- `rateTargetImage(image, { signal?, workerUrl?, maxEdgePx = 640 })`:图片文件(JPEG、PNG、WebP…,按图片自身的方向)。`maxEdgePx` 要与引擎 `compileTargetImage` 用的一致。
|
|
41
|
+
- `rateTarget(target, { signal?, workerUrl? })`:已编译的目标——引擎 `compileTargetImage`、`parseTarget` 得到的 bundle,或其 `bytes`。
|
|
42
|
+
- 换图时用 `signal` 取消上一次评分,过时的结果不会回来。
|
|
43
|
+
- 桌面每张约 0.5 秒,手机约 1–2 秒。
|
|
44
|
+
|
|
45
|
+
### 错误
|
|
46
|
+
|
|
47
|
+
`RatingError` 有 `code` 与 `message`:
|
|
48
|
+
|
|
49
|
+
| `code` | 含义 |
|
|
50
|
+
| ------------------- | ---------------------------------------------------------- |
|
|
51
|
+
| `UNSUITABLE_TARGET` | 引擎不会接受这张图:太小,或细节太少(识别点不足 48 个)。 |
|
|
52
|
+
| `DECODE_FAILED` | 图片无法解码,或文件为空。 |
|
|
53
|
+
| `INVALID_TARGET` | 目标数据损坏(`rateTarget`)。 |
|
|
54
|
+
| `INVALID_INPUT` | 不是图片文件或目标,或选项超出范围。 |
|
|
55
|
+
| `WORKER_FAILED` | Worker 无法启动(多见于 CSP 禁止 `blob:`)或运行出错。 |
|
|
56
|
+
| `ABORTED` | 通过 `signal` 取消。 |
|
|
57
|
+
|
|
58
|
+
## 结果
|
|
59
|
+
|
|
60
|
+
```ts
|
|
61
|
+
{ stars, score, factors: { detail, spread, distinctiveness, recognition }, metrics, advice, points, width, height }
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
| 星级 | 含义 |
|
|
65
|
+
| ------- | ------------------------------------------------------ |
|
|
66
|
+
| 5 | 各方面都接近最好:小、斜、晃、暗、被挡时也多能认出 |
|
|
67
|
+
| 4–4.5 | 好,适合作为目标图 |
|
|
68
|
+
| 3–3.5 | 可用:正对、距离适中时稳定,偏小、斜拍或晃动时容易丢失 |
|
|
69
|
+
| 2–2.5 | 勉强,建议换图或换帧 |
|
|
70
|
+
| 0.5–1.5 | 不建议 |
|
|
71
|
+
|
|
72
|
+
建议:低于 3 星时提示换一张;3–3.5 星可以接受,但提示"扫描时正对、靠近"。
|
|
73
|
+
|
|
74
|
+
四项各 0..1:
|
|
75
|
+
|
|
76
|
+
| 项目 | 含义 |
|
|
77
|
+
| ------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
78
|
+
| `detail` 细节 | 编译器 14 个模拟视角中,至少 5 个都能检出的"稳定点"的数量(40 个以下为 0,250 个以上为满分) |
|
|
79
|
+
| `spread` 分布 | 稳定点在画面网格(约 16 格,随长宽比)里铺开的程度:有足够点的格子占比、各格均匀度,再乘上最弱一角的系数(只要有一角几乎没有点,最多扣 40%) |
|
|
80
|
+
| `distinctiveness` 独特性 | 稳定点中,在远处找不到相似描述子的比例;同一角点在旁边几个像素处的近亲不算 |
|
|
81
|
+
| `recognition` 实测识别 | 用引擎自己的检测器认 640 × 360 的合成画面,五项测试各由易到难:缩小(长边 160/120/90/70 px)、斜拍(45°/60°/70°)、晃动模糊(6/10/16 px)、暗光(对比降到 35%/20% 并加噪)、部分遮挡(遮住任一角或任一半);每个画面换两种握持各试一次,按内点数计余量(刚好认出只算一部分)。各项结果见 `metrics.tests` |
|
|
82
|
+
|
|
83
|
+
分数 = 100 × (0.15 细节 + 0.4 分布 + 0.05 独特性 + 0.4 实测识别);星级 = 0.5 + 4.5 × (分数从 15 到 95 线性对应),并封顶于 1 + 4 × min(分布, 实测识别)^0.4,按半星取整。所以细节再多,只要挤在一处,或在合成画面里几乎认不出,也给不了高分。
|
|
84
|
+
|
|
85
|
+
`advice` 按重要性列出原因:
|
|
86
|
+
|
|
87
|
+
| 代码 | 含义 |
|
|
88
|
+
| ---------------------------------------------------------------------------- | ---------------------------------------------------------- |
|
|
89
|
+
| `HARD_TO_FIND` | 合成画面中多数认不出或很勉强 |
|
|
90
|
+
| `CONCENTRATED` | 细节集中在画面的一部分 |
|
|
91
|
+
| `WEAK_QUARTER` | 某一角几乎没有细节(`metrics.weakestQuarter`) |
|
|
92
|
+
| `FEW_DETAILS` | 能从不同角度稳定认出的细节点偏少 |
|
|
93
|
+
| `REPETITIVE` | 相似图案多(格子、砖墙、条纹等),容易认错位置 |
|
|
94
|
+
| `FAILS_SMALL`、`FAILS_TILTED`、`FAILS_BLURRED`、`FAILS_DIM`、`FAILS_COVERED` | 缩小、斜拍、晃动模糊、暗光、部分遮挡这一项明显差于一般照片 |
|
|
95
|
+
|
|
96
|
+
中文提示文字可参考示例 `demo/rate.js` 里的 `ADVICE`。`points` 是计入细节的点(目标像素坐标,`width` × `height`),可画在图上说明分布;`metrics.grid` 是各格的点数。
|
|
97
|
+
|
|
98
|
+
## 校准
|
|
99
|
+
|
|
100
|
+
权重与阈值按 28 张公开测试图(0.4.1 示例图所在的整套图,CC0)和 LiveMorae 演示照片校准:以仿真场景中引擎在较难条件下的检测率与手持跟踪的实测为准,评分与实测的等级相关约 0.6。星级的松紧经两轮真机体验调整,取在偏松与偏严两版的中间:这 28 张平均 4 星,其中 5 星 2 张、4.5 星 12 张、4 星 7 张、3.5 星 3–4 张、3 星及以下 3–4 张;0.4.1 的四张示例为鸟屋 4.5、森林 4.5、山脉 3–3.5、船 2.5,LiveMorae 演示照片 4。浏览器与 Node 的图片解码略有差别,个别图可能差半星。
|
|
101
|
+
|
|
102
|
+
示例里的 `rate.html` 是一个完整的评分页面:上传图片或视频(选首帧、尾帧、任意帧),显示星级、四项与五项识别明细、原因,以及稳定点与网格密度叠加图。
|
|
103
|
+
|
|
104
|
+
## 内容安全策略(CSP)
|
|
105
|
+
|
|
106
|
+
评分模块用 `blob:` 地址启动 Worker:请允许 `worker-src blob:`。不允许时,部署 `livemira-target-rating-worker.min.js` 并传 `workerUrl`。评分不需要 WebAssembly,也不发出任何网络请求。
|
|
107
|
+
|
|
108
|
+
## 浏览器要求
|
|
109
|
+
|
|
110
|
+
Worker、`createImageBitmap`;Worker 中的 `OffscreenCanvas` 2D 可用时在 Worker 内解码,否则在页面上解码后把灰度像素交给 Worker。
|
|
111
|
+
|
|
112
|
+
## 版本
|
|
113
|
+
|
|
114
|
+
评分模块与引擎分开构建(各自的构建 ID,见 `release-manifest.json` 的 `modules`):评分的改动不会改变引擎文件。`RATING_VERSION`、`ratingBuildInfo` 给出版本与构建 ID。
|
|
115
|
+
|
|
116
|
+
## 许可
|
|
117
|
+
|
|
118
|
+
Apache-2.0。见 [LICENSE](https://github.com/andy-mira/livemira/blob/v0.6.0/release/0.6.0/LICENSE) 与 [NOTICE](https://github.com/andy-mira/livemira/blob/v0.6.0/release/0.6.0/NOTICE)。
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";(()=>{var le=Object.defineProperty;var Ae=(e,t,r)=>t in e?le(e,t,{enumerable:!0,configurable:!0,writable:!0,value:r}):e[t]=r;var F=(e,t,r)=>Ae(e,typeof t!="symbol"?t+"":t,r);var X=class{constructor(){F(this,"rows",new Int32Array(0));F(this,"levels",[])}build(t,r,n=24){let o=[{...t,scale:1}];for(let l=1;l<r;l++){let c=o[l-1],s=c.width>>1,a=c.height>>1;if(s<n||a<n)break;this.rows.length<c.width*a&&(this.rows=new Int32Array(c.width*a));let i=this.levels[l]=this.levels[l]&&this.levels[l].length===s*a?this.levels[l]:new Uint8Array(s*a);me(c,s,a,this.rows,i),o.push({width:s,height:a,stride:s,data:i,scale:c.scale*2})}return{levels:o}}};function me(e,t,r,n,o){let l=e.width,c=e.height,s=e.data,a=e.stride;for(let i=0;i<r;i++){let A=2*i,u=Math.max(0,A-1)*a,m=A*a,h=Math.min(c-1,A+1)*a,g=Math.min(c-1,A+2)*a,y=i*l;for(let f=0;f<l;f++)n[y+f]=s[u+f]+3*s[m+f]+3*s[h+f]+s[g+f]}for(let i=0;i<r;i++){let A=i*l;for(let u=0;u<t;u++){let m=2*u,h=n[A+Math.max(0,m-1)],g=n[A+m],y=n[A+Math.min(l-1,m+1)],f=n[A+Math.min(l-1,m+2)];o[i*t+u]=h+3*g+3*y+f+32>>6}}}function Ct(e,t,r=24){return new X().build(e,t,r)}function tt(e,t,r){let n=t-.5,o=r-.5,l=Math.floor(n),c=Math.floor(o);if(l<0||c<0||l+1>=e.width||c+1>=e.height)return NaN;let s=n-l,a=o-c,i=e.data,A=c*e.stride+l,u=i[A]+s*(i[A+1]-i[A]),m=i[A+e.stride]+s*(i[A+e.stride+1]-i[A+e.stride]);return u+a*(m-u)}var Kt=Object.freeze({sha256:"d51fe1439175bb1343361385d7910036f36a63ed17a83311619a3b04cfa3e837",byteLength:4375,base64:"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"});function ue(e){let t=atob(e),r=new Uint8Array(t.length);for(let n=0;n<t.length;n++)r[n]=t.charCodeAt(n);return r}var et;function ut(){if(typeof WebAssembly>"u")return null;try{if(et===void 0){let e=ue(Kt.base64);et=WebAssembly.validate(e)?new WebAssembly.Module(e):null}return et?new WebAssembly.Instance(et,{}).exports:null}catch{return et=null,null}}var fe=[[0,-3],[1,-3],[2,-2],[3,-1],[3,0],[3,1],[2,2],[1,3],[0,3],[-1,3],[-2,2],[-3,1],[-3,0],[-3,-1],[-2,-2],[-1,-3]];function ye(e,t,r,n){let o=e[t],l=o+n,c=o-n,s=e[t+r[0]],a=e[t+r[8]];if(s<=l&&s>=c&&a<=l&&a>=c)return 0;let i=e[t+r[4]],A=e[t+r[12]];if(i<=l&&i>=c&&A<=l&&A>=c)return 0;let u=0,m=0,h=0,g=0;for(let y=0;y<16;y++){let f=e[t+r[y]];f>l?(u|=1<<y,h+=f-l):f<c&&(m|=1<<y,g+=c-f)}return qt(u)?h+1:qt(m)?g+1:0}function qt(e){if(!e)return!1;let t=(e|e<<16)>>>0,r=t;for(let n=1;n<9;n++)r&=t>>>n;return r!==0}var yt=class{constructor(){F(this,"backend","js");F(this,"map",new Uint16Array(0));F(this,"image",null);F(this,"offsets",new Int32Array(16))}begin(t){return this.map.length<t.width*t.height&&(this.map=new Uint16Array(t.width*t.height)),this.image=t,this.offsets=Int32Array.from(fe.map(([r,n])=>n*t.stride+r)),this.map}score(t,r,n,o,l,c){let{width:s,stride:a,data:i}=this.image;for(let A=r;A<o;A++)for(let u=t;u<n;u++){let m=ye(i,A*a+u,this.offsets,l);m>0&&(this.map[A*s+u]=m,c.push(A*s+u))}}},Qt=class{constructor(t){F(this,"backend","wasm-simd");F(this,"k");F(this,"capacity",0);F(this,"img",0);F(this,"scores",0);F(this,"found",0);F(this,"map",new Uint16Array(0));F(this,"image",null);this.k=t}begin(t){let r=t.stride*t.height;if(r>this.capacity){let n=this.k;if(n.reset_heap(),this.img=n.reserve(r+16),this.scores=n.reserve(2*r),this.found=n.reserve(4*r),!this.img||!this.scores||!this.found)throw new Error("fast9: out of memory");this.capacity=r,new Uint16Array(n.memory.buffer,this.scores,r).fill(0)}return new Uint8Array(this.k.memory.buffer,this.img,r).set(t.data.subarray(0,r)),this.map=new Uint16Array(this.k.memory.buffer,this.scores,t.width*t.height),this.image=t,this.map}score(t,r,n,o,l,c){let s=this.image,a=this.k.fast9(this.img,s.stride,t,r,n,o,l,this.scores,s.width,this.found),i=new Uint32Array(this.k.memory.buffer,this.found,a);for(let A=0;A<a;A++)c.push(i[A])}};function he(e){let n=new Uint8Array(2867),o=2654435769;for(let s=0;s<47;s++)for(let a=0;a<61;a++){o^=o<<13,o^=o>>>17,o^=o<<5;let i=(o>>>0)%64;n[s*61+a]=a<20?128:a<40?((a>>2)+(s>>2))%2?230-i:20+i:i*4}let l={width:61,height:47,stride:61,data:n},c=new yt;for(let s of[4,16,40]){let a=[],i=[],A=c.begin(l),u=e.begin(l);if(c.score(3,3,43,44,s,a),e.score(3,3,43,44,s,i),a.length!==i.length||a.some((m,h)=>m!==i[h]||A[m]!==u[m]))return!1;for(let m of a)A[m]=0;for(let m of i)u[m]=0;if(s===16&&a.length<10)return!1}return!0}var ft=null;function Yt(e=!0){if(!ft){let t=ut(),r=null;try{r=t?new Qt(t):null,r&&!he(r)&&(r=null)}catch{r=null}ft={wasm:r,js:new yt}}return e&&ft.wasm||ft.js}var _=15,ht=_+3,V=32,ge=(()=>{let e=625341585,t=()=>(e^=e<<13,e>>>=0,e^=e>>>17,e^=e<<5,e>>>=0,(e+.5)/4294967296),r=()=>Math.sqrt(-2*Math.log(t()))*Math.cos(2*Math.PI*t()),n=new Int8Array(1024);for(let o=0;o<256;){let l=[0,1,2,3].map(()=>Math.round(r()*8.5));Math.hypot(l[0],l[1])>15||Math.hypot(l[2],l[3])>15||Math.hypot(l[0]-l[2],l[1]-l[3])<2||(n.set(l,4*o),o++)}return n})(),dt=2;function de(e){return Array.from({length:V},(t,r)=>{let n=2*Math.PI*r/V,o=Math.cos(n),l=Math.sin(n),c=new Int8Array(1024);for(let s=0;s<512;s++){let a=e[2*s],i=e[2*s+1];c[2*s]=Math.round(o*a-l*i),c[2*s+1]=Math.round(l*a+o*i)}return c})}var _t=new WeakMap;function pe(e){let t=_t.get(e);if(!t){let r=e.length===1024;for(let n=0;n<512&&r;n++)r=Math.hypot(e[2*n],e[2*n+1])<=15;if(!r)throw new Error("BRIEF pattern: 256 tests with box centres within 15 px");t=de(e),_t.set(e,t)}return t}var Ie=(()=>{let e=new Int32Array(_+1);for(let t=0;t<=_;t++)e[t]=Math.floor(Math.sqrt(_*_-t*t)+.5);return e})(),gt=class{constructor(){F(this,"octaves",new X);F(this,"halfOctaves",new X);F(this,"horizontal",new Uint16Array(0));F(this,"blur",new Uint8Array(0));F(this,"half",new Uint8Array(0));F(this,"integral",new Int32Array(0))}},Jt=new gt;function be(e,t){let r=e.width,n=e.height,o=Math.floor(r/Math.SQRT2),l=Math.floor(n/Math.SQRT2),c=e.data;t.horizontal.length<r*n&&(t.horizontal=new Uint16Array(r*n),t.blur=new Uint8Array(r*n)),t.half.length!==o*l&&(t.half=new Uint8Array(o*l));let s=t.horizontal;for(let m=0;m<n;m++){let h=m*e.stride,g=m*r;s[g]=3*c[h]+c[h+1];for(let y=1;y<r-1;y++)s[g+y]=c[h+y-1]+2*c[h+y]+c[h+y+1];s[g+r-1]=c[h+r-2]+3*c[h+r-1]}let a=t.blur;for(let m=0;m<n;m++){let h=Math.max(0,m-1)*r,g=m*r,y=Math.min(n-1,m+1)*r;for(let f=0;f<r;f++)a[g+f]=s[h+f]+2*s[g+f]+s[y+f]+8>>4}let i=new Int32Array(o),A=new Float32Array(o);for(let m=0;m<o;m++){let h=(m+.5)*Math.SQRT2-.5;i[m]=Math.min(r-2,Math.max(0,Math.floor(h))),A[m]=h-i[m]}let u=t.half;for(let m=0;m<l;m++){let h=(m+.5)*Math.SQRT2-.5,g=Math.min(n-2,Math.max(0,Math.floor(h))),y=h-g,f=g*r,d=f+r;for(let p=0;p<o;p++){let x=i[p],b=A[p],M=a[f+x]+b*(a[f+x+1]-a[f+x]),I=a[d+x]+b*(a[d+x+1]-a[d+x]);u[m*o+p]=M+y*(I-M)+.5|0}}return{width:o,height:l,stride:o,data:u}}function we(e,t,r=new gt){let n=Math.ceil(t/2),o=r.octaves.build(e,n,24).levels,l=r.halfOctaves.build(be(e,r),n,24).levels,c=[];for(let s=0;s<n;s++)o[s]&&c.push({image:o[s],scale:2**s}),l[s]&&c.push({image:l[s],scale:Math.SQRT2*2**s});return c.slice(0,t)}function xe(e,t,r,n){let o=0,l=0,c=0;for(let s=-3;s<=3;s++)for(let a=-3;a<=3;a++){let i=(n+s)*t+r+a,A=e[i+1]-e[i-1],u=e[i+t]-e[i-t];o+=A*A,l+=u*u,c+=A*u}return o*l-c*c-.04*(o+l)**2}function Ee(e,t){let r=e.width+1;t.integral.length<r*(e.height+1)&&(t.integral=new Int32Array(r*(e.height+1)));let n=t.integral;n.fill(0,0,r);for(let o=0;o<e.height;o++){n[(o+1)*r]=0;let l=0;for(let c=0;c<e.width;c++)l+=e.data[o*e.stride+c],n[(o+1)*r+c+1]=n[o*r+c+1]+l}return n}function Me(e,t,r,n,o){let{width:l,height:c,stride:s,data:a}=e,i=r.cell,A=[],u=Math.max(ht,Math.floor(n.x0)),m=Math.max(ht,Math.floor(n.y0)),h=Math.min(l-ht,Math.ceil(n.x1)),g=Math.min(c-ht,Math.ceil(n.y1));if(h<=u||g<=m)return A;let y=o.begin(e),f=[];for(let d=m;d<g;d+=i)for(let p=u;p<h;p+=i){let x=Math.min(g,d+i),b=Math.min(h,p+i);f.length=0;let M=255,I=0;for(let E=d;E<x;E+=2)for(let B=p;B<b;B+=2){let C=a[E*s+B];C<M&&(M=C),C>I&&(I=C)}if(I-M<2*r.minFastThreshold)continue;for(let E=r.fastThreshold;o.score(p,d,b,x,E,f),!(f.length>=2||E===r.minFastThreshold);E=r.minFastThreshold){for(let B of f)y[B]=0;f.length=0}let w=[];for(let E of f){let B=y[E],C=!0;for(let P=-1;P<=1&&C;P++)for(let k=-1;k<=1;k++){if(!k&&!P)continue;let v=y[E+P*l+k];if(v>B||v===B&&(P<0||P===0&&k<0)){C=!1;break}}C&&w.push({i:E,s:B})}for(let E of f)y[E]=0;w.sort((E,B)=>B.s-E.s||E.i-B.i);let Q=[];for(let E of w.slice(0,8)){let B=E.i%l,C=(E.i-B)/l,P=xe(a,s,B,C);P>0&&Q.push({x:B,y:C,score:P})}Q.sort((E,B)=>B.score-E.score||E.y-B.y||E.x-B.x),A.push(...Q.slice(0,2))}return A.sort((d,p)=>p.score-d.score||d.y-p.y||d.x-p.x),A.slice(0,t)}function Be(e,t,r){let n=e.data,o=e.stride,l=0,c=0,s=r*o+t;for(let a=-_;a<=_;a++)c+=a*n[s+a];for(let a=1;a<=_;a++){let i=0,A=Ie[a];for(let u=-A;u<=A;u++){let m=n[s-a*o+u],h=n[s+a*o+u];i+=h-m,c+=u*(m+h)}l+=a*i}return Math.atan2(l,c)}function Ce(e){return(Math.round(e*V/(2*Math.PI))%V+V)%V}function Qe(e,t,r,n,o,l,c,s){let a=l[Ce(o)],i=(n-2)*t+(r-2),A=5*t;for(let u=0;u<32;u++){let m=0;for(let h=0;h<8;h++){let g=4*(8*u+h),y=i+a[g+1]*t+a[g],f=i+a[g+3]*t+a[g+2],d=e[y+A+5]-e[y+5]-e[y+A]+e[y],p=e[f+A+5]-e[f+5]-e[f+A]+e[f];d<p&&(m|=1<<h)}c[s+u]=m}}function rt(e,t={}){let r=t.budget??700,n=we(e,t.levels??6,Jt),o={fastThreshold:t.fastThreshold??16,minFastThreshold:t.minFastThreshold??4,cell:t.cell??24},l=Yt(t.wasm!==!1),c=pe(t.pattern??ge),s=n.reduce((d,p)=>d+p.image.width*p.image.height,0),a=t.roi??{x:0,y:0,width:e.width,height:e.height},i=[],A=[],u=[],m=[],h=[],g=[];n.forEach((d,p)=>{let x=Math.max(16,Math.round(r*d.image.width*d.image.height/s)),b=d.scale,M=Me(d.image,x,o,{x0:a.x/b,y0:a.y/b,x1:(a.x+a.width)/b,y1:(a.y+a.height)/b},l);if(!M.length)return;let I=Ee(d.image,Jt),w=new Uint8Array(32*M.length);M.forEach((Q,E)=>{let B=Be(d.image,Q.x,Q.y);Qe(I,d.image.width+1,Q.x,Q.y,B,c,w,32*E),i.push((Q.x+.5)*b),A.push((Q.y+.5)*b),u.push(p),m.push(B),h.push(Q.score)}),g.push(w)});let y=new Uint8Array(32*i.length),f=0;for(let d of g)y.set(d,f),f+=d.length;return{count:i.length,x:Float32Array.from(i),y:Float32Array.from(A),level:Uint8Array.from(u),angle:Float32Array.from(m),score:Float32Array.from(h),descriptors:y,descriptorBytes:32}}function Ue(e,t,r){let n=r*Math.PI/180,o=Math.cos(n),l=Math.sin(n),c=o*o/t+l*l,s=o*l/t-o*l,a=l*l/t+o*o,i=[[0,0],[e.width,0],[e.width,e.height],[0,e.height]].map(([I,w])=>[c*I+s*w,s*I+a*w]),A=Math.min(...i.map(I=>I[0])),u=Math.min(...i.map(I=>I[1])),m=Math.ceil(Math.max(...i.map(I=>I[0]))-A),h=Math.ceil(Math.max(...i.map(I=>I[1]))-u),g=[c,s,-A,s,a,-u],y=.8*Math.sqrt(Math.max(0,t*t-1)),f=y>.3?jt(e,y):e,d=c*a-s*s,p=a/d,x=-s/d,b=c/d,M=new Uint8Array(m*h).fill(128);for(let I=0;I<h;I++)for(let w=0;w<m;w++){let Q=w+.5-g[2],E=I+.5-g[5],B=p*Q+x*E-.5,C=x*Q+b*E-.5,P=Math.floor(B),k=Math.floor(C);if(P<0||k<0||P+1>=e.width||k+1>=e.height)continue;let v=B-P,T=C-k,D=k*f.stride+P,R=f.data,L=R[D]+v*(R[D+1]-R[D]),O=R[D+f.stride]+v*(R[D+f.stride+1]-R[D+f.stride]);M[I*m+w]=Math.round(L+T*(O-L))}return{image:{width:m,height:h,stride:m,data:M},A:g}}function jt(e,t){let r=Math.ceil(3*t),n=Array.from({length:2*r+1},(a,i)=>Math.exp(-((i-r)**2)/(2*t*t))),o=n.reduce((a,i)=>a+i,0),l=n.map(a=>a/o),c=new Float32Array(e.width*e.height);for(let a=0;a<e.height;a++)for(let i=0;i<e.width;i++){let A=0;for(let u=-r;u<=r;u++)A+=l[u+r]*e.data[a*e.stride+Math.min(e.width-1,Math.max(0,i+u))];c[a*e.width+i]=A}let s=new Uint8Array(e.width*e.height);for(let a=0;a<e.height;a++)for(let i=0;i<e.width;i++){let A=0;for(let u=-r;u<=r;u++)A+=l[u+r]*c[Math.min(e.height-1,Math.max(0,a+u))*e.width+i];s[a*e.width+i]=Math.round(A)}return{width:e.width,height:e.height,stride:e.width,data:s}}function Fe(e,t,r,n){let o=0;for(let l=0;l<32;l++){let c=e[t+l]^r[n+l];for(;c;)c&=c-1,o++}return o}function nt(e,t={}){let r=t.tilts??[1.4,2,2.8],n=t.longitudes??[0,45,90,135],o=[{image:e.image,A:[1,0,0,0,1,0]}];for(let d of r)for(let p of n)o.push(Ue(e.image,d,p));for(let d of t.blurs??[2.5])o.push({image:jt(e.image,d),A:[1,0,0,0,1,0]});let l=[],c=new Map,s=4,a=(d,p)=>Math.floor(p/s)*65536+Math.floor(d/s);o.forEach((d,p)=>{let x=rt(d.image,{budget:900,...t.orb}),[b,M,I,w,Q,E]=d.A,B=b*Q-M*w;for(let C=0;C<x.count;C++){let P=x.x[C]-I,k=x.y[C]-E,v=(Q*P-M*k)/B,T=(-w*P+b*k)/B;if(v<0||T<0||v>=e.info.width||T>=e.info.height)continue;let D=2+1.5*(2**(x.level[C]/2)-1),R=-1;for(let O=-1;O<=1&&R<0;O++)for(let N=-1;N<=1&&R<0;N++)for(let W of c.get(a(v+N*s,T+O*s))??[]){let S=l[W];if(Math.hypot(S.x-v,S.y-T)<=D){R=W;break}}let L=x.descriptors.slice(32*C,32*C+32);if(R<0){l.push({x:v,y:T,n:1,views:new Set([p]),score:x.score[C],desc:[L]});let O=a(v,T);c.set(O,[...c.get(O)??[],l.length-1])}else{let O=l[R];O.views.add(p),O.n++,O.score=Math.max(O.score,x.score[C]),O.desc.push(L)}}});let i=t.maxPoints??700,A=t.descriptorsPerPoint??3,u=l.map((d,p)=>({g:d,i:p})).sort((d,p)=>p.g.views.size-d.g.views.size||p.g.score-d.g.score||d.i-p.i).slice(0,i).map(d=>d.g),m=new Float32Array(2*u.length),h=new Uint8Array(u.length),g=[],y=[];u.forEach((d,p)=>{m[2*p]=d.x,m[2*p+1]=d.y,h[p]=d.views.size;let x=[];for(let b of d.desc){if(x.length>=A)break;x.every(M=>Fe(M,0,b,0)>24)&&x.push(b)}for(let b of x)g.push(b),y.push(p)});let f=new Uint8Array(32*g.length);return g.forEach((d,p)=>f.set(d,32*p)),{info:e.info,points:m,repeatability:h,descriptors:f,descriptorPoint:Uint32Array.from(y)}}function ot(e,t,r){let n=e[6]*t+e[7]*r+e[8];return Math.abs(n)<1e-12?[NaN,NaN]:[(e[0]*t+e[1]*r+e[2])/n,(e[3]*t+e[4]*r+e[5])/n]}function at(e,t,r){return[ot(e,0,0),ot(e,t,0),ot(e,t,r),ot(e,0,r)]}function pt(e,t,r){for(let n=0;n<r;n++){let o=n,l=Math.abs(e[n*r+n]);for(let s=n+1;s<r;s++){let a=Math.abs(e[s*r+n]);a>l&&(l=a,o=s)}if(l<1e-12)return!1;if(o!==n){for(let a=0;a<r;a++){let i=e[n*r+a];e[n*r+a]=e[o*r+a],e[o*r+a]=i}let s=t[n];t[n]=t[o],t[o]=s}let c=e[n*r+n];for(let s=n+1;s<r;s++){let a=e[s*r+n]/c;if(a!==0){for(let i=n;i<r;i++)e[s*r+i]-=a*e[n*r+i];t[s]-=a*t[n]}}}for(let n=r-1;n>=0;n--){let o=t[n];for(let l=n+1;l<r;l++)o-=e[n*r+l]*t[l];t[n]=o/e[n*r+n]}return t.every(Number.isFinite)}function Xt(e,t){let r=new Float64Array(e),n=new Float64Array(t*t);for(let o=0;o<t;o++)n[o*t+o]=1;for(let o=0;o<t;o++){let l=o;for(let s=o+1;s<t;s++)Math.abs(r[s*t+o])>Math.abs(r[l*t+o])&&(l=s);if(Math.abs(r[l*t+o])<1e-15)return null;if(l!==o)for(let s=0;s<t;s++){let a=r[o*t+s];r[o*t+s]=r[l*t+s],r[l*t+s]=a,a=n[o*t+s],n[o*t+s]=n[l*t+s],n[l*t+s]=a}let c=r[o*t+o];for(let s=0;s<t;s++)r[o*t+s]/=c,n[o*t+s]/=c;for(let s=0;s<t;s++){if(s===o)continue;let a=r[s*t+o];if(a!==0)for(let i=0;i<t;i++)r[s*t+i]-=a*r[o*t+i],n[s*t+i]-=a*n[o*t+i]}}return n}function zt(e,t){let r=new Float64Array(e),n=new Float64Array(t*t);for(let c=0;c<t;c++)n[c*t+c]=1;for(let c=0;c<50;c++){let s=0;for(let a=0;a<t;a++)for(let i=a+1;i<t;i++)s+=r[a*t+i]**2;if(s<1e-24)break;for(let a=0;a<t;a++)for(let i=a+1;i<t;i++){let A=r[a*t+i];if(Math.abs(A)<1e-300)continue;let u=(r[i*t+i]-r[a*t+a])/(2*A),m=Math.sign(u||1)/(Math.abs(u)+Math.sqrt(u*u+1)),h=1/Math.sqrt(m*m+1),g=m*h;for(let y=0;y<t;y++){let f=r[y*t+a],d=r[y*t+i];r[y*t+a]=h*f-g*d,r[y*t+i]=g*f+h*d}for(let y=0;y<t;y++){let f=r[a*t+y],d=r[i*t+y];r[a*t+y]=h*f-g*d,r[i*t+y]=g*f+h*d}for(let y=0;y<t;y++){let f=n[y*t+a],d=n[y*t+i];n[y*t+a]=h*f-g*d,n[y*t+i]=g*f+h*d}}}let o=0;for(let c=1;c<t;c++)r[c*t+c]<r[o*t+o]&&(o=c);let l=new Float64Array(t);for(let c=0;c<t;c++)l[c]=n[c*t+o];return l}function Ft(e,t){let r=new Float64Array(9);for(let n=0;n<3;n++)for(let o=0;o<3;o++)r[n*3+o]=e[n*3]*t[o]+e[n*3+1]*t[3+o]+e[n*3+2]*t[6+o];return r}function vt(e){let[t,r,n,o,l,c,s,a,i]=[e[0],e[1],e[2],e[3],e[4],e[5],e[6],e[7],e[8]],A=[l*i-c*a,n*a-r*i,r*c-n*l,c*s-o*i,t*i-n*s,n*o-t*c,o*a-l*s,r*s-t*a,t*l-r*o],u=t*A[0]+r*A[3]+n*A[6];return!Number.isFinite(u)||Math.abs(u)<1e-300?null:Float64Array.from(A,m=>m/u)}function Vt(e){let t=e[8];if(!Number.isFinite(t)||Math.abs(t)<1e-12)return null;for(let r=0;r<9;r++)e[r]/=t;return e.every(Number.isFinite)?e:null}function wt(e,t,r){let n=0,o=0;for(let c=0;c<r;c++){let s=t[c];n+=e[2*s],o+=e[2*s+1]}n/=r,o/=r;let l=0;for(let c=0;c<r;c++){let s=t[c];l+=Math.hypot(e[2*s]-n,e[2*s+1]-o)}return l/=r,l<1e-9?null:[Math.SQRT2/l,n,o]}function Zt(e,t,r){let[n,o,l]=t,[c,s,a]=r,i=[n,0,-n*o,0,n,-n*l,0,0,1],A=[1/c,0,s,0,1/c,a,0,0,1];return Vt(Ft(Ft(A,e),i))}function Pt(e,t,r,n,o){if(n<4)return null;let l=wt(e,r,n),c=wt(t,r,n);if(!l||!c)return null;let s=new Float64Array(81),a=new Float64Array(9);for(let i=0;i<n;i++){let A=r[i],u=o?o[A]:1;if(u<=0)continue;let m=l[0]*(e[2*A]-l[1]),h=l[0]*(e[2*A+1]-l[2]),g=c[0]*(t[2*A]-c[1]),y=c[0]*(t[2*A+1]-c[2]);for(let f=0;f<2;f++){f===0?a.set([m,h,1,0,0,0,-g*m,-g*h,-g]):a.set([0,0,0,m,h,1,-y*m,-y*h,-y]);for(let d=0;d<9;d++){let p=a[d]*u;if(p!==0)for(let x=d;x<9;x++)s[d*9+x]+=p*a[x]}}}for(let i=0;i<9;i++)for(let A=0;A<i;A++)s[i*9+A]=s[A*9+i];return Zt(zt(s,9),l,c)}var It=new Float64Array(64),bt=new Float64Array(8);function st(e,t,r){let n=wt(e,r,4),o=wt(t,r,4);if(!n||!o)return null;It.fill(0);for(let l=0;l<4;l++){let c=r[l],s=n[0]*(e[2*c]-n[1]),a=n[0]*(e[2*c+1]-n[2]),i=o[0]*(t[2*c]-o[1]),A=o[0]*(t[2*c+1]-o[2]);It.set([s,a,1,0,0,0,-i*s,-i*a],16*l),It.set([0,0,0,s,a,1,-A*s,-A*a],16*l+8),bt[2*l]=i,bt[2*l+1]=A}return pt(It,bt,8)?Zt([...bt,1],n,o):null}function $t(e,t,r,n){let o=e[6]*t+e[7]*r+e[8];return o>1e-12?(n[0]=(e[0]*t+e[1]*r+e[2])/o,n[1]=(e[3]*t+e[4]*r+e[5])/o,!0):!1}var J=new Float64Array(8),Ut=new Float64Array(2);function it(e,t,r,n=64,o=30){let l=[0,t,t,0],c=[0,0,r,r];for(let A=0;A<4;A++){if(!$t(e,l[A],c[A],Ut))return!1;J[2*A]=Ut[0],J[2*A+1]=Ut[1]}let s=0,a=1/0,i=0;for(let A=0;A<4;A++){let u=J[2*A],m=J[2*A+1],h=J[(2*A+2)%8],g=J[(2*A+3)%8],y=J[(2*A+4)%8],f=J[(2*A+5)%8];if((h-u)*(f-g)-(g-m)*(y-h)<=0)return!1;s+=u*g-h*m;let d=Math.hypot(h-u,g-m);a=Math.min(a,d),i=Math.max(i,d)}return s/2>=n&&i<=o*Math.max(a,1e-9)}function Tt(e,t,r){let n=[];for(let i=0;i<r;i++)(!t||t[i])&&n.push([e[2*i],e[2*i+1]]);if(n.length<3)return 0;n.sort((i,A)=>i[0]-A[0]||i[1]-A[1]);let o=(i,A,u)=>(A[0]-i[0])*(u[1]-i[1])-(A[1]-i[1])*(u[0]-i[0]),l=[];for(let i of n){for(;l.length>=2&&o(l.at(-2),l.at(-1),i)<=0;)l.pop();l.push(i)}let c=[];for(let i=n.length-1;i>=0;i--){let A=n[i];for(;c.length>=2&&o(c.at(-2),c.at(-1),A)<=0;)c.pop();c.push(A)}let s=[...l.slice(0,-1),...c.slice(0,-1)],a=0;for(let i=0;i<s.length;i++){let A=s[i],u=s[(i+1)%s.length];a+=A[0]*u[1]-u[0]*A[1]}return Math.abs(a)/2}var ve=(e,t)=>e<=t?1:t/e;function Rt(e,t,r,n,o,l={}){let c=l.iterations??8,s=l.huberPx??1.5,a=Float64Array.from(e.subarray(0,8)),i=.001,A=new Float64Array(64),u=new Float64Array(8),m=new Float64Array(8),h=new Float64Array(8),g=b=>{let M=0;for(let I=0;I<o;I++){if(!n[I])continue;let w=t[2*I],Q=t[2*I+1],E=b[6]*w+b[7]*Q+1;if(!(E>1e-12))return 1/0;let B=(b[0]*w+b[1]*Q+b[2])/E-r[2*I],C=(b[3]*w+b[4]*Q+b[5])/E-r[2*I+1],P=Math.hypot(B,C);M+=P<=s?P*P:2*s*P-s*s}return M},y=g(a);for(let b=0;b<c;b++){A.fill(0),u.fill(0);for(let I=0;I<o;I++){if(!n[I])continue;let w=t[2*I],Q=t[2*I+1],E=a[6]*w+a[7]*Q+1,B=(a[0]*w+a[1]*Q+a[2])/E,C=(a[3]*w+a[4]*Q+a[5])/E,P=B-r[2*I],k=C-r[2*I+1],v=ve(Math.hypot(P,k),s);m.set([w/E,Q/E,1/E,0,0,0,-B*w/E,-B*Q/E]),h.set([0,0,0,w/E,Q/E,1/E,-C*w/E,-C*Q/E]);for(let T=0;T<8;T++){u[T]+=v*(m[T]*P+h[T]*k);for(let D=T;D<8;D++)A[T*8+D]+=v*(m[T]*m[D]+h[T]*h[D])}}for(let I=0;I<8;I++)for(let w=0;w<I;w++)A[I*8+w]=A[w*8+I];let M=!1;for(let I=0;I<4&&!M;I++){let w=new Float64Array(A);for(let C=0;C<8;C++)w[C*9]*=1+i;let Q=Float64Array.from(u,C=>-C);if(!pt(w,Q,8)){i*=10;continue}let E=Float64Array.from(a,(C,P)=>C+Q[P]),B=g(E);B<y?(a=E,y=B,i=Math.max(1e-7,i/10),M=!0):i*=10}if(!M)break}let f=0,d=0;A.fill(0);for(let b=0;b<o;b++){if(!n[b])continue;let M=t[2*b],I=t[2*b+1],w=a[6]*M+a[7]*I+1,Q=(a[0]*M+a[1]*I+a[2])/w,E=(a[3]*M+a[4]*I+a[5])/w;d+=(Q-r[2*b])**2+(E-r[2*b+1])**2,f++,m.set([M/w,I/w,1/w,0,0,0,-Q*M/w,-Q*I/w]),h.set([0,0,0,M/w,I/w,1/w,-E*M/w,-E*I/w]);for(let B=0;B<8;B++)for(let C=0;C<8;C++)A[B*8+C]+=m[B]*m[C]+h[B]*h[C]}let p=f>4?d/(2*f-8):d,x=Xt(A,8);return{H:Float64Array.from([...a,1]),rmsPx:f?Math.sqrt(d/f):1/0,paramCovariance:x?x.map(b=>b*p):null}}function Pe(e){let t=e>>>0||625341585;return()=>(t^=t<<13,t>>>=0,t^=t>>>17,t^=t<<5,t>>>=0,t)}function Gt(e,t,r,n,o,l){let c=0,s=0;for(let a=0;a<n;a++){let i=t[2*a],A=t[2*a+1],u=e[6]*i+e[7]*A+e[8],m=o;if(u>1e-12){let g=(e[0]*i+e[1]*A+e[2])/u-r[2*a],y=(e[3]*i+e[4]*A+e[5])/u-r[2*a+1];m=Math.min(o,g*g+y*y)}let h=m<o;l[a]=h?1:0,h&&s++,c+=m}return{score:c,inliers:s}}function Te(e,t){let r=0;for(let o=0;o<t;o++)e[o]&&r++;let n=new Int32Array(r);for(let o=0,l=0;o<t;o++)e[o]&&(n[l++]=o);return n}function xt(e,t,r,n){let o=(e[2*r]-e[2*t])*(e[2*n+1]-e[2*t+1])-(e[2*r+1]-e[2*t+1])*(e[2*n]-e[2*t]);return Math.abs(o)<.001*(1+Math.abs(e[2*t])+Math.abs(e[2*r]))}function kt(e,t,r,n,o,l={}){let c=l.thresholdPx??3,s=c*c,a=l.confidence??.995,i=l.maxIterations??500,A=l.minInliers??12,u=l.minInlierRatio??.2,m=l.minCoverage??.08;if(r<Math.max(4,A))return{accepted:!1,reason:"INSUFFICIENT_MATCHES",iterations:0};let h=Pe(l.seed??12345),g=new Uint8Array(r),y=new Uint8Array(r),f=null,d=1/0,p=0,x=i,b=4,M=i;for(let v=0;v<4;v++)M*=(b-v)/(r-v);let I=1,w=new Int32Array(4),Q=0,E=v=>{if(!v||!it(v,n,o))return;let{score:T,inliers:D}=Gt(v,e,t,r,s,g);if(T>=d)return;f=v,d=T,p=D,y.set(g);for(let O=0;O<3&&p>=8;O++){let N=Pt(e,t,Te(y,r),p);if(!N||!it(N,n,o))break;let W=Gt(N,e,t,r,s,g);if(W.score>=d)break;f=N,d=W.score,p=W.inliers,y.set(g)}let R=p/r,L=Math.log(1-a)/Math.log(Math.max(1e-12,1-R**4));x=Math.min(i,Math.max(16,Math.ceil(L)))};for(;Q<x;){if(Q++,Q>=I&&b<r){let W=M*(b+1)/(b+1-4);b++,I+=Math.ceil(W-M),M=W}let v=I>Q?b-1:b,T=0;I>Q&&(w[T++]=b-1);let D=0;for(;T<4&&D++<64;){let W=h()%v,S=!1;for(let U=0;U<T;U++)w[U]===W&&(S=!0);S||(w[T++]=W)}if(T<4)continue;let[R,L,O,N]=[w[0],w[1],w[2],w[3]];xt(e,R,L,O)||xt(e,R,L,N)||xt(e,R,O,N)||xt(e,L,O,N)||E(st(e,t,w))}if(!f||p<A)return{accepted:!1,reason:"NO_MODEL",iterations:Q};let B=f,C=Rt(B,e,t,y,r,{huberPx:c/2});for(let v=0;v<2;v++){if(!it(C.H,n,o))return{accepted:!1,reason:"IMPLAUSIBLE_MODEL",iterations:Q};B=C.H,p=Gt(B,e,t,r,s,y).inliers,v===0&&(C=Rt(B,e,t,y,r,{huberPx:c/2}))}let P=p/r;if(p<A||P<u)return{accepted:!1,reason:"LOW_INLIER_SUPPORT",iterations:Q};let k=Tt(e,y,r)/(n*o);return k<m?{accepted:!1,reason:"LOW_SPATIAL_COVERAGE",iterations:Q}:{accepted:!0,H:B,mask:y,inliers:p,ratio:P,rmsPx:C.rmsPx,coverage:k,paramCovariance:C.paramCovariance,iterations:Q}}var te=844385612;function Re(e,t,r){if(e.width===t&&e.height===r)return e;let n=new Uint8Array(t*r),o=e.width/t,l=e.height/r;for(let c=0;c<r;c++){let s=Math.min(e.height-1.001,Math.max(0,(c+.5)*l-.5)),a=Math.floor(s),i=s-a;for(let A=0;A<t;A++){let u=Math.min(e.width-1.001,Math.max(0,(A+.5)*o-.5)),m=Math.floor(u),h=u-m,g=a*e.stride+m,y=e.data,f=y[g]+h*(y[g+1]-y[g]),d=y[g+e.stride]+h*(y[g+e.stride+1]-y[g+e.stride]);n[c*t+A]=Math.round(f+i*(d-f))}}return{width:t,height:r,stride:t,data:n}}function Ge(e,t){let r=Math.max(e.width,e.height)/t;if(r<=1)return e;let n=Math.round(e.width/r),o=Math.round(e.height/r),l=new Uint8Array(n*o);for(let c=0;c<o;c++)for(let s=0;s<n;s++){let a=Math.floor(s*r),i=Math.max(a+1,Math.floor((s+1)*r)),A=Math.floor(c*r),u=Math.max(A+1,Math.floor((c+1)*r)),m=0;for(let h=A;h<u;h++)for(let g=a;g<i;g++)m+=e.data[h*e.stride+g];l[c*n+s]=Math.round(m/((i-a)*(u-A)))}return{width:n,height:o,stride:n,data:l}}async function ct(e,t,r={}){let n=Ge(t,r.templateMaxEdge??480),o=new TextEncoder().encode(e.info.id),l=e.points.length/2,c=e.descriptorPoint.length,s=10+o.length+8+8+8*l+l+4*c+32*c+4+n.width*n.height,a=new Uint8Array(s+32),i=new DataView(a.buffer),A=0;i.setUint32(A,te,!0),i.setUint16(A+4,2,!0),i.setUint16(A+6,dt,!0),A+=8,i.setUint16(A,o.length,!0),a.set(o,A+2),A+=2+o.length,i.setUint16(A,e.info.width,!0),i.setUint16(A+2,e.info.height,!0),i.setFloat32(A+4,e.info.physicalWidthMeters??NaN,!0),A+=8,i.setUint32(A,l,!0),i.setUint32(A+4,c,!0),A+=8;for(let m=0;m<2*l;m++,A+=4)i.setFloat32(A,e.points[m],!0);a.set(e.repeatability,A),A+=l;for(let m=0;m<c;m++,A+=4)i.setUint32(A,e.descriptorPoint[m],!0);a.set(e.descriptors,A),A+=32*c,i.setUint16(A,n.width,!0),i.setUint16(A+2,n.height,!0),A+=4;for(let m=0;m<n.height;m++)a.set(n.data.subarray(m*n.stride,m*n.stride+n.width),A+m*n.width);A+=n.width*n.height;let u=new Uint8Array(await crypto.subtle.digest("SHA-256",a.subarray(0,A)));return a.set(u,A),a}async function Z(e){let t=new DataView(e.buffer,e.byteOffset,e.byteLength),r=M=>{throw new Error(`Invalid target asset: ${M}`)};(e.length<64||t.getUint32(0,!0)!==te||t.getUint16(4,!0)!==2)&&r("header");let n=t.getUint16(6,!0)&255;n!==dt&&r(`descriptor pattern v${n}, this engine uses v${dt}; recompile the target`);let o=e.subarray(0,e.length-32);new Uint8Array(await crypto.subtle.digest("SHA-256",new Uint8Array(o))).every((M,I)=>M===e[e.length-32+I])||r("checksum");let c=8,s=t.getUint16(c,!0),a=new TextDecoder().decode(e.subarray(c+2,c+2+s));c+=2+s;let i=t.getUint16(c,!0),A=t.getUint16(c+2,!0),u=t.getFloat32(c+4,!0);c+=8;let m=t.getUint32(c,!0),h=t.getUint32(c+4,!0);c+=8;let g=new Float32Array(2*m);for(let M=0;M<2*m;M++,c+=4)g[M]=t.getFloat32(c,!0);let y=e.slice(c,c+m);c+=m;let f=new Uint32Array(h);for(let M=0;M<h;M++,c+=4)f[M]=t.getUint32(c,!0);let d=e.slice(c,c+32*h);c+=32*h;let p=t.getUint16(c,!0),x=t.getUint16(c+2,!0);c+=4;let b={width:p,height:x,stride:p,data:e.slice(c,c+p*x)};return{compiled:{info:{id:a,width:i,height:A,physicalWidthMeters:Number.isNaN(u)?null:u},points:g,repeatability:y,descriptors:d,descriptorPoint:f},template:Re(b,i,A)}}function Ot(e,t){let r=e.length/32,n=t.length/32,o=new Uint32Array(3*r),l=new Uint32Array(e.buffer,e.byteOffset,e.length/4),c=new Uint32Array(t.buffer,t.byteOffset,t.length/4);for(let s=0;s<r;s++){let a=257,i=257,A=4294967295,u=8*s;for(let m=0;m<n;m++){let h=8*m,g=0;for(let y=0;y<8;y++){let f=(l[u+y]^c[h+y])>>>0;f-=f>>>1&1431655765,f=(f&858993459)+(f>>>2&858993459),g+=(f+(f>>>4)&252645135)*16843009>>>24}g<a?(i=a,a=g,A=m):g<i&&(i=g)}o[3*s]=A,o[3*s+1]=a,o[3*s+2]=i}return o}function Dt(e,t,r){let n=e.length/32,o=t.length/32,l=new Uint32Array(3*n),c=new Uint32Array(e.buffer,e.byteOffset,e.length/4),s=new Uint32Array(t.buffer,t.byteOffset,t.length/4);for(let a=0;a<n;a++){let i=257,A=4294967295,u=257,m=4294967295,h=8*a;for(let g=0;g<o;g++){let y=8*g,f=0;for(let p=0;p<8;p++){let x=(c[h+p]^s[y+p])>>>0;x-=x>>>1&1431655765,x=(x&858993459)+(x>>>2&858993459),f+=(x+(x>>>4)&252645135)*16843009>>>24}let d=r[g];d===A?f<i&&(i=f,m=g):f<i?(u=i,i=f,A=d,m=g):f<u&&(u=f)}l[3*a]=m,l[3*a+1]=i,l[3*a+2]=u}return l}var Et=class e{constructor(t){F(this,"backend");F(this,"kernel");this.kernel=t,this.backend=t?"wasm-simd":"js"}static create(t={}){let r=t.allowWasm===!1?null:ut();if(!r)return new e(null);try{let n=new e(r);return n.selfTest()?n:new e(null)}catch{return new e(null)}}selfTest(){let t=19088743,r=A=>Uint8Array.from({length:A},()=>(t^=t<<13,t^=t>>>17,t^=t<<5,t&255)),n=r(1184),o=new Uint8Array(352);o.set(r(320)),o.set(n.subarray(160,192),320);let l=Ot(o,n),c=this.nearest2(o,n),s=Uint32Array.from({length:37},(A,u)=>u>>1),a=Dt(o,n,s),i=this.nearest2Grouped(o,n,s);return c.length===l.length&&c.every((A,u)=>A===l[u])&&i.length===a.length&&i.every((A,u)=>A===a[u])}nearest2(t,r){let n=this.kernel;if(!n)return Ot(t,r);let o=t.length/32,l=r.length/32;n.reset_heap();let c=n.reserve(t.length),s=n.reserve(r.length),a=n.reserve(12*o);return!c||!s||!a?Ot(t,r):(new Uint8Array(n.memory.buffer).set(t,c),new Uint8Array(n.memory.buffer).set(r,s),n.nearest2(c,o,s,l,a),new Uint32Array(n.memory.buffer.slice(a,a+12*o)))}nearest2Grouped(t,r,n){let o=this.kernel;if(!o)return Dt(t,r,n);let l=t.length/32;o.reset_heap();let c=o.reserve(t.length),s=o.reserve(r.length),a=o.reserve(n.byteLength),i=o.reserve(12*l);if(!c||!s||!a||!i)return Dt(t,r,n);let A=o.memory.buffer;return new Uint8Array(A).set(t,c),new Uint8Array(A).set(r,s),new Uint8Array(A).set(new Uint8Array(n.buffer,n.byteOffset,n.byteLength),a),o.nearest2_grouped(c,l,s,a,r.length/32,i),new Uint32Array(o.memory.buffer.slice(i,i+12*l))}};var ke=e=>Math.max(700,Math.round(700*e.width*e.height/(640*360))),Oe=36e4;function De(e,t,r,n){let o=Math.min(r,3,Math.sqrt(Oe/(t.width*t.height)));if(o<1.2)return null;let l=Math.round(t.width*o),c=Math.round(t.height*o),s=new Uint8Array(l*c),a=e.width-1.001,i=e.height-1.001;for(let u=0;u<c;u++){let m=Math.min(i,Math.max(.5,t.y+(u+.5)/o));for(let h=0;h<l;h++)s[u*l+h]=Math.round(tt(e,Math.min(a,Math.max(.5,t.x+(h+.5)/o)),m))}let A=rt({width:l,height:c,stride:l,data:s},{budget:500,...n});return{...A,x:A.x.map(u=>t.x+(u+.5)/o-.5),y:A.y.map(u=>t.y+(u+.5)/o-.5)}}var lt=class{constructor(t={}){F(this,"info",{id:"C1.orb-views",layer:"L2",route:"light",determinism:"bit-exact",requires:[]});F(this,"options");F(this,"matcher");F(this,"targets",[]);this.options=t,this.matcher=Et.create(t.allowWasm===!1?{allowWasm:!1}:{})}get backend(){return this.matcher.backend}probe(t){return{isAvailable:!0}}async load(t){let r=[];this.targets=[];for(let n of t){let o=performance.now(),l=nt(n,this.options.compile),c=await ct(l,n.image,{templateMaxEdge:this.options.templateMaxEdge??480}),s=await Z(c);this.targets.push(s.compiled),r.push({id:n.info.id,compileUs:(performance.now()-o)*1e3,serialized:c,template:s.template})}return{targets:r}}async loadEncoded(t){let r=[];this.targets=[];for(let n of t){let o=performance.now(),l=await Z(n);this.targets.push(l.compiled),r.push({id:l.compiled.info.id,compileUs:(performance.now()-o)*1e3,serialized:n,template:l.template,info:l.compiled.info})}return{targets:r}}async detect(t,r={}){if(t.image.kind!=="cpu-gray")throw new Error("OrbDetector needs cpu-gray frames");let n=r.roi??null,o=(n&&(r.upscale??1)>1?De(t.image.image,n,r.upscale,this.options.orb??{}):null)??rt(t.image.image,{budget:n?500:ke(t.image.image),...this.options.orb,roi:n});if(o.count<8)return[];let l=this.options.ratio??.85,c=this.options.maxDistance??80,s=[];for(let a of this.targets){let i=this.matcher.nearest2Grouped(o.descriptors,a.descriptors,a.descriptorPoint),A=new Map;for(let y=0;y<o.count;y++){let f=i[3*y+1],d=i[3*y+2];if(f>c||l<1&&f>=l*d)continue;let p=a.descriptorPoint[i[3*y]],x=f/Math.max(1,d),b=A.get(p);(!b||x<b.r)&&A.set(p,{q:y,r:x})}let u=[...A].sort((y,f)=>y[1].r-f[1].r||y[0]-f[0]);if(u.length<12)continue;let m=new Float64Array(2*u.length),h=new Float64Array(2*u.length);u.forEach(([y,{q:f}],d)=>{m[2*d]=a.points[2*y],m[2*d+1]=a.points[2*y+1],h[2*d]=o.x[f],h[2*d+1]=o.y[f]});let g=kt(m,h,u.length,a.info.width,a.info.height,{thresholdPx:this.options.thresholdPx??4,maxIterations:1e3,minInliers:12,minInlierRatio:.1,minCoverage:.05});g.accepted&&s.push({inliers:g.inliers,pose:{targetId:a.info.id,frameId:t.frameId,timeUs:t.time.captureUs,H_imageFromTarget:g.H,corners:at(g.H,a.info.width,a.info.height),covariance:null,quality:{support:g.inliers,ratio:g.ratio,residual:g.rmsPx,residualKind:"px",coverage:g.coverage},origin:"detector"}})}return s.sort((a,i)=>i.inliers-a.inliers||a.pose.targetId.localeCompare(i.pose.targetId)),s.length>1&&s[1].inliers>=.85*s[0].inliers?[]:s.map(a=>a.pose)}};var Se=48,Le=64,z=class extends Error{constructor(r,n,o){super(n,o);F(this,"code");this.name="PictureError",this.code=r}};function Ne(e,t,r){let n=Math.min(1,r/Math.max(e,t));return[Math.max(1,Math.round(e*n)),Math.max(1,Math.round(t*n))]}function He(e,t,r){let n=new Uint8Array(t*r);for(let o=0;o<n.length;o++)n[o]=77*e[4*o]+150*e[4*o+1]+29*e[4*o+2]+128>>8;return n}function ee(){try{return typeof OffscreenCanvas=="function"&&!!new OffscreenCanvas(1,1).getContext("2d")}catch{return!1}}async function re(e,t){let r;try{r=await createImageBitmap(e,{imageOrientation:"from-image"})}catch{try{r=await createImageBitmap(e)}catch(n){throw new z("DECODE_FAILED",`The picture cannot be decoded: ${String(n)}`,{cause:n})}}try{let[n,o]=Ne(r.width,r.height,t),l=r,c=r.width,s=r.height;for(;c>2*n&&s>2*o;){let u=new OffscreenCanvas(Math.ceil(c/2),Math.ceil(s/2)),m=u.getContext("2d");m.imageSmoothingQuality="high",m.drawImage(l,0,0,u.width,u.height),l=u,c=u.width,s=u.height}let i=new OffscreenCanvas(n,o).getContext("2d",{willReadFrequently:!0});i.imageSmoothingQuality="high",i.drawImage(l,0,0,n,o);let A=i.getImageData(0,0,n,o).data;return{width:n,height:o,stride:n,data:He(A,n,o)}}finally{r.close()}}async function ne(e,t){if(Math.min(t.width,t.height)<Le)throw new z("UNSUITABLE_TARGET",`The picture is too small (${t.width}×${t.height} px)`);let r=nt({info:{id:e,width:t.width,height:t.height,physicalWidthMeters:null},image:t}),n=r.points.length/2;if(n<Se)throw new z("UNSUITABLE_TARGET",`The picture has too little detail (${n} recognition points)`);return{bytes:await ct(r,t),pointCount:n}}var We=5,Ke=24,qe=.08,Mt={detail:.15,spread:.4,distinctiveness:.05,recognition:.4},Ye=[.1,.7],_e=[15,95],Je=.4,At={size:.08,tilt:.15,blur:.33,dim:.6,covered:.6},j=(e,t,r)=>e<=t?0:e>=r?1:(e-t)/(r-t),q=640,mt=360,St=500,Lt=118,je=[...[160,120,90,70].map(e=>({name:`size-${e}`,test:"size",longPx:e})),...[45,60,70].map(e=>({name:`tilt-${e}`,test:"tilt",longPx:240,tiltDeg:e})),...[6,10,16].map(e=>({name:`blur-${e}`,test:"blur",longPx:240,blurPx:e})),...[.35,.2].map(e=>({name:`dim-${e}`,test:"dim",longPx:240,gain:e})),...[[0],[1],[2],[3],[0,2],[1,3],[0,1],[2,3]].map(e=>({name:`covered-${e.join("")}`,test:"covered",longPx:240,covered:e}))].flatMap(e=>[{...e,rollDeg:8,offset:[24,12]},{...e,rollDeg:-12,offset:[-30,-8]}]),oe=[12,40],Nt={size:.25,tilt:.2,blur:.2,dim:.15,covered:.2};function Xe(e,t,r){let n=e.longPx/Math.max(t,r),o=t*n,l=r*n,c=q/2+e.offset[0],s=mt/2+e.offset[1],a=[[c-o/2,s-l/2],[c+o/2,s-l/2],[c+o/2,s+l/2],[c-o/2,s+l/2]];if(e.tiltDeg){let m=e.tiltDeg*Math.PI/180;a=a.map(([h,g])=>{let y=St+(g-s)*Math.sin(m);return[c+(h-c)*St/y,s+(g-s)*Math.cos(m)*St/y]})}let i=e.rollDeg*Math.PI/180;a=a.map(([m,h])=>[c+(m-c)*Math.cos(i)-(h-s)*Math.sin(i),s+(m-c)*Math.sin(i)+(h-s)*Math.cos(i)]);let A=Float64Array.from([0,0,t,0,t,r,0,r]),u=Float64Array.from(a.flat());return st(A,u,[0,1,2,3])}function ze(e,t,r){let n=e.levels[0],o=t.longPx/Math.max(n.width,n.height),l=Math.max(0,Math.min(e.levels.length-1,Math.floor(Math.log2(1/o)))),c=e.levels[l],s=2**l,a=vt(r),i=new Uint8Array(q*mt),A=12345,u=n.width/2,m=n.height/2;for(let h=0;h<mt;h++)for(let g=0;g<q;g++){A=A*1103515245+12345>>>0;let y=Lt+(A>>>16)%9-4,f=g+.5,d=h+.5,p=a[6]*f+a[7]*d+a[8],x=(a[0]*f+a[1]*d+a[2])/p,b=(a[3]*f+a[4]*d+a[5])/p;if(x>=0&&b>=0&&x<n.width&&b<n.height){let M=(b<m?0:2)+(x<u?0:1);if(!t.covered?.includes(M)){let I=tt(c,Math.min(c.width-.51,Math.max(.5,x/s)),Math.min(c.height-.51,Math.max(.5,b/s)));Number.isNaN(I)||(y=I)}}t.gain!==void 0&&(A=A*1103515245+12345>>>0,y=Lt+(y-Lt)*t.gain+(A>>>16)%13-6),i[h*q+g]=Math.max(0,Math.min(255,Math.round(y)))}if(t.blurPx){let h=Math.floor(t.blurPx/2),g=new Float64Array(q+1);for(let y=0;y<mt;y++){for(let f=0;f<q;f++)g[f+1]=g[f]+i[y*q+f];for(let f=0;f<q;f++){let d=Math.max(0,f-h),p=Math.min(q,f-h+t.blurPx);i[y*q+f]=Math.round((g[p]-g[d])/(p-d))}}}return{width:q,height:mt,stride:q,data:i}}async function ae(e){let t=await Z(e),{compiled:r,template:n}=t,{width:o,height:l}=r.info,c=r.points.length/2,s=[];for(let U=0;U<c;U++)r.repeatability[U]>=We&&s.push(U);let a=new Float32Array(2*s.length);s.forEach((U,G)=>{a[2*G]=r.points[2*U],a[2*G+1]=r.points[2*U+1]});let i=j(s.length,40,250),A=o/l,u=Math.max(2,Math.round(4*Math.sqrt(A))),m=Math.max(2,Math.round(4/Math.sqrt(A))),h=new Array(u*m).fill(0),g=[0,0,0,0];for(let U=0;U<s.length;U++){let G=a[2*U],K=a[2*U+1];h[Math.min(m-1,Math.floor(K/l*m))*u+Math.min(u-1,Math.floor(G/o*u))]++,g[(K<l/2?0:2)+(G<o/2?0:1)]++}let y=Math.max(1,s.length),f=u*m,d=h.filter(U=>U>=Math.max(3,.3*y/f)).length,p=h.reduce((U,G)=>U+(G/y)**2,0),x=s.length?1/p/f:0,b=g.indexOf(Math.min(...g)),M=g[b]/y,I=(.5*j(d/f,.3,.85)+.5*j(x,.25,.65))*(.6+.4*j(M,.03,.15)),w=new Uint32Array(r.descriptors.buffer,r.descriptors.byteOffset,r.descriptors.length/4),Q=Array.from({length:c},()=>[]);r.descriptorPoint.forEach((U,G)=>Q[U].push(G));let E=(qe*Math.hypot(o,l))**2,B=(U,G)=>{let K=0;for(let H=0;H<8;H++){let Y=(w[8*U+H]^w[8*G+H])>>>0;Y-=Y>>>1&1431655765,Y=(Y&858993459)+(Y>>>2&858993459),K+=(Y+(Y>>>4)&252645135)*16843009>>>24}return K},C=U=>{let G=r.points[2*U],K=r.points[2*U+1];for(let H=0;H<c;H++)if(!((r.points[2*H]-G)**2+(r.points[2*H+1]-K)**2<E)){for(let Y of Q[U])for(let $ of Q[H])if(B(Y,$)<=Ke)return!0}return!1},P=s.length?s.filter(U=>!C(U)).length/s.length:0,k=j(P,.35,.7),v=new lt;await v.loadEncoded([e]);let T=Ct(n,6,16),D=[];for(let[U,G]of je.entries()){let K=Xe(G,o,l),H=at(K,o,l),$=(await v.detect({sessionId:"rate",frameId:U+1,time:{captureUs:U,receiveUs:U,source:"replay"},image:{kind:"cpu-gray",image:ze(T,G,K)}})).find(ie=>ie.corners.reduce((ce,Ht,Wt)=>ce+Math.hypot(Ht[0]-H[Wt][0],Ht[1]-H[Wt][1]),0)/4<.08*G.longPx);D.push({name:G.name,test:G.test,found:!!$,margin:$?j($.quality.support,oe[0],oe[1]):0})}let R=Object.fromEntries(Object.keys(Nt).map(U=>{let G=D.filter(K=>K.test===U);return[U,G.reduce((K,H)=>K+H.margin,0)/G.length]})),L=j(Object.keys(Nt).reduce((U,G)=>U+Nt[G]*R[G],0),...Ye),O=100*(Mt.detail*i+Mt.spread*I+Mt.distinctiveness*k+Mt.recognition*L),N=Math.min(.5+4.5*j(O,..._e),1+4*Math.min(I,L)**Je),W=Math.min(5,Math.max(.5,Math.round(N*2)/2)),S=[];return L<.4&&S.push("HARD_TO_FIND"),I<.6&&(d/f<.6||x<.45)&&S.push("CONCENTRATED"),M<.08&&S.push("WEAK_QUARTER"),i<.5&&S.push("FEW_DETAILS"),k<.4&&S.push("REPETITIVE"),R.size<At.size&&S.push("FAILS_SMALL"),R.tilt<At.tilt&&S.push("FAILS_TILTED"),R.blur<At.blur&&S.push("FAILS_BLURRED"),R.dim<At.dim&&S.push("FAILS_DIM"),R.covered<At.covered&&S.push("FAILS_COVERED"),{stars:W,score:Math.round(20*N),factors:{detail:i,spread:I,distinctiveness:k,recognition:L},metrics:{robustPoints:s.length,grid:{columns:u,rows:m,counts:h},populatedCells:d,evenness:x,weakestQuarter:{index:b,share:M},distinctShare:P,tests:R,views:D},advice:S,points:a,width:o,height:l}}var Bt=class extends Error{constructor(r,n){super(n);F(this,"code");this.code=r}};async function Ve(e){if(e.bytes)return new Uint8Array(e.bytes);let t;if(e.gray)t={width:e.gray.width,height:e.gray.height,stride:e.gray.width,data:new Uint8Array(e.gray.data)};else if(ee())t=await re(e.image,e.maxEdgePx);else return null;return(await ne("rated",t)).bytes}function se(e){let t=Promise.resolve();e.addEventListener("message",r=>{let n=r.data;n.type==="rate"&&(t=t.then(async()=>{try{let o=await Ve(n);if(!o){e.postMessage({type:"need-gray",id:n.id});return}let l;try{l=await ae(o)}catch(c){throw n.bytes?new Bt("INVALID_TARGET",c instanceof Error?c.message:String(c)):c}e.postMessage({type:"rated",id:n.id,rating:l},[l.points.buffer])}catch(o){e.postMessage({type:"failed",id:n.id,code:o instanceof z||o instanceof Bt?o.code:"WORKER_FAILED",message:o instanceof Error?o.message:String(o)})}}))})}se(globalThis);})();
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// LiveMira 0.6.0
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Why a picture rated as it did, most important first:
|
|
6
|
+
* - `'HARD_TO_FIND'`: the detector misses the picture, or finds it narrowly, in most synthetic views;
|
|
7
|
+
* - `'CONCENTRATED'`: the detail sits in part of the picture;
|
|
8
|
+
* - `'WEAK_QUARTER'`: one quarter has almost no detail (`metrics.weakestQuarter`);
|
|
9
|
+
* - `'FEW_DETAILS'`: few points the engine finds again from other angles;
|
|
10
|
+
* - `'REPETITIVE'`: repeating patterns, points that look alike;
|
|
11
|
+
* - `'FAILS_SMALL'`, `'FAILS_TILTED'`, `'FAILS_BLURRED'`, `'FAILS_DIM'`, `'FAILS_COVERED'`: that recognition test
|
|
12
|
+
* (`metrics.tests`) clearly weaker than for most photos: small, tilted, blurred by motion, in dim light, partly
|
|
13
|
+
* covered.
|
|
14
|
+
*/
|
|
15
|
+
export type RatingAdvice = 'HARD_TO_FIND' | 'CONCENTRATED' | 'WEAK_QUARTER' | 'FEW_DETAILS' | 'REPETITIVE' | 'FAILS_SMALL' | 'FAILS_TILTED' | 'FAILS_BLURRED' | 'FAILS_DIM' | 'FAILS_COVERED';
|
|
16
|
+
/** The recognition tests, each a ladder of synthetic views from easy to hard. */
|
|
17
|
+
export type RecognitionTest = 'size' | 'tilt' | 'blur' | 'dim' | 'covered';
|
|
18
|
+
/**
|
|
19
|
+
* How well the engine will find and keep a picture. Four factors, each 0..1: `detail` (points found again from other
|
|
20
|
+
* angles), `spread` (those points over the whole picture: grid cells, evenness, the weakest quarter),
|
|
21
|
+
* `distinctiveness` (no look-alikes far apart) and `recognition` (the engine's detector on synthetic views: smaller,
|
|
22
|
+
* tilted, motion-blurred, dimmer, partly covered). The stars weigh them and are capped by the weaker of spread and
|
|
23
|
+
* recognition, so detail crowded in one place cannot rate high.
|
|
24
|
+
*/
|
|
25
|
+
export interface TargetRating {
|
|
26
|
+
/** 0.5 … 5 in halves: 5 near the best on every count, 4–4.5 good, 3–3.5 usable, 2–2.5 better replaced. */
|
|
27
|
+
readonly stars: number;
|
|
28
|
+
/** 0 … 100 before rounding to stars. */
|
|
29
|
+
readonly score: number;
|
|
30
|
+
readonly factors: {
|
|
31
|
+
readonly detail: number;
|
|
32
|
+
readonly spread: number;
|
|
33
|
+
readonly distinctiveness: number;
|
|
34
|
+
readonly recognition: number;
|
|
35
|
+
};
|
|
36
|
+
readonly metrics: {
|
|
37
|
+
readonly robustPoints: number;
|
|
38
|
+
readonly grid: {
|
|
39
|
+
readonly columns: number;
|
|
40
|
+
readonly rows: number;
|
|
41
|
+
readonly counts: readonly number[];
|
|
42
|
+
};
|
|
43
|
+
readonly populatedCells: number;
|
|
44
|
+
/** Effective share of cells holding the points (1: even, 1/cells: all in one). */
|
|
45
|
+
readonly evenness: number;
|
|
46
|
+
/** 0 top-left, 1 top-right, 2 bottom-left, 3 bottom-right, and its share of the points. */
|
|
47
|
+
readonly weakestQuarter: {
|
|
48
|
+
readonly index: number;
|
|
49
|
+
readonly share: number;
|
|
50
|
+
};
|
|
51
|
+
readonly distinctShare: number;
|
|
52
|
+
/** Each recognition test's views found, counted by their margin (0..1). */
|
|
53
|
+
readonly tests: Readonly<Record<RecognitionTest, number>>;
|
|
54
|
+
readonly views: readonly {
|
|
55
|
+
readonly name: string;
|
|
56
|
+
readonly test: RecognitionTest;
|
|
57
|
+
readonly found: boolean;
|
|
58
|
+
}[];
|
|
59
|
+
};
|
|
60
|
+
readonly advice: readonly RatingAdvice[];
|
|
61
|
+
/** The points counted as detail, in target pixels (x, y interleaved), for drawing over the picture. */
|
|
62
|
+
readonly points: Float32Array;
|
|
63
|
+
/** Size of the compiled picture (target pixels). */
|
|
64
|
+
readonly width: number;
|
|
65
|
+
readonly height: number;
|
|
66
|
+
}
|
|
67
|
+
export interface RateOptions {
|
|
68
|
+
readonly signal?: AbortSignal;
|
|
69
|
+
/** The rating Worker file, for pages whose CSP forbids `blob:` Workers. */
|
|
70
|
+
readonly workerUrl?: string | URL;
|
|
71
|
+
}
|
|
72
|
+
export interface RateImageOptions extends RateOptions {
|
|
73
|
+
/** Long edge the picture is compiled at; keep it equal to the engine's `compileTargetImage` (default 640). */
|
|
74
|
+
readonly maxEdgePx?: number;
|
|
75
|
+
}
|
|
76
|
+
export type RatingErrorCode =
|
|
77
|
+
/** Not a picture file or target bytes, or an option out of range. */
|
|
78
|
+
'INVALID_INPUT' | 'DECODE_FAILED'
|
|
79
|
+
/** The engine would refuse this picture: too small, or too little detail to track. */
|
|
80
|
+
| 'UNSUITABLE_TARGET'
|
|
81
|
+
/** Damaged target bytes. */
|
|
82
|
+
| 'INVALID_TARGET' | 'WORKER_FAILED' | 'ABORTED';
|
|
83
|
+
export interface RatingErrorShape extends Error {
|
|
84
|
+
readonly name: 'RatingError';
|
|
85
|
+
readonly code: RatingErrorCode;
|
|
86
|
+
}
|
|
87
|
+
export interface RatingBuildInfo {
|
|
88
|
+
readonly version: string;
|
|
89
|
+
readonly buildId: string;
|
|
90
|
+
readonly variant: 'minified' | 'readable' | 'development';
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export declare const RATING_VERSION: string;
|
|
94
|
+
export declare const ratingBuildInfo: RatingBuildInfo;
|
|
95
|
+
/** Rates a picture file as a target (decoded and compiled as the engine's `compileTargetImage` would), in a Worker. */
|
|
96
|
+
export declare function rateTargetImage(image: Blob, options?: RateImageOptions): Promise<TargetRating>;
|
|
97
|
+
/** Rates an already compiled target: the engine's target bundle or its bytes. */
|
|
98
|
+
export declare function rateTarget(target: { readonly bytes: Uint8Array } | Uint8Array, options?: RateOptions): Promise<TargetRating>;
|
|
99
|
+
export declare class RatingError extends Error implements RatingErrorShape {
|
|
100
|
+
readonly name: 'RatingError';
|
|
101
|
+
readonly code: RatingErrorCode;
|
|
102
|
+
constructor(code: RatingErrorCode, message: string, options?: { cause?: unknown });
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
var _=Object.defineProperty;var R=(n,t,e)=>t in n?_(n,t,{enumerable:!0,configurable:!0,writable:!0,value:e}):n[t]=e;var o=(n,t,e)=>R(n,typeof t!="symbol"?t+"":t,e);var g="0.6.0",y=Object.freeze({version:g,buildId:"147b918dc0232a92a4232661db7ed083bbeca340b9cdb721c17ad1337a014eff",variant:"minified"});var c=class extends Error{constructor(e,r,i){super(r,i);o(this,"code");this.name="PictureError",this.code=e}};function w(n,t,e){let r=Math.min(1,e/Math.max(n,t));return[Math.max(1,Math.round(n*r)),Math.max(1,Math.round(t*r))]}function E(n,t,e){let r=new Uint8Array(t*e);for(let i=0;i<r.length;i++)r[i]=77*n[4*i]+150*n[4*i+1]+29*n[4*i+2]+128>>8;return r}async function h(n,t){let e;try{e=await createImageBitmap(n)}catch(I){throw new c("DECODE_FAILED","The picture cannot be decoded",{cause:I})}let[r,i]=w(e.width,e.height,t),s=document.createElement("canvas");s.width=r,s.height=i;let u=s.getContext("2d",{willReadFrequently:!0});u.imageSmoothingQuality="high",u.drawImage(e,0,0,r,i),e.close();let f=E(u.getImageData(0,0,r,i).data,r,i);return{width:r,height:i,data:f.buffer}}var a=class extends Error{constructor(e,r,i={}){super(r,i.cause===void 0?void 0:{cause:i.cause});o(this,"name","RatingError");o(this,"code");this.code=e}};var b=4e3,d=null;function T(){return d||(d=URL.createObjectURL(new Blob(['"use strict";(()=>{var le=Object.defineProperty;var Ae=(e,t,r)=>t in e?le(e,t,{enumerable:!0,configurable:!0,writable:!0,value:r}):e[t]=r;var F=(e,t,r)=>Ae(e,typeof t!="symbol"?t+"":t,r);var X=class{constructor(){F(this,"rows",new Int32Array(0));F(this,"levels",[])}build(t,r,n=24){let o=[{...t,scale:1}];for(let l=1;l<r;l++){let c=o[l-1],s=c.width>>1,a=c.height>>1;if(s<n||a<n)break;this.rows.length<c.width*a&&(this.rows=new Int32Array(c.width*a));let i=this.levels[l]=this.levels[l]&&this.levels[l].length===s*a?this.levels[l]:new Uint8Array(s*a);me(c,s,a,this.rows,i),o.push({width:s,height:a,stride:s,data:i,scale:c.scale*2})}return{levels:o}}};function me(e,t,r,n,o){let l=e.width,c=e.height,s=e.data,a=e.stride;for(let i=0;i<r;i++){let A=2*i,u=Math.max(0,A-1)*a,m=A*a,h=Math.min(c-1,A+1)*a,g=Math.min(c-1,A+2)*a,y=i*l;for(let f=0;f<l;f++)n[y+f]=s[u+f]+3*s[m+f]+3*s[h+f]+s[g+f]}for(let i=0;i<r;i++){let A=i*l;for(let u=0;u<t;u++){let m=2*u,h=n[A+Math.max(0,m-1)],g=n[A+m],y=n[A+Math.min(l-1,m+1)],f=n[A+Math.min(l-1,m+2)];o[i*t+u]=h+3*g+3*y+f+32>>6}}}function Ct(e,t,r=24){return new X().build(e,t,r)}function tt(e,t,r){let n=t-.5,o=r-.5,l=Math.floor(n),c=Math.floor(o);if(l<0||c<0||l+1>=e.width||c+1>=e.height)return NaN;let s=n-l,a=o-c,i=e.data,A=c*e.stride+l,u=i[A]+s*(i[A+1]-i[A]),m=i[A+e.stride]+s*(i[A+e.stride+1]-i[A+e.stride]);return u+a*(m-u)}var Kt=Object.freeze({sha256:"d51fe1439175bb1343361385d7910036f36a63ed17a83311619a3b04cfa3e837",byteLength:4375,base64:"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"});function ue(e){let t=atob(e),r=new Uint8Array(t.length);for(let n=0;n<t.length;n++)r[n]=t.charCodeAt(n);return r}var et;function ut(){if(typeof WebAssembly>"u")return null;try{if(et===void 0){let e=ue(Kt.base64);et=WebAssembly.validate(e)?new WebAssembly.Module(e):null}return et?new WebAssembly.Instance(et,{}).exports:null}catch{return et=null,null}}var fe=[[0,-3],[1,-3],[2,-2],[3,-1],[3,0],[3,1],[2,2],[1,3],[0,3],[-1,3],[-2,2],[-3,1],[-3,0],[-3,-1],[-2,-2],[-1,-3]];function ye(e,t,r,n){let o=e[t],l=o+n,c=o-n,s=e[t+r[0]],a=e[t+r[8]];if(s<=l&&s>=c&&a<=l&&a>=c)return 0;let i=e[t+r[4]],A=e[t+r[12]];if(i<=l&&i>=c&&A<=l&&A>=c)return 0;let u=0,m=0,h=0,g=0;for(let y=0;y<16;y++){let f=e[t+r[y]];f>l?(u|=1<<y,h+=f-l):f<c&&(m|=1<<y,g+=c-f)}return qt(u)?h+1:qt(m)?g+1:0}function qt(e){if(!e)return!1;let t=(e|e<<16)>>>0,r=t;for(let n=1;n<9;n++)r&=t>>>n;return r!==0}var yt=class{constructor(){F(this,"backend","js");F(this,"map",new Uint16Array(0));F(this,"image",null);F(this,"offsets",new Int32Array(16))}begin(t){return this.map.length<t.width*t.height&&(this.map=new Uint16Array(t.width*t.height)),this.image=t,this.offsets=Int32Array.from(fe.map(([r,n])=>n*t.stride+r)),this.map}score(t,r,n,o,l,c){let{width:s,stride:a,data:i}=this.image;for(let A=r;A<o;A++)for(let u=t;u<n;u++){let m=ye(i,A*a+u,this.offsets,l);m>0&&(this.map[A*s+u]=m,c.push(A*s+u))}}},Qt=class{constructor(t){F(this,"backend","wasm-simd");F(this,"k");F(this,"capacity",0);F(this,"img",0);F(this,"scores",0);F(this,"found",0);F(this,"map",new Uint16Array(0));F(this,"image",null);this.k=t}begin(t){let r=t.stride*t.height;if(r>this.capacity){let n=this.k;if(n.reset_heap(),this.img=n.reserve(r+16),this.scores=n.reserve(2*r),this.found=n.reserve(4*r),!this.img||!this.scores||!this.found)throw new Error("fast9: out of memory");this.capacity=r,new Uint16Array(n.memory.buffer,this.scores,r).fill(0)}return new Uint8Array(this.k.memory.buffer,this.img,r).set(t.data.subarray(0,r)),this.map=new Uint16Array(this.k.memory.buffer,this.scores,t.width*t.height),this.image=t,this.map}score(t,r,n,o,l,c){let s=this.image,a=this.k.fast9(this.img,s.stride,t,r,n,o,l,this.scores,s.width,this.found),i=new Uint32Array(this.k.memory.buffer,this.found,a);for(let A=0;A<a;A++)c.push(i[A])}};function he(e){let n=new Uint8Array(2867),o=2654435769;for(let s=0;s<47;s++)for(let a=0;a<61;a++){o^=o<<13,o^=o>>>17,o^=o<<5;let i=(o>>>0)%64;n[s*61+a]=a<20?128:a<40?((a>>2)+(s>>2))%2?230-i:20+i:i*4}let l={width:61,height:47,stride:61,data:n},c=new yt;for(let s of[4,16,40]){let a=[],i=[],A=c.begin(l),u=e.begin(l);if(c.score(3,3,43,44,s,a),e.score(3,3,43,44,s,i),a.length!==i.length||a.some((m,h)=>m!==i[h]||A[m]!==u[m]))return!1;for(let m of a)A[m]=0;for(let m of i)u[m]=0;if(s===16&&a.length<10)return!1}return!0}var ft=null;function Yt(e=!0){if(!ft){let t=ut(),r=null;try{r=t?new Qt(t):null,r&&!he(r)&&(r=null)}catch{r=null}ft={wasm:r,js:new yt}}return e&&ft.wasm||ft.js}var _=15,ht=_+3,V=32,ge=(()=>{let e=625341585,t=()=>(e^=e<<13,e>>>=0,e^=e>>>17,e^=e<<5,e>>>=0,(e+.5)/4294967296),r=()=>Math.sqrt(-2*Math.log(t()))*Math.cos(2*Math.PI*t()),n=new Int8Array(1024);for(let o=0;o<256;){let l=[0,1,2,3].map(()=>Math.round(r()*8.5));Math.hypot(l[0],l[1])>15||Math.hypot(l[2],l[3])>15||Math.hypot(l[0]-l[2],l[1]-l[3])<2||(n.set(l,4*o),o++)}return n})(),dt=2;function de(e){return Array.from({length:V},(t,r)=>{let n=2*Math.PI*r/V,o=Math.cos(n),l=Math.sin(n),c=new Int8Array(1024);for(let s=0;s<512;s++){let a=e[2*s],i=e[2*s+1];c[2*s]=Math.round(o*a-l*i),c[2*s+1]=Math.round(l*a+o*i)}return c})}var _t=new WeakMap;function pe(e){let t=_t.get(e);if(!t){let r=e.length===1024;for(let n=0;n<512&&r;n++)r=Math.hypot(e[2*n],e[2*n+1])<=15;if(!r)throw new Error("BRIEF pattern: 256 tests with box centres within 15 px");t=de(e),_t.set(e,t)}return t}var Ie=(()=>{let e=new Int32Array(_+1);for(let t=0;t<=_;t++)e[t]=Math.floor(Math.sqrt(_*_-t*t)+.5);return e})(),gt=class{constructor(){F(this,"octaves",new X);F(this,"halfOctaves",new X);F(this,"horizontal",new Uint16Array(0));F(this,"blur",new Uint8Array(0));F(this,"half",new Uint8Array(0));F(this,"integral",new Int32Array(0))}},Jt=new gt;function be(e,t){let r=e.width,n=e.height,o=Math.floor(r/Math.SQRT2),l=Math.floor(n/Math.SQRT2),c=e.data;t.horizontal.length<r*n&&(t.horizontal=new Uint16Array(r*n),t.blur=new Uint8Array(r*n)),t.half.length!==o*l&&(t.half=new Uint8Array(o*l));let s=t.horizontal;for(let m=0;m<n;m++){let h=m*e.stride,g=m*r;s[g]=3*c[h]+c[h+1];for(let y=1;y<r-1;y++)s[g+y]=c[h+y-1]+2*c[h+y]+c[h+y+1];s[g+r-1]=c[h+r-2]+3*c[h+r-1]}let a=t.blur;for(let m=0;m<n;m++){let h=Math.max(0,m-1)*r,g=m*r,y=Math.min(n-1,m+1)*r;for(let f=0;f<r;f++)a[g+f]=s[h+f]+2*s[g+f]+s[y+f]+8>>4}let i=new Int32Array(o),A=new Float32Array(o);for(let m=0;m<o;m++){let h=(m+.5)*Math.SQRT2-.5;i[m]=Math.min(r-2,Math.max(0,Math.floor(h))),A[m]=h-i[m]}let u=t.half;for(let m=0;m<l;m++){let h=(m+.5)*Math.SQRT2-.5,g=Math.min(n-2,Math.max(0,Math.floor(h))),y=h-g,f=g*r,d=f+r;for(let p=0;p<o;p++){let x=i[p],b=A[p],M=a[f+x]+b*(a[f+x+1]-a[f+x]),I=a[d+x]+b*(a[d+x+1]-a[d+x]);u[m*o+p]=M+y*(I-M)+.5|0}}return{width:o,height:l,stride:o,data:u}}function we(e,t,r=new gt){let n=Math.ceil(t/2),o=r.octaves.build(e,n,24).levels,l=r.halfOctaves.build(be(e,r),n,24).levels,c=[];for(let s=0;s<n;s++)o[s]&&c.push({image:o[s],scale:2**s}),l[s]&&c.push({image:l[s],scale:Math.SQRT2*2**s});return c.slice(0,t)}function xe(e,t,r,n){let o=0,l=0,c=0;for(let s=-3;s<=3;s++)for(let a=-3;a<=3;a++){let i=(n+s)*t+r+a,A=e[i+1]-e[i-1],u=e[i+t]-e[i-t];o+=A*A,l+=u*u,c+=A*u}return o*l-c*c-.04*(o+l)**2}function Ee(e,t){let r=e.width+1;t.integral.length<r*(e.height+1)&&(t.integral=new Int32Array(r*(e.height+1)));let n=t.integral;n.fill(0,0,r);for(let o=0;o<e.height;o++){n[(o+1)*r]=0;let l=0;for(let c=0;c<e.width;c++)l+=e.data[o*e.stride+c],n[(o+1)*r+c+1]=n[o*r+c+1]+l}return n}function Me(e,t,r,n,o){let{width:l,height:c,stride:s,data:a}=e,i=r.cell,A=[],u=Math.max(ht,Math.floor(n.x0)),m=Math.max(ht,Math.floor(n.y0)),h=Math.min(l-ht,Math.ceil(n.x1)),g=Math.min(c-ht,Math.ceil(n.y1));if(h<=u||g<=m)return A;let y=o.begin(e),f=[];for(let d=m;d<g;d+=i)for(let p=u;p<h;p+=i){let x=Math.min(g,d+i),b=Math.min(h,p+i);f.length=0;let M=255,I=0;for(let E=d;E<x;E+=2)for(let B=p;B<b;B+=2){let C=a[E*s+B];C<M&&(M=C),C>I&&(I=C)}if(I-M<2*r.minFastThreshold)continue;for(let E=r.fastThreshold;o.score(p,d,b,x,E,f),!(f.length>=2||E===r.minFastThreshold);E=r.minFastThreshold){for(let B of f)y[B]=0;f.length=0}let w=[];for(let E of f){let B=y[E],C=!0;for(let P=-1;P<=1&&C;P++)for(let k=-1;k<=1;k++){if(!k&&!P)continue;let v=y[E+P*l+k];if(v>B||v===B&&(P<0||P===0&&k<0)){C=!1;break}}C&&w.push({i:E,s:B})}for(let E of f)y[E]=0;w.sort((E,B)=>B.s-E.s||E.i-B.i);let Q=[];for(let E of w.slice(0,8)){let B=E.i%l,C=(E.i-B)/l,P=xe(a,s,B,C);P>0&&Q.push({x:B,y:C,score:P})}Q.sort((E,B)=>B.score-E.score||E.y-B.y||E.x-B.x),A.push(...Q.slice(0,2))}return A.sort((d,p)=>p.score-d.score||d.y-p.y||d.x-p.x),A.slice(0,t)}function Be(e,t,r){let n=e.data,o=e.stride,l=0,c=0,s=r*o+t;for(let a=-_;a<=_;a++)c+=a*n[s+a];for(let a=1;a<=_;a++){let i=0,A=Ie[a];for(let u=-A;u<=A;u++){let m=n[s-a*o+u],h=n[s+a*o+u];i+=h-m,c+=u*(m+h)}l+=a*i}return Math.atan2(l,c)}function Ce(e){return(Math.round(e*V/(2*Math.PI))%V+V)%V}function Qe(e,t,r,n,o,l,c,s){let a=l[Ce(o)],i=(n-2)*t+(r-2),A=5*t;for(let u=0;u<32;u++){let m=0;for(let h=0;h<8;h++){let g=4*(8*u+h),y=i+a[g+1]*t+a[g],f=i+a[g+3]*t+a[g+2],d=e[y+A+5]-e[y+5]-e[y+A]+e[y],p=e[f+A+5]-e[f+5]-e[f+A]+e[f];d<p&&(m|=1<<h)}c[s+u]=m}}function rt(e,t={}){let r=t.budget??700,n=we(e,t.levels??6,Jt),o={fastThreshold:t.fastThreshold??16,minFastThreshold:t.minFastThreshold??4,cell:t.cell??24},l=Yt(t.wasm!==!1),c=pe(t.pattern??ge),s=n.reduce((d,p)=>d+p.image.width*p.image.height,0),a=t.roi??{x:0,y:0,width:e.width,height:e.height},i=[],A=[],u=[],m=[],h=[],g=[];n.forEach((d,p)=>{let x=Math.max(16,Math.round(r*d.image.width*d.image.height/s)),b=d.scale,M=Me(d.image,x,o,{x0:a.x/b,y0:a.y/b,x1:(a.x+a.width)/b,y1:(a.y+a.height)/b},l);if(!M.length)return;let I=Ee(d.image,Jt),w=new Uint8Array(32*M.length);M.forEach((Q,E)=>{let B=Be(d.image,Q.x,Q.y);Qe(I,d.image.width+1,Q.x,Q.y,B,c,w,32*E),i.push((Q.x+.5)*b),A.push((Q.y+.5)*b),u.push(p),m.push(B),h.push(Q.score)}),g.push(w)});let y=new Uint8Array(32*i.length),f=0;for(let d of g)y.set(d,f),f+=d.length;return{count:i.length,x:Float32Array.from(i),y:Float32Array.from(A),level:Uint8Array.from(u),angle:Float32Array.from(m),score:Float32Array.from(h),descriptors:y,descriptorBytes:32}}function Ue(e,t,r){let n=r*Math.PI/180,o=Math.cos(n),l=Math.sin(n),c=o*o/t+l*l,s=o*l/t-o*l,a=l*l/t+o*o,i=[[0,0],[e.width,0],[e.width,e.height],[0,e.height]].map(([I,w])=>[c*I+s*w,s*I+a*w]),A=Math.min(...i.map(I=>I[0])),u=Math.min(...i.map(I=>I[1])),m=Math.ceil(Math.max(...i.map(I=>I[0]))-A),h=Math.ceil(Math.max(...i.map(I=>I[1]))-u),g=[c,s,-A,s,a,-u],y=.8*Math.sqrt(Math.max(0,t*t-1)),f=y>.3?jt(e,y):e,d=c*a-s*s,p=a/d,x=-s/d,b=c/d,M=new Uint8Array(m*h).fill(128);for(let I=0;I<h;I++)for(let w=0;w<m;w++){let Q=w+.5-g[2],E=I+.5-g[5],B=p*Q+x*E-.5,C=x*Q+b*E-.5,P=Math.floor(B),k=Math.floor(C);if(P<0||k<0||P+1>=e.width||k+1>=e.height)continue;let v=B-P,T=C-k,D=k*f.stride+P,R=f.data,L=R[D]+v*(R[D+1]-R[D]),O=R[D+f.stride]+v*(R[D+f.stride+1]-R[D+f.stride]);M[I*m+w]=Math.round(L+T*(O-L))}return{image:{width:m,height:h,stride:m,data:M},A:g}}function jt(e,t){let r=Math.ceil(3*t),n=Array.from({length:2*r+1},(a,i)=>Math.exp(-((i-r)**2)/(2*t*t))),o=n.reduce((a,i)=>a+i,0),l=n.map(a=>a/o),c=new Float32Array(e.width*e.height);for(let a=0;a<e.height;a++)for(let i=0;i<e.width;i++){let A=0;for(let u=-r;u<=r;u++)A+=l[u+r]*e.data[a*e.stride+Math.min(e.width-1,Math.max(0,i+u))];c[a*e.width+i]=A}let s=new Uint8Array(e.width*e.height);for(let a=0;a<e.height;a++)for(let i=0;i<e.width;i++){let A=0;for(let u=-r;u<=r;u++)A+=l[u+r]*c[Math.min(e.height-1,Math.max(0,a+u))*e.width+i];s[a*e.width+i]=Math.round(A)}return{width:e.width,height:e.height,stride:e.width,data:s}}function Fe(e,t,r,n){let o=0;for(let l=0;l<32;l++){let c=e[t+l]^r[n+l];for(;c;)c&=c-1,o++}return o}function nt(e,t={}){let r=t.tilts??[1.4,2,2.8],n=t.longitudes??[0,45,90,135],o=[{image:e.image,A:[1,0,0,0,1,0]}];for(let d of r)for(let p of n)o.push(Ue(e.image,d,p));for(let d of t.blurs??[2.5])o.push({image:jt(e.image,d),A:[1,0,0,0,1,0]});let l=[],c=new Map,s=4,a=(d,p)=>Math.floor(p/s)*65536+Math.floor(d/s);o.forEach((d,p)=>{let x=rt(d.image,{budget:900,...t.orb}),[b,M,I,w,Q,E]=d.A,B=b*Q-M*w;for(let C=0;C<x.count;C++){let P=x.x[C]-I,k=x.y[C]-E,v=(Q*P-M*k)/B,T=(-w*P+b*k)/B;if(v<0||T<0||v>=e.info.width||T>=e.info.height)continue;let D=2+1.5*(2**(x.level[C]/2)-1),R=-1;for(let O=-1;O<=1&&R<0;O++)for(let N=-1;N<=1&&R<0;N++)for(let W of c.get(a(v+N*s,T+O*s))??[]){let S=l[W];if(Math.hypot(S.x-v,S.y-T)<=D){R=W;break}}let L=x.descriptors.slice(32*C,32*C+32);if(R<0){l.push({x:v,y:T,n:1,views:new Set([p]),score:x.score[C],desc:[L]});let O=a(v,T);c.set(O,[...c.get(O)??[],l.length-1])}else{let O=l[R];O.views.add(p),O.n++,O.score=Math.max(O.score,x.score[C]),O.desc.push(L)}}});let i=t.maxPoints??700,A=t.descriptorsPerPoint??3,u=l.map((d,p)=>({g:d,i:p})).sort((d,p)=>p.g.views.size-d.g.views.size||p.g.score-d.g.score||d.i-p.i).slice(0,i).map(d=>d.g),m=new Float32Array(2*u.length),h=new Uint8Array(u.length),g=[],y=[];u.forEach((d,p)=>{m[2*p]=d.x,m[2*p+1]=d.y,h[p]=d.views.size;let x=[];for(let b of d.desc){if(x.length>=A)break;x.every(M=>Fe(M,0,b,0)>24)&&x.push(b)}for(let b of x)g.push(b),y.push(p)});let f=new Uint8Array(32*g.length);return g.forEach((d,p)=>f.set(d,32*p)),{info:e.info,points:m,repeatability:h,descriptors:f,descriptorPoint:Uint32Array.from(y)}}function ot(e,t,r){let n=e[6]*t+e[7]*r+e[8];return Math.abs(n)<1e-12?[NaN,NaN]:[(e[0]*t+e[1]*r+e[2])/n,(e[3]*t+e[4]*r+e[5])/n]}function at(e,t,r){return[ot(e,0,0),ot(e,t,0),ot(e,t,r),ot(e,0,r)]}function pt(e,t,r){for(let n=0;n<r;n++){let o=n,l=Math.abs(e[n*r+n]);for(let s=n+1;s<r;s++){let a=Math.abs(e[s*r+n]);a>l&&(l=a,o=s)}if(l<1e-12)return!1;if(o!==n){for(let a=0;a<r;a++){let i=e[n*r+a];e[n*r+a]=e[o*r+a],e[o*r+a]=i}let s=t[n];t[n]=t[o],t[o]=s}let c=e[n*r+n];for(let s=n+1;s<r;s++){let a=e[s*r+n]/c;if(a!==0){for(let i=n;i<r;i++)e[s*r+i]-=a*e[n*r+i];t[s]-=a*t[n]}}}for(let n=r-1;n>=0;n--){let o=t[n];for(let l=n+1;l<r;l++)o-=e[n*r+l]*t[l];t[n]=o/e[n*r+n]}return t.every(Number.isFinite)}function Xt(e,t){let r=new Float64Array(e),n=new Float64Array(t*t);for(let o=0;o<t;o++)n[o*t+o]=1;for(let o=0;o<t;o++){let l=o;for(let s=o+1;s<t;s++)Math.abs(r[s*t+o])>Math.abs(r[l*t+o])&&(l=s);if(Math.abs(r[l*t+o])<1e-15)return null;if(l!==o)for(let s=0;s<t;s++){let a=r[o*t+s];r[o*t+s]=r[l*t+s],r[l*t+s]=a,a=n[o*t+s],n[o*t+s]=n[l*t+s],n[l*t+s]=a}let c=r[o*t+o];for(let s=0;s<t;s++)r[o*t+s]/=c,n[o*t+s]/=c;for(let s=0;s<t;s++){if(s===o)continue;let a=r[s*t+o];if(a!==0)for(let i=0;i<t;i++)r[s*t+i]-=a*r[o*t+i],n[s*t+i]-=a*n[o*t+i]}}return n}function zt(e,t){let r=new Float64Array(e),n=new Float64Array(t*t);for(let c=0;c<t;c++)n[c*t+c]=1;for(let c=0;c<50;c++){let s=0;for(let a=0;a<t;a++)for(let i=a+1;i<t;i++)s+=r[a*t+i]**2;if(s<1e-24)break;for(let a=0;a<t;a++)for(let i=a+1;i<t;i++){let A=r[a*t+i];if(Math.abs(A)<1e-300)continue;let u=(r[i*t+i]-r[a*t+a])/(2*A),m=Math.sign(u||1)/(Math.abs(u)+Math.sqrt(u*u+1)),h=1/Math.sqrt(m*m+1),g=m*h;for(let y=0;y<t;y++){let f=r[y*t+a],d=r[y*t+i];r[y*t+a]=h*f-g*d,r[y*t+i]=g*f+h*d}for(let y=0;y<t;y++){let f=r[a*t+y],d=r[i*t+y];r[a*t+y]=h*f-g*d,r[i*t+y]=g*f+h*d}for(let y=0;y<t;y++){let f=n[y*t+a],d=n[y*t+i];n[y*t+a]=h*f-g*d,n[y*t+i]=g*f+h*d}}}let o=0;for(let c=1;c<t;c++)r[c*t+c]<r[o*t+o]&&(o=c);let l=new Float64Array(t);for(let c=0;c<t;c++)l[c]=n[c*t+o];return l}function Ft(e,t){let r=new Float64Array(9);for(let n=0;n<3;n++)for(let o=0;o<3;o++)r[n*3+o]=e[n*3]*t[o]+e[n*3+1]*t[3+o]+e[n*3+2]*t[6+o];return r}function vt(e){let[t,r,n,o,l,c,s,a,i]=[e[0],e[1],e[2],e[3],e[4],e[5],e[6],e[7],e[8]],A=[l*i-c*a,n*a-r*i,r*c-n*l,c*s-o*i,t*i-n*s,n*o-t*c,o*a-l*s,r*s-t*a,t*l-r*o],u=t*A[0]+r*A[3]+n*A[6];return!Number.isFinite(u)||Math.abs(u)<1e-300?null:Float64Array.from(A,m=>m/u)}function Vt(e){let t=e[8];if(!Number.isFinite(t)||Math.abs(t)<1e-12)return null;for(let r=0;r<9;r++)e[r]/=t;return e.every(Number.isFinite)?e:null}function wt(e,t,r){let n=0,o=0;for(let c=0;c<r;c++){let s=t[c];n+=e[2*s],o+=e[2*s+1]}n/=r,o/=r;let l=0;for(let c=0;c<r;c++){let s=t[c];l+=Math.hypot(e[2*s]-n,e[2*s+1]-o)}return l/=r,l<1e-9?null:[Math.SQRT2/l,n,o]}function Zt(e,t,r){let[n,o,l]=t,[c,s,a]=r,i=[n,0,-n*o,0,n,-n*l,0,0,1],A=[1/c,0,s,0,1/c,a,0,0,1];return Vt(Ft(Ft(A,e),i))}function Pt(e,t,r,n,o){if(n<4)return null;let l=wt(e,r,n),c=wt(t,r,n);if(!l||!c)return null;let s=new Float64Array(81),a=new Float64Array(9);for(let i=0;i<n;i++){let A=r[i],u=o?o[A]:1;if(u<=0)continue;let m=l[0]*(e[2*A]-l[1]),h=l[0]*(e[2*A+1]-l[2]),g=c[0]*(t[2*A]-c[1]),y=c[0]*(t[2*A+1]-c[2]);for(let f=0;f<2;f++){f===0?a.set([m,h,1,0,0,0,-g*m,-g*h,-g]):a.set([0,0,0,m,h,1,-y*m,-y*h,-y]);for(let d=0;d<9;d++){let p=a[d]*u;if(p!==0)for(let x=d;x<9;x++)s[d*9+x]+=p*a[x]}}}for(let i=0;i<9;i++)for(let A=0;A<i;A++)s[i*9+A]=s[A*9+i];return Zt(zt(s,9),l,c)}var It=new Float64Array(64),bt=new Float64Array(8);function st(e,t,r){let n=wt(e,r,4),o=wt(t,r,4);if(!n||!o)return null;It.fill(0);for(let l=0;l<4;l++){let c=r[l],s=n[0]*(e[2*c]-n[1]),a=n[0]*(e[2*c+1]-n[2]),i=o[0]*(t[2*c]-o[1]),A=o[0]*(t[2*c+1]-o[2]);It.set([s,a,1,0,0,0,-i*s,-i*a],16*l),It.set([0,0,0,s,a,1,-A*s,-A*a],16*l+8),bt[2*l]=i,bt[2*l+1]=A}return pt(It,bt,8)?Zt([...bt,1],n,o):null}function $t(e,t,r,n){let o=e[6]*t+e[7]*r+e[8];return o>1e-12?(n[0]=(e[0]*t+e[1]*r+e[2])/o,n[1]=(e[3]*t+e[4]*r+e[5])/o,!0):!1}var J=new Float64Array(8),Ut=new Float64Array(2);function it(e,t,r,n=64,o=30){let l=[0,t,t,0],c=[0,0,r,r];for(let A=0;A<4;A++){if(!$t(e,l[A],c[A],Ut))return!1;J[2*A]=Ut[0],J[2*A+1]=Ut[1]}let s=0,a=1/0,i=0;for(let A=0;A<4;A++){let u=J[2*A],m=J[2*A+1],h=J[(2*A+2)%8],g=J[(2*A+3)%8],y=J[(2*A+4)%8],f=J[(2*A+5)%8];if((h-u)*(f-g)-(g-m)*(y-h)<=0)return!1;s+=u*g-h*m;let d=Math.hypot(h-u,g-m);a=Math.min(a,d),i=Math.max(i,d)}return s/2>=n&&i<=o*Math.max(a,1e-9)}function Tt(e,t,r){let n=[];for(let i=0;i<r;i++)(!t||t[i])&&n.push([e[2*i],e[2*i+1]]);if(n.length<3)return 0;n.sort((i,A)=>i[0]-A[0]||i[1]-A[1]);let o=(i,A,u)=>(A[0]-i[0])*(u[1]-i[1])-(A[1]-i[1])*(u[0]-i[0]),l=[];for(let i of n){for(;l.length>=2&&o(l.at(-2),l.at(-1),i)<=0;)l.pop();l.push(i)}let c=[];for(let i=n.length-1;i>=0;i--){let A=n[i];for(;c.length>=2&&o(c.at(-2),c.at(-1),A)<=0;)c.pop();c.push(A)}let s=[...l.slice(0,-1),...c.slice(0,-1)],a=0;for(let i=0;i<s.length;i++){let A=s[i],u=s[(i+1)%s.length];a+=A[0]*u[1]-u[0]*A[1]}return Math.abs(a)/2}var ve=(e,t)=>e<=t?1:t/e;function Rt(e,t,r,n,o,l={}){let c=l.iterations??8,s=l.huberPx??1.5,a=Float64Array.from(e.subarray(0,8)),i=.001,A=new Float64Array(64),u=new Float64Array(8),m=new Float64Array(8),h=new Float64Array(8),g=b=>{let M=0;for(let I=0;I<o;I++){if(!n[I])continue;let w=t[2*I],Q=t[2*I+1],E=b[6]*w+b[7]*Q+1;if(!(E>1e-12))return 1/0;let B=(b[0]*w+b[1]*Q+b[2])/E-r[2*I],C=(b[3]*w+b[4]*Q+b[5])/E-r[2*I+1],P=Math.hypot(B,C);M+=P<=s?P*P:2*s*P-s*s}return M},y=g(a);for(let b=0;b<c;b++){A.fill(0),u.fill(0);for(let I=0;I<o;I++){if(!n[I])continue;let w=t[2*I],Q=t[2*I+1],E=a[6]*w+a[7]*Q+1,B=(a[0]*w+a[1]*Q+a[2])/E,C=(a[3]*w+a[4]*Q+a[5])/E,P=B-r[2*I],k=C-r[2*I+1],v=ve(Math.hypot(P,k),s);m.set([w/E,Q/E,1/E,0,0,0,-B*w/E,-B*Q/E]),h.set([0,0,0,w/E,Q/E,1/E,-C*w/E,-C*Q/E]);for(let T=0;T<8;T++){u[T]+=v*(m[T]*P+h[T]*k);for(let D=T;D<8;D++)A[T*8+D]+=v*(m[T]*m[D]+h[T]*h[D])}}for(let I=0;I<8;I++)for(let w=0;w<I;w++)A[I*8+w]=A[w*8+I];let M=!1;for(let I=0;I<4&&!M;I++){let w=new Float64Array(A);for(let C=0;C<8;C++)w[C*9]*=1+i;let Q=Float64Array.from(u,C=>-C);if(!pt(w,Q,8)){i*=10;continue}let E=Float64Array.from(a,(C,P)=>C+Q[P]),B=g(E);B<y?(a=E,y=B,i=Math.max(1e-7,i/10),M=!0):i*=10}if(!M)break}let f=0,d=0;A.fill(0);for(let b=0;b<o;b++){if(!n[b])continue;let M=t[2*b],I=t[2*b+1],w=a[6]*M+a[7]*I+1,Q=(a[0]*M+a[1]*I+a[2])/w,E=(a[3]*M+a[4]*I+a[5])/w;d+=(Q-r[2*b])**2+(E-r[2*b+1])**2,f++,m.set([M/w,I/w,1/w,0,0,0,-Q*M/w,-Q*I/w]),h.set([0,0,0,M/w,I/w,1/w,-E*M/w,-E*I/w]);for(let B=0;B<8;B++)for(let C=0;C<8;C++)A[B*8+C]+=m[B]*m[C]+h[B]*h[C]}let p=f>4?d/(2*f-8):d,x=Xt(A,8);return{H:Float64Array.from([...a,1]),rmsPx:f?Math.sqrt(d/f):1/0,paramCovariance:x?x.map(b=>b*p):null}}function Pe(e){let t=e>>>0||625341585;return()=>(t^=t<<13,t>>>=0,t^=t>>>17,t^=t<<5,t>>>=0,t)}function Gt(e,t,r,n,o,l){let c=0,s=0;for(let a=0;a<n;a++){let i=t[2*a],A=t[2*a+1],u=e[6]*i+e[7]*A+e[8],m=o;if(u>1e-12){let g=(e[0]*i+e[1]*A+e[2])/u-r[2*a],y=(e[3]*i+e[4]*A+e[5])/u-r[2*a+1];m=Math.min(o,g*g+y*y)}let h=m<o;l[a]=h?1:0,h&&s++,c+=m}return{score:c,inliers:s}}function Te(e,t){let r=0;for(let o=0;o<t;o++)e[o]&&r++;let n=new Int32Array(r);for(let o=0,l=0;o<t;o++)e[o]&&(n[l++]=o);return n}function xt(e,t,r,n){let o=(e[2*r]-e[2*t])*(e[2*n+1]-e[2*t+1])-(e[2*r+1]-e[2*t+1])*(e[2*n]-e[2*t]);return Math.abs(o)<.001*(1+Math.abs(e[2*t])+Math.abs(e[2*r]))}function kt(e,t,r,n,o,l={}){let c=l.thresholdPx??3,s=c*c,a=l.confidence??.995,i=l.maxIterations??500,A=l.minInliers??12,u=l.minInlierRatio??.2,m=l.minCoverage??.08;if(r<Math.max(4,A))return{accepted:!1,reason:"INSUFFICIENT_MATCHES",iterations:0};let h=Pe(l.seed??12345),g=new Uint8Array(r),y=new Uint8Array(r),f=null,d=1/0,p=0,x=i,b=4,M=i;for(let v=0;v<4;v++)M*=(b-v)/(r-v);let I=1,w=new Int32Array(4),Q=0,E=v=>{if(!v||!it(v,n,o))return;let{score:T,inliers:D}=Gt(v,e,t,r,s,g);if(T>=d)return;f=v,d=T,p=D,y.set(g);for(let O=0;O<3&&p>=8;O++){let N=Pt(e,t,Te(y,r),p);if(!N||!it(N,n,o))break;let W=Gt(N,e,t,r,s,g);if(W.score>=d)break;f=N,d=W.score,p=W.inliers,y.set(g)}let R=p/r,L=Math.log(1-a)/Math.log(Math.max(1e-12,1-R**4));x=Math.min(i,Math.max(16,Math.ceil(L)))};for(;Q<x;){if(Q++,Q>=I&&b<r){let W=M*(b+1)/(b+1-4);b++,I+=Math.ceil(W-M),M=W}let v=I>Q?b-1:b,T=0;I>Q&&(w[T++]=b-1);let D=0;for(;T<4&&D++<64;){let W=h()%v,S=!1;for(let U=0;U<T;U++)w[U]===W&&(S=!0);S||(w[T++]=W)}if(T<4)continue;let[R,L,O,N]=[w[0],w[1],w[2],w[3]];xt(e,R,L,O)||xt(e,R,L,N)||xt(e,R,O,N)||xt(e,L,O,N)||E(st(e,t,w))}if(!f||p<A)return{accepted:!1,reason:"NO_MODEL",iterations:Q};let B=f,C=Rt(B,e,t,y,r,{huberPx:c/2});for(let v=0;v<2;v++){if(!it(C.H,n,o))return{accepted:!1,reason:"IMPLAUSIBLE_MODEL",iterations:Q};B=C.H,p=Gt(B,e,t,r,s,y).inliers,v===0&&(C=Rt(B,e,t,y,r,{huberPx:c/2}))}let P=p/r;if(p<A||P<u)return{accepted:!1,reason:"LOW_INLIER_SUPPORT",iterations:Q};let k=Tt(e,y,r)/(n*o);return k<m?{accepted:!1,reason:"LOW_SPATIAL_COVERAGE",iterations:Q}:{accepted:!0,H:B,mask:y,inliers:p,ratio:P,rmsPx:C.rmsPx,coverage:k,paramCovariance:C.paramCovariance,iterations:Q}}var te=844385612;function Re(e,t,r){if(e.width===t&&e.height===r)return e;let n=new Uint8Array(t*r),o=e.width/t,l=e.height/r;for(let c=0;c<r;c++){let s=Math.min(e.height-1.001,Math.max(0,(c+.5)*l-.5)),a=Math.floor(s),i=s-a;for(let A=0;A<t;A++){let u=Math.min(e.width-1.001,Math.max(0,(A+.5)*o-.5)),m=Math.floor(u),h=u-m,g=a*e.stride+m,y=e.data,f=y[g]+h*(y[g+1]-y[g]),d=y[g+e.stride]+h*(y[g+e.stride+1]-y[g+e.stride]);n[c*t+A]=Math.round(f+i*(d-f))}}return{width:t,height:r,stride:t,data:n}}function Ge(e,t){let r=Math.max(e.width,e.height)/t;if(r<=1)return e;let n=Math.round(e.width/r),o=Math.round(e.height/r),l=new Uint8Array(n*o);for(let c=0;c<o;c++)for(let s=0;s<n;s++){let a=Math.floor(s*r),i=Math.max(a+1,Math.floor((s+1)*r)),A=Math.floor(c*r),u=Math.max(A+1,Math.floor((c+1)*r)),m=0;for(let h=A;h<u;h++)for(let g=a;g<i;g++)m+=e.data[h*e.stride+g];l[c*n+s]=Math.round(m/((i-a)*(u-A)))}return{width:n,height:o,stride:n,data:l}}async function ct(e,t,r={}){let n=Ge(t,r.templateMaxEdge??480),o=new TextEncoder().encode(e.info.id),l=e.points.length/2,c=e.descriptorPoint.length,s=10+o.length+8+8+8*l+l+4*c+32*c+4+n.width*n.height,a=new Uint8Array(s+32),i=new DataView(a.buffer),A=0;i.setUint32(A,te,!0),i.setUint16(A+4,2,!0),i.setUint16(A+6,dt,!0),A+=8,i.setUint16(A,o.length,!0),a.set(o,A+2),A+=2+o.length,i.setUint16(A,e.info.width,!0),i.setUint16(A+2,e.info.height,!0),i.setFloat32(A+4,e.info.physicalWidthMeters??NaN,!0),A+=8,i.setUint32(A,l,!0),i.setUint32(A+4,c,!0),A+=8;for(let m=0;m<2*l;m++,A+=4)i.setFloat32(A,e.points[m],!0);a.set(e.repeatability,A),A+=l;for(let m=0;m<c;m++,A+=4)i.setUint32(A,e.descriptorPoint[m],!0);a.set(e.descriptors,A),A+=32*c,i.setUint16(A,n.width,!0),i.setUint16(A+2,n.height,!0),A+=4;for(let m=0;m<n.height;m++)a.set(n.data.subarray(m*n.stride,m*n.stride+n.width),A+m*n.width);A+=n.width*n.height;let u=new Uint8Array(await crypto.subtle.digest("SHA-256",a.subarray(0,A)));return a.set(u,A),a}async function Z(e){let t=new DataView(e.buffer,e.byteOffset,e.byteLength),r=M=>{throw new Error(`Invalid target asset: ${M}`)};(e.length<64||t.getUint32(0,!0)!==te||t.getUint16(4,!0)!==2)&&r("header");let n=t.getUint16(6,!0)&255;n!==dt&&r(`descriptor pattern v${n}, this engine uses v${dt}; recompile the target`);let o=e.subarray(0,e.length-32);new Uint8Array(await crypto.subtle.digest("SHA-256",new Uint8Array(o))).every((M,I)=>M===e[e.length-32+I])||r("checksum");let c=8,s=t.getUint16(c,!0),a=new TextDecoder().decode(e.subarray(c+2,c+2+s));c+=2+s;let i=t.getUint16(c,!0),A=t.getUint16(c+2,!0),u=t.getFloat32(c+4,!0);c+=8;let m=t.getUint32(c,!0),h=t.getUint32(c+4,!0);c+=8;let g=new Float32Array(2*m);for(let M=0;M<2*m;M++,c+=4)g[M]=t.getFloat32(c,!0);let y=e.slice(c,c+m);c+=m;let f=new Uint32Array(h);for(let M=0;M<h;M++,c+=4)f[M]=t.getUint32(c,!0);let d=e.slice(c,c+32*h);c+=32*h;let p=t.getUint16(c,!0),x=t.getUint16(c+2,!0);c+=4;let b={width:p,height:x,stride:p,data:e.slice(c,c+p*x)};return{compiled:{info:{id:a,width:i,height:A,physicalWidthMeters:Number.isNaN(u)?null:u},points:g,repeatability:y,descriptors:d,descriptorPoint:f},template:Re(b,i,A)}}function Ot(e,t){let r=e.length/32,n=t.length/32,o=new Uint32Array(3*r),l=new Uint32Array(e.buffer,e.byteOffset,e.length/4),c=new Uint32Array(t.buffer,t.byteOffset,t.length/4);for(let s=0;s<r;s++){let a=257,i=257,A=4294967295,u=8*s;for(let m=0;m<n;m++){let h=8*m,g=0;for(let y=0;y<8;y++){let f=(l[u+y]^c[h+y])>>>0;f-=f>>>1&1431655765,f=(f&858993459)+(f>>>2&858993459),g+=(f+(f>>>4)&252645135)*16843009>>>24}g<a?(i=a,a=g,A=m):g<i&&(i=g)}o[3*s]=A,o[3*s+1]=a,o[3*s+2]=i}return o}function Dt(e,t,r){let n=e.length/32,o=t.length/32,l=new Uint32Array(3*n),c=new Uint32Array(e.buffer,e.byteOffset,e.length/4),s=new Uint32Array(t.buffer,t.byteOffset,t.length/4);for(let a=0;a<n;a++){let i=257,A=4294967295,u=257,m=4294967295,h=8*a;for(let g=0;g<o;g++){let y=8*g,f=0;for(let p=0;p<8;p++){let x=(c[h+p]^s[y+p])>>>0;x-=x>>>1&1431655765,x=(x&858993459)+(x>>>2&858993459),f+=(x+(x>>>4)&252645135)*16843009>>>24}let d=r[g];d===A?f<i&&(i=f,m=g):f<i?(u=i,i=f,A=d,m=g):f<u&&(u=f)}l[3*a]=m,l[3*a+1]=i,l[3*a+2]=u}return l}var Et=class e{constructor(t){F(this,"backend");F(this,"kernel");this.kernel=t,this.backend=t?"wasm-simd":"js"}static create(t={}){let r=t.allowWasm===!1?null:ut();if(!r)return new e(null);try{let n=new e(r);return n.selfTest()?n:new e(null)}catch{return new e(null)}}selfTest(){let t=19088743,r=A=>Uint8Array.from({length:A},()=>(t^=t<<13,t^=t>>>17,t^=t<<5,t&255)),n=r(1184),o=new Uint8Array(352);o.set(r(320)),o.set(n.subarray(160,192),320);let l=Ot(o,n),c=this.nearest2(o,n),s=Uint32Array.from({length:37},(A,u)=>u>>1),a=Dt(o,n,s),i=this.nearest2Grouped(o,n,s);return c.length===l.length&&c.every((A,u)=>A===l[u])&&i.length===a.length&&i.every((A,u)=>A===a[u])}nearest2(t,r){let n=this.kernel;if(!n)return Ot(t,r);let o=t.length/32,l=r.length/32;n.reset_heap();let c=n.reserve(t.length),s=n.reserve(r.length),a=n.reserve(12*o);return!c||!s||!a?Ot(t,r):(new Uint8Array(n.memory.buffer).set(t,c),new Uint8Array(n.memory.buffer).set(r,s),n.nearest2(c,o,s,l,a),new Uint32Array(n.memory.buffer.slice(a,a+12*o)))}nearest2Grouped(t,r,n){let o=this.kernel;if(!o)return Dt(t,r,n);let l=t.length/32;o.reset_heap();let c=o.reserve(t.length),s=o.reserve(r.length),a=o.reserve(n.byteLength),i=o.reserve(12*l);if(!c||!s||!a||!i)return Dt(t,r,n);let A=o.memory.buffer;return new Uint8Array(A).set(t,c),new Uint8Array(A).set(r,s),new Uint8Array(A).set(new Uint8Array(n.buffer,n.byteOffset,n.byteLength),a),o.nearest2_grouped(c,l,s,a,r.length/32,i),new Uint32Array(o.memory.buffer.slice(i,i+12*l))}};var ke=e=>Math.max(700,Math.round(700*e.width*e.height/(640*360))),Oe=36e4;function De(e,t,r,n){let o=Math.min(r,3,Math.sqrt(Oe/(t.width*t.height)));if(o<1.2)return null;let l=Math.round(t.width*o),c=Math.round(t.height*o),s=new Uint8Array(l*c),a=e.width-1.001,i=e.height-1.001;for(let u=0;u<c;u++){let m=Math.min(i,Math.max(.5,t.y+(u+.5)/o));for(let h=0;h<l;h++)s[u*l+h]=Math.round(tt(e,Math.min(a,Math.max(.5,t.x+(h+.5)/o)),m))}let A=rt({width:l,height:c,stride:l,data:s},{budget:500,...n});return{...A,x:A.x.map(u=>t.x+(u+.5)/o-.5),y:A.y.map(u=>t.y+(u+.5)/o-.5)}}var lt=class{constructor(t={}){F(this,"info",{id:"C1.orb-views",layer:"L2",route:"light",determinism:"bit-exact",requires:[]});F(this,"options");F(this,"matcher");F(this,"targets",[]);this.options=t,this.matcher=Et.create(t.allowWasm===!1?{allowWasm:!1}:{})}get backend(){return this.matcher.backend}probe(t){return{isAvailable:!0}}async load(t){let r=[];this.targets=[];for(let n of t){let o=performance.now(),l=nt(n,this.options.compile),c=await ct(l,n.image,{templateMaxEdge:this.options.templateMaxEdge??480}),s=await Z(c);this.targets.push(s.compiled),r.push({id:n.info.id,compileUs:(performance.now()-o)*1e3,serialized:c,template:s.template})}return{targets:r}}async loadEncoded(t){let r=[];this.targets=[];for(let n of t){let o=performance.now(),l=await Z(n);this.targets.push(l.compiled),r.push({id:l.compiled.info.id,compileUs:(performance.now()-o)*1e3,serialized:n,template:l.template,info:l.compiled.info})}return{targets:r}}async detect(t,r={}){if(t.image.kind!=="cpu-gray")throw new Error("OrbDetector needs cpu-gray frames");let n=r.roi??null,o=(n&&(r.upscale??1)>1?De(t.image.image,n,r.upscale,this.options.orb??{}):null)??rt(t.image.image,{budget:n?500:ke(t.image.image),...this.options.orb,roi:n});if(o.count<8)return[];let l=this.options.ratio??.85,c=this.options.maxDistance??80,s=[];for(let a of this.targets){let i=this.matcher.nearest2Grouped(o.descriptors,a.descriptors,a.descriptorPoint),A=new Map;for(let y=0;y<o.count;y++){let f=i[3*y+1],d=i[3*y+2];if(f>c||l<1&&f>=l*d)continue;let p=a.descriptorPoint[i[3*y]],x=f/Math.max(1,d),b=A.get(p);(!b||x<b.r)&&A.set(p,{q:y,r:x})}let u=[...A].sort((y,f)=>y[1].r-f[1].r||y[0]-f[0]);if(u.length<12)continue;let m=new Float64Array(2*u.length),h=new Float64Array(2*u.length);u.forEach(([y,{q:f}],d)=>{m[2*d]=a.points[2*y],m[2*d+1]=a.points[2*y+1],h[2*d]=o.x[f],h[2*d+1]=o.y[f]});let g=kt(m,h,u.length,a.info.width,a.info.height,{thresholdPx:this.options.thresholdPx??4,maxIterations:1e3,minInliers:12,minInlierRatio:.1,minCoverage:.05});g.accepted&&s.push({inliers:g.inliers,pose:{targetId:a.info.id,frameId:t.frameId,timeUs:t.time.captureUs,H_imageFromTarget:g.H,corners:at(g.H,a.info.width,a.info.height),covariance:null,quality:{support:g.inliers,ratio:g.ratio,residual:g.rmsPx,residualKind:"px",coverage:g.coverage},origin:"detector"}})}return s.sort((a,i)=>i.inliers-a.inliers||a.pose.targetId.localeCompare(i.pose.targetId)),s.length>1&&s[1].inliers>=.85*s[0].inliers?[]:s.map(a=>a.pose)}};var Se=48,Le=64,z=class extends Error{constructor(r,n,o){super(n,o);F(this,"code");this.name="PictureError",this.code=r}};function Ne(e,t,r){let n=Math.min(1,r/Math.max(e,t));return[Math.max(1,Math.round(e*n)),Math.max(1,Math.round(t*n))]}function He(e,t,r){let n=new Uint8Array(t*r);for(let o=0;o<n.length;o++)n[o]=77*e[4*o]+150*e[4*o+1]+29*e[4*o+2]+128>>8;return n}function ee(){try{return typeof OffscreenCanvas=="function"&&!!new OffscreenCanvas(1,1).getContext("2d")}catch{return!1}}async function re(e,t){let r;try{r=await createImageBitmap(e,{imageOrientation:"from-image"})}catch{try{r=await createImageBitmap(e)}catch(n){throw new z("DECODE_FAILED",`The picture cannot be decoded: ${String(n)}`,{cause:n})}}try{let[n,o]=Ne(r.width,r.height,t),l=r,c=r.width,s=r.height;for(;c>2*n&&s>2*o;){let u=new OffscreenCanvas(Math.ceil(c/2),Math.ceil(s/2)),m=u.getContext("2d");m.imageSmoothingQuality="high",m.drawImage(l,0,0,u.width,u.height),l=u,c=u.width,s=u.height}let i=new OffscreenCanvas(n,o).getContext("2d",{willReadFrequently:!0});i.imageSmoothingQuality="high",i.drawImage(l,0,0,n,o);let A=i.getImageData(0,0,n,o).data;return{width:n,height:o,stride:n,data:He(A,n,o)}}finally{r.close()}}async function ne(e,t){if(Math.min(t.width,t.height)<Le)throw new z("UNSUITABLE_TARGET",`The picture is too small (${t.width}×${t.height} px)`);let r=nt({info:{id:e,width:t.width,height:t.height,physicalWidthMeters:null},image:t}),n=r.points.length/2;if(n<Se)throw new z("UNSUITABLE_TARGET",`The picture has too little detail (${n} recognition points)`);return{bytes:await ct(r,t),pointCount:n}}var We=5,Ke=24,qe=.08,Mt={detail:.15,spread:.4,distinctiveness:.05,recognition:.4},Ye=[.1,.7],_e=[15,95],Je=.4,At={size:.08,tilt:.15,blur:.33,dim:.6,covered:.6},j=(e,t,r)=>e<=t?0:e>=r?1:(e-t)/(r-t),q=640,mt=360,St=500,Lt=118,je=[...[160,120,90,70].map(e=>({name:`size-${e}`,test:"size",longPx:e})),...[45,60,70].map(e=>({name:`tilt-${e}`,test:"tilt",longPx:240,tiltDeg:e})),...[6,10,16].map(e=>({name:`blur-${e}`,test:"blur",longPx:240,blurPx:e})),...[.35,.2].map(e=>({name:`dim-${e}`,test:"dim",longPx:240,gain:e})),...[[0],[1],[2],[3],[0,2],[1,3],[0,1],[2,3]].map(e=>({name:`covered-${e.join("")}`,test:"covered",longPx:240,covered:e}))].flatMap(e=>[{...e,rollDeg:8,offset:[24,12]},{...e,rollDeg:-12,offset:[-30,-8]}]),oe=[12,40],Nt={size:.25,tilt:.2,blur:.2,dim:.15,covered:.2};function Xe(e,t,r){let n=e.longPx/Math.max(t,r),o=t*n,l=r*n,c=q/2+e.offset[0],s=mt/2+e.offset[1],a=[[c-o/2,s-l/2],[c+o/2,s-l/2],[c+o/2,s+l/2],[c-o/2,s+l/2]];if(e.tiltDeg){let m=e.tiltDeg*Math.PI/180;a=a.map(([h,g])=>{let y=St+(g-s)*Math.sin(m);return[c+(h-c)*St/y,s+(g-s)*Math.cos(m)*St/y]})}let i=e.rollDeg*Math.PI/180;a=a.map(([m,h])=>[c+(m-c)*Math.cos(i)-(h-s)*Math.sin(i),s+(m-c)*Math.sin(i)+(h-s)*Math.cos(i)]);let A=Float64Array.from([0,0,t,0,t,r,0,r]),u=Float64Array.from(a.flat());return st(A,u,[0,1,2,3])}function ze(e,t,r){let n=e.levels[0],o=t.longPx/Math.max(n.width,n.height),l=Math.max(0,Math.min(e.levels.length-1,Math.floor(Math.log2(1/o)))),c=e.levels[l],s=2**l,a=vt(r),i=new Uint8Array(q*mt),A=12345,u=n.width/2,m=n.height/2;for(let h=0;h<mt;h++)for(let g=0;g<q;g++){A=A*1103515245+12345>>>0;let y=Lt+(A>>>16)%9-4,f=g+.5,d=h+.5,p=a[6]*f+a[7]*d+a[8],x=(a[0]*f+a[1]*d+a[2])/p,b=(a[3]*f+a[4]*d+a[5])/p;if(x>=0&&b>=0&&x<n.width&&b<n.height){let M=(b<m?0:2)+(x<u?0:1);if(!t.covered?.includes(M)){let I=tt(c,Math.min(c.width-.51,Math.max(.5,x/s)),Math.min(c.height-.51,Math.max(.5,b/s)));Number.isNaN(I)||(y=I)}}t.gain!==void 0&&(A=A*1103515245+12345>>>0,y=Lt+(y-Lt)*t.gain+(A>>>16)%13-6),i[h*q+g]=Math.max(0,Math.min(255,Math.round(y)))}if(t.blurPx){let h=Math.floor(t.blurPx/2),g=new Float64Array(q+1);for(let y=0;y<mt;y++){for(let f=0;f<q;f++)g[f+1]=g[f]+i[y*q+f];for(let f=0;f<q;f++){let d=Math.max(0,f-h),p=Math.min(q,f-h+t.blurPx);i[y*q+f]=Math.round((g[p]-g[d])/(p-d))}}}return{width:q,height:mt,stride:q,data:i}}async function ae(e){let t=await Z(e),{compiled:r,template:n}=t,{width:o,height:l}=r.info,c=r.points.length/2,s=[];for(let U=0;U<c;U++)r.repeatability[U]>=We&&s.push(U);let a=new Float32Array(2*s.length);s.forEach((U,G)=>{a[2*G]=r.points[2*U],a[2*G+1]=r.points[2*U+1]});let i=j(s.length,40,250),A=o/l,u=Math.max(2,Math.round(4*Math.sqrt(A))),m=Math.max(2,Math.round(4/Math.sqrt(A))),h=new Array(u*m).fill(0),g=[0,0,0,0];for(let U=0;U<s.length;U++){let G=a[2*U],K=a[2*U+1];h[Math.min(m-1,Math.floor(K/l*m))*u+Math.min(u-1,Math.floor(G/o*u))]++,g[(K<l/2?0:2)+(G<o/2?0:1)]++}let y=Math.max(1,s.length),f=u*m,d=h.filter(U=>U>=Math.max(3,.3*y/f)).length,p=h.reduce((U,G)=>U+(G/y)**2,0),x=s.length?1/p/f:0,b=g.indexOf(Math.min(...g)),M=g[b]/y,I=(.5*j(d/f,.3,.85)+.5*j(x,.25,.65))*(.6+.4*j(M,.03,.15)),w=new Uint32Array(r.descriptors.buffer,r.descriptors.byteOffset,r.descriptors.length/4),Q=Array.from({length:c},()=>[]);r.descriptorPoint.forEach((U,G)=>Q[U].push(G));let E=(qe*Math.hypot(o,l))**2,B=(U,G)=>{let K=0;for(let H=0;H<8;H++){let Y=(w[8*U+H]^w[8*G+H])>>>0;Y-=Y>>>1&1431655765,Y=(Y&858993459)+(Y>>>2&858993459),K+=(Y+(Y>>>4)&252645135)*16843009>>>24}return K},C=U=>{let G=r.points[2*U],K=r.points[2*U+1];for(let H=0;H<c;H++)if(!((r.points[2*H]-G)**2+(r.points[2*H+1]-K)**2<E)){for(let Y of Q[U])for(let $ of Q[H])if(B(Y,$)<=Ke)return!0}return!1},P=s.length?s.filter(U=>!C(U)).length/s.length:0,k=j(P,.35,.7),v=new lt;await v.loadEncoded([e]);let T=Ct(n,6,16),D=[];for(let[U,G]of je.entries()){let K=Xe(G,o,l),H=at(K,o,l),$=(await v.detect({sessionId:"rate",frameId:U+1,time:{captureUs:U,receiveUs:U,source:"replay"},image:{kind:"cpu-gray",image:ze(T,G,K)}})).find(ie=>ie.corners.reduce((ce,Ht,Wt)=>ce+Math.hypot(Ht[0]-H[Wt][0],Ht[1]-H[Wt][1]),0)/4<.08*G.longPx);D.push({name:G.name,test:G.test,found:!!$,margin:$?j($.quality.support,oe[0],oe[1]):0})}let R=Object.fromEntries(Object.keys(Nt).map(U=>{let G=D.filter(K=>K.test===U);return[U,G.reduce((K,H)=>K+H.margin,0)/G.length]})),L=j(Object.keys(Nt).reduce((U,G)=>U+Nt[G]*R[G],0),...Ye),O=100*(Mt.detail*i+Mt.spread*I+Mt.distinctiveness*k+Mt.recognition*L),N=Math.min(.5+4.5*j(O,..._e),1+4*Math.min(I,L)**Je),W=Math.min(5,Math.max(.5,Math.round(N*2)/2)),S=[];return L<.4&&S.push("HARD_TO_FIND"),I<.6&&(d/f<.6||x<.45)&&S.push("CONCENTRATED"),M<.08&&S.push("WEAK_QUARTER"),i<.5&&S.push("FEW_DETAILS"),k<.4&&S.push("REPETITIVE"),R.size<At.size&&S.push("FAILS_SMALL"),R.tilt<At.tilt&&S.push("FAILS_TILTED"),R.blur<At.blur&&S.push("FAILS_BLURRED"),R.dim<At.dim&&S.push("FAILS_DIM"),R.covered<At.covered&&S.push("FAILS_COVERED"),{stars:W,score:Math.round(20*N),factors:{detail:i,spread:I,distinctiveness:k,recognition:L},metrics:{robustPoints:s.length,grid:{columns:u,rows:m,counts:h},populatedCells:d,evenness:x,weakestQuarter:{index:b,share:M},distinctShare:P,tests:R,views:D},advice:S,points:a,width:o,height:l}}var Bt=class extends Error{constructor(r,n){super(n);F(this,"code");this.code=r}};async function Ve(e){if(e.bytes)return new Uint8Array(e.bytes);let t;if(e.gray)t={width:e.gray.width,height:e.gray.height,stride:e.gray.width,data:new Uint8Array(e.gray.data)};else if(ee())t=await re(e.image,e.maxEdgePx);else return null;return(await ne("rated",t)).bytes}function se(e){let t=Promise.resolve();e.addEventListener("message",r=>{let n=r.data;n.type==="rate"&&(t=t.then(async()=>{try{let o=await Ve(n);if(!o){e.postMessage({type:"need-gray",id:n.id});return}let l;try{l=await ae(o)}catch(c){throw n.bytes?new Bt("INVALID_TARGET",c instanceof Error?c.message:String(c)):c}e.postMessage({type:"rated",id:n.id,rating:l},[l.points.buffer])}catch(o){e.postMessage({type:"failed",id:n.id,code:o instanceof z||o instanceof Bt?o.code:"WORKER_FAILED",message:o instanceof Error?o.message:String(o)})}}))})}se(globalThis);})();\n'],{type:"text/javascript"})),d)}var l=class{constructor(t){o(this,"worker",null);o(this,"queue",[]);o(this,"current",null);o(this,"nextId",1);o(this,"idleTimer",null);o(this,"workerUrl");this.workerUrl=t}run(t,e){return new Promise((r,i)=>{if(e?.aborted)return i(new a("ABORTED","Rating aborted"));let s={id:this.nextId++,request:t,resolve:r,reject:i};e?.addEventListener("abort",()=>{let u=this.queue.indexOf(s);u>=0&&this.queue.splice(u,1),this.current===s&&(this.current=null,this.pump()),i(new a("ABORTED","Rating aborted"))},{once:!0}),this.queue.push(s),this.pump()})}pump(){if(this.idleTimer&&(clearTimeout(this.idleTimer),this.idleTimer=null),this.current)return;let t=this.queue.shift();if(!t){this.idleTimer=setTimeout(()=>this.close(),b);return}try{this.worker??=this.spawn()}catch(e){for(let r of[t,...this.queue.splice(0)])r.reject(e instanceof a?e:new a("WORKER_FAILED",String(e)));return}this.current=t,this.worker.postMessage({...t.request,id:t.id})}spawn(){let t;try{t=new Worker(this.workerUrl??T(),{name:"LiveMira rating"})}catch(e){throw e instanceof a?e:new a("WORKER_FAILED","Cannot start the rating Worker (a CSP may forbid blob: Workers)",{cause:e})}return t.onmessage=e=>{this.answer(e.data)},t.onerror=e=>{e.preventDefault(),t.terminate(),this.worker===t&&(this.worker=null);let r=this.current;this.current=null,r?.reject(new a("WORKER_FAILED",`The rating Worker failed: ${e.message}`)),this.pump()},t}async answer(t){let e=this.current;if(!(!e||e.id!==t.id)){if(t.type==="need-gray"){try{let r=await h(e.request.image,e.request.maxEdgePx);if(this.current!==e)return;this.worker.postMessage({...e.request,id:e.id,image:null,gray:r},[r.data])}catch(r){this.current=null,e.reject(r instanceof c?new a(r.code,r.message,{cause:r.cause}):new a("DECODE_FAILED",String(r))),this.pump()}return}this.current=null,t.type==="rated"?e.resolve(Object.freeze(t.rating)):e.reject(new a(t.code,t.message)),this.pump()}}close(){this.idleTimer=null,this.worker?.terminate(),this.worker=null}},m=new Map,p=n=>{let t=n?String(n):"",e=m.get(t);return e||m.set(t,e=new l(n)),e};function A(n,t={}){if(!(typeof Blob<"u"&&n instanceof Blob))return Promise.reject(new a("INVALID_INPUT","rateTargetImage needs a Blob or File"));let e=t.maxEdgePx??640;return!Number.isInteger(e)||e<64||e>2048?Promise.reject(new a("INVALID_INPUT","maxEdgePx must be an integer 64..2048")):n.size===0?Promise.reject(new a("DECODE_FAILED","The picture file is empty")):p(t.workerUrl).run({type:"rate",maxEdgePx:e,image:n,gray:null,bytes:null},t.signal)}function x(n,t={}){let e=n instanceof Uint8Array?n:n?.bytes;return e instanceof Uint8Array?p(t.workerUrl).run({type:"rate",maxEdgePx:640,image:null,gray:null,bytes:e.slice().buffer},t.signal):Promise.reject(new a("INVALID_INPUT","rateTarget needs a target bundle or its bytes"))}export{g as RATING_VERSION,a as RatingError,x as rateTarget,A as rateTargetImage,y as ratingBuildInfo};
|
package/package.json
CHANGED
|
@@ -1,6 +1,42 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@livemira/target-rating",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.6.0",
|
|
4
|
+
"license": "Apache-2.0",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"sideEffects": false,
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/andy-mira/livemira.git"
|
|
10
|
+
},
|
|
11
|
+
"homepage": "https://github.com/andy-mira/livemira#readme",
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/andy-mira/livemira/issues"
|
|
14
|
+
},
|
|
15
|
+
"publishConfig": {
|
|
16
|
+
"access": "public"
|
|
17
|
+
},
|
|
18
|
+
"description": "LiveMira target rating: how well the LiveMira engine will find and keep a picture, in stars, without the engine.",
|
|
19
|
+
"keywords": [
|
|
20
|
+
"webar",
|
|
21
|
+
"image-tracking",
|
|
22
|
+
"target-rating",
|
|
23
|
+
"livemira"
|
|
24
|
+
],
|
|
25
|
+
"main": "./dist/livemira-target-rating.min.mjs",
|
|
26
|
+
"types": "./dist/livemira-target-rating.d.ts",
|
|
27
|
+
"exports": {
|
|
28
|
+
".": {
|
|
29
|
+
"types": "./dist/livemira-target-rating.d.ts",
|
|
30
|
+
"default": "./dist/livemira-target-rating.min.mjs"
|
|
31
|
+
},
|
|
32
|
+
"./worker": "./dist/livemira-target-rating-worker.min.js"
|
|
33
|
+
},
|
|
34
|
+
"files": [
|
|
35
|
+
"dist",
|
|
36
|
+
"README.md",
|
|
37
|
+
"README.zh-CN.md",
|
|
38
|
+
"CHANGELOG.md",
|
|
39
|
+
"LICENSE",
|
|
40
|
+
"NOTICE"
|
|
41
|
+
]
|
|
42
|
+
}
|