cbrowser 18.76.0 → 18.78.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.
@@ -0,0 +1,233 @@
1
+ /**
2
+ * AI attention layer — persona-aware semantic reading of the rendered page.
3
+ *
4
+ * WHAT THE EXISTING TWO LAYERS CANNOT SEE
5
+ *
6
+ * Layer 1 (bottom-up saliency) knows where colour and texture pop. Layer 2
7
+ * (DOM semantics) knows element TYPE — CTA, heading, nav — times persona
8
+ * priority, plus keyword overlap with the goal. Neither knows what the content
9
+ * MEANS to this particular person. "Start free trial" and "Contact enterprise
10
+ * sales" are both CTAs of identical type and similar contrast; to a
11
+ * price-anxious first-timer they are not remotely the same object. Nor do the
12
+ * first two layers read text baked into images, judge compositional hierarchy
13
+ * (size, whitespace, alignment), or model reading order.
14
+ *
15
+ * That is the headroom this layer addresses: meaning, weighted by what this
16
+ * persona values.
17
+ *
18
+ * WHY IT IS A SEPARATE LAYER AND NOT A BLEND
19
+ *
20
+ * The deliberate design choice, and the one worth defending. Layers 1 and 2 are
21
+ * deterministic — the same screenshot yields the same map, which is what makes
22
+ * visual baselines and regression comparisons meaningful. An LLM's opinion is
23
+ * not deterministic, and silently averaging it into the same `saliency` number
24
+ * would make every downstream metric unreproducible while looking unchanged.
25
+ * This codebase has repeatedly shipped exactly that shape: a value quietly
26
+ * folded in under a name that already meant something else.
27
+ *
28
+ * So the AI regions are returned ALONGSIDE, named, with the model's own
29
+ * reasoning attached, and a caller opts in explicitly. A blended convenience
30
+ * field exists but says so in `attentionModel`.
31
+ *
32
+ * WHAT WE CANNOT CURRENTLY CLAIM
33
+ *
34
+ * There is no eye-tracking ground truth in this repository, so there is no
35
+ * measurement that says this layer makes the map more accurate — only that it
36
+ * makes it different. It is offered as an interpretive aid, and its output is
37
+ * labelled as a model's judgement rather than a measurement. Validating it
38
+ * needs a fixation dataset; until then, treat confident agreement between this
39
+ * layer and the other two as the useful signal, and disagreement as a question,
40
+ * not an answer.
41
+ *
42
+ * @copyright 2026 Alexandria Eden alexandria.shai.eden@gmail.com https://cbrowser.ai
43
+ * @license MIT
44
+ */
45
+ import { createHash } from "crypto";
46
+ import { readFileSync, existsSync, mkdirSync, writeFileSync } from "fs";
47
+ import { join } from "path";
48
+ import { homedir } from "os";
49
+ const CACHE_DIR = join(process.env.CBROWSER_DATA_DIR || join(homedir(), ".cbrowser"), "ai-attention-cache");
50
+ /**
51
+ * Cache key covers everything that can change the answer: the exact image, the
52
+ * persona, the goal, the values and the model. An LLM layer is only usable in a
53
+ * regression context if repeating a run repeats the answer — and it makes the
54
+ * layer nearly free on re-analysis of an unchanged page.
55
+ */
56
+ function cacheKey(imageBytes, persona, goal, values, model) {
57
+ const h = createHash("sha256");
58
+ h.update(imageBytes);
59
+ h.update(`|${persona}|${goal ?? ""}|${model}|`);
60
+ h.update(JSON.stringify(values ?? {}));
61
+ return h.digest("hex").slice(0, 32);
62
+ }
63
+ function readCache(key) {
64
+ try {
65
+ const p = join(CACHE_DIR, `${key}.json`);
66
+ if (!existsSync(p))
67
+ return null;
68
+ return { ...JSON.parse(readFileSync(p, "utf8")), cached: true };
69
+ }
70
+ catch {
71
+ return null;
72
+ }
73
+ }
74
+ function writeCache(key, value) {
75
+ try {
76
+ if (!existsSync(CACHE_DIR))
77
+ mkdirSync(CACHE_DIR, { recursive: true });
78
+ writeFileSync(join(CACHE_DIR, `${key}.json`), JSON.stringify(value));
79
+ }
80
+ catch {
81
+ /* cache is an optimisation; never fail the analysis for it */
82
+ }
83
+ }
84
+ /** Describe persona values in words the model can act on. */
85
+ function describeValues(values) {
86
+ if (!values)
87
+ return "No specific motivational profile supplied.";
88
+ const notable = Object.entries(values)
89
+ .filter(([, v]) => typeof v === "number" && (v >= 0.7 || v <= 0.3))
90
+ .map(([k, v]) => `${k}=${v.toFixed(2)} (${v >= 0.7 ? "high" : "low"})`);
91
+ return notable.length ? notable.join(", ") : "Values are mid-range across the board.";
92
+ }
93
+ /**
94
+ * Ask the model where this persona looks and why.
95
+ *
96
+ * Returns `unavailable` rather than throwing when there is no key or the call
97
+ * fails: this layer is additive, and an attention map without it is the same
98
+ * map the product shipped before it existed.
99
+ */
100
+ export async function analyzeAttentionWithAI(screenshotPath, persona, options = {}) {
101
+ const { getAnthropicApiKey, getAnthropicModel, isApiKeyConfigured } = await import("../cognitive/index.js");
102
+ if (!isApiKeyConfigured()) {
103
+ return {
104
+ regions: [],
105
+ narrative: "",
106
+ model: "",
107
+ cached: false,
108
+ unavailable: "No Anthropic API key configured. The AI attention layer is opt-in and keyed; the visual and semantic layers are unaffected.",
109
+ };
110
+ }
111
+ let bytes;
112
+ try {
113
+ bytes = readFileSync(screenshotPath);
114
+ }
115
+ catch {
116
+ return { regions: [], narrative: "", model: "", cached: false, unavailable: `Could not read ${screenshotPath}` };
117
+ }
118
+ const model = getAnthropicModel();
119
+ const key = cacheKey(bytes, persona, options.goal, options.values, model);
120
+ const hit = readCache(key);
121
+ if (hit)
122
+ return hit;
123
+ const dims = options.imageWidth && options.imageHeight
124
+ ? `The image is ${options.imageWidth}x${options.imageHeight} pixels.`
125
+ : "Coordinates must be in the image's own pixel space.";
126
+ const system = `You model visual attention for a specific person looking at a web page. You are not describing what is on the page — you are predicting where THIS person's eyes go, and why, given who they are.
127
+
128
+ Return ONLY a JSON object, no prose outside it:
129
+ {"regions":[{"rect":{"x":0,"y":0,"width":0,"height":0},"weight":0.0,"label":"","reasoning":""}],"narrative":""}
130
+
131
+ Rules:
132
+ - ${dims} Coordinates are top-left origin.
133
+ - weight is 0-1: how strongly this person attends, relative to everything else on the page.
134
+ - Return at most 8 regions. Fewer is better than padding.
135
+ - reasoning must name something about THIS PERSON, not the page in general. "Large red button" is a description; "a price-anxious first-timer checks cost before anything else, so the pricing link pulls first" is a prediction.
136
+ - Attend to what the DOM cannot express: what the copy MEANS to them, text inside images, compositional hierarchy, and what they will deliberately SKIP.
137
+ - narrative: 2-4 sentences on how this person reads this page.`;
138
+ const goalLine = options.goal
139
+ ? `They are trying to: ${options.goal}. Goal-relevant content should dominate.`
140
+ : `They have no specific task — this is undirected first-contact browsing.`;
141
+ const user = `Person: ${persona}
142
+ ${options.personaDescription ? `Who they are: ${options.personaDescription}` : ""}
143
+ Motivational profile: ${describeValues(options.values)}
144
+ ${goalLine}
145
+
146
+ Where do their eyes go on this page, and why?`;
147
+ try {
148
+ const { default: Anthropic } = await import("@anthropic-ai/sdk");
149
+ const client = new Anthropic({ apiKey: getAnthropicApiKey() });
150
+ const response = await client.messages.create({
151
+ model,
152
+ max_tokens: 1500,
153
+ system,
154
+ messages: [
155
+ {
156
+ role: "user",
157
+ content: [
158
+ {
159
+ type: "image",
160
+ source: { type: "base64", media_type: "image/png", data: bytes.toString("base64") },
161
+ },
162
+ { type: "text", text: user },
163
+ ],
164
+ },
165
+ ],
166
+ });
167
+ const text = response.content[0]?.type === "text" ? response.content[0].text : "";
168
+ const match = text.match(/\{[\s\S]*\}/);
169
+ if (!match) {
170
+ return { regions: [], narrative: "", model, cached: false, unavailable: "Model returned no parseable JSON" };
171
+ }
172
+ const parsed = JSON.parse(match[0]);
173
+ const result = {
174
+ // Clamp rather than trust: a hallucinated weight of 12 would silently
175
+ // dominate any blend it is fed into.
176
+ regions: (parsed.regions ?? []).slice(0, 8).map((r) => ({
177
+ rect: {
178
+ x: Math.max(0, Math.round(r.rect?.x ?? 0)),
179
+ y: Math.max(0, Math.round(r.rect?.y ?? 0)),
180
+ width: Math.max(1, Math.round(r.rect?.width ?? 1)),
181
+ height: Math.max(1, Math.round(r.rect?.height ?? 1)),
182
+ },
183
+ weight: Math.min(1, Math.max(0, Number(r.weight) || 0)),
184
+ label: String(r.label ?? "").slice(0, 120),
185
+ reasoning: String(r.reasoning ?? "").slice(0, 400),
186
+ })),
187
+ narrative: String(parsed.narrative ?? "").slice(0, 1200),
188
+ model,
189
+ cached: false,
190
+ };
191
+ writeCache(key, result);
192
+ return result;
193
+ }
194
+ catch (err) {
195
+ return {
196
+ regions: [],
197
+ narrative: "",
198
+ model,
199
+ cached: false,
200
+ unavailable: `AI attention layer failed: ${err instanceof Error ? err.message : String(err)}`,
201
+ };
202
+ }
203
+ }
204
+ /**
205
+ * Rasterise AI regions onto the analysis grid so they can be compared with, or
206
+ * blended into, the existing saliency cells.
207
+ *
208
+ * Kept separate from the fetch so a caller can display the regions and their
209
+ * reasoning WITHOUT blending — which is the recommended use, since the blend
210
+ * makes the map non-deterministic.
211
+ */
212
+ export function aiRegionsToGrid(regions, rows, cols, imageWidth, imageHeight) {
213
+ const grid = new Float64Array(rows * cols);
214
+ if (regions.length === 0 || imageWidth <= 0 || imageHeight <= 0)
215
+ return grid;
216
+ for (const region of regions) {
217
+ const c0 = Math.max(0, Math.floor((region.rect.x / imageWidth) * cols));
218
+ const c1 = Math.min(cols - 1, Math.ceil(((region.rect.x + region.rect.width) / imageWidth) * cols) - 1);
219
+ const r0 = Math.max(0, Math.floor((region.rect.y / imageHeight) * rows));
220
+ const r1 = Math.min(rows - 1, Math.ceil(((region.rect.y + region.rect.height) / imageHeight) * rows) - 1);
221
+ for (let r = r0; r <= r1; r++) {
222
+ for (let c = c0; c <= c1; c++) {
223
+ const i = r * cols + c;
224
+ // Max, not sum: overlapping regions should not compound into a hotspot
225
+ // that neither region claimed on its own.
226
+ if (region.weight > grid[i])
227
+ grid[i] = region.weight;
228
+ }
229
+ }
230
+ }
231
+ return grid;
232
+ }
233
+ //# sourceMappingURL=attention-ai-layer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"attention-ai-layer.js","sourceRoot":"","sources":["../../src/visual/attention-ai-layer.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,132 @@
1
+ /**
2
+ * Fixation corpora — human eye-tracking ground truth, in one shape.
3
+ *
4
+ * WHAT A CORPUS IS FOR HERE
5
+ *
6
+ * Three distinct uses, and conflating them is how saliency work goes wrong:
7
+ *
8
+ * 1. VALIDATION — score the shipped model against human fixations. This is
9
+ * what makes an accuracy claim sayable out loud. Study 1 did this against
10
+ * UEyes and reported AUC-Judd 0.663 / NSS 0.544 / CC 0.216.
11
+ * 2. CALIBRATION — tune model parameters (channel weights, surround radius,
12
+ * cell size) to maximise those metrics. Legitimate, but it spends the
13
+ * corpus: a model tuned on a set can no longer be honestly validated on
14
+ * that same set, which is why `split` exists below.
15
+ * 3. PRIORS — derive empirical structure from the corpus and bake it into the
16
+ * model. A centre-bias prior alone lifts AUC substantially on almost any
17
+ * dataset, because humans fixate the middle regardless of content.
18
+ *
19
+ * The interface is deliberately corpus-agnostic. UEyes is what is on disk
20
+ * today; MIT1003, CAT2000 and SALICON have the same shape (images + fixation
21
+ * points + density maps) and should not need new plumbing.
22
+ *
23
+ * WHAT UEYES GIVES THAT MOST CORPORA DO NOT
24
+ *
25
+ * - It is WEB PAGES (1980 of them), not natural scenes. Saliency models tuned on
26
+ * photographs transfer poorly to interfaces, where text and affordances
27
+ * dominate.
28
+ * - Fixation maps at 1s / 3s / 7s separately. Attention at first impression is
29
+ * not attention after consideration, and cbrowser's personas differ precisely
30
+ * on patience — an impatient persona should be scored against 1s, a thorough
31
+ * one against 7s. Scoring every persona against one horizon throws that away.
32
+ * - Per-fixation logs with duration and saccade magnitude/direction, so scanpath
33
+ * ORDER is available, not just aggregate density.
34
+ *
35
+ * @copyright 2026 Alexandria Eden alexandria.shai.eden@gmail.com https://cbrowser.ai
36
+ * @license MIT
37
+ */
38
+ import type { Fixation, SaliencyGrid } from "./saliency-metrics.js";
39
+ /** Viewing durations UEyes separates. Others may expose only one. */
40
+ export type FixationHorizon = "1s" | "3s" | "7s";
41
+ export interface CorpusStimulus {
42
+ /** Stable id — the image filename without extension. */
43
+ id: string;
44
+ /** Absolute path to the stimulus image. */
45
+ imagePath: string;
46
+ /** Ground-truth fixation density map per horizon, when the corpus has them. */
47
+ fixationMaps: Partial<Record<FixationHorizon, string>>;
48
+ }
49
+ export interface CorpusInfo {
50
+ name: string;
51
+ root: string;
52
+ stimuli: CorpusStimulus[];
53
+ horizons: FixationHorizon[];
54
+ /** Absent when the corpus ships only density maps and no raw logs. */
55
+ fixationLogDir?: string;
56
+ citation: string;
57
+ }
58
+ /**
59
+ * One participant fixation as recorded by the tracker.
60
+ *
61
+ * Coordinates are NORMALIZED 0-1 in the source logs and stay that way here.
62
+ * Converting to pixels requires the stimulus dimensions, and doing it at load
63
+ * time — before the caller says which grid they are scoring against — is how
64
+ * coordinate-space bugs get baked in. `fixationsToGrid` does the conversion at
65
+ * the point the target size is actually known.
66
+ */
67
+ export interface RawFixation {
68
+ /** 0-1, left to right. */
69
+ xNorm: number;
70
+ /** 0-1, top to bottom. */
71
+ yNorm: number;
72
+ /** Dwell in seconds. */
73
+ duration: number;
74
+ /** Seconds since stimulus onset — this is what makes horizon filtering possible. */
75
+ startTime: number;
76
+ participantId: string;
77
+ stimulusId: string;
78
+ }
79
+ /**
80
+ * Discover a UEyes-layout corpus.
81
+ *
82
+ * Returns null rather than throwing when the directory is absent: the corpus is
83
+ * 13GB and is not, and should not be, a dependency of the shipped package.
84
+ * Anything that needs it degrades to "not available" rather than failing.
85
+ */
86
+ export declare function loadUEyesCorpus(root: string): CorpusInfo | null;
87
+ /**
88
+ * Parse one Gazepoint-format tracker log.
89
+ *
90
+ * The columns that matter: MEDIA_NAME (which stimulus), FPOGX/FPOGY (gaze point
91
+ * of regard, normalized), FPOGD (duration), FPOGS (start time), FPOGV (valid).
92
+ *
93
+ * INVALID SAMPLES ARE DROPPED, not clamped. FPOGV=0 means the tracker lost the
94
+ * eye — a blink, a look away, a calibration slip. Those rows still carry
95
+ * coordinates, and treating them as fixations manufactures ground truth from
96
+ * noise. Off-screen coordinates are dropped for the same reason.
97
+ */
98
+ export declare function parseGazepointLog(logPath: string): RawFixation[];
99
+ /**
100
+ * Keep only fixations that occurred within `seconds` of stimulus onset.
101
+ *
102
+ * This is what lets a persona be scored against the horizon that matches it: an
103
+ * impatient persona compared to 1s of human viewing, a thorough one to 7s.
104
+ * Comparing every persona to the same aggregate map discards the one dimension
105
+ * this corpus was built to expose.
106
+ */
107
+ export declare function filterByHorizon(fixations: RawFixation[], seconds: number): RawFixation[];
108
+ /** Convert normalized fixations to a grid's pixel space. */
109
+ export declare function toGridFixations(fixations: RawFixation[], gridWidth: number, gridHeight: number): Fixation[];
110
+ /**
111
+ * Build a fixation density map by accumulating Gaussian blobs.
112
+ *
113
+ * Ground-truth maps ship with UEyes, but a corpus that provides only raw logs
114
+ * needs this to score CC at all. Sigma is in grid cells; the literature
115
+ * convention is roughly one degree of visual angle, and for a 32x32 analysis
116
+ * grid that lands near 1.
117
+ */
118
+ export declare function fixationsToGrid(fixations: Fixation[], width: number, height: number, sigma?: number): SaliencyGrid;
119
+ /**
120
+ * Deterministic train/test split.
121
+ *
122
+ * Calibrating on a corpus and then reporting accuracy on the same corpus is
123
+ * measuring how well the parameters were fitted, not how well the model
124
+ * generalises — and it is the single easiest way to publish a number that does
125
+ * not survive review. The split is by stimulus id hash so it is stable across
126
+ * runs and machines without storing a manifest.
127
+ */
128
+ export declare function splitCorpus(corpus: CorpusInfo, testFraction?: number): {
129
+ train: CorpusStimulus[];
130
+ test: CorpusStimulus[];
131
+ };
132
+ //# sourceMappingURL=fixation-corpus.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fixation-corpus.d.ts","sourceRoot":"","sources":["../../src/visual/fixation-corpus.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AAIH,OAAO,KAAK,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAEpE,qEAAqE;AACrE,MAAM,MAAM,eAAe,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;AAEjD,MAAM,WAAW,cAAc;IAC7B,wDAAwD;IACxD,EAAE,EAAE,MAAM,CAAC;IACX,2CAA2C;IAC3C,SAAS,EAAE,MAAM,CAAC;IAClB,+EAA+E;IAC/E,YAAY,EAAE,OAAO,CAAC,MAAM,CAAC,eAAe,EAAE,MAAM,CAAC,CAAC,CAAC;CACxD;AAED,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,cAAc,EAAE,CAAC;IAC1B,QAAQ,EAAE,eAAe,EAAE,CAAC;IAC5B,sEAAsE;IACtE,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED;;;;;;;;GAQG;AACH,MAAM,WAAW,WAAW;IAC1B,0BAA0B;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,0BAA0B;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,wBAAwB;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,oFAAoF;IACpF,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,MAAM,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI,CAgC/D;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,WAAW,EAAE,CA+ChE;AAED;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,SAAS,EAAE,WAAW,EAAE,EAAE,OAAO,EAAE,MAAM,GAAG,WAAW,EAAE,CAExF;AAED,4DAA4D;AAC5D,wBAAgB,eAAe,CAC7B,SAAS,EAAE,WAAW,EAAE,EACxB,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,GACjB,QAAQ,EAAE,CAMZ;AAED;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAC7B,SAAS,EAAE,QAAQ,EAAE,EACrB,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,EACd,KAAK,SAAM,GACV,YAAY,CAoBd;AAED;;;;;;;;GAQG;AACH,wBAAgB,WAAW,CACzB,MAAM,EAAE,UAAU,EAClB,YAAY,SAAM,GACjB;IAAE,KAAK,EAAE,cAAc,EAAE,CAAC;IAAC,IAAI,EAAE,cAAc,EAAE,CAAA;CAAE,CAarD"}
@@ -0,0 +1,212 @@
1
+ /**
2
+ * Fixation corpora — human eye-tracking ground truth, in one shape.
3
+ *
4
+ * WHAT A CORPUS IS FOR HERE
5
+ *
6
+ * Three distinct uses, and conflating them is how saliency work goes wrong:
7
+ *
8
+ * 1. VALIDATION — score the shipped model against human fixations. This is
9
+ * what makes an accuracy claim sayable out loud. Study 1 did this against
10
+ * UEyes and reported AUC-Judd 0.663 / NSS 0.544 / CC 0.216.
11
+ * 2. CALIBRATION — tune model parameters (channel weights, surround radius,
12
+ * cell size) to maximise those metrics. Legitimate, but it spends the
13
+ * corpus: a model tuned on a set can no longer be honestly validated on
14
+ * that same set, which is why `split` exists below.
15
+ * 3. PRIORS — derive empirical structure from the corpus and bake it into the
16
+ * model. A centre-bias prior alone lifts AUC substantially on almost any
17
+ * dataset, because humans fixate the middle regardless of content.
18
+ *
19
+ * The interface is deliberately corpus-agnostic. UEyes is what is on disk
20
+ * today; MIT1003, CAT2000 and SALICON have the same shape (images + fixation
21
+ * points + density maps) and should not need new plumbing.
22
+ *
23
+ * WHAT UEYES GIVES THAT MOST CORPORA DO NOT
24
+ *
25
+ * - It is WEB PAGES (1980 of them), not natural scenes. Saliency models tuned on
26
+ * photographs transfer poorly to interfaces, where text and affordances
27
+ * dominate.
28
+ * - Fixation maps at 1s / 3s / 7s separately. Attention at first impression is
29
+ * not attention after consideration, and cbrowser's personas differ precisely
30
+ * on patience — an impatient persona should be scored against 1s, a thorough
31
+ * one against 7s. Scoring every persona against one horizon throws that away.
32
+ * - Per-fixation logs with duration and saccade magnitude/direction, so scanpath
33
+ * ORDER is available, not just aggregate density.
34
+ *
35
+ * @copyright 2026 Alexandria Eden alexandria.shai.eden@gmail.com https://cbrowser.ai
36
+ * @license MIT
37
+ */
38
+ import { existsSync, readFileSync, readdirSync } from "fs";
39
+ import { join, basename, extname } from "path";
40
+ /**
41
+ * Discover a UEyes-layout corpus.
42
+ *
43
+ * Returns null rather than throwing when the directory is absent: the corpus is
44
+ * 13GB and is not, and should not be, a dependency of the shipped package.
45
+ * Anything that needs it degrades to "not available" rather than failing.
46
+ */
47
+ export function loadUEyesCorpus(root) {
48
+ const imagesDir = join(root, "images");
49
+ if (!existsSync(imagesDir))
50
+ return null;
51
+ const horizons = [];
52
+ for (const h of ["1s", "3s", "7s"]) {
53
+ if (existsSync(join(root, "saliency_maps", `fixmaps_${h}`)))
54
+ horizons.push(h);
55
+ }
56
+ const stimuli = [];
57
+ for (const file of readdirSync(imagesDir)) {
58
+ const ext = extname(file).toLowerCase();
59
+ if (ext !== ".jpg" && ext !== ".png" && ext !== ".jpeg")
60
+ continue;
61
+ const id = basename(file, ext);
62
+ const fixationMaps = {};
63
+ for (const h of horizons) {
64
+ const candidate = join(root, "saliency_maps", `fixmaps_${h}`, file);
65
+ if (existsSync(candidate))
66
+ fixationMaps[h] = candidate;
67
+ }
68
+ stimuli.push({ id, imagePath: join(imagesDir, file), fixationMaps });
69
+ }
70
+ const logDir = join(root, "eyetracker_logs");
71
+ return {
72
+ name: "UEyes",
73
+ root,
74
+ stimuli: stimuli.sort((a, b) => a.id.localeCompare(b.id)),
75
+ horizons,
76
+ fixationLogDir: existsSync(logDir) ? logDir : undefined,
77
+ citation: "Jiang et al., UEyes: Understanding Visual Saliency across User Interface Types. CHI 2023.",
78
+ };
79
+ }
80
+ /**
81
+ * Parse one Gazepoint-format tracker log.
82
+ *
83
+ * The columns that matter: MEDIA_NAME (which stimulus), FPOGX/FPOGY (gaze point
84
+ * of regard, normalized), FPOGD (duration), FPOGS (start time), FPOGV (valid).
85
+ *
86
+ * INVALID SAMPLES ARE DROPPED, not clamped. FPOGV=0 means the tracker lost the
87
+ * eye — a blink, a look away, a calibration slip. Those rows still carry
88
+ * coordinates, and treating them as fixations manufactures ground truth from
89
+ * noise. Off-screen coordinates are dropped for the same reason.
90
+ */
91
+ export function parseGazepointLog(logPath) {
92
+ const text = readFileSync(logPath, "utf8");
93
+ const lines = text.split("\n").filter((l) => l.trim().length > 0);
94
+ if (lines.length < 2)
95
+ return [];
96
+ const header = lines[0].split(",").map((h) => h.trim());
97
+ const col = (name) => header.findIndex((h) => h === name || h.startsWith(`${name}(`));
98
+ const iMedia = col("MEDIA_NAME");
99
+ const iX = col("FPOGX");
100
+ const iY = col("FPOGY");
101
+ const iD = col("FPOGD");
102
+ const iS = col("FPOGS");
103
+ const iV = col("FPOGV");
104
+ const iId = col("FPOGID");
105
+ if (iX < 0 || iY < 0)
106
+ return [];
107
+ const participantId = basename(logPath).replace(/_fixations\.csv$/i, "");
108
+ const out = [];
109
+ const seen = new Set();
110
+ for (let i = 1; i < lines.length; i++) {
111
+ const parts = lines[i].split(",");
112
+ if (iV >= 0 && parts[iV]?.trim() !== "1")
113
+ continue; // tracker lost the eye
114
+ const xNorm = Number(parts[iX]);
115
+ const yNorm = Number(parts[iY]);
116
+ if (!Number.isFinite(xNorm) || !Number.isFinite(yNorm))
117
+ continue;
118
+ if (xNorm < 0 || xNorm > 1 || yNorm < 0 || yNorm > 1)
119
+ continue; // off-stimulus
120
+ // One row per SAMPLE, many samples per fixation. Deduplicate on FPOGID so a
121
+ // long fixation counts once — otherwise dwell time silently becomes a vote
122
+ // multiplier and every metric is weighted by how long the tracker sampled.
123
+ const fixId = iId >= 0 ? parts[iId]?.trim() : undefined;
124
+ const media = iMedia >= 0 ? (parts[iMedia] ?? "").trim() : "";
125
+ const key = `${media}|${fixId ?? `${i}`}`;
126
+ if (fixId && seen.has(key))
127
+ continue;
128
+ if (fixId)
129
+ seen.add(key);
130
+ out.push({
131
+ xNorm,
132
+ yNorm,
133
+ duration: iD >= 0 ? Number(parts[iD]) || 0 : 0,
134
+ startTime: iS >= 0 ? Number(parts[iS]) || 0 : 0,
135
+ participantId,
136
+ stimulusId: media ? basename(media, extname(media)) : "",
137
+ });
138
+ }
139
+ return out;
140
+ }
141
+ /**
142
+ * Keep only fixations that occurred within `seconds` of stimulus onset.
143
+ *
144
+ * This is what lets a persona be scored against the horizon that matches it: an
145
+ * impatient persona compared to 1s of human viewing, a thorough one to 7s.
146
+ * Comparing every persona to the same aggregate map discards the one dimension
147
+ * this corpus was built to expose.
148
+ */
149
+ export function filterByHorizon(fixations, seconds) {
150
+ return fixations.filter((f) => f.startTime <= seconds);
151
+ }
152
+ /** Convert normalized fixations to a grid's pixel space. */
153
+ export function toGridFixations(fixations, gridWidth, gridHeight) {
154
+ return fixations.map((f) => ({
155
+ x: f.xNorm * gridWidth,
156
+ y: f.yNorm * gridHeight,
157
+ duration: f.duration * 1000,
158
+ }));
159
+ }
160
+ /**
161
+ * Build a fixation density map by accumulating Gaussian blobs.
162
+ *
163
+ * Ground-truth maps ship with UEyes, but a corpus that provides only raw logs
164
+ * needs this to score CC at all. Sigma is in grid cells; the literature
165
+ * convention is roughly one degree of visual angle, and for a 32x32 analysis
166
+ * grid that lands near 1.
167
+ */
168
+ export function fixationsToGrid(fixations, width, height, sigma = 1.0) {
169
+ const values = new Float64Array(width * height);
170
+ const radius = Math.max(1, Math.ceil(sigma * 3));
171
+ const twoSigmaSq = 2 * sigma * sigma;
172
+ for (const f of fixations) {
173
+ const cx = f.x;
174
+ const cy = f.y;
175
+ const x0 = Math.max(0, Math.floor(cx - radius));
176
+ const x1 = Math.min(width - 1, Math.ceil(cx + radius));
177
+ const y0 = Math.max(0, Math.floor(cy - radius));
178
+ const y1 = Math.min(height - 1, Math.ceil(cy + radius));
179
+ for (let y = y0; y <= y1; y++) {
180
+ for (let x = x0; x <= x1; x++) {
181
+ const d2 = (x + 0.5 - cx) ** 2 + (y + 0.5 - cy) ** 2;
182
+ values[y * width + x] += Math.exp(-d2 / twoSigmaSq);
183
+ }
184
+ }
185
+ }
186
+ return { values, width, height };
187
+ }
188
+ /**
189
+ * Deterministic train/test split.
190
+ *
191
+ * Calibrating on a corpus and then reporting accuracy on the same corpus is
192
+ * measuring how well the parameters were fitted, not how well the model
193
+ * generalises — and it is the single easiest way to publish a number that does
194
+ * not survive review. The split is by stimulus id hash so it is stable across
195
+ * runs and machines without storing a manifest.
196
+ */
197
+ export function splitCorpus(corpus, testFraction = 0.3) {
198
+ const hash = (s) => {
199
+ let h = 2166136261;
200
+ for (let i = 0; i < s.length; i++) {
201
+ h ^= s.charCodeAt(i);
202
+ h = Math.imul(h, 16777619);
203
+ }
204
+ return (h >>> 0) / 4294967296;
205
+ };
206
+ const train = [];
207
+ const test = [];
208
+ for (const s of corpus.stimuli)
209
+ (hash(s.id) < testFraction ? test : train).push(s);
210
+ return { train, test };
211
+ }
212
+ //# sourceMappingURL=fixation-corpus.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fixation-corpus.js","sourceRoot":"","sources":["../../src/visual/fixation-corpus.ts"],"names":[],"mappings":"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"}