@wafertools/wafermap 0.31.0 → 0.33.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/AGENTS.md +13 -13
- package/CHANGELOG.md +334 -1
- package/README.md +3 -3
- package/dist/packages/canvas-adapter/charts/chartShell.d.ts +2 -12
- package/dist/packages/canvas-adapter/charts/chartShell.js +1 -1
- package/dist/packages/canvas-adapter/dieList.js +7 -7
- package/dist/packages/canvas-adapter/drilldown.js +1 -1
- package/dist/packages/canvas-adapter/insightsTab.js +1 -1
- package/dist/packages/canvas-adapter/renderWaferGallery.js +1 -1
- package/dist/packages/canvas-adapter/renderWaferMap.d.ts +47 -4
- package/dist/packages/canvas-adapter/renderWaferMap.js +1 -1
- package/dist/packages/canvas-adapter/summaryPanel.d.ts +22 -3
- package/dist/packages/canvas-adapter/summaryPanel.js +4 -3
- package/dist/packages/canvas-adapter/toCanvas.d.ts +23 -2
- package/dist/packages/canvas-adapter/toCanvas.js +1 -1
- package/dist/packages/canvas-adapter/toolbar.d.ts +54 -1
- package/dist/packages/canvas-adapter/toolbar.js +2 -2
- package/dist/packages/canvas-adapter/userGuideHtml.d.ts +1 -1
- package/dist/packages/canvas-adapter/userGuideHtml.js +100 -27
- package/dist/packages/canvas-adapter/version.d.ts +2 -2
- package/dist/packages/canvas-adapter/version.js +1 -1
- package/dist/packages/core/aggregates.d.ts +6 -0
- package/dist/packages/core/aggregates.js +1 -1
- package/dist/packages/core/compact.d.ts +205 -0
- package/dist/packages/core/compact.js +3 -0
- package/dist/packages/core/dieTable.d.ts +137 -0
- package/dist/packages/core/dieTable.js +1 -0
- package/dist/packages/core/dies.d.ts +22 -0
- package/dist/packages/core/dies.js +1 -1
- package/dist/packages/core/passBins.d.ts +6 -0
- package/dist/packages/core/passBins.js +1 -1
- package/dist/packages/core/utils.d.ts +14 -0
- package/dist/packages/core/utils.js +1 -1
- package/dist/packages/renderer/axisTicks.d.ts +14 -0
- package/dist/packages/renderer/axisTicks.js +1 -1
- package/dist/packages/renderer/buildView.d.ts +29 -4
- package/dist/packages/renderer/buildView.js +1 -1
- package/dist/packages/renderer/buildWaferMap.d.ts +29 -11
- package/dist/packages/renderer/buildWaferMap.js +1 -1
- package/dist/packages/renderer/columnarInput.d.ts +87 -0
- package/dist/packages/renderer/columnarInput.js +1 -0
- package/dist/packages/renderer/compactView.d.ts +22 -0
- package/dist/packages/renderer/compactView.js +1 -0
- package/dist/packages/renderer/derivedTests/apply.d.ts +8 -6
- package/dist/packages/renderer/derivedTests/apply.js +1 -1
- package/dist/packages/renderer/index.d.ts +1 -0
- package/dist/packages/renderer/spec.d.ts +3 -7
- package/dist/packages/renderer/spec.js +1 -1
- package/dist/packages/renderer/xyIndicator.d.ts +27 -0
- package/dist/packages/renderer/xyIndicator.js +1 -0
- package/dist/packages/stats/analyzeWaferLot.d.ts +28 -0
- package/dist/packages/stats/analyzeWaferLot.js +1 -1
- package/dist/packages/stats/analyzeWaferMap.d.ts +169 -2
- package/dist/packages/stats/analyzeWaferMap.js +1 -1
- package/dist/packages/stats/binRows.d.ts +38 -0
- package/dist/packages/stats/binRows.js +1 -0
- package/dist/packages/stats/boxplot.js +1 -1
- package/dist/packages/stats/capability.d.ts +7 -7
- package/dist/packages/stats/capability.js +1 -1
- package/dist/packages/stats/clusterDetection.js +1 -1
- package/dist/packages/stats/connectedComponents.js +1 -1
- package/dist/packages/stats/correlation.js +1 -1
- package/dist/packages/stats/filterFindings.d.ts +30 -0
- package/dist/packages/stats/filterFindings.js +1 -1
- package/dist/packages/stats/findingText.d.ts +9 -0
- package/dist/packages/stats/findingText.js +2 -0
- package/dist/packages/stats/histogram.js +1 -1
- package/dist/packages/stats/lotDrift.d.ts +16 -0
- package/dist/packages/stats/lotDrift.js +1 -0
- package/dist/packages/stats/lotPattern.d.ts +33 -0
- package/dist/packages/stats/lotPattern.js +1 -0
- package/dist/packages/stats/math.d.ts +73 -6
- package/dist/packages/stats/math.js +1 -1
- package/dist/packages/stats/normalizeInput.js +1 -1
- package/dist/packages/stats/patternClassification.d.ts +34 -3
- package/dist/packages/stats/patternClassification.js +1 -1
- package/dist/packages/stats/presentation.d.ts +42 -0
- package/dist/packages/stats/presentation.js +1 -0
- package/dist/packages/stats/regions.d.ts +16 -0
- package/dist/packages/stats/regions.js +1 -1
- package/dist/packages/stats/renderSummaryReport.d.ts +15 -2
- package/dist/packages/stats/renderSummaryReport.js +32 -27
- package/dist/packages/stats/reportHtml.d.ts +54 -9
- package/dist/packages/stats/reportHtml.js +130 -47
- package/dist/packages/stats/scatter.js +1 -1
- package/dist/packages/stats/sweep.js +1 -1
- package/dist/packages/stats/synthesis.d.ts +66 -0
- package/dist/packages/stats/synthesis.js +2 -0
- package/dist/packages/stats/testPassRate.js +1 -1
- package/dist/packages/stats/trend.js +1 -1
- package/dist/packages/stats/types.d.ts +6 -1
- package/dist/packages/stats/yieldRows.d.ts +31 -0
- package/dist/packages/stats/yieldRows.js +1 -0
- package/dist/packages/worker/index.js +1 -1
- package/dist/packages/worker/wafermap.worker.d.ts +3 -0
- package/dist/packages/worker/wafermap.worker.js +1 -1
- package/llms.txt +1 -1
- package/package.json +1 -1
- package/dist/packages/stats/findingsNarrative.d.ts +0 -3
- package/dist/packages/stats/findingsNarrative.js +0 -1
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import type { Die } from './dies.js';
|
|
2
|
+
/** Verdict column entries: no verdict / fail / pass. */
|
|
3
|
+
export declare const VERDICT_NONE = -1;
|
|
4
|
+
export declare const VERDICT_FAIL = 0;
|
|
5
|
+
export declare const VERDICT_PASS = 1;
|
|
6
|
+
/** `DieTable.fields` bits: the record carried a `testValues` / `testPass` object. */
|
|
7
|
+
export declare const HAS_VALUES = 1;
|
|
8
|
+
export declare const HAS_VERDICTS = 2;
|
|
9
|
+
export interface DieTable {
|
|
10
|
+
/** Number of rows. A die's row is an index below this. */
|
|
11
|
+
readonly rows: number;
|
|
12
|
+
/** Test number → value per row; `NaN` = no value. Stored as supplied, never narrowed. */
|
|
13
|
+
readonly values: ReadonlyMap<number, Float32Array | Float64Array>;
|
|
14
|
+
/** Test number → verdict per row: −1 none, 0 fail, 1 pass. */
|
|
15
|
+
readonly verdicts: ReadonlyMap<number, Int8Array>;
|
|
16
|
+
/**
|
|
17
|
+
* Per row, which of `testValues` / `testPass` the die exposes (bits
|
|
18
|
+
* `HAS_VALUES`, `HAS_VERDICTS`): the fields its record carried, so a die
|
|
19
|
+
* with no verdicts has no `testPass`, exactly as before.
|
|
20
|
+
*/
|
|
21
|
+
readonly fields: Uint8Array;
|
|
22
|
+
}
|
|
23
|
+
/** What the readers need of a die. A `DieResult` qualifies. */
|
|
24
|
+
export type DieData = Pick<Die, 'testValues' | 'testPass'>;
|
|
25
|
+
/** Marks a die being built as `row` of `table`, in place. `finaliseDie` completes it. */
|
|
26
|
+
export declare function linkDie<D extends object>(die: D, table: DieTable, row: number): D;
|
|
27
|
+
/** The table and row `die` is linked to, or `undefined` for a die with its own objects. */
|
|
28
|
+
export declare function dieLink(die: DieData): {
|
|
29
|
+
table: DieTable;
|
|
30
|
+
row: number;
|
|
31
|
+
} | undefined;
|
|
32
|
+
type Row = {
|
|
33
|
+
testValues?: Record<number, number>;
|
|
34
|
+
testPass?: Record<number, boolean>;
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* A table built from row-shaped records (`DieResult`s): row `i` holds
|
|
38
|
+
* `rows[i]`'s values and verdicts. Values are copied as-is (`Float64Array`,
|
|
39
|
+
* the precision a JS number has); a missing entry is `NaN` / no verdict.
|
|
40
|
+
*/
|
|
41
|
+
export declare function tableFromRows(rows: readonly Row[]): DieTable;
|
|
42
|
+
/**
|
|
43
|
+
* A copy of `table` with more columns: `values` / `verdicts` are added (a test
|
|
44
|
+
* number already present is replaced), and `fields` is copied, so the caller
|
|
45
|
+
* can mark rows that gained data. The original table is never changed.
|
|
46
|
+
*/
|
|
47
|
+
export declare function extendTable(table: DieTable, values: ReadonlyMap<number, Float32Array | Float64Array>, verdicts: ReadonlyMap<number, Int8Array>): DieTable & {
|
|
48
|
+
fields: Uint8Array;
|
|
49
|
+
};
|
|
50
|
+
/**
|
|
51
|
+
* One reusable die-shaped view over `table`: `at(row)` points it at a row and
|
|
52
|
+
* returns it, so a rule that takes a die (`getTestPassStatus`) can be applied
|
|
53
|
+
* row by row without an object per row. Valid until the next `at`.
|
|
54
|
+
*/
|
|
55
|
+
export declare function rowView(table: DieTable): (row: number) => DieData;
|
|
56
|
+
/**
|
|
57
|
+
* The die a host receives, from a die built with `linkDie`: a fresh object with
|
|
58
|
+
* the same fields, a non-enumerable link, and `testValues` / `testPass` getters
|
|
59
|
+
* for the fields its record carried. A die with no link is returned as is.
|
|
60
|
+
*
|
|
61
|
+
* A die that already carries a value object its record does not replace (a
|
|
62
|
+
* host-supplied layout die with its own `testPass`, say, and a record with
|
|
63
|
+
* only values) is not linked: it gets its record's objects built onto it now,
|
|
64
|
+
* beside its own, which is exactly what it had before tables existed.
|
|
65
|
+
*/
|
|
66
|
+
export declare function finaliseDie<D extends Die>(die: D): D;
|
|
67
|
+
/**
|
|
68
|
+
* A copy of `die` with `patch` applied. For a linked die this copies the
|
|
69
|
+
* fields and the link without calling the getters: an object spread would
|
|
70
|
+
* call them and build the value objects of every die it copies (3 s and the
|
|
71
|
+
* lot's full memory on 266k dies). A patch that sets `testValues` or
|
|
72
|
+
* `testPass` gives an own-objects die (the other field built from the row).
|
|
73
|
+
*/
|
|
74
|
+
export declare function copyDie<D extends DieData>(die: D, patch: Partial<D>): D;
|
|
75
|
+
/** What `detachTables` sends beside the dies: their tables, and each die's table and row. */
|
|
76
|
+
export interface DetachedTables {
|
|
77
|
+
tables: DieTable[];
|
|
78
|
+
/** Per die, in order across every die array: its table's index in `tables` (−1 unlinked), then its row. */
|
|
79
|
+
links: Int32Array;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Prepares linked dies to be posted from a worker without their values being
|
|
83
|
+
* built and copied: takes their `testValues` / `testPass` getters off (a
|
|
84
|
+
* structured clone would call them), and returns their tables and links. Post
|
|
85
|
+
* the dies, and the result with `transfer` as the transfer list, which moves
|
|
86
|
+
* the columns without a copy; the other side calls `attachTables`.
|
|
87
|
+
*
|
|
88
|
+
* **Changes the dies in place**, and transferring empties the columns: only
|
|
89
|
+
* for dies the sender discards once they are posted.
|
|
90
|
+
*/
|
|
91
|
+
export declare function detachTables(dieArrays: ReadonlyArray<readonly Die[]>): DetachedTables & {
|
|
92
|
+
transfer: ArrayBuffer[];
|
|
93
|
+
};
|
|
94
|
+
/** The receiving half of `detachTables`: links the posted dies to their tables again, in place. */
|
|
95
|
+
export declare function attachTables(dieArrays: ReadonlyArray<readonly Die[]>, detached: DetachedTables): void;
|
|
96
|
+
/** `die`'s value for test `testNumber`, or `undefined` when it has none. */
|
|
97
|
+
export declare function testValue(die: DieData, testNumber: number): number | undefined;
|
|
98
|
+
/**
|
|
99
|
+
* `die`'s recorded verdict for test `testNumber` (true = pass), or `undefined`.
|
|
100
|
+
* The raw record only: `getTestPassStatus` owns the functional-test fallback
|
|
101
|
+
* and is what callers use.
|
|
102
|
+
*/
|
|
103
|
+
export declare function recordedVerdict(die: DieData, testNumber: number): boolean | undefined;
|
|
104
|
+
/** True when `die` has any measured value or recorded verdict. */
|
|
105
|
+
export declare function dieHasAnyTestData(die: DieData): boolean;
|
|
106
|
+
/** True when `die` has at least one measured value. */
|
|
107
|
+
export declare function dieHasValues(die: DieData): boolean;
|
|
108
|
+
/** True when `die` has at least one recorded verdict. */
|
|
109
|
+
export declare function dieHasVerdicts(die: DieData): boolean;
|
|
110
|
+
/** `die`'s values as `[testNumber, value]`, test numbers ascending. */
|
|
111
|
+
export declare function dieValueEntries(die: DieData): Array<[number, number]>;
|
|
112
|
+
/** `die`'s recorded verdicts as `[testNumber, pass]`, test numbers ascending. */
|
|
113
|
+
export declare function dieVerdictEntries(die: DieData): Array<[number, boolean]>;
|
|
114
|
+
/**
|
|
115
|
+
* A reader of tests `testNumbers` for die after die: `read(die, out)` writes
|
|
116
|
+
* test `testNumbers[k]`'s value to `out[k]` (`NaN` when none) and returns
|
|
117
|
+
* `out`. For a linked die it is one indexed read per test: the columns are
|
|
118
|
+
* looked up once per table, not once per die. The bulk path for statistics
|
|
119
|
+
* that walk many dies × many tests.
|
|
120
|
+
*/
|
|
121
|
+
export declare function testValuesReader(testNumbers: readonly number[]): (die: DieData, out: Float64Array) => Float64Array;
|
|
122
|
+
/**
|
|
123
|
+
* {@link testValuesReader} for recorded verdicts: `read(die, out)` writes test
|
|
124
|
+
* `testNumbers[k]`'s recorded verdict to `out[k]` — `VERDICT_PASS`,
|
|
125
|
+
* `VERDICT_FAIL` or `VERDICT_NONE` — and returns `out`. The raw record only, as
|
|
126
|
+
* {@link recordedVerdict}: `testPassStatusReader` adds the functional-test
|
|
127
|
+
* fallback and is what callers use.
|
|
128
|
+
*/
|
|
129
|
+
export declare function recordedVerdictsReader(testNumbers: readonly number[]): (die: DieData, out: Int8Array) => Int8Array;
|
|
130
|
+
/**
|
|
131
|
+
* Every test number with at least one value or verdict among `dies` (only
|
|
132
|
+
* values, with `which: 'values'`), ascending. Linked dies are checked column
|
|
133
|
+
* by column per table, not die by die, and each column stops at its first hit.
|
|
134
|
+
*/
|
|
135
|
+
export declare function testsPresent(dies: readonly DieData[], which?: 'all' | 'values'): number[];
|
|
136
|
+
export {};
|
|
137
|
+
//# sourceMappingURL=dieTable.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{hasAnyKey as O}from"./utils.js";const k=NaN;export const VERDICT_NONE=-1,VERDICT_FAIL=0,VERDICT_PASS=1,HAS_VALUES=1,HAS_VERDICTS=2;const a=Symbol("wmap.dieTable"),u=Symbol("wmap.dieRow");export function linkDie(e,t,s){return e[a]=t,e[u]=s,e}export function dieLink(e){const t=e[a];return t===void 0?void 0:{table:t,row:e[u]}}export function tableFromRows(e){const t=e.length,s=new Map,r=new Map,o=new Uint8Array(t),n=g(s,()=>new Float64Array(t).fill(k)),i=g(r,()=>new Int8Array(t).fill(VERDICT_NONE));for(let f=0;f<t;f++){const c=e[f].testValues;if(c){o[f]|=HAS_VALUES;const d=n(c),w=Object.values(c);for(let b=0;b<d.length;b++)d[b][f]=w[b]}const l=e[f].testPass;if(l){o[f]|=HAS_VERDICTS;const d=i(l),w=Object.values(l);for(let b=0;b<d.length;b++)d[b][f]=w[b]?VERDICT_PASS:VERDICT_FAIL}}return{rows:t,values:m(s),verdicts:m(r),fields:o}}function g(e,t){let s=[],r=[];return o=>{const n=Object.keys(o);let i=n.length===s.length;for(let f=0;i&&f<n.length;f++)i=n[f]===s[f];return i||(s=n,r=n.map(f=>{let c=e.get(f);return c===void 0&&e.set(f,c=t()),c})),r}}function m(e){return new Map([...e].map(([t,s])=>[Number(t),s]).sort((t,s)=>t[0]-s[0]))}export function extendTable(e,t,s){const r=new Map(e.values),o=new Map(e.verdicts);for(const[n,i]of t)r.set(n,i);for(const[n,i]of s)o.set(n,i);return{rows:e.rows,values:new Map([...r].sort((n,i)=>n[0]-i[0])),verdicts:new Map([...o].sort((n,i)=>n[0]-i[0])),fields:e.fields.slice()}}export function rowView(e){const t=linkDie({},e,0);return s=>(t[u]=s,t)}function v(e,t){const s={},r=[...e.values];for(let o=r.length-1;o>=0;o--){const n=r[o][1][t];Number.isNaN(n)||(s[r[o][0]]=n)}return s}function V(e,t){const s={},r=[...e.verdicts];for(let o=r.length-1;o>=0;o--){const n=r[o][1][t];n!==VERDICT_NONE&&(s[r[o][0]]=n===VERDICT_PASS)}return s}function x(){return Object.freeze(v(this[a],this[u]))}function N(){return Object.freeze(V(this[a],this[u]))}function j(e){return function(){throw new TypeError(`wafermap: die.${e} is read-only on a die built by buildWaferMap (die ${this.id}). Pass the values you want to buildWaferMap, or copy the die: { ...die, ${e}: yours }.`)}}const y={get:x,set:j("testValues"),enumerable:!0,configurable:!0},P={get:N,set:j("testPass"),enumerable:!0,configurable:!0},p=e=>({value:e,writable:!0,enumerable:!1,configurable:!0});export function finaliseDie(e){const t=e[a];if(t===void 0)return e;const s=e[u],r=t.fields[s],o=e.testValues!==void 0&&!(r&HAS_VALUES)||e.testPass!==void 0&&!(r&HAS_VERDICTS),n={};for(const i of Object.keys(e))i==="testValues"&&r&HAS_VALUES||i==="testPass"&&r&HAS_VERDICTS||(n[i]=e[i]);return o?(r&HAS_VALUES&&(n.testValues=v(t,s)),r&HAS_VERDICTS&&(n.testPass=V(t,s)),n):(Object.defineProperty(n,a,p(t)),Object.defineProperty(n,u,p(s)),r&HAS_VALUES&&Object.defineProperty(n,"testValues",y),r&HAS_VERDICTS&&Object.defineProperty(n,"testPass",P),n)}export function copyDie(e,t){const s=e[a];if(s===void 0)return{...e,...t};const r=e[u],o=s.fields[r],n={};for(const i of Object.keys(e))i!=="testValues"&&i!=="testPass"&&(n[i]=e[i]);return"testValues"in t||"testPass"in t?(o&HAS_VALUES&&!("testValues"in t)&&(n.testValues=v(s,r)),o&HAS_VERDICTS&&!("testPass"in t)&&(n.testPass=V(s,r)),Object.assign(n,t)):(Object.assign(n,t),Object.defineProperty(n,a,p(s)),Object.defineProperty(n,u,p(r)),o&HAS_VALUES&&Object.defineProperty(n,"testValues",y),o&HAS_VERDICTS&&Object.defineProperty(n,"testPass",P),n)}export function detachTables(e){const t=[],s=new Map,r=e.reduce((f,c)=>f+c.length,0),o=new Int32Array(r*2);let n=0;for(const f of e)for(const c of f){const l=dieLink(c);if(l===void 0){o[n++]=-1,o[n++]=0;continue}let d=s.get(l.table);d===void 0&&(s.set(l.table,d=t.length),t.push(l.table)),o[n++]=d,o[n++]=l.row,delete c.testValues,delete c.testPass}const i=new Set;for(const f of t){for(const c of f.values.values())i.add(c.buffer);for(const c of f.verdicts.values())i.add(c.buffer);i.add(f.fields.buffer)}return{tables:t,links:o,transfer:[...i]}}export function attachTables(e,t){let s=0;for(const r of e)for(const o of r){const n=t.links[s++],i=t.links[s++];if(n<0)continue;const f=t.tables[n],c=f.fields[i];Object.defineProperty(o,a,p(f)),Object.defineProperty(o,u,p(i)),c&HAS_VALUES&&Object.defineProperty(o,"testValues",y),c&HAS_VERDICTS&&Object.defineProperty(o,"testPass",P)}}export function testValue(e,t){const s=e[a];if(s===void 0)return e.testValues?.[t];const r=s.values.get(t)?.[e[u]];return r===void 0||Number.isNaN(r)?void 0:r}export function recordedVerdict(e,t){const s=e[a];if(s===void 0)return e.testPass?.[t];const r=s.verdicts.get(t)?.[e[u]];return r===void 0||r===VERDICT_NONE?void 0:r===VERDICT_PASS}export function dieHasAnyTestData(e){return dieHasValues(e)||dieHasVerdicts(e)}export function dieHasValues(e){const t=dieLink(e);if(t===void 0)return O(e.testValues);for(const s of t.table.values.values())if(!Number.isNaN(s[t.row]))return!0;return!1}export function dieHasVerdicts(e){const t=dieLink(e);if(t===void 0)return O(e.testPass);for(const s of t.table.verdicts.values())if(s[t.row]!==VERDICT_NONE)return!0;return!1}export function dieValueEntries(e){const t=dieLink(e);if(t===void 0){const r=e.testValues;return r===void 0?[]:Object.keys(r).map(o=>[Number(o),r[Number(o)]])}const s=[];for(const[r,o]of t.table.values){const n=o[t.row];Number.isNaN(n)||s.push([r,n])}return s}export function dieVerdictEntries(e){const t=dieLink(e);if(t===void 0){const r=e.testPass;return r===void 0?[]:Object.keys(r).map(o=>[Number(o),r[Number(o)]])}const s=[];for(const[r,o]of t.table.verdicts){const n=o[t.row];n!==VERDICT_NONE&&s.push([r,n===VERDICT_PASS])}return s}export function testValuesReader(e){let t,s=[];return function(o,n){const i=dieLink(o);if(i===void 0){const f=o.testValues;for(let c=0;c<e.length;c++){const l=f?.[e[c]];n[c]=l===void 0?NaN:l}return n}i.table!==t&&(t=i.table,s=e.map(f=>i.table.values.get(f)));for(let f=0;f<s.length;f++){const c=s[f];n[f]=c===void 0?NaN:c[i.row]}return n}}export function recordedVerdictsReader(e){let t,s=[];return function(o,n){const i=dieLink(o);if(i===void 0){const f=o.testPass;for(let c=0;c<e.length;c++){const l=f?.[e[c]];n[c]=l===void 0?VERDICT_NONE:l?VERDICT_PASS:VERDICT_FAIL}return n}i.table!==t&&(t=i.table,s=e.map(f=>i.table.verdicts.get(f)));for(let f=0;f<s.length;f++){const c=s[f];n[f]=c===void 0?VERDICT_NONE:c[i.row]}return n}}export function testsPresent(e,t="all"){const s=new Set,r=new Map;for(const o of e){const n=dieLink(o);if(n===void 0){if(o.testValues)for(const f in o.testValues)s.add(Number(f));if(t==="all"&&o.testPass)for(const f in o.testPass)s.add(Number(f));continue}let i=r.get(n.table);i===void 0&&r.set(n.table,i=[]),i.push(n.row)}for(const[o,n]of r){for(const[i,f]of o.values)!s.has(i)&&n.some(c=>!Number.isNaN(f[c]))&&s.add(i);if(t!=="values")for(const[i,f]of o.verdicts)!s.has(i)&&n.some(c=>f[c]!==VERDICT_NONE)&&s.add(i)}return[...s].sort((o,n)=>o-n)}
|
|
@@ -141,6 +141,28 @@ export declare function getDieKey(die: {
|
|
|
141
141
|
y?: number;
|
|
142
142
|
id?: string;
|
|
143
143
|
}): string;
|
|
144
|
+
/**
|
|
145
|
+
* A number identifying grid position (`x`, `y`), for neighbour lookups inside
|
|
146
|
+
* the library: cluster detection and connected components probe up to 25 cells
|
|
147
|
+
* per die, and building a `getDieKey` string for each probe was most of their
|
|
148
|
+
* cost. Exact and collision-free for integer coordinates with |x| < 2^20 and
|
|
149
|
+
* |y| < 2^31, which covers STDF's ±32767 many times over. Never shown and
|
|
150
|
+
* never stored — findings carry `getDieKey` strings.
|
|
151
|
+
*/
|
|
152
|
+
export declare function gridKey(x: number, y: number): number;
|
|
153
|
+
/**
|
|
154
|
+
* A die's `getDieKey` identity as a value that is cheap to hash: two dies get
|
|
155
|
+
* equal position keys exactly when their `getDieKey` strings are equal. Integer
|
|
156
|
+
* coordinates in STDF's range map one-to-one to {@link gridKey}; anything else
|
|
157
|
+
* keeps its `getDieKey` string. For sets and maps over many dies (stacked cards,
|
|
158
|
+
* analysis), where building a string per die was most of the work. Never shown
|
|
159
|
+
* and never stored — findings carry `getDieKey` strings.
|
|
160
|
+
*/
|
|
161
|
+
export declare function positionKey(die: {
|
|
162
|
+
x?: number;
|
|
163
|
+
y?: number;
|
|
164
|
+
id?: string;
|
|
165
|
+
}): number | string;
|
|
144
166
|
export declare function isYieldEligibleDie(die: Die, options?: DieEligibilityOptions): boolean;
|
|
145
167
|
/**
|
|
146
168
|
* Whether a die passed: its hard bin (else its soft bin) is in `passBins`.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export function hasPosition(
|
|
1
|
+
export function hasPosition(t){return t.x!=null&&t.y!=null}export function isPositionedDie(t){return t.x!=null&&t.y!=null}export function getDieKey(t){return hasPosition(t)?`${t.x},${t.y}`:`id:${t.id}`}export function gridKey(t,e){return t*4294967296+e}export function positionKey(t){const{x:e,y:n}=t;return Number.isInteger(e)&&Number.isInteger(n)&&Math.abs(e)<=32767&&Math.abs(n)<=32767?gridKey(e,n):getDieKey(t)}export function isYieldEligibleDie(t,e={}){return!(t.partial||!e.includeEdgeExcluded&&t.edgeExcluded)}export function diePassStatus(t,e){const n=t.hbin??t.sbin;return n===void 0?void 0:e.has(n)}export function generateDies(t,e){const{width:n,height:o,offset:s={x:0,y:0}}=e,u=e.gridSize??Math.ceil(t.radius/Math.min(n,o))+1,c=[];for(let r=-u;r<=u;r++)for(let i=-u;i<=u;i++){const l=t.center.x+i*n+s.x,x=t.center.y+r*o+s.y;c.push({id:`${i}_${r}`,x:i,y:r,physX:l,physY:x,width:n,height:o})}return c}
|
|
@@ -23,6 +23,12 @@ export declare function itemPassBins(item: {
|
|
|
23
23
|
} | null | undefined, fallback?: readonly number[]): readonly number[];
|
|
24
24
|
/** Same bins, in any order. */
|
|
25
25
|
export declare function sameBins(a: readonly number[], b: readonly number[]): boolean;
|
|
26
|
+
/**
|
|
27
|
+
* The pass bins every wafer is judged by, or `undefined` when they differ (or there are none to
|
|
28
|
+
* ask). For a surface that must name one set — a headline, or which bins count as failures — and
|
|
29
|
+
* so says nothing rather than naming one wafer's set for a population judged several ways.
|
|
30
|
+
*/
|
|
31
|
+
export declare function commonPassBins(sets: Iterable<readonly number[] | undefined>): readonly number[] | undefined;
|
|
26
32
|
/**
|
|
27
33
|
* Names the pass bins in use, for a yield label: "bin 1", "bins 1, 2", or —
|
|
28
34
|
* when the wafers shown disagree — "per wafer: bin 1 · bins 1, 2". A label that
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export const INPUT_DEFAULT_PASS_BINS=[1];export function itemPassBins(s,
|
|
1
|
+
export const INPUT_DEFAULT_PASS_BINS=[1];export function itemPassBins(s,n=INPUT_DEFAULT_PASS_BINS){return s?.passBins??n}export function sameBins(s,n){const t=new Set(s),e=new Set(n);return t.size===e.size&&[...t].every(i=>e.has(i))}export function commonPassBins(s){let n;for(const t of s){if(t===void 0)return;if(n===void 0)n=t;else if(!sameBins(n,t))return}return n}export function passBinsLabel(s){const n=[];for(const e of s)n.some(i=>sameBins(i,e))||n.push(e);const t=e=>e.length===0?"no bins":e.length===1?`bin ${e[0]}`:`bins ${e.join(", ")}`;return n.length<=1?t(n[0]??INPUT_DEFAULT_PASS_BINS):`per wafer: ${n.map(t).join(" \xB7 ")}`}
|
|
@@ -25,6 +25,12 @@ export declare function compareNatural(a: string, b: string): number;
|
|
|
25
25
|
*/
|
|
26
26
|
export declare function prettyKey(key: string): string;
|
|
27
27
|
/** Clamp `v` into [0, 1] — the normalization range every colour scale expects. */
|
|
28
|
+
/**
|
|
29
|
+
* Whether `obj` has an own key. The per-die form of `Object.keys(obj).length > 0`
|
|
30
|
+
* without building the key array: on a 266k-die lot that allocation, once per
|
|
31
|
+
* die, cost seconds in WebKit's engine (the desktop app on Linux and macOS).
|
|
32
|
+
*/
|
|
33
|
+
export declare function hasAnyKey<T extends object>(obj: T | null | undefined): obj is T;
|
|
28
34
|
/**
|
|
29
35
|
* Smallest / largest of `values`, by iteration rather than `Math.min(...values)`.
|
|
30
36
|
*
|
|
@@ -135,4 +141,12 @@ export type Chunked<T> = Generator<void, T, void>;
|
|
|
135
141
|
/** Run a {@link Chunked} computation to completion without yielding, and
|
|
136
142
|
* return its result — the synchronous entry point for any chunked builder. */
|
|
137
143
|
export declare function drain<T>(work: Chunked<T>): T;
|
|
144
|
+
/** A highlight option (one value or several) as a list; `undefined` is none. */
|
|
145
|
+
export declare function asList<T>(value: T | readonly T[] | undefined): readonly T[];
|
|
146
|
+
/**
|
|
147
|
+
* The highlight after a click on `value`. A plain click shows only that value,
|
|
148
|
+
* or clears the highlight when it was the only one; an additive click
|
|
149
|
+
* (Ctrl/Cmd) adds or removes it. One value stays a plain value.
|
|
150
|
+
*/
|
|
151
|
+
export declare function toggleHighlight<T>(current: T | readonly T[] | undefined, value: T, additive: boolean): T | T[] | undefined;
|
|
138
152
|
//# sourceMappingURL=utils.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export function compareNatural(t,
|
|
1
|
+
export function compareNatural(t,r){return t.localeCompare(r,"en",{numeric:!0})}export function prettyKey(t){return t.replace(/([A-Z])/g," $1").replace(/_/g," ").trim().replace(/^./,r=>r.toUpperCase())}export function hasAnyKey(t){if(t==null)return!1;for(const r in t)if(Object.hasOwn(t,r))return!0;return!1}export function minOf(t){if(t.length===0)return 0;let r=t[0];for(let e=1;e<t.length;e++)t[e]<r&&(r=t[e]);return r}export function maxOf(t){if(t.length===0)return 0;let r=t[0];for(let e=1;e<t.length;e++)t[e]>r&&(r=t[e]);return r}export function clamp01(t){return Math.max(0,Math.min(1,t))}export function mean(t){if(t.length===0)return NaN;let r=0;for(const e of t)r+=e;return r/t.length}export function medianOfSorted(t){const r=t.length;if(r===0)return 0;const e=Math.floor(r/2);return r%2===1?t[e]:(t[e-1]+t[e])/2}export function median(t){return medianOfSorted([...t].sort((r,e)=>r-e))}export function percentile98(t){if(t.length===0)return 0;const r=Float64Array.from(t);r.sort();const e=Math.floor(r.length*.98);return r[Math.min(e,r.length-1)]}export function modeOf(t){if(!t.length)return null;const r=new Map;for(const o of t)r.set(o,(r.get(o)??0)+1);let e=0,n=t[0];for(const[o,f]of r)f>e&&(e=f,n=o);return n}const i=/^[=+\-@]/;export function csvField(t){const r=i.test(t)&&Number.isNaN(Number(t))?`'${t}`:t;return/[",\n\r]/.test(r)?`"${r.replace(/"/g,'""')}"`:r}export function escHtml(t){return t.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""").replace(/'/g,"'")}export function arrayEqual(t,r){if(t===r)return!0;if(!t||!r||t.length!==r.length)return!1;for(let e=0;e<t.length;e++)if(t[e]!==r[e])return!1;return!0}export function mapEqual(t,r){if(t===r)return!0;if(!t||!r||t.size!==r.size)return!1;for(const[e,n]of t)if(!r.has(e)||r.get(e)!==n)return!1;return!0}export function setEqual(t,r){if(t===r)return!0;if(!t||!r||t.size!==r.size)return!1;for(const e of t)if(!r.has(e))return!1;return!0}export function drain(t){let r=t.next();for(;!r.done;)r=t.next();return r.value}export function asList(t){return t===void 0?[]:Array.isArray(t)?t:[t]}export function toggleHighlight(t,r,e){const n=asList(t),o=e?n.includes(r)?n.filter(f=>f!==r):[...n,r]:n.length===1&&n[0]===r?[]:[r];return o.length===0?void 0:o.length===1?o[0]:o}
|
|
@@ -1,3 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which columns (or rows) of a compact layout get an axis label.
|
|
3
|
+
*
|
|
4
|
+
* Group starts when the cells are too narrow to label every one: each is the first column of
|
|
5
|
+
* a reticle group, so the labels name the boundaries, there are few of them, and they are
|
|
6
|
+
* spread out. Every cell once there is room for that, so a zoomed-in map labels each die.
|
|
7
|
+
* A layout with a single group has no boundaries to name, so it is thinned evenly instead.
|
|
8
|
+
*
|
|
9
|
+
* @param count cells on the axis
|
|
10
|
+
* @param breaks compact indices that start a group after the first (see `CompactMap`)
|
|
11
|
+
* @param cellPx on-screen size of one cell along the axis
|
|
12
|
+
* @param minGapPx least distance between two labels
|
|
13
|
+
*/
|
|
14
|
+
export declare function compactTickIndices(count: number, breaks: readonly number[], cellPx: number, minGapPx: number): number[];
|
|
1
15
|
/**
|
|
2
16
|
* Round a raw spacing to the nearest 1, 2 or 5 × 10ⁿ — THE rounding rule for
|
|
3
17
|
* every tick grid in the library, the wafer map's own mm axes included.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export function
|
|
1
|
+
export function compactTickIndices(e,n,t,s){if(e<=0)return[];const r=Math.max(1,Math.ceil(s/Math.max(t,1e-9)));if(r===1)return Array.from({length:e},(i,c)=>c);if(n.length===0){const i=[];for(let c=0;c<e;c+=r)i.push(c);return i}const o=[];let u=-1/0;for(const i of[0,...n])((i-u)*t>=s||o.length===0)&&(o.push(i),u=i);return o}export function niceStep(e){if(!(e>0)||!Number.isFinite(e))return 0;const n=Math.pow(10,Math.floor(Math.log10(e))),t=e/n;return(t<1.5?1:t<3.5?2:t<7.5?5:10)*n}export function stepDecimals(e){if(!(e>0)||!Number.isFinite(e))return 0;for(let n=0;n<=9;n++){const t=e*10**n;if(Math.abs(t-Math.round(t))<1e-6*Math.max(1,t))return n}return 9}function h(e,n,t){const s=Math.ceil(e/t-1e-9)*t,r=[];for(let o=s;o<=n+t*1e-9;o+=t)r.push(Number((Math.round(o/t)*t).toFixed(stepDecimals(t)+2)));return r}export function fitTicks(e,n,t,s){const r=n-e;if(!(r>0)||!Number.isFinite(r)||!(t>0))return{ticks:Number.isFinite(e)?[e]:[],step:0};const o=[1,2,5];let u=Math.floor(Math.log10(r/Math.max(1,t/4))),i=0;for(let c=0;c<60;c++){const f=Number((o[i]*10**u).toPrecision(12)),a=h(e,n,f);if(a.length<=1||f/r*t>=s(f,a))return{ticks:a,step:f};++i===o.length&&(i=0,u++)}return{ticks:[e,n],step:r}}
|
|
@@ -4,6 +4,7 @@ import type { Reticle } from '../core/reticle.js';
|
|
|
4
4
|
import type { DieMetadata, WaferMetadata } from '../core/metadata.js';
|
|
5
5
|
import { type Affine } from '../core/transforms.js';
|
|
6
6
|
import { type BinColors } from './binColors.js';
|
|
7
|
+
import type { CompactMap } from '../core/compact.js';
|
|
7
8
|
import type { TestDef, BinDef, MetadataFieldDef, ReticleConfig } from './buildWaferMap.js';
|
|
8
9
|
import { classifySpec, type SpecCategory } from './spec.js';
|
|
9
10
|
export { classifySpec };
|
|
@@ -89,7 +90,7 @@ export interface ViewHoverPoint {
|
|
|
89
90
|
y: number;
|
|
90
91
|
}
|
|
91
92
|
export interface ViewOverlay {
|
|
92
|
-
kind: 'wafer-boundary' | 'reticle' | 'probe-path' | 'ring-boundary' | 'quadrant-boundary' | 'xy-indicator';
|
|
93
|
+
kind: 'wafer-boundary' | 'reticle' | 'probe-path' | 'ring-boundary' | 'quadrant-boundary' | 'xy-indicator' | 'compact-group';
|
|
93
94
|
points: Point[][];
|
|
94
95
|
closed: boolean;
|
|
95
96
|
lineColor: string;
|
|
@@ -267,6 +268,21 @@ export interface View {
|
|
|
267
268
|
pass: number;
|
|
268
269
|
fail: number;
|
|
269
270
|
};
|
|
271
|
+
/**
|
|
272
|
+
* Present only on a compact view (see `ViewOptions.compact`): the original die coordinate of
|
|
273
|
+
* each compact column and row, and the compact index that sits at the display origin.
|
|
274
|
+
* Axis labels read this instead of inverting `gridToScreen`, so they stay original die
|
|
275
|
+
* coordinates, never compact indices.
|
|
276
|
+
*/
|
|
277
|
+
compact?: {
|
|
278
|
+
columns: readonly number[];
|
|
279
|
+
rows: readonly number[];
|
|
280
|
+
/** Compact indices that start a group after the first, per axis: where the axis labels go. */
|
|
281
|
+
columnBreaks: readonly number[];
|
|
282
|
+
rowBreaks: readonly number[];
|
|
283
|
+
centreColumn: number;
|
|
284
|
+
centreRow: number;
|
|
285
|
+
};
|
|
270
286
|
/** Bounding box of all die centres in scene coordinates (mm). */
|
|
271
287
|
dieBounds: {
|
|
272
288
|
minX: number;
|
|
@@ -308,14 +324,23 @@ export interface ViewOptions {
|
|
|
308
324
|
* bin present here: a stale assignment must never leave a real bin uncoloured.
|
|
309
325
|
*/
|
|
310
326
|
binColors?: BinColors;
|
|
311
|
-
highlightBin?: number;
|
|
312
|
-
/** Dim every die except
|
|
313
|
-
highlightMetadataValue?: string;
|
|
327
|
+
highlightBin?: number | number[];
|
|
328
|
+
/** Dim every die except these metadata values, `'metadata'` mode's analogue of `highlightBin`. */
|
|
329
|
+
highlightMetadataValue?: string | string[];
|
|
314
330
|
interactiveTransform?: {
|
|
315
331
|
rotation?: number;
|
|
316
332
|
flipX?: boolean;
|
|
317
333
|
flipY?: boolean;
|
|
318
334
|
};
|
|
335
|
+
/**
|
|
336
|
+
* Draw the dies on this compact layout (see `buildCompactMap`) instead of at their physical
|
|
337
|
+
* positions: the empty columns and rows are gone, each group of dies is outlined, and the
|
|
338
|
+
* wafer outline, rings, quadrants, reticle grid and XY indicator are not drawn, because the
|
|
339
|
+
* dies no longer sit where they are. Every die is still drawn and still counts: only the
|
|
340
|
+
* layout changes. Rotation, flips and the notch marker follow the same transform as the
|
|
341
|
+
* physical view.
|
|
342
|
+
*/
|
|
343
|
+
compact?: CompactMap;
|
|
319
344
|
/**
|
|
320
345
|
* Explicit value colour normalization range.
|
|
321
346
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{hasPosition as fe}from"../core/dies.js";import{getReticleCell as de}from"../core/reticle.js";import{metadataCategoricalValue as he,metadataDisplayValue as me}from"../core/metadata.js";import{affineRotation as bt,affineMirror as Ht,affineCompose as st,affinePoint as G,affineVector as xt,affineSwapsAxes as pe}from"../core/transforms.js";import{contrastTextColor as Q,SPEC_PASS_FILL as Mt,SPEC_FAIL_LOW as ye,SPEC_FAIL_HIGH as St}from"./colorMap.js";import{resolveValueColorFn as ge}from"./colorSchemes.js";import{resolveBinColors as be,binColorsCover as xe}from"./binColors.js";import{NO_DATA_FILL as w}from"./colorMap.js";import{diePassStatus as Me}from"../core/dies.js";import{getDieTestValue as L,getTestPassStatus as U,isParametricTest as vt}from"./buildWaferMap.js";import{classifySpec as ot}from"./spec.js";export{ot as classifySpec};import{fmt as kt,fmtColorbarAxis as $t,fmtAggregationMethod as _t}from"./fmt.js";import{metadataValueColor as Se}from"./colorMap.js";import{clamp01 as Kt,compareNatural as ve,escHtml as P,minOf as ke}from"../core/utils.js";import{prettyKey as Wt}from"../core/utils.js";import{testLabel as rt,markedTestLabel as lt,derivedTestNote as jt,isDerivedTest as $e,DERIVED_MARK as Be,DERIVED_KEY as Te}from"./testLabel.js";export function resolveTestNumber(t,e){return e?.length?{testNumber:(findTestDef(e,t)??e[0]).testNumber}:{testNumber:t}}export function collectMetadataValues(t,e){const i=new Set;for(const a of t){if(a.partial||a.edgeExcluded)continue;const s=ct(a,e);s!==void 0&&i.add(s)}return[...i].sort(ve)}export function findTestDef(t,e){return t?.find(i=>i.testNumber===e)}export function getUniqueTestNumbers(t){return[...new Set(t.flatMap(e=>e.testValues?Object.keys(e.testValues).map(Number):[]))].sort((e,i)=>e-i)}const Ie="#d3d6db",zt="#e8e9ea",De="#aab0ba";function ct(t,e){return e?he(t.metadata?.[e]):void 0}function Ve(t,e,i){const{x:a,y:s}=t.center,n=bt(t.orientation,a,s),o=st(Ht(e?.x??!1,e?.y??!1,a,s),bt(t.orientation,a,s)),l=st(Ht(i?.flipX??!1,i?.flipY??!1,a,s),bt(i?.rotation??0,a,s));return{bakedToScreen:l,physicalToScreen:st(l,n),gridToScreen:st(l,o)}}function q(t,e=!1){return{points:[t],closed:e}}function Ce(t,e){const{center:i,radius:a,notch:s}=t;let n=i.x+a*Math.cos(e),o=i.y+a*Math.sin(e);if(!s)return{x:n,y:o};const l=Math.sqrt(a**2-(s.length/2)**2),d=s.length/2,g=n-i.x,p=o-i.y;return s.type==="bottom"&&p<-l?(o=i.y-l,n=i.x+Math.max(-d,Math.min(d,g))):s.type==="top"&&p>l?(o=i.y+l,n=i.x+Math.max(-d,Math.min(d,g))):s.type==="left"&&g<-l?(n=i.x-l,o=i.y+Math.max(-d,Math.min(d,p))):s.type==="right"&&g>l&&(n=i.x+l,o=i.y+Math.max(-d,Math.min(d,p))),{x:n,y:o}}function wt(t,e){return t.map(e).join(" / ")}function Fe(t,e=256){const i=Array.from({length:e},(a,s)=>t(s/(e-1)));return a=>i[Math.min(e-1,Math.max(0,Math.round(a*(e-1))))]}function Le(t,e){const i=Math.max(1,Math.min(t.width,t.height)),a=t.width/Math.max(e.length,1);return Math.max(8,Math.min(16,Math.round(Math.min(i*.55,a*1.8))))}function Pe(t,e,i){if(e?.length){const a=e.flatMap(s=>{const n=s.testNumber;if(!vt(s)){const l=U(t,n,s);return l===void 0?[]:[{key:n,label:lt(s,n),value:l?"Pass":"Fail"}]}const o=L(t,n);return o===void 0?[]:[{key:n,label:lt(s,n),value:kt(o,s.unit,i),recordedFail:t.testPass?.[n]===!1}]});if(a.length)return a}return t.testValues&&Object.keys(t.testValues).length>0?Object.entries(t.testValues).map(([a,s])=>({key:Number(a),label:rt(void 0,Number(a)),value:kt(s,void 0,i)})):t.testPass&&Object.keys(t.testPass).length>0?Object.entries(t.testPass).map(([a,s])=>({key:Number(a),label:rt(void 0,Number(a)),value:s?"Pass":"Fail"})):[]}export function buildHoverText(t,e,i={}){const{testDefs:a,hbinDefs:s,sbinDefs:n,fallbackFormat:o,aggrMethod:l,lotSize:d,activeTest:g,reticleConfig:p,metadataFields:B}=i,F=s?new Map(s.map(f=>[f.bin,f])):null,T=n?new Map(n.map(f=>[f.bin,f])):null,b=[P(`Die (${t.x}, ${t.y})`)];if(p&&fe(t)){const f=de(t,p);b.push(P(`Reticle (${f.column}, ${f.row})`))}if(e==="stackedValues"){const f=L(t,0);if(f!==void 0){const u=a?.[0],h=u?lt(u,u.testNumber):"Value",v=l?` (${l})`:"";b.push(P(`${h}${v}: ${kt(f,u?.unit,o)}`));const m=jt(u);m&&b.push(`<i>${P(m)}</i>`)}}else if(e==="stackedBins"||e==="stackedSoftBins"){const f=L(t,0),u=e==="stackedSoftBins"?t.sbin:t.hbin;if(f!==void 0){const h=e==="stackedSoftBins"?T:F,v=u!==void 0?h?.get(u)?.name?`${u} \xB7 ${h.get(u).name}`:`Bin ${u}`:"Bin";let m;if(l==="percent")m=`${f.toFixed(0)}%`;else{const V=d?` (${(f/d*100).toFixed(0)}% of stacked wafers)`:"";m=`${f}${V}`}const x=l?` [${_t(l)}]`:"";b.push(P(`${v}: ${m}${x}`))}}else{const f=Pe(t,a,o);if(e==="value"&&f.length){const{testNumber:u}=resolveTestNumber(g??0,a),h=findTestDef(a,u),v=L(t,u);let m=f.findIndex(A=>A.key===u);m<0&&(m=0);const x=f[m];let V=`<b>${P(`${x.label}: ${x.value}`)}${x.recordedFail?" <i>(recorded fail)</i>":""}</b>`;if(x.key===u&&h){const A=ot(v,h);(A==="failLow"||A==="failHigh")&&(V+=" <i>(outside test limits)</i>")}b.push(V);const E=jt(findTestDef(a,x.key));E&&b.push(`<i>${P(E)}</i>`);const y=f.length-1;y>0&&b.push(`<i>+${y} more test${y===1?"":"s"}</i>`)}else if(f.length){const u=f.length;b.push(`<i>${u} test result${u===1?"":"s"} recorded</i>`)}if(t.hbin!==void 0||t.sbin!==void 0){const u=[];if(t.hbin!==void 0){const h=F?.get(t.hbin)?.name,v=h?`${t.hbin} \xB7 ${h}`:String(t.hbin);u.push(`HBin: ${v}`)}if(t.sbin!==void 0){const h=T?.get(t.sbin)?.name,v=h?`${t.sbin} \xB7 ${h}`:String(t.sbin);u.push(`SBin: ${v}`)}b.push(u.map(P).join(" \xB7 "))}}t.retestCount!==void 0&&b.push(P(`Retests: ${t.retestCount}`)),t.siteNum!==void 0&&b.push(P(`Site: ${t.siteNum}`)),t.partId!==void 0&&b.push(P(`Part ID: ${t.partId}`)),t.partial&&b.push("<i>partial die</i>"),t.probeIndex!==void 0&&b.push(P(`Probe: #${t.probeIndex}`));const $={...t.metadata??{}};for(const[f,u]of Object.entries($)){const h=me(u);if(h===void 0)continue;const v=B?.find(m=>m.key===f)?.label??Wt(f);b.push(P(`${v}: ${h}`))}return b.join("<br>")}function Bt(t){return t?.name?lt(t,t.testNumber):void 0}function Tt(t){return $e(t)?{note:`${Be} ${Te}`}:{}}export function buildMapTitle(t,e="engineering",i){const a=t.isLotStack&&t.lotSize!==void 0?`stacked (${t.lotSize} ${t.lotSize===1?"wafer":"wafers"})`:"stacked",s=n=>{const o=i?.length===1?i[0]:void 0;return o===void 0?`${n} Bin`:o.name?`${n} Bin ${o.bin} \xB7 ${o.name}`:`${n} Bin ${o.bin}`};switch(t.plotMode){case"hardBin":return{primary:"Hard Bin",secondary:""};case"softBin":return{primary:"Soft Bin",secondary:""};case"stackedBins":return{primary:s("Hard"),secondary:a};case"stackedSoftBins":return{primary:s("Soft"),secondary:a};case"metadata":{const n=t.metadataFields?.find(l=>l.key===t.activeMetadataKey),o=t.activeMetadataKey?Wt(t.activeMetadataKey):"Metadata";return{primary:n?.label??o,secondary:""}}case"stackedValues":{const n=t.testDefs?.[0],o=t.valueRange[1]||t.valueRange[0]||0,{axisLabel:l}=$t(o,Bt(n),n?.unit,e),d=l||"Value",g=t.aggrMethod?` \xB7 ${_t(t.aggrMethod)}`:"";return{primary:`${d}${g}`,secondary:a,...Tt(n)}}default:{const n=findTestDef(t.testDefs,t.activeTest),o=t.valueRange[1]||t.valueRange[0]||0,{axisLabel:l}=$t(o,Bt(n),n?.unit,e),d=l||Bt(n);if(t.passFailDisplay!=="off"){const g=n?.testNumber??t.activeTest,p=d?`${d} \xB7 #${g}`:rt(void 0,g),B=t.passFailDisplay==="spec"?"Limit pass/fail":n!==void 0&&!vt(n)?"Functional pass/fail":"Tester pass/fail";return{primary:p,secondary:B,...Tt(n)}}return{primary:d??rt(void 0,t.activeTest),secondary:"",...Tt(n)}}}}export function generateTextOverlay(t,e,i){const{plotMode:a,colorFns:s,normalize:n,activeTest:o,valueRange:l,testDefs:d,fallbackFormat:g,passFailDisplay:p="off",activeMetadataKey:B,metadataColorMap:F,allIntegerValues:T}=i,b=findTestDef(d,o),{tickFmt:$}=$t(l[1],b?.name,b?.unit,g),f=T?h=>String(Math.round(h)):$,{testNumber:u}=resolveTestNumber(o,d);return t.flatMap((h,v)=>{let m="",x="#111111";if(a==="value"&&p==="test"){const y=U(h,u,b);if(y===void 0)return[];m=y?"P":"F",x=Q(y?Mt:St)}else if(a==="value"){const y=L(h,u);if(y===void 0)return[];m=wt([y],f),x=Q(s.forValue(n(y)))}else if(a==="hardBin"||a==="softBin"){const y=a==="softBin"?h.sbin:h.hbin;if(y===void 0)return[];m=String(y),x=Q(s.forBin(y))}else if(a==="metadata"){const y=ct(h,B);if(y===void 0)return[];m=y,x=Q(F?.get(m)??w)}else{const y=L(h,0);if(y===void 0)return[];m=wt([y],f),x=Q(s.forValue(n(y)))}const V=e?e[v*2]:h.physX,E=e?e[v*2+1]:h.physY;return[{x:V,y:E,text:m,fontSize:Le(h,m),color:x,align:"center"}]})}function qt(t){return t==="top"?Math.PI/2:t==="bottom"?-Math.PI/2:t==="left"?Math.PI:0}function Re(t,e,i=720){const{center:a,radius:s,notch:n}=t,o=$=>G(e,$.x,$.y);if(!(n!==void 0&&t.diameter>150)){const $=[];for(let f=0;f<=i;f++){const u=2*Math.PI*f/i;$.push(o(Ce(t,u)))}return[{kind:"wafer-boundary",...q($,!0),lineColor:"#888888",lineWidth:1}]}const d=1.25,g=n.length,p=qt(n.type),B=Math.atan2(g,s),F=s-d,T=[];let b=!1;for(let $=0;$<=i;$++){const f=2*Math.PI*$/i;let u=f-p;for(;u>Math.PI;)u-=2*Math.PI;for(;u<-Math.PI;)u+=2*Math.PI;if(Math.abs(u)<=B){if(!b){b=!0;const h={x:a.x+s*Math.cos(p-B),y:a.y+s*Math.sin(p-B)},v={x:a.x+F*Math.cos(p),y:a.y+F*Math.sin(p)},m={x:a.x+s*Math.cos(p+B),y:a.y+s*Math.sin(p+B)};T.push(o(h),o(v),o(m))}continue}T.push(o({x:a.x+s*Math.cos(f),y:a.y+s*Math.sin(f)}))}return[{kind:"wafer-boundary",...q(T,!0),lineColor:"#888888",lineWidth:1}]}function Ne(t,e,i,a=360){const s=[],n=Math.max(1,Math.floor(i));for(let o=1;o<n;o++){const l=t.radius*o/n,d=[];for(let g=0;g<=a;g++){const p=2*Math.PI*g/a,B={x:t.center.x+l*Math.cos(p),y:t.center.y+l*Math.sin(p)};d.push(G(e,B.x,B.y))}s.push({kind:"ring-boundary",...q(d,!0),lineColor:"rgba(255,255,255,0.7)",lineWidth:1.5})}return s}function Oe(t,e,i,a){const{x:s,y:n}=t.center,o=t.radius,l=(f,u)=>G(e,f,u),d=i-s,g=Math.sqrt(Math.max(0,o*o-d*d)),p=l(i,n-g),B=l(i,n+g),F=a-n,T=Math.sqrt(Math.max(0,o*o-F*F)),b=l(s-T,a),$=l(s+T,a);return[{kind:"quadrant-boundary",...q([p,B]),lineColor:"rgba(255,255,255,0.7)",lineWidth:1.5},{kind:"quadrant-boundary",...q([b,$]),lineColor:"rgba(255,255,255,0.7)",lineWidth:1.5}]}function Ee(t,e){return t.map(i=>{const a=i.width/2,s=i.height/2;return{kind:"reticle",points:[[[i.x-a,i.y-s],[i.x+a,i.y-s],[i.x+a,i.y+s],[i.x-a,i.y+s]].map(([o,l])=>G(e,o,l))],closed:!0,lineColor:"rgba(255,255,255,0.7)",lineWidth:1.5,fill:"rgba(0,0,0,0)"}})}function Ae(t){const e=t.filter(i=>i.probeIndex!==void 0).sort((i,a)=>(i.probeIndex??0)-(a.probeIndex??0));return e.length?[{kind:"probe-path",...q(e.map(i=>({x:i.physX,y:i.physY}))),lineColor:"rgba(220,80,0,0.55)",lineWidth:1}]:[]}function He(t,e,i,a,s,n,o,l,d,g,p,B,F,T,b="off",$,f,u){const h=e.width-o,v=e.height-o,m=n?v:h,x=n?h:v,V=S=>s==="softBin"?S.sbin:S.hbin,E=S=>ct(S,$);if(e.partial){t.push({x:i,y:a,width:m,height:x,fill:Ie,type:"stacked",metadata:e.metadata});return}if(e.edgeExcluded){t.push({x:i,y:a,width:m,height:x,fill:De,type:"stacked",metadata:e.metadata});return}if(d!==void 0&&(s==="hardBin"||s==="softBin")&&V(e)!==d){t.push({x:i,y:a,width:m,height:x,fill:zt,type:"hardBin",metadata:e.metadata});return}if(u!==void 0&&s==="metadata"&&E(e)!==u){t.push({x:i,y:a,width:m,height:x,fill:zt,type:"metadata",metadata:e.metadata});return}if(s==="value"){if(b==="test"){const j=U(e,p,T),ut=j===void 0?w:j?Mt:St;t.push({x:i,y:a,width:m,height:x,fill:ut,type:"value",metadata:e.metadata});return}const S=L(e,p),N=ot(S,T);let W,J;S===void 0?W=w:b==="spec"?W=N==="failLow"?ye:N==="failHigh"?St:Mt:(W=l.forValue(g(S)),(N==="failLow"||N==="failHigh")&&(J=N)),t.push({x:i,y:a,width:m,height:x,fill:W,type:"value",specMark:J,metadata:e.metadata});return}if(s==="hardBin"||s==="softBin"){const S=V(e),N=S!=null?l.forBin(S):w,W=S!=null&&Me(e,F)===!1?!0:void 0;t.push({x:i,y:a,width:m,height:x,fill:N,type:s,binFail:W,metadata:e.metadata});return}if(s==="metadata"){const S=E(e),N=S!==void 0?f?.get(S)??w:w;t.push({x:i,y:a,width:m,height:x,fill:N,type:"metadata",metadata:e.metadata});return}const y=L(e,0),A=y!==void 0?l.forValue(g(y)):w;t.push({x:i,y:a,width:m,height:x,fill:A,type:"value",metadata:e.metadata})}function _e(t,e,i){const a=t.radius*.15,s=xt(e,a,0),n=xt(e,0,a),o=s.x+n.x>=0?-1:1,l=s.y+n.y>=0?-1:1,d={x:t.center.x+o*t.radius*.9,y:t.center.y+l*t.radius*.9},g={x:d.x+s.x,y:d.y+s.y},p={x:d.x+n.x,y:d.y+n.y};return i.push({x:g.x+s.x*.35,y:g.y+s.y*.35,text:"+X",fontSize:10,color:"#cc3300",align:"center",role:"indicator"},{x:p.x+n.x*.35,y:p.y+n.y*.35,text:"+Y",fontSize:10,color:"#0044cc",align:"center",role:"indicator"}),[{kind:"xy-indicator",...q([d,g]),lineColor:"#cc3300",lineWidth:2},{kind:"xy-indicator",...q([d,p]),lineColor:"#0044cc",lineWidth:2}]}export function buildView(t,e,i={},a){const s=i.reticles??[],{plotMode:n="value",showDieLabels:o=!1,showReticle:l=!1,showProbePath:d=!1,ringCount:g=4,showRingBoundaries:p=!1,showQuadrantBoundaries:B=!1,showXYIndicator:F=!1,binColorScheme:T="default",valueColorScheme:b="default",reverseValueScheme:$=!1,useDefinedBinColors:f=!0,binColors:u,passBins:h=[1],highlightBin:v,highlightMetadataValue:m,interactiveTransform:x,valueRange:V,metadataValueOrder:E,testDefs:y,activeTest:A=0,activeMetadataKey:S,fallbackFormat:N="engineering",aggregationMethod:W,lotSize:J,dataAxisFlip:j,isLotStack:ut=!1,logScale:Xt,colorbarRangeMode:Yt="spec",passFailDisplay:Gt}=i,It=Gt??"off",ft=a?.hbinDefs,dt=a?.sbinDefs,Dt=a?.metadataFields,Qt=Dt?.find(c=>c.key===S),Ut={x:(j?.x??!1)!==(x?.flipX??!1),y:(j?.y??!1)!==(x?.flipY??!1)},Jt=ft?new Map(ft.map(c=>[c.bin,c])):null,Zt=dt?new Map(dt.map(c=>[c.bin,c])):null,te=n==="softBin"?Zt:Jt,ee=new Set(h),ht=u&&xe(u,e)?u:be(e,{passBins:h,binColorScheme:T,hbinDefs:ft,sbinDefs:dt,useDefinedBinColors:f}),ne=n==="softBin"?ht.soft:ht.hard,Vt={forValue:Fe(ge(b,$)),forBin:c=>ne.get(c)??w};let{testNumber:H}=resolveTestNumber(A,y);if(!y?.length&&n==="value"&&!e.some(r=>r.testValues&&H in r.testValues)){const r=e.reduce((M,D)=>{if(!D.testValues)return M;const k=Object.keys(D.testValues).map(Number),R=k.length?ke(k):void 0;return R!==void 0&&(M===void 0||R<M)?R:M},void 0);r!==void 0&&(H=r)}let Z;if(Array.isArray(V))Z=V;else if(V){const{testNumber:c}=resolveTestNumber(V.test,y);Z=c===H?V.range:void 0}const I=findTestDef(y,H),Ct=n==="value"&&I!==void 0&&!vt(I),ae=I!==void 0&&(I.limitLow!==void 0||I.limitHigh!==void 0);let K="off";n==="value"&&(Ct?K="test":It==="spec"&&ae?K="spec":It==="test"&&e.some(c=>U(c,H,I)!==void 0)&&(K="test"));const Ft=K==="spec"?"spec":Yt,mt=n==="stackedValues"||n==="stackedBins"||n==="stackedSoftBins";let O,_,tt=!1;if(Z){[O,_]=Z;let c=!0,r=!1;for(const M of e){const D=mt?L(M,0):L(M,H);if(D!==void 0&&(r=!0,!Number.isInteger(D))){c=!1;break}}tt=r&&c}else{const c=!mt&&n==="value"&&Ft==="spec"&&I&&(I.limitLow!==void 0||I.limitHigh!==void 0);let r=1/0,M=-1/0,D=!0;for(const k of e){const R=mt?L(k,0):L(k,H);R!==void 0&&(R<r&&(r=R),R>M&&(M=R),Number.isInteger(R)||(D=!1))}isFinite(r)||(D=!1),tt=D,c?(O=I.limitLow!==void 0?I.limitLow:isFinite(r)?r:0,_=I.limitHigh!==void 0?I.limitHigh:isFinite(M)?M:1):(O=isFinite(r)?r:0,_=isFinite(M)?M:1)}O===_&&(_=O+1);const Lt=Ct?!1:Xt??I?.logScale??!1,Pt=Lt&&O>0&&_>0,ie=Pt;let et;if(Pt){const c=Math.log10(O),r=Math.log10(_)-c;et=M=>M<=0?0:Kt((Math.log10(M)-c)/r)}else et=c=>Kt((c-O)/(_-O));const C=Ve(t,j,x);let Rt=null;if(t.notch){const c=qt(t.notch.type),r=xt(C.physicalToScreen,Math.cos(c)*t.radius,Math.sin(c)*t.radius),M=Math.hypot(r.x,r.y);M>0&&(Rt={x:r.x/M,y:r.y/M})}let X=1/0;for(let c=0;c<e.length;c++){const r=e[c];r.width<X&&(X=r.width),r.height<X&&(X=r.height)}const Nt=1,se=X!==1/0?Math.min(Nt,X*.12):Nt,Ot=[],pt=[],oe=C.bakedToScreen.a===1&&C.bakedToScreen.b===0&&C.bakedToScreen.c===0&&C.bakedToScreen.d===1&&C.bakedToScreen.e===0&&C.bakedToScreen.f===0;let z=null;if(!oe){z=new Float64Array(e.length*2);for(let c=0;c<e.length;c++){const r=G(C.bakedToScreen,e[c].physX,e[c].physY);z[c*2]=r.x,z[c*2+1]=r.y}}const re=pe(C.gridToScreen),le=n==="hardBin"||n==="softBin"||n==="stackedBins"||n==="stackedSoftBins",ce=n==="softBin"||n==="stackedSoftBins";let nt=le?new Map:void 0;const yt=n==="value"&&K==="spec"?{pass:0,failHigh:0,failLow:0}:void 0,gt=n==="value"&&K==="test"?{pass:0,fail:0}:void 0,Et=n==="metadata";let at=Et?new Map:void 0,it=null;if(Et&&S){const c=collectMetadataValues(e,S),r=E?.key===S?E.values:void 0,M=r?[...r,...c.filter(D=>!r.includes(D))]:c;it=new Map(M.map((D,k)=>[D,Qt?.values?.find(R=>R.value===D)?.color??Se(k)]))}for(let c=0;c<e.length;c++){const r=e[c],M=z?z[c*2]:r.physX,D=z?z[c*2+1]:r.physY;if(pt.push({x:M,y:D}),nt&&!r.partial&&!r.edgeExcluded){const k=ce?r.sbin:r.hbin;k!=null&&nt.set(k,(nt.get(k)??0)+1)}if(yt&&!r.partial&&!r.edgeExcluded){const k=ot(L(r,H),I);k&&yt[k]++}if(gt&&!r.partial&&!r.edgeExcluded){const k=U(r,H,I);k!==void 0&>[k?"pass":"fail"]++}if(at&&!r.partial&&!r.edgeExcluded){const k=ct(r,S);k!==void 0&&at.set(k,(at.get(k)??0)+1)}He(Ot,r,M,D,n,re,se,Vt,v,et,H,te,ee,I,K,S,it,m)}const At=o?generateTextOverlay(e,z,{plotMode:n,colorFns:Vt,normalize:et,activeTest:A,valueRange:[O,_],testDefs:y,fallbackFormat:N,passFailDisplay:K,activeMetadataKey:S,metadataColorMap:it,allIntegerValues:tt}):[],Y=Re(t,C.physicalToScreen);p&&Y.push(...Ne(t,C.physicalToScreen,g)),B&&Y.push(...Oe(t,C.physicalToScreen,t.center.x,t.center.y)),l&&Y.push(...Ee(s,C.gridToScreen)),d&&Y.push(...Ae(e)),F&&Y.push(..._e(t,C.gridToScreen,At));const ue=pt.length>0?{minX:t.center.x-t.radius,maxX:t.center.x+t.radius,minY:t.center.y-t.radius,maxY:t.center.y+t.radius}:null;return{rectangles:Ot,hoverPoints:pt,texts:At,overlays:Y,plotMode:n,binColorScheme:T,valueColorScheme:b,reverseValueScheme:$,binColors:ht,metadata:t.metadata??null,dies:e,valueRange:[O,_],allIntegerValues:tt,testDefs:y,activeTest:A,logScale:ie,logScaleRequested:Lt,aggrMethod:W,lotSize:J,axisFlip:Ut,dataAxisFlip:{x:j?.x??!1,y:j?.y??!1},gridToScreen:C.gridToScreen,rotation:((t.orientation+(x?.rotation??0))%360+360)%360,hasReticle:s.length>0,isLotStack:ut,passFailDisplay:K,colorbarRangeMode:Ft,waferCenter:t.center,waferRadius:t.radius,notchDir:Rt,binCounts:nt,metadataCounts:at,metadataColorMap:it,activeMetadataKey:S,metadataFields:Dt,specCounts:yt,passFailCounts:gt,dieBounds:ue}}export{getDieKey}from"../core/dies.js";
|
|
1
|
+
import{hasPosition as pe}from"../core/dies.js";import{dieValueEntries as ge,dieVerdictEntries as ye,recordedVerdict as be,testsPresent as _t}from"../core/dieTable.js";import{getReticleCell as xe}from"../core/reticle.js";import{metadataCategoricalValue as Me,metadataDisplayValue as Se}from"../core/metadata.js";import{affineRotation as bt,affineMirror as Ot,affineCompose as st,affinePoint as G,affineVector as ve,affineSwapsAxes as ke}from"../core/transforms.js";import{contrastTextColor as Q,SPEC_PASS_FILL as xt,SPEC_FAIL_LOW as Be,SPEC_FAIL_HIGH as Mt}from"./colorMap.js";import{resolveValueColorFn as $e}from"./colorSchemes.js";import{resolveBinColors as Ie,binColorsCover as Te}from"./binColors.js";import{NO_DATA_FILL as z}from"./colorMap.js";import{diePassStatus as De}from"../core/dies.js";import{compactView as Fe}from"./compactView.js";import{buildXYIndicator as Ve}from"./xyIndicator.js";import{getDieTestValue as L,getTestPassStatus as U,isParametricTest as St}from"./buildWaferMap.js";import{classifySpec as ot}from"./spec.js";export{ot as classifySpec};import{fmt as vt,fmtColorbarAxis as kt,fmtAggregationMethod as wt}from"./fmt.js";import{metadataValueColor as Ce}from"./colorMap.js";import{asList as Kt,clamp01 as Wt,compareNatural as Le,escHtml as R}from"../core/utils.js";import{prettyKey as qt}from"../core/utils.js";import{testLabel as rt,markedTestLabel as lt,derivedTestNote as zt,isDerivedTest as Re,DERIVED_MARK as Pe,DERIVED_KEY as Ne}from"./testLabel.js";export function resolveTestNumber(t,e){return e?.length?{testNumber:(findTestDef(e,t)??e[0]).testNumber}:{testNumber:t}}export function collectMetadataValues(t,e){const n=new Set;for(const i of t){if(i.partial||i.edgeExcluded)continue;const s=ct(i,e);s!==void 0&&n.add(s)}return[...n].sort(Le)}export function findTestDef(t,e){return t?.find(n=>n.testNumber===e)}export function getUniqueTestNumbers(t){return _t(t,"values")}const Ee="#d3d6db",jt="#e8e9ea",Ae="#aab0ba";function ct(t,e){return e?Me(t.metadata?.[e]):void 0}function He(t,e,n){const{x:i,y:s}=t.center,a=bt(t.orientation,i,s),o=st(Ot(e?.x??!1,e?.y??!1,i,s),bt(t.orientation,i,s)),r=st(Ot(n?.flipX??!1,n?.flipY??!1,i,s),bt(n?.rotation??0,i,s));return{bakedToScreen:r,physicalToScreen:st(r,a),gridToScreen:st(r,o)}}function Y(t,e=!1){return{points:[t],closed:e}}function _e(t,e){const{center:n,radius:i,notch:s}=t;let a=n.x+i*Math.cos(e),o=n.y+i*Math.sin(e);if(!s)return{x:a,y:o};const r=Math.sqrt(i**2-(s.length/2)**2),d=s.length/2,y=a-n.x,b=o-n.y;return s.type==="bottom"&&b<-r?(o=n.y-r,a=n.x+Math.max(-d,Math.min(d,y))):s.type==="top"&&b>r?(o=n.y+r,a=n.x+Math.max(-d,Math.min(d,y))):s.type==="left"&&y<-r?(a=n.x-r,o=n.y+Math.max(-d,Math.min(d,b))):s.type==="right"&&y>r&&(a=n.x+r,o=n.y+Math.max(-d,Math.min(d,b))),{x:a,y:o}}function Xt(t,e){return t.map(e).join(" / ")}function Oe(t,e=256){const n=Array.from({length:e},(i,s)=>t(s/(e-1)));return i=>n[Math.min(e-1,Math.max(0,Math.round(i*(e-1))))]}function we(t,e){const n=Math.max(1,Math.min(t.width,t.height)),i=t.width/Math.max(e.length,1);return Math.max(8,Math.min(16,Math.round(Math.min(n*.55,i*1.8))))}function Ke(t,e,n){if(e?.length){const a=e.flatMap(o=>{const r=o.testNumber;if(!St(o)){const y=U(t,r,o);return y===void 0?[]:[{key:r,label:lt(o,r),value:y?"Pass":"Fail"}]}const d=L(t,r);return d===void 0?[]:[{key:r,label:lt(o,r),value:vt(d,o.unit,n),recordedFail:be(t,r)===!1}]});if(a.length)return a}const i=ge(t);if(i.length)return i.map(([a,o])=>({key:a,label:rt(void 0,a),value:vt(o,void 0,n)}));const s=ye(t);return s.length?s.map(([a,o])=>({key:a,label:rt(void 0,a),value:o?"Pass":"Fail"})):[]}export function buildHoverText(t,e,n={}){const{testDefs:i,hbinDefs:s,sbinDefs:a,fallbackFormat:o,aggrMethod:r,lotSize:d,activeTest:y,reticleConfig:b,metadataFields:B}=n,D=s?new Map(s.map(f=>[f.bin,f])):null,I=a?new Map(a.map(f=>[f.bin,f])):null,g=[R(`Die (${t.x}, ${t.y})`)];if(b&&pe(t)){const f=xe(t,b);g.push(R(`Reticle (${f.column}, ${f.row})`))}if(e==="stackedValues"){const f=L(t,0);if(f!==void 0){const u=i?.[0],h=u?lt(u,u.testNumber):"Value",S=r?` (${r})`:"";g.push(R(`${h}${S}: ${vt(f,u?.unit,o)}`));const m=zt(u);m&&g.push(`<i>${R(m)}</i>`)}}else if(e==="stackedBins"||e==="stackedSoftBins"){const f=L(t,0),u=e==="stackedSoftBins"?t.sbin:t.hbin;if(f!==void 0){const h=e==="stackedSoftBins"?I:D,S=u!==void 0?h?.get(u)?.name?`${u} \xB7 ${h.get(u).name}`:`Bin ${u}`:"Bin";let m;if(r==="percent")m=`${f.toFixed(0)}%`;else{const F=d?` (${(f/d*100).toFixed(0)}% of stacked wafers)`:"";m=`${f}${F}`}const x=r?` [${wt(r)}]`:"";g.push(R(`${S}: ${m}${x}`))}}else{const f=Ke(t,i,o);if(e==="value"&&f.length){const{testNumber:u}=resolveTestNumber(y??0,i),h=findTestDef(i,u),S=L(t,u);let m=f.findIndex(A=>A.key===u);m<0&&(m=0);const x=f[m];let F=`<b>${R(`${x.label}: ${x.value}`)}${x.recordedFail?" <i>(recorded fail)</i>":""}</b>`;if(x.key===u&&h){const A=ot(S,h);(A==="failLow"||A==="failHigh")&&(F+=" <i>(outside test limits)</i>")}g.push(F);const E=zt(findTestDef(i,x.key));E&&g.push(`<i>${R(E)}</i>`);const p=f.length-1;p>0&&g.push(`<i>+${p} more test${p===1?"":"s"}</i>`)}else if(f.length){const u=f.length;g.push(`<i>${u} test result${u===1?"":"s"} recorded</i>`)}if(t.hbin!==void 0||t.sbin!==void 0){const u=[];if(t.hbin!==void 0){const h=D?.get(t.hbin)?.name,S=h?`${t.hbin} \xB7 ${h}`:String(t.hbin);u.push(`HBin: ${S}`)}if(t.sbin!==void 0){const h=I?.get(t.sbin)?.name,S=h?`${t.sbin} \xB7 ${h}`:String(t.sbin);u.push(`SBin: ${S}`)}g.push(u.map(R).join(" \xB7 "))}}t.retestCount!==void 0&&g.push(R(`Retests: ${t.retestCount}`)),t.siteNum!==void 0&&g.push(R(`Site: ${t.siteNum}`)),t.partId!==void 0&&g.push(R(`Part ID: ${t.partId}`)),t.partial&&g.push("<i>partial die</i>"),t.probeIndex!==void 0&&g.push(R(`Probe: #${t.probeIndex}`));const k={...t.metadata??{}};for(const[f,u]of Object.entries(k)){const h=Se(u);if(h===void 0)continue;const S=B?.find(m=>m.key===f)?.label??qt(f);g.push(R(`${S}: ${h}`))}return g.join("<br>")}function Bt(t){return t?.name?lt(t,t.testNumber):void 0}function $t(t){return Re(t)?{note:`${Pe} ${Ne}`}:{}}export function buildMapTitle(t,e="engineering",n){const i=t.isLotStack&&t.lotSize!==void 0?`stacked (${t.lotSize} ${t.lotSize===1?"wafer":"wafers"})`:"stacked",s=a=>{const o=n?.length===1?n[0]:void 0;return o===void 0?`${a} Bin`:o.name?`${a} Bin ${o.bin} \xB7 ${o.name}`:`${a} Bin ${o.bin}`};switch(t.plotMode){case"hardBin":return{primary:"Hard Bin",secondary:""};case"softBin":return{primary:"Soft Bin",secondary:""};case"stackedBins":return{primary:s("Hard"),secondary:i};case"stackedSoftBins":return{primary:s("Soft"),secondary:i};case"metadata":{const a=t.metadataFields?.find(r=>r.key===t.activeMetadataKey),o=t.activeMetadataKey?qt(t.activeMetadataKey):"Metadata";return{primary:a?.label??o,secondary:""}}case"stackedValues":{const a=t.testDefs?.[0],o=t.valueRange[1]||t.valueRange[0]||0,{axisLabel:r}=kt(o,Bt(a),a?.unit,e),d=r||"Value",y=t.aggrMethod?` \xB7 ${wt(t.aggrMethod)}`:"";return{primary:`${d}${y}`,secondary:i,...$t(a)}}default:{const a=findTestDef(t.testDefs,t.activeTest),o=t.valueRange[1]||t.valueRange[0]||0,{axisLabel:r}=kt(o,Bt(a),a?.unit,e),d=r||Bt(a);if(t.passFailDisplay!=="off"){const y=a?.testNumber??t.activeTest,b=d?`${d} \xB7 #${y}`:rt(void 0,y),B=t.passFailDisplay==="spec"?"Limit pass/fail":a!==void 0&&!St(a)?"Functional pass/fail":"Tester pass/fail";return{primary:b,secondary:B,...$t(a)}}return{primary:d??rt(void 0,t.activeTest),secondary:"",...$t(a)}}}}export function generateTextOverlay(t,e,n){const{plotMode:i,colorFns:s,normalize:a,activeTest:o,valueRange:r,testDefs:d,fallbackFormat:y,passFailDisplay:b="off",activeMetadataKey:B,metadataColorMap:D,allIntegerValues:I}=n,g=findTestDef(d,o),{tickFmt:k}=kt(r[1],g?.name,g?.unit,y),f=I?h=>String(Math.round(h)):k,{testNumber:u}=resolveTestNumber(o,d);return t.flatMap((h,S)=>{let m="",x="#111111";if(i==="value"&&b==="test"){const p=U(h,u,g);if(p===void 0)return[];m=p?"P":"F",x=Q(p?xt:Mt)}else if(i==="value"){const p=L(h,u);if(p===void 0)return[];m=Xt([p],f),x=Q(s.forValue(a(p)))}else if(i==="hardBin"||i==="softBin"){const p=i==="softBin"?h.sbin:h.hbin;if(p===void 0)return[];m=String(p),x=Q(s.forBin(p))}else if(i==="metadata"){const p=ct(h,B);if(p===void 0)return[];m=p,x=Q(D?.get(m)??z)}else{const p=L(h,0);if(p===void 0)return[];m=Xt([p],f),x=Q(s.forValue(a(p)))}const F=e?e[S*2]:h.physX,E=e?e[S*2+1]:h.physY;return[{x:F,y:E,text:m,fontSize:we(h,m),color:x,align:"center"}]})}function Yt(t){return t==="top"?Math.PI/2:t==="bottom"?-Math.PI/2:t==="left"?Math.PI:0}function We(t,e,n=720){const{center:i,radius:s,notch:a}=t,o=k=>G(e,k.x,k.y);if(!(a!==void 0&&t.diameter>150)){const k=[];for(let f=0;f<=n;f++){const u=2*Math.PI*f/n;k.push(o(_e(t,u)))}return[{kind:"wafer-boundary",...Y(k,!0),lineColor:"#888888",lineWidth:1}]}const d=1.25,y=a.length,b=Yt(a.type),B=Math.atan2(y,s),D=s-d,I=[];let g=!1;for(let k=0;k<=n;k++){const f=2*Math.PI*k/n;let u=f-b;for(;u>Math.PI;)u-=2*Math.PI;for(;u<-Math.PI;)u+=2*Math.PI;if(Math.abs(u)<=B){if(!g){g=!0;const h={x:i.x+s*Math.cos(b-B),y:i.y+s*Math.sin(b-B)},S={x:i.x+D*Math.cos(b),y:i.y+D*Math.sin(b)},m={x:i.x+s*Math.cos(b+B),y:i.y+s*Math.sin(b+B)};I.push(o(h),o(S),o(m))}continue}I.push(o({x:i.x+s*Math.cos(f),y:i.y+s*Math.sin(f)}))}return[{kind:"wafer-boundary",...Y(I,!0),lineColor:"#888888",lineWidth:1}]}function qe(t,e,n,i=360){const s=[],a=Math.max(1,Math.floor(n));for(let o=1;o<a;o++){const r=t.radius*o/a,d=[];for(let y=0;y<=i;y++){const b=2*Math.PI*y/i,B={x:t.center.x+r*Math.cos(b),y:t.center.y+r*Math.sin(b)};d.push(G(e,B.x,B.y))}s.push({kind:"ring-boundary",...Y(d,!0),lineColor:"rgba(255,255,255,0.7)",lineWidth:1.5})}return s}function ze(t,e,n,i){const{x:s,y:a}=t.center,o=t.radius,r=(f,u)=>G(e,f,u),d=n-s,y=Math.sqrt(Math.max(0,o*o-d*d)),b=r(n,a-y),B=r(n,a+y),D=i-a,I=Math.sqrt(Math.max(0,o*o-D*D)),g=r(s-I,i),k=r(s+I,i);return[{kind:"quadrant-boundary",...Y([b,B]),lineColor:"rgba(255,255,255,0.7)",lineWidth:1.5},{kind:"quadrant-boundary",...Y([g,k]),lineColor:"rgba(255,255,255,0.7)",lineWidth:1.5}]}function je(t,e){return t.map(n=>{const i=n.width/2,s=n.height/2;return{kind:"reticle",points:[[[n.x-i,n.y-s],[n.x+i,n.y-s],[n.x+i,n.y+s],[n.x-i,n.y+s]].map(([o,r])=>G(e,o,r))],closed:!0,lineColor:"rgba(255,255,255,0.7)",lineWidth:1.5,fill:"rgba(0,0,0,0)"}})}function Xe(t){const e=t.filter(n=>n.probeIndex!==void 0).sort((n,i)=>(n.probeIndex??0)-(i.probeIndex??0));return e.length?[{kind:"probe-path",...Y(e.map(n=>({x:n.physX,y:n.physY}))),lineColor:"rgba(220,80,0,0.55)",lineWidth:1}]:[]}function Ye(t,e,n,i,s,a,o,r,d,y,b,B,D,I,g="off",k,f,u){const h=e.width-o,S=e.height-o,m=a?S:h,x=a?h:S,F=M=>s==="softBin"?M.sbin:M.hbin,E=M=>ct(M,k);if(e.partial){t.push({x:n,y:i,width:m,height:x,fill:Ee,type:"stacked",metadata:e.metadata});return}if(e.edgeExcluded){t.push({x:n,y:i,width:m,height:x,fill:Ae,type:"stacked",metadata:e.metadata});return}if(d!==void 0&&(s==="hardBin"||s==="softBin")&&!d.has(F(e))){t.push({x:n,y:i,width:m,height:x,fill:jt,type:"hardBin",metadata:e.metadata});return}if(u!==void 0&&s==="metadata"&&!u.has(E(e))){t.push({x:n,y:i,width:m,height:x,fill:jt,type:"metadata",metadata:e.metadata});return}if(s==="value"){if(g==="test"){const K=U(e,b,I),ut=K===void 0?z:K?xt:Mt;t.push({x:n,y:i,width:m,height:x,fill:ut,type:"value",metadata:e.metadata});return}const M=L(e,b),P=ot(M,I);let w,J;M===void 0?w=z:g==="spec"?w=P==="failLow"?Be:P==="failHigh"?Mt:xt:(w=r.forValue(y(M)),(P==="failLow"||P==="failHigh")&&(J=P)),t.push({x:n,y:i,width:m,height:x,fill:w,type:"value",specMark:J,metadata:e.metadata});return}if(s==="hardBin"||s==="softBin"){const M=F(e),P=M!=null?r.forBin(M):z,w=M!=null&&De(e,D)===!1?!0:void 0;t.push({x:n,y:i,width:m,height:x,fill:P,type:s,binFail:w,metadata:e.metadata});return}if(s==="metadata"){const M=E(e),P=M!==void 0?f?.get(M)??z:z;t.push({x:n,y:i,width:m,height:x,fill:P,type:"metadata",metadata:e.metadata});return}const p=L(e,0),A=p!==void 0?r.forValue(y(p)):z;t.push({x:n,y:i,width:m,height:x,fill:A,type:"value",metadata:e.metadata})}function Ge(t,e,n){const i=t.radius;return Ve(e,i*.15,(s,a)=>({x:t.center.x+s*i*.9,y:t.center.y+a*i*.9}),n).overlays}export function buildView(t,e,n={},i){const s=n.reticles??[],{plotMode:a="value",showDieLabels:o=!1,showReticle:r=!1,showProbePath:d=!1,ringCount:y=4,showRingBoundaries:b=!1,showQuadrantBoundaries:B=!1,showXYIndicator:D=!1,binColorScheme:I="default",valueColorScheme:g="default",reverseValueScheme:k=!1,useDefinedBinColors:f=!0,binColors:u,passBins:h=[1],highlightBin:S,highlightMetadataValue:m,interactiveTransform:x,valueRange:F,metadataValueOrder:E,testDefs:p,activeTest:A=0,activeMetadataKey:M,fallbackFormat:P="engineering",aggregationMethod:w,lotSize:J,dataAxisFlip:K,isLotStack:ut=!1,logScale:Gt,colorbarRangeMode:Qt="spec",passFailDisplay:Ut}=n,It=Ut??"off",ft=i?.hbinDefs,dt=i?.sbinDefs,Tt=i?.metadataFields,Jt=Tt?.find(l=>l.key===M),Zt={x:(K?.x??!1)!==(x?.flipX??!1),y:(K?.y??!1)!==(x?.flipY??!1)},te=ft?new Map(ft.map(l=>[l.bin,l])):null,ee=dt?new Map(dt.map(l=>[l.bin,l])):null,ne=a==="softBin"?ee:te,ae=new Set(h),ht=u&&Te(u,e)?u:Ie(e,{passBins:h,binColorScheme:I,hbinDefs:ft,sbinDefs:dt,useDefinedBinColors:f}),ie=a==="softBin"?ht.soft:ht.hard,Dt={forValue:Oe($e(g,k)),forBin:l=>ie.get(l)??z};let{testNumber:H}=resolveTestNumber(A,p);if(!p?.length&&a==="value"){const l=_t(e,"values");!l.includes(H)&&l.length&&(H=l[0])}let Z;if(Array.isArray(F))Z=F;else if(F){const{testNumber:l}=resolveTestNumber(F.test,p);Z=l===H?F.range:void 0}const T=findTestDef(p,H),Ft=a==="value"&&T!==void 0&&!St(T),se=T!==void 0&&(T.limitLow!==void 0||T.limitHigh!==void 0);let O="off";a==="value"&&(Ft?O="test":It==="spec"&&se?O="spec":It==="test"&&e.some(l=>U(l,H,T)!==void 0)&&(O="test"));const Vt=O==="spec"?"spec":Qt,mt=a==="stackedValues"||a==="stackedBins"||a==="stackedSoftBins";let N,_,tt=!1;if(Z){[N,_]=Z;let l=!0,c=!1;for(const v of e){const C=mt?L(v,0):L(v,H);if(C!==void 0&&(c=!0,!Number.isInteger(C))){l=!1;break}}tt=c&&l}else{const l=!mt&&a==="value"&&Vt==="spec"&&T&&(T.limitLow!==void 0||T.limitHigh!==void 0);let c=1/0,v=-1/0,C=!0;for(const $ of e){const q=mt?L($,0):L($,H);q!==void 0&&(q<c&&(c=q),q>v&&(v=q),Number.isInteger(q)||(C=!1))}isFinite(c)||(C=!1),tt=C,l?(N=T.limitLow!==void 0?T.limitLow:isFinite(c)?c:0,_=T.limitHigh!==void 0?T.limitHigh:isFinite(v)?v:1):(N=isFinite(c)?c:0,_=isFinite(v)?v:1)}N===_&&(_=N+1);const Ct=Ft?!1:Gt??T?.logScale??!1,Lt=Ct&&N>0&&_>0,oe=Lt;let et;if(Lt){const l=Math.log10(N),c=Math.log10(_)-l;et=v=>v<=0?0:Wt((Math.log10(v)-l)/c)}else et=l=>Wt((l-N)/(_-N));const V=He(t,K,x);let Rt=null;if(t.notch){const l=Yt(t.notch.type),c=ve(V.physicalToScreen,Math.cos(l)*t.radius,Math.sin(l)*t.radius),v=Math.hypot(c.x,c.y);v>0&&(Rt={x:c.x/v,y:c.y/v})}let j=1/0;for(let l=0;l<e.length;l++){const c=e[l];c.width<j&&(j=c.width),c.height<j&&(j=c.height)}const Pt=1,re=j!==1/0?Math.min(Pt,j*.12):Pt,Nt=[],pt=[],le=V.bakedToScreen.a===1&&V.bakedToScreen.b===0&&V.bakedToScreen.c===0&&V.bakedToScreen.d===1&&V.bakedToScreen.e===0&&V.bakedToScreen.f===0;let W=null;if(!le){W=new Float64Array(e.length*2);for(let l=0;l<e.length;l++){const c=G(V.bakedToScreen,e[l].physX,e[l].physY);W[l*2]=c.x,W[l*2+1]=c.y}}const ce=ke(V.gridToScreen),ue=a==="hardBin"||a==="softBin"||a==="stackedBins"||a==="stackedSoftBins",fe=a==="softBin"||a==="stackedSoftBins";let nt=ue?new Map:void 0;const gt=a==="value"&&O==="spec"?{pass:0,failHigh:0,failLow:0}:void 0,yt=a==="value"&&O==="test"?{pass:0,fail:0}:void 0,Et=a==="metadata";let at=Et?new Map:void 0,it=null;if(Et&&M){const l=collectMetadataValues(e,M),c=E?.key===M?E.values:void 0,v=c?[...c,...l.filter(C=>!c.includes(C))]:l;it=new Map(v.map((C,$)=>[C,Jt?.values?.find(q=>q.value===C)?.color??Ce($)]))}const de=S===void 0?void 0:new Set(Kt(S)),he=m===void 0?void 0:new Set(Kt(m));for(let l=0;l<e.length;l++){const c=e[l],v=W?W[l*2]:c.physX,C=W?W[l*2+1]:c.physY;if(pt.push({x:v,y:C}),nt&&!c.partial&&!c.edgeExcluded){const $=fe?c.sbin:c.hbin;$!=null&&nt.set($,(nt.get($)??0)+1)}if(gt&&!c.partial&&!c.edgeExcluded){const $=ot(L(c,H),T);$&>[$]++}if(yt&&!c.partial&&!c.edgeExcluded){const $=U(c,H,T);$!==void 0&&yt[$?"pass":"fail"]++}if(at&&!c.partial&&!c.edgeExcluded){const $=ct(c,M);$!==void 0&&at.set($,(at.get($)??0)+1)}Ye(Nt,c,v,C,a,ce,re,Dt,de,et,H,ne,ae,T,O,M,it,he)}const At=o?generateTextOverlay(e,W,{plotMode:a,colorFns:Dt,normalize:et,activeTest:A,valueRange:[N,_],testDefs:p,fallbackFormat:P,passFailDisplay:O,activeMetadataKey:M,metadataColorMap:it,allIntegerValues:tt}):[],X=We(t,V.physicalToScreen);b&&X.push(...qe(t,V.physicalToScreen,y)),B&&X.push(...ze(t,V.physicalToScreen,t.center.x,t.center.y)),r&&X.push(...je(s,V.gridToScreen)),d&&X.push(...Xe(e)),D&&X.push(...Ge(t,V.gridToScreen,At));const me=pt.length>0?{minX:t.center.x-t.radius,maxX:t.center.x+t.radius,minY:t.center.y-t.radius,maxY:t.center.y+t.radius}:null,Ht={rectangles:Nt,hoverPoints:pt,texts:At,overlays:X,plotMode:a,binColorScheme:I,valueColorScheme:g,reverseValueScheme:k,binColors:ht,metadata:t.metadata??null,dies:e,valueRange:[N,_],allIntegerValues:tt,testDefs:p,activeTest:A,logScale:oe,logScaleRequested:Ct,aggrMethod:w,lotSize:J,axisFlip:Zt,dataAxisFlip:{x:K?.x??!1,y:K?.y??!1},gridToScreen:V.gridToScreen,rotation:((t.orientation+(x?.rotation??0))%360+360)%360,hasReticle:s.length>0,isLotStack:ut,passFailDisplay:O,colorbarRangeMode:Vt,waferCenter:t.center,waferRadius:t.radius,notchDir:Rt,binCounts:nt,metadataCounts:at,metadataColorMap:it,activeMetadataKey:M,metadataFields:Tt,specCounts:gt,passFailCounts:yt,dieBounds:me};return n.compact?Fe(Ht,n.compact,{showXYIndicator:D}):Ht}export{getDieKey}from"../core/dies.js";
|
|
@@ -2,9 +2,10 @@ import type { Die } from '../core/dies.js';
|
|
|
2
2
|
import type { DieMetadata, WaferMetadata } from '../core/metadata.js';
|
|
3
3
|
import type { Wafer } from '../core/wafer.js';
|
|
4
4
|
import type { Reticle } from '../core/reticle.js';
|
|
5
|
+
import type { DieColumns } from './columnarInput.js';
|
|
5
6
|
import { type DerivedTestDef } from './derivedTests/apply.js';
|
|
6
7
|
export type { DerivedTestDef } from './derivedTests/apply.js';
|
|
7
|
-
import
|
|
8
|
+
import type { PlotMode } from './buildView.js';
|
|
8
9
|
/**
|
|
9
10
|
* Test result for a single die position, as output by the prober.
|
|
10
11
|
* `x` and `y` are **die grid positions** (prober step coordinates) — integers
|
|
@@ -458,8 +459,11 @@ export interface WaferMapInputBase {
|
|
|
458
459
|
}
|
|
459
460
|
/** Single-wafer input — pass one wafer's die results directly. */
|
|
460
461
|
export interface WaferMapInputSingle extends WaferMapInputBase {
|
|
461
|
-
/**
|
|
462
|
-
|
|
462
|
+
/**
|
|
463
|
+
* Per-die test results from the prober: one object per record, or the same
|
|
464
|
+
* records as columns (`DieColumns`), for a host that already holds columns.
|
|
465
|
+
*/
|
|
466
|
+
results?: DieResult[] | DieColumns;
|
|
463
467
|
lotStack?: never;
|
|
464
468
|
layout?: never;
|
|
465
469
|
}
|
|
@@ -649,15 +653,14 @@ export interface WaferMapResult {
|
|
|
649
653
|
/** `true` when the result was built from a `lotStack` aggregation. */
|
|
650
654
|
isLotStack: boolean;
|
|
651
655
|
/**
|
|
652
|
-
* @internal The
|
|
653
|
-
*
|
|
654
|
-
*
|
|
655
|
-
*
|
|
656
|
-
* Read the promoted top-level fields instead (`plotMode`, `metadata`,
|
|
657
|
-
* `isLotStack`, `hbinDefs`, `sbinDefs`, `testDefs`): the renderers rebuild the
|
|
658
|
-
* draw list on every option change, so anything cached from this goes stale.
|
|
656
|
+
* @internal The data's own axis flip (`dieConfig.xAxisDirection` / `yAxisDirection` /
|
|
657
|
+
* `coordinateOrigin`), which the renderers feed back into every view they build.
|
|
658
|
+
* Its presence also marks a map as built by `buildWaferMap` (`isBuiltMap`).
|
|
659
659
|
*/
|
|
660
|
-
|
|
660
|
+
dataAxisFlip: {
|
|
661
|
+
x: boolean;
|
|
662
|
+
y: boolean;
|
|
663
|
+
};
|
|
661
664
|
/** Reticle configuration used to generate the overlay and reticle-local groupings. */
|
|
662
665
|
reticleConfig?: ReticleConfig;
|
|
663
666
|
/**
|
|
@@ -758,6 +761,12 @@ export interface WaferMapResult {
|
|
|
758
761
|
*/
|
|
759
762
|
lotSize?: number;
|
|
760
763
|
}
|
|
764
|
+
/**
|
|
765
|
+
* @internal Whether `map` was built by `buildWaferMap`, rather than being an input
|
|
766
|
+
* or a map a host assembled from its own pieces. Survives a spread and a worker's
|
|
767
|
+
* structured clone, which is why it tests a field and not object identity.
|
|
768
|
+
*/
|
|
769
|
+
export declare function isBuiltMap(map: object): map is WaferMapResult;
|
|
761
770
|
/** Read a test value from a die by test number. */
|
|
762
771
|
export declare function getDieTestValue(die: Die, testNumber: number): number | undefined;
|
|
763
772
|
/**
|
|
@@ -774,6 +783,15 @@ export declare function getDieTestValue(die: Die, testNumber: number): number |
|
|
|
774
783
|
* as no-data, never as a fail.
|
|
775
784
|
*/
|
|
776
785
|
export declare function getTestPassStatus(die: Pick<Die, 'testValues' | 'testPass'>, testNumber: number, testDef?: TestDef): boolean | undefined;
|
|
786
|
+
/**
|
|
787
|
+
* @internal {@link getTestPassStatus} for many dies × tests: `read(die, out)`
|
|
788
|
+
* writes each of `testDefs`' verdicts for `die` to `out` — `1` pass, `0` fail,
|
|
789
|
+
* `-1` none — and returns `out`. The same rule, the same fallback: the recorded
|
|
790
|
+
* verdicts come through a bulk column reader (looked up once per table, not once
|
|
791
|
+
* per die and test), which is what a lot-wide chart walking every die needs.
|
|
792
|
+
* `tests/testPassStatusReader.test.mjs` holds the two to the same answers.
|
|
793
|
+
*/
|
|
794
|
+
export declare function testPassStatusReader(testDefs: readonly TestDef[]): (die: Die, out: Int8Array) => Int8Array;
|
|
777
795
|
/**
|
|
778
796
|
* True when the die carries any per-test data — a measured test value or a
|
|
779
797
|
* recorded pass/fail verdict. The single source for "does this die have test
|