@wafertools/wafermap 0.21.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +751 -0
- package/LICENSE +21 -0
- package/README.md +79 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +1 -0
- package/dist/packages/canvas-adapter/canvasTheme.d.ts +28 -0
- package/dist/packages/canvas-adapter/canvasTheme.js +1 -0
- package/dist/packages/canvas-adapter/charts/barPanel.d.ts +52 -0
- package/dist/packages/canvas-adapter/charts/barPanel.js +1 -0
- package/dist/packages/canvas-adapter/charts/binCluster.d.ts +17 -0
- package/dist/packages/canvas-adapter/charts/binCluster.js +1 -0
- package/dist/packages/canvas-adapter/charts/boxplot.d.ts +45 -0
- package/dist/packages/canvas-adapter/charts/boxplot.js +1 -0
- package/dist/packages/canvas-adapter/charts/capability.d.ts +41 -0
- package/dist/packages/canvas-adapter/charts/capability.js +1 -0
- package/dist/packages/canvas-adapter/charts/chartShell.d.ts +156 -0
- package/dist/packages/canvas-adapter/charts/chartShell.js +1 -0
- package/dist/packages/canvas-adapter/charts/correlation.d.ts +35 -0
- package/dist/packages/canvas-adapter/charts/correlation.js +1 -0
- package/dist/packages/canvas-adapter/charts/histogram.d.ts +30 -0
- package/dist/packages/canvas-adapter/charts/histogram.js +1 -0
- package/dist/packages/canvas-adapter/charts/index.d.ts +16 -0
- package/dist/packages/canvas-adapter/charts/index.js +1 -0
- package/dist/packages/canvas-adapter/charts/palette.d.ts +26 -0
- package/dist/packages/canvas-adapter/charts/palette.js +1 -0
- package/dist/packages/canvas-adapter/charts/regionYieldDiagram.d.ts +18 -0
- package/dist/packages/canvas-adapter/charts/regionYieldDiagram.js +1 -0
- package/dist/packages/canvas-adapter/charts/scatter.d.ts +37 -0
- package/dist/packages/canvas-adapter/charts/scatter.js +1 -0
- package/dist/packages/canvas-adapter/icons.d.ts +2 -0
- package/dist/packages/canvas-adapter/icons.js +42 -0
- package/dist/packages/canvas-adapter/index.d.ts +12 -0
- package/dist/packages/canvas-adapter/index.js +1 -0
- package/dist/packages/canvas-adapter/insightsTab.d.ts +76 -0
- package/dist/packages/canvas-adapter/insightsTab.js +1 -0
- package/dist/packages/canvas-adapter/metadataBadge.d.ts +26 -0
- package/dist/packages/canvas-adapter/metadataBadge.js +1 -0
- package/dist/packages/canvas-adapter/renderWaferGallery.d.ts +181 -0
- package/dist/packages/canvas-adapter/renderWaferGallery.js +20 -0
- package/dist/packages/canvas-adapter/renderWaferMap.d.ts +316 -0
- package/dist/packages/canvas-adapter/renderWaferMap.js +1 -0
- package/dist/packages/canvas-adapter/summaryPanel.d.ts +262 -0
- package/dist/packages/canvas-adapter/summaryPanel.js +3 -0
- package/dist/packages/canvas-adapter/toCanvas.d.ts +106 -0
- package/dist/packages/canvas-adapter/toCanvas.js +1 -0
- package/dist/packages/canvas-adapter/toolbar.d.ts +496 -0
- package/dist/packages/canvas-adapter/toolbar.js +9 -0
- package/dist/packages/canvas-adapter/userGuideHtml.d.ts +2 -0
- package/dist/packages/canvas-adapter/userGuideHtml.js +968 -0
- package/dist/packages/canvas-adapter/version.d.ts +3 -0
- package/dist/packages/canvas-adapter/version.js +1 -0
- package/dist/packages/core/aggregates.d.ts +54 -0
- package/dist/packages/core/aggregates.js +1 -0
- package/dist/packages/core/classify.d.ts +24 -0
- package/dist/packages/core/classify.js +1 -0
- package/dist/packages/core/dies.d.ts +92 -0
- package/dist/packages/core/dies.js +1 -0
- package/dist/packages/core/index.d.ts +13 -0
- package/dist/packages/core/index.js +1 -0
- package/dist/packages/core/inference/grid.d.ts +35 -0
- package/dist/packages/core/inference/grid.js +1 -0
- package/dist/packages/core/inference/index.d.ts +7 -0
- package/dist/packages/core/inference/index.js +1 -0
- package/dist/packages/core/inference/pitch.d.ts +26 -0
- package/dist/packages/core/inference/pitch.js +1 -0
- package/dist/packages/core/inference/wafer.d.ts +30 -0
- package/dist/packages/core/inference/wafer.js +1 -0
- package/dist/packages/core/metadata.d.ts +31 -0
- package/dist/packages/core/metadata.js +1 -0
- package/dist/packages/core/probe.d.ts +16 -0
- package/dist/packages/core/probe.js +1 -0
- package/dist/packages/core/reticle.d.ts +68 -0
- package/dist/packages/core/reticle.js +1 -0
- package/dist/packages/core/transforms.d.ts +142 -0
- package/dist/packages/core/transforms.js +1 -0
- package/dist/packages/core/utils.d.ts +60 -0
- package/dist/packages/core/utils.js +1 -0
- package/dist/packages/core/wafer.d.ts +45 -0
- package/dist/packages/core/wafer.js +1 -0
- package/dist/packages/renderer/buildView.d.ts +420 -0
- package/dist/packages/renderer/buildView.js +1 -0
- package/dist/packages/renderer/buildWaferMap.d.ts +543 -0
- package/dist/packages/renderer/buildWaferMap.js +1 -0
- package/dist/packages/renderer/colorMap.d.ts +46 -0
- package/dist/packages/renderer/colorMap.js +1 -0
- package/dist/packages/renderer/colorSchemes.d.ts +40 -0
- package/dist/packages/renderer/colorSchemes.js +1 -0
- package/dist/packages/renderer/fmt.d.ts +30 -0
- package/dist/packages/renderer/fmt.js +1 -0
- package/dist/packages/renderer/index.d.ts +7 -0
- package/dist/packages/renderer/index.js +1 -0
- package/dist/packages/stats/analyzeWaferLot.d.ts +5 -0
- package/dist/packages/stats/analyzeWaferLot.js +1 -0
- package/dist/packages/stats/analyzeWaferMap.d.ts +13 -0
- package/dist/packages/stats/analyzeWaferMap.js +1 -0
- package/dist/packages/stats/binPareto.d.ts +49 -0
- package/dist/packages/stats/binPareto.js +1 -0
- package/dist/packages/stats/boxplot.d.ts +39 -0
- package/dist/packages/stats/boxplot.js +1 -0
- package/dist/packages/stats/capability.d.ts +55 -0
- package/dist/packages/stats/capability.js +1 -0
- package/dist/packages/stats/clusterDetection.d.ts +18 -0
- package/dist/packages/stats/clusterDetection.js +1 -0
- package/dist/packages/stats/correlation.d.ts +55 -0
- package/dist/packages/stats/correlation.js +1 -0
- package/dist/packages/stats/facets.d.ts +74 -0
- package/dist/packages/stats/facets.js +1 -0
- package/dist/packages/stats/filterFindings.d.ts +9 -0
- package/dist/packages/stats/filterFindings.js +1 -0
- package/dist/packages/stats/findingsNarrative.d.ts +3 -0
- package/dist/packages/stats/findingsNarrative.js +1 -0
- package/dist/packages/stats/histogram.d.ts +42 -0
- package/dist/packages/stats/histogram.js +1 -0
- package/dist/packages/stats/index.d.ts +27 -0
- package/dist/packages/stats/index.js +1 -0
- package/dist/packages/stats/math.d.ts +10 -0
- package/dist/packages/stats/math.js +1 -0
- package/dist/packages/stats/patternClassification.d.ts +107 -0
- package/dist/packages/stats/patternClassification.js +1 -0
- package/dist/packages/stats/regions.d.ts +57 -0
- package/dist/packages/stats/regions.js +1 -0
- package/dist/packages/stats/renderFindingsReport.d.ts +7 -0
- package/dist/packages/stats/renderFindingsReport.js +41 -0
- package/dist/packages/stats/renderSummaryReport.d.ts +60 -0
- package/dist/packages/stats/renderSummaryReport.js +68 -0
- package/dist/packages/stats/reportHtml.d.ts +42 -0
- package/dist/packages/stats/reportHtml.js +301 -0
- package/dist/packages/stats/scatter.d.ts +24 -0
- package/dist/packages/stats/scatter.js +1 -0
- package/dist/packages/stats/types.d.ts +269 -0
- package/dist/packages/stats/types.js +1 -0
- package/dist/packages/stats/yield.d.ts +56 -0
- package/dist/packages/stats/yield.js +1 -0
- package/dist/packages/worker/index.d.ts +44 -0
- package/dist/packages/worker/index.js +1 -0
- package/dist/packages/worker/wafermap.worker.d.ts +44 -0
- package/dist/packages/worker/wafermap.worker.js +1 -0
- package/package.json +122 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const WMAP_VERSION="0.21.1",WMAP_BUILD_TIME="2026-08-01T11:07:58.032Z";
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type { Die } from './dies.js';
|
|
2
|
+
export type AggregationMethod = 'mean' | 'median' | 'stddev' | 'min' | 'max' | 'count';
|
|
3
|
+
/** Minimal die shape required by aggregation functions — satisfied by both Die and DieResult. */
|
|
4
|
+
export interface DieLike {
|
|
5
|
+
x: number;
|
|
6
|
+
y: number;
|
|
7
|
+
testValues?: Record<number, number>;
|
|
8
|
+
/** Recorded per-test pass/fail verdicts (true = pass). Never aggregated — always stripped from outputs. */
|
|
9
|
+
testPass?: Record<number, boolean>;
|
|
10
|
+
hbin?: number;
|
|
11
|
+
sbin?: number;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Aggregate a per-test-parameter numeric value across a lot of wafers.
|
|
15
|
+
*
|
|
16
|
+
* @param diesByWafer One `Die[]` per wafer — all wafers must share the same grid layout.
|
|
17
|
+
* @param method Aggregation function applied per (x,y) position across all wafers.
|
|
18
|
+
* @param paramIndex Which `die.testValues` key to aggregate — a test number such as
|
|
19
|
+
* `1050`, not a positional index (default 0).
|
|
20
|
+
* @returns One Die per unique (x,y) position with the aggregate stored at
|
|
21
|
+
* `testValues[0]` regardless of the input key. Dies that had no value on any
|
|
22
|
+
* wafer are included with `testValues: undefined` as a "no data" signal —
|
|
23
|
+
* they are not filtered out.
|
|
24
|
+
*
|
|
25
|
+
* @example Compute mean test value across a lot:
|
|
26
|
+
* ```ts
|
|
27
|
+
* const lotMean = aggregateValues(diesByWafer, 'mean');
|
|
28
|
+
* const scene = buildView(wafer, lotMean, { plotMode: 'value' });
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
export declare function aggregateValues(diesByWafer: DieLike[][], method: AggregationMethod, paramIndex?: number): DieLike[];
|
|
32
|
+
/**
|
|
33
|
+
* Return all unique bin values present in dies, sorted ascending.
|
|
34
|
+
* `binSpace` selects whether to inspect `die.hbin` (default) or `die.sbin`.
|
|
35
|
+
*/
|
|
36
|
+
export declare function getUniqueBins(dies: Die[], binSpace?: 'hard' | 'soft'): number[];
|
|
37
|
+
/**
|
|
38
|
+
* Aggregate multiple wafers by counting, per die position, how many wafers
|
|
39
|
+
* had a specific bin value.
|
|
40
|
+
*
|
|
41
|
+
* Returns one Die per unique (x,y) position (using the first wafer's layout
|
|
42
|
+
* as the spatial template) where:
|
|
43
|
+
* values[0] = number of wafers that had targetBin at this position
|
|
44
|
+
* hbin/sbin = targetBin (in the appropriate bin space)
|
|
45
|
+
*
|
|
46
|
+
* Use with plotMode: 'value' and valueRange: [0, diesByWafer.length] to get
|
|
47
|
+
* a colour scale that runs from "never" to "always".
|
|
48
|
+
*
|
|
49
|
+
* @param diesByWafer One Die[] per wafer, all sharing the same grid layout.
|
|
50
|
+
* @param targetBin The bin value to count.
|
|
51
|
+
* @param binSpace Which bin space to test — `'hard'` (default) or `'soft'`.
|
|
52
|
+
*/
|
|
53
|
+
export declare function aggregateBinCounts(diesByWafer: DieLike[][], targetBin: number, binSpace?: 'hard' | 'soft'): DieLike[];
|
|
54
|
+
//# sourceMappingURL=aggregates.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{getDieKey as g}from"./dies.js";export function aggregateValues(u,s,l=0){if(!u.length)return[];const n=new Map,t=new Map;for(const d of u)for(const o of d){const e=g(o),f=o.testValues?.[l];f!==void 0?(n.has(e)||(n.set(e,[]),t.set(e,o)),n.get(e).push(f)):t.has(e)||t.set(e,o)}const r=[];for(const[d,o]of t){const e=n.get(d);if(!e?.length){r.push({...o,testValues:void 0,testPass:void 0});continue}let f;if(s==="mean")f=e.reduce((i,a)=>i+a,0)/e.length;else if(s==="median"){const i=[...e].sort((c,h)=>c-h),a=Math.floor(i.length/2);f=i.length%2?i[a]:(i[a-1]+i[a])/2}else if(s==="stddev"){if(e.length<2){r.push({...o,testValues:{0:0},testPass:void 0});continue}const i=e.reduce((a,c)=>a+c,0)/e.length;f=Math.sqrt(e.reduce((a,c)=>a+(c-i)**2,0)/(e.length-1))}else s==="min"?f=Math.min(...e):s==="max"?f=Math.max(...e):f=e.length;r.push({...o,testValues:{0:f},testPass:void 0})}return r}export function getUniqueBins(u,s="hard"){const l=new Set;for(const n of u){const t=s==="soft"?n.sbin:n.hbin;t!==void 0&&l.add(t)}return[...l].sort((n,t)=>n-t)}export function aggregateBinCounts(u,s,l="hard"){if(!u.length)return[];const n=new Map;for(const t of u)for(const r of t)if((l==="soft"?r.sbin:r.hbin)===s){const o=g(r);n.set(o,(n.get(o)??0)+1)}return(u[0]??[]).map(t=>({...t,testValues:{0:n.get(g(t))??0},testPass:void 0,...l==="soft"?{sbin:s}:{hbin:s}}))}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { Wafer } from './wafer.js';
|
|
2
|
+
import type { Die } from './dies.js';
|
|
3
|
+
export type Quadrant = 'NE' | 'NW' | 'SW' | 'SE';
|
|
4
|
+
export interface DieClassification {
|
|
5
|
+
ring: number;
|
|
6
|
+
quadrant: Quadrant;
|
|
7
|
+
}
|
|
8
|
+
export interface ClassifyOptions {
|
|
9
|
+
ringCount?: number;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Classify a die by its radial ring (1 = innermost) and physical wafer quadrant.
|
|
13
|
+
*
|
|
14
|
+
* Quadrant and ring are computed from `physX/physY`, which carry `wafer.orientation`
|
|
15
|
+
* (notch position) but NOT the view's interactive rotation. This is deliberate:
|
|
16
|
+
* spatial findings ("NE quadrant yield is low") describe the physical wafer relative
|
|
17
|
+
* to its notch, so they must follow orientation and stay invariant to how the user
|
|
18
|
+
* has rotated the on-screen view. (Not "screen" coordinates — those are post-
|
|
19
|
+
* interactive-transform and live only in the renderer.)
|
|
20
|
+
*/
|
|
21
|
+
export declare function classifyDie(die: Die, wafer: Wafer, options?: ClassifyOptions): DieClassification;
|
|
22
|
+
/** Human-readable label for a ring index (1-based) given a total ring count. */
|
|
23
|
+
export declare function getRingLabel(ring: number, ringCount: number): string;
|
|
24
|
+
//# sourceMappingURL=classify.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export function classifyDie(t,e,s={}){const o=Math.max(1,s.ringCount??4),n=t.physX-e.center.x,r=t.physY-e.center.y,a=Math.sqrt(n*n+r*r)/e.radius,c=Math.min(o,Math.max(1,Math.floor(a*o)+1));let i;return n>=0&&r>=0?i="NE":n<0&&r>=0?i="NW":n<0&&r<0?i="SW":i="SE",{ring:c,quadrant:i}}export function getRingLabel(t,e){return e===1?"Full Wafer":t===1?"Ring 1 (core)":t===e?`Ring ${t} (edge)`:`Ring ${t}`}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import type { Wafer } from './wafer.js';
|
|
2
|
+
import type { DieMetadata } from './metadata.js';
|
|
3
|
+
export interface DieSpec {
|
|
4
|
+
width: number;
|
|
5
|
+
height: number;
|
|
6
|
+
gridSize?: number;
|
|
7
|
+
offset?: {
|
|
8
|
+
x: number;
|
|
9
|
+
y: number;
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
export interface Die {
|
|
13
|
+
id: string;
|
|
14
|
+
x: number;
|
|
15
|
+
y: number;
|
|
16
|
+
physX: number;
|
|
17
|
+
physY: number;
|
|
18
|
+
width: number;
|
|
19
|
+
height: number;
|
|
20
|
+
/**
|
|
21
|
+
* Test values keyed by test number (a stable per-test identity, e.g. STDF TEST_NUM).
|
|
22
|
+
* Example: `{ 1050: 1.42e-3, 1060: 0.487, 1070: 8.3e-12 }`
|
|
23
|
+
*/
|
|
24
|
+
testValues?: Record<number, number>;
|
|
25
|
+
/**
|
|
26
|
+
* Recorded per-test pass/fail verdicts keyed by test number (true = pass),
|
|
27
|
+
* parallel to `testValues`. Parametric tests carry a value in `testValues`
|
|
28
|
+
* and may optionally carry a verdict here; functional tests (`testType: 'F'`)
|
|
29
|
+
* carry a verdict here ONLY — they have no measured value.
|
|
30
|
+
* Example: `{ 2001: true, 2002: false }`
|
|
31
|
+
*/
|
|
32
|
+
testPass?: Record<number, boolean>;
|
|
33
|
+
hbin?: number;
|
|
34
|
+
sbin?: number;
|
|
35
|
+
metadata?: DieMetadata;
|
|
36
|
+
insideWafer?: boolean;
|
|
37
|
+
partial?: boolean;
|
|
38
|
+
edgeExcluded?: boolean;
|
|
39
|
+
probeIndex?: number;
|
|
40
|
+
/** Number of times this die position appeared in the input results. Only set when > 1. */
|
|
41
|
+
retestCount?: number;
|
|
42
|
+
/** STDF site_num — which parallel test site tested this die. Only meaningful when > 1 distinct value appears per wafer. */
|
|
43
|
+
siteNum?: number;
|
|
44
|
+
/** STDF pir.part_id — tester-assigned identifier for this tested unit. Encodes probe sequence at most fabs. */
|
|
45
|
+
partId?: number;
|
|
46
|
+
}
|
|
47
|
+
export interface DieEligibilityOptions {
|
|
48
|
+
/** Include dies that straddle the wafer boundary (`die.partial`). Default: excluded. */
|
|
49
|
+
includePartial?: boolean;
|
|
50
|
+
/** Include dies inside the edge-exclusion zone (`die.edgeExcluded`). Default: excluded. */
|
|
51
|
+
includeEdgeExcluded?: boolean;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Whether a die counts toward yield/rollup calculations, per wmap's standard
|
|
55
|
+
* fab-reporting convention: partial (boundary-straddling) and edge-excluded
|
|
56
|
+
* dies are skipped by default, even though they may carry real measured
|
|
57
|
+
* values — many fabs exclude them from yield/bin reporting specifically,
|
|
58
|
+
* not from other per-die analysis (a partial/edge-excluded die's test
|
|
59
|
+
* values still belong in distributions, correlations, scatter, etc. — this
|
|
60
|
+
* predicate is for yield-style pass/fail rollups only).
|
|
61
|
+
*
|
|
62
|
+
* Single source of truth for this rule — previously duplicated independently
|
|
63
|
+
* in `renderer/buildWaferMap.ts`'s yield calculation and
|
|
64
|
+
* `stats/analyzeWaferMap.ts`'s eligible-die filter, which could (and did)
|
|
65
|
+
* silently drift apart. Both now call this.
|
|
66
|
+
*/
|
|
67
|
+
/**
|
|
68
|
+
* Return a stable string key for a die — guaranteed format `"x,y"`.
|
|
69
|
+
*
|
|
70
|
+
* THE way to build a die key. Use it for Map keys and post-enrichment lookups
|
|
71
|
+
* rather than an ad-hoc template literal: findings carry `dieKeys` in this exact
|
|
72
|
+
* format and the renderer resolves highlights by matching them, so a single site
|
|
73
|
+
* that formats keys differently silently breaks click-to-highlight. Same reason
|
|
74
|
+
* it lives in `core/` — `renderer/`, `stats/` and `canvas-adapter/` all need to
|
|
75
|
+
* agree on one format.
|
|
76
|
+
*
|
|
77
|
+
* ```ts
|
|
78
|
+
* const map = new Map(result.dies.map(d => [getDieKey(d), d]));
|
|
79
|
+
* const die = map.get(getDieKey({ x: 3, y: -2 }));
|
|
80
|
+
* ```
|
|
81
|
+
*/
|
|
82
|
+
export declare function getDieKey(die: {
|
|
83
|
+
x: number;
|
|
84
|
+
y: number;
|
|
85
|
+
}): string;
|
|
86
|
+
export declare function isYieldEligibleDie(die: Die, options?: DieEligibilityOptions): boolean;
|
|
87
|
+
/**
|
|
88
|
+
* Generate a rectangular grid of dies centered on the wafer.
|
|
89
|
+
* Each die carries its width/height for use by the renderer.
|
|
90
|
+
*/
|
|
91
|
+
export declare function generateDies(wafer: Wafer, dieConfig: DieSpec): Die[];
|
|
92
|
+
//# sourceMappingURL=dies.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export function getDieKey(e){return`${e.x},${e.y}`}export function isYieldEligibleDie(e,t={}){return!(!t.includePartial&&e.partial||!t.includeEdgeExcluded&&e.edgeExcluded)}export function generateDies(e,t){const{width:c,height:l,offset:u={x:0,y:0}}=t,n=t.gridSize??Math.ceil(e.radius/Math.min(c,l))+1,s=[];for(let r=-n;r<=n;r++)for(let i=-n;i<=n;i++){const o=e.center.x+i*c+u.x,d=e.center.y+r*l+u.y;s.push({id:`${i}_${r}`,x:i,y:r,physX:o,physY:d,width:c,height:l})}return s}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export * from './aggregates.js';
|
|
2
|
+
export * from './classify.js';
|
|
3
|
+
export * from './dies.js';
|
|
4
|
+
export * from './metadata.js';
|
|
5
|
+
export * from './probe.js';
|
|
6
|
+
export * from './reticle.js';
|
|
7
|
+
export type { DataRow, MapOptions, TransformOptions, Affine, CoordFrame } from './transforms.js';
|
|
8
|
+
export { isInsideWafer, clipDiesToWafer, applyOrientation, transformDies, mapDataToDies } from './transforms.js';
|
|
9
|
+
export { affineIdentity, affineRotation, affineMirror, affineCompose, affineInvert, affinePoint, affineVector, affineSwapsAxes, } from './transforms.js';
|
|
10
|
+
export * from './wafer.js';
|
|
11
|
+
export type { PitchResult } from './inference/pitch.js';
|
|
12
|
+
export { resolveGridPitch } from './inference/pitch.js';
|
|
13
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export*from"./aggregates.js";export*from"./classify.js";export*from"./dies.js";export*from"./metadata.js";export*from"./probe.js";export*from"./reticle.js";export{isInsideWafer,clipDiesToWafer,applyOrientation,transformDies,mapDataToDies}from"./transforms.js";export{affineIdentity,affineRotation,affineMirror,affineCompose,affineInvert,affinePoint,affineVector,affineSwapsAxes}from"./transforms.js";export*from"./wafer.js";export{resolveGridPitch}from"./inference/pitch.js";
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export interface GridAssignment {
|
|
2
|
+
/** Parallel to the input points array — x,y index for each point. */
|
|
3
|
+
indices: Array<{
|
|
4
|
+
x: number;
|
|
5
|
+
y: number;
|
|
6
|
+
}>;
|
|
7
|
+
/**
|
|
8
|
+
* Integer offset of the grid origin from (0,0) in prober-step units.
|
|
9
|
+
* To map a generated die's (x,y) back to the original prober position:
|
|
10
|
+
* origX = die.x + offsetX, origY = die.y + offsetY
|
|
11
|
+
*/
|
|
12
|
+
offsetX: number;
|
|
13
|
+
offsetY: number;
|
|
14
|
+
/**
|
|
15
|
+
* 1.0 when all input positions are already integers (typical prober data);
|
|
16
|
+
* lower when positions have significant fractional parts.
|
|
17
|
+
*/
|
|
18
|
+
confidence: number;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Assign grid indices to prober-step grid positions.
|
|
22
|
+
*
|
|
23
|
+
* Input x,y are die grid positions (prober step coordinates — integers in
|
|
24
|
+
* normal use). The offset is the rounded centroid of all positions so that
|
|
25
|
+
* the generated die grid is centred at the wafer's physical origin (0,0).
|
|
26
|
+
*
|
|
27
|
+
* The offset is always an integer, which ensures that x = inputX − offsetX is
|
|
28
|
+
* also an integer for integer inputs regardless of whether the centroid is
|
|
29
|
+
* a whole number or a half-integer (e.g. even-count symmetric grids).
|
|
30
|
+
*/
|
|
31
|
+
export declare function assignGridIndices(gridPoints: Array<{
|
|
32
|
+
x: number;
|
|
33
|
+
y: number;
|
|
34
|
+
}>): GridAssignment;
|
|
35
|
+
//# sourceMappingURL=grid.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export function assignGridIndices(e){if(e.length===0)return{indices:[],offsetX:0,offsetY:0,confidence:1};const f=e.reduce((n,t)=>n+t.x,0)/e.length,h=e.reduce((n,t)=>n+t.y,0)/e.length,c=Math.round(f),o=Math.round(h),a=e.map(n=>({x:Math.round(n.x)-c,y:Math.round(n.y)-o})),s=1-e.filter(n=>Math.abs(n.x-Math.round(n.x))>.01||Math.abs(n.y-Math.round(n.y))>.01).length/e.length;return{indices:a,offsetX:c,offsetY:o,confidence:s}}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export type { WaferInference } from './wafer.js';
|
|
2
|
+
export { inferWaferFromXY } from './wafer.js';
|
|
3
|
+
export type { PitchResult } from './pitch.js';
|
|
4
|
+
export { resolveGridPitch } from './pitch.js';
|
|
5
|
+
export type { GridAssignment } from './grid.js';
|
|
6
|
+
export { assignGridIndices } from './grid.js';
|
|
7
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export{inferWaferFromXY}from"./wafer.js";export{resolveGridPitch}from"./pitch.js";export{assignGridIndices}from"./grid.js";
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export interface PitchResult {
|
|
2
|
+
pitchX: number;
|
|
3
|
+
pitchY: number;
|
|
4
|
+
/** 'mm' when at least one physical dimension is known; 'normalized' when
|
|
5
|
+
* dimensions are estimated solely from the circular grid constraint. */
|
|
6
|
+
units: 'mm' | 'normalized';
|
|
7
|
+
confidence: number;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Resolve die pitch from prober-step grid positions and optional geometry.
|
|
11
|
+
*
|
|
12
|
+
* Input x,y are integer prober step coordinates (die grid positions), not mm.
|
|
13
|
+
* Physical mm position = grid_pos × pitch.
|
|
14
|
+
*
|
|
15
|
+
* When neither die dimensions nor wafer diameter are known, the function tries
|
|
16
|
+
* nearest-neighbour step analysis first (works well for regular grids with ≥ 4
|
|
17
|
+
* points), then falls back to the circular-wafer aspect-ratio constraint.
|
|
18
|
+
*/
|
|
19
|
+
export declare function resolveGridPitch(gridPoints: Array<{
|
|
20
|
+
x: number;
|
|
21
|
+
y: number;
|
|
22
|
+
}>, dieOpts?: {
|
|
23
|
+
width?: number;
|
|
24
|
+
height?: number;
|
|
25
|
+
}, waferDiameter?: number): PitchResult;
|
|
26
|
+
//# sourceMappingURL=pitch.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{modeOf as Y}from"../utils.js";function X(s){if(!s.length)return null;const i=s.map(e=>Math.round(e*10)/10);return Y(i)}function R(s){if(s.length<2)return null;const i=s.map(t=>({x:Math.round(t.x),y:Math.round(t.y)})),e=new Map;for(const t of i){const n=e.get(t.y)??[];n.push(t.x),e.set(t.y,n)}const l=[];for(const t of e.values()){t.sort((n,c)=>n-c);for(let n=1;n<t.length;n++){const c=t[n]-t[n-1];c>0&&l.push(c)}}const h=new Map;for(const t of i){const n=h.get(t.x)??[];n.push(t.y),h.set(t.x,n)}const r=[];for(const t of h.values()){t.sort((n,c)=>n-c);for(let n=1;n<t.length;n++){const c=t[n]-t[n-1];c>0&&r.push(c)}}const f=X(l),u=X(r);return f===null&&u===null?null:{pitchX:f??u,pitchY:u??f}}export function resolveGridPitch(s,i,e){const l=i?.width!==void 0,h=i?.height!==void 0;if(l&&h)return{pitchX:i.width,pitchY:i.height,units:"mm",confidence:1};if(s.length===0){const o=l?i.width:h?i.height:10;return{pitchX:o,pitchY:o,units:l||h||e!==void 0?"mm":"normalized",confidence:0}}let r=1/0,f=-1/0,u=1/0,y=-1/0;for(const o of s)o.x<r&&(r=o.x),o.x>f&&(f=o.x),o.y<u&&(u=o.y),o.y>y&&(y=o.y);const x=f-r+1,t=y-u+1;let n,c,d,a;if(l)n=i.width,c=n*x/t,d="mm",a=.8;else if(h)c=i.height,n=c*t/x,d="mm",a=.8;else if(e!==void 0)n=e/x,c=e/t,d="mm",a=.6;else{const o=R(s),M=o!==null?o.pitchY/o.pitchX:1,p=M===1&&t>0?x/t:M;n=1,c=p,d="normalized",a=o!==null?.5:.4}if(e!==void 0){const o=e/2,M=(r+f)/2,g=(u+y)/2;let p=0;for(const m of s){const b=Math.hypot(n*(Math.abs(m.x-M)+.5),c*(Math.abs(m.y-g)+.5));b>p&&(p=b)}if(p>o){const m=o/p;n*=m,c*=m}}return{pitchX:n,pitchY:c,units:d,confidence:a}}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export interface WaferInference {
|
|
2
|
+
center: {
|
|
3
|
+
x: number;
|
|
4
|
+
y: number;
|
|
5
|
+
};
|
|
6
|
+
diameter: number;
|
|
7
|
+
radius: number;
|
|
8
|
+
confidence: number;
|
|
9
|
+
method: string;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Infer wafer geometry (center + diameter) from a set of XY die positions.
|
|
13
|
+
* Snaps to standard wafer sizes; prefers 200mm/300mm if within ±10%.
|
|
14
|
+
*
|
|
15
|
+
* `minRadius` is a HARD floor: the resulting wafer is never smaller than this.
|
|
16
|
+
* Callers pass the distance to the furthest corner of the furthest die, because a
|
|
17
|
+
* die carrying test results is a real tested prober position and is therefore
|
|
18
|
+
* always fully on the wafer — a prober cannot step to a site that overhangs the
|
|
19
|
+
* edge. That makes the die extent ground truth and the inferred diameter the
|
|
20
|
+
* guess, so the guess must yield to it. Without this floor the p98 sizing below
|
|
21
|
+
* can undersize the wafer and manufacture "partial" dies that cannot physically
|
|
22
|
+
* exist.
|
|
23
|
+
*/
|
|
24
|
+
export declare function inferWaferFromXY(points: Array<{
|
|
25
|
+
x: number;
|
|
26
|
+
y: number;
|
|
27
|
+
}>, options?: {
|
|
28
|
+
minRadius?: number;
|
|
29
|
+
}): WaferInference;
|
|
30
|
+
//# sourceMappingURL=wafer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{percentile98 as R,clamp01 as D}from"../utils.js";const M=[25,50,75,100,150,200,300,450],x=[100,150,200,300];export function inferWaferFromXY(t,a={}){if(t.length===0)return{center:{x:0,y:0},diameter:300,radius:150,confidence:0,method:"default"};const d=t.reduce((n,o)=>n+o.x,0)/t.length,c=t.reduce((n,o)=>n+o.y,0)/t.length,r=t.map(n=>Math.sqrt((n.x-d)**2+(n.y-c)**2)),f=R(r),e=(a.minRadius??0)*2,i=Math.max(f*2*1.05,e),s=y(i,e),m=r.reduce((n,o)=>n+o,0)/r.length,u=Math.sqrt(r.reduce((n,o)=>n+(o-m)**2,0)/r.length),h=m>0?u/m:1,l=D(1-h*.5);return{center:{x:d,y:c},diameter:s.diameter,radius:s.diameter/2,confidence:l,method:s.method}}function y(t,a=0){const d=e=>e>=a;for(const e of x)if(d(e)&&Math.abs(t-e)/e<=.1)return{diameter:e,method:`snapped-${e}mm`};let c=1/0,r=t;for(const e of M){if(!d(e))continue;const i=Math.abs(t-e);i<c&&(c=i,r=e)}return c!==1/0&&c/t<=.2?{diameter:r,method:`snapped-${r}mm`}:{diameter:Math.max(Math.ceil(t/10)*10,a),method:"rounded"}}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Genuinely per-die annotations for hover tooltips. Wafer/lot-level facts (lot,
|
|
3
|
+
* wafer id, product, test program, temperature, …) live on {@link WaferMetadata}
|
|
4
|
+
* and are merged into the tooltip automatically — do NOT duplicate them here.
|
|
5
|
+
* A die cannot differ from its wafer on those fields, so storing them per-die was
|
|
6
|
+
* pure redundancy (removed in 0.15.0). Only set keys here that genuinely vary
|
|
7
|
+
* die-to-die; a key present here overrides the wafer-level value of the same name.
|
|
8
|
+
*/
|
|
9
|
+
export interface DieMetadata {
|
|
10
|
+
[key: string]: unknown;
|
|
11
|
+
}
|
|
12
|
+
export interface WaferMetadata {
|
|
13
|
+
lot?: string;
|
|
14
|
+
waferId?: string | number;
|
|
15
|
+
product?: string;
|
|
16
|
+
testDate?: string;
|
|
17
|
+
operator?: string;
|
|
18
|
+
testProgram?: string;
|
|
19
|
+
temperature?: number;
|
|
20
|
+
/**
|
|
21
|
+
* User-assigned experiment/process-corner tag (e.g. "TT", "FF", a custom
|
|
22
|
+
* group name) — distinct from any parser-derived field. A first-class slot
|
|
23
|
+
* so hosts that support wafer-split assignment (grouping wafers into
|
|
24
|
+
* ad-hoc experiment buckets after the fact) get it picked up automatically
|
|
25
|
+
* by anything that facets/groups on `WaferMetadata` — reports, an
|
|
26
|
+
* eventual Group-by control — without per-host special-casing.
|
|
27
|
+
*/
|
|
28
|
+
split?: string;
|
|
29
|
+
[key: string]: unknown;
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=metadata.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export{};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { Die } from './dies.js';
|
|
2
|
+
export interface ProbeSequenceConfig {
|
|
3
|
+
type: 'row' | 'column' | 'snake' | 'custom';
|
|
4
|
+
/** Ordered array of die IDs — required when type='custom'. */
|
|
5
|
+
customOrder?: string[];
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Assign a probeIndex to each die according to the chosen scan strategy.
|
|
9
|
+
*
|
|
10
|
+
* row — left→right, top→bottom
|
|
11
|
+
* snake — alternating direction per row (boustrophedon)
|
|
12
|
+
* column — top→bottom, left→right
|
|
13
|
+
* custom — explicit die ID ordering
|
|
14
|
+
*/
|
|
15
|
+
export declare function applyProbeSequence(dies: Die[], config: ProbeSequenceConfig): Die[];
|
|
16
|
+
//# sourceMappingURL=probe.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export function applyProbeSequence(s,i){const{type:p,customOrder:d}=i;if(p==="custom"){if(!d)throw new Error('customOrder is required for type="custom"');const e=new Map(d.map((t,c)=>[t,c])),r=s.map(t=>({...t,probeIndex:e.get(t.id)})),o=r.filter(t=>t.probeIndex===void 0).map(t=>t.id);if(o.length>0)throw new Error(`applyProbeSequence: die IDs not found in customOrder: ${o.join(", ")}`);return r}if(p==="column")return[...s].sort((r,o)=>r.x-o.x||o.y-r.y).map((r,o)=>({...r,probeIndex:o}));const n=new Map;for(const e of s)n.has(e.y)||n.set(e.y,[]),n.get(e.y).push(e);const a=[...n.entries()].sort(([e],[r])=>r-e),u=[];return a.forEach(([,e],r)=>{const o=e.sort((t,c)=>t.x-c.x);u.push(...p==="snake"&&r%2===1?[...o].reverse():o)}),u.map((e,r)=>({...e,probeIndex:r}))}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import type { Wafer } from './wafer.js';
|
|
2
|
+
export interface ReticleSpec {
|
|
3
|
+
width: number;
|
|
4
|
+
height: number;
|
|
5
|
+
diePitchX: number;
|
|
6
|
+
diePitchY: number;
|
|
7
|
+
anchorDie?: {
|
|
8
|
+
x: number;
|
|
9
|
+
y: number;
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* Physical position of die index (0,0) — i.e. where `anchorDie` is measured
|
|
13
|
+
* from. Defaults to the wafer centre `{0,0}`, correct only when the die grid
|
|
14
|
+
* is itself centred on the wafer. When the die grid's physical placement is
|
|
15
|
+
* offset from wafer centre by a non-whole-die-pitch amount (e.g. partial/
|
|
16
|
+
* off-centre wafer data, where `buildWaferMap` centres the *data extent*
|
|
17
|
+
* rather than the wafer), that fractional remainder must be passed here or
|
|
18
|
+
* field boundaries silently drift out of alignment with die edges.
|
|
19
|
+
*/
|
|
20
|
+
gridOrigin?: {
|
|
21
|
+
x: number;
|
|
22
|
+
y: number;
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
/** A reticle rectangle in wafer-local (pre-rotation) coordinates. */
|
|
26
|
+
export interface Reticle {
|
|
27
|
+
x: number;
|
|
28
|
+
y: number;
|
|
29
|
+
width: number;
|
|
30
|
+
height: number;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* The field-local (column, row) that a die occupies within its reticle field,
|
|
34
|
+
* per the `anchorDie` convention documented on `generateReticleGrid`:
|
|
35
|
+
* `anchorDie` is the field's min-x/min-y (bottom-left) corner, so a die's
|
|
36
|
+
* local position is `die - anchorDie`, wrapped to the field dimensions.
|
|
37
|
+
*
|
|
38
|
+
* This is the single source of truth for "which reticle cell is this die
|
|
39
|
+
* in" — every consumer that needs the answer (e.g. stats findings, region
|
|
40
|
+
* labeling) must call this rather than re-deriving the phase math, so field
|
|
41
|
+
* geometry and cell labeling can never drift apart again.
|
|
42
|
+
*/
|
|
43
|
+
export declare function getReticleCell(die: {
|
|
44
|
+
x: number;
|
|
45
|
+
y: number;
|
|
46
|
+
}, config: {
|
|
47
|
+
width: number;
|
|
48
|
+
height: number;
|
|
49
|
+
anchorDie?: {
|
|
50
|
+
x: number;
|
|
51
|
+
y: number;
|
|
52
|
+
};
|
|
53
|
+
}): {
|
|
54
|
+
column: number;
|
|
55
|
+
row: number;
|
|
56
|
+
};
|
|
57
|
+
/**
|
|
58
|
+
* Generate the grid of reticle rectangles that cover the wafer area.
|
|
59
|
+
* Returns positions in wafer-local coordinates (before orientation rotation).
|
|
60
|
+
* Reticles that don't overlap the wafer circle are excluded.
|
|
61
|
+
*
|
|
62
|
+
* Width and height are in die counts; diePitchX/diePitchY convert to display units.
|
|
63
|
+
* The anchorDie index lands at the reticle's min-x/min-y corner (bottom-left,
|
|
64
|
+
* since +Y is up) — i.e. anchorDie is the first (leftmost, bottom-most) die
|
|
65
|
+
* of the field it belongs to.
|
|
66
|
+
*/
|
|
67
|
+
export declare function generateReticleGrid(wafer: Wafer, config: ReticleSpec): Reticle[];
|
|
68
|
+
//# sourceMappingURL=reticle.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
function x(t,n){return(t%n+n)%n}export function getReticleCell(t,n){const{width:o,height:s,anchorDie:i={x:0,y:0}}=n,h=Math.max(1,Math.floor(o)),r=Math.max(1,Math.floor(s));return{column:x(t.x-i.x,h),row:x(t.y-i.y,r)}}export function generateReticleGrid(t,n){const{width:o,height:s,diePitchX:i,diePitchY:h,anchorDie:r={x:0,y:0},gridOrigin:m={x:0,y:0}}=n,c=o*i,e=s*h,f=x(r.x,o),p=x(r.y,s),l=m.x+(f+(o-1)/2)*i,M=m.y+(p+(s-1)/2)*h,a=t.radius+Math.max(c,e),P=Math.ceil((-a-(l-t.center.x))/c),b=Math.floor((a-(l-t.center.x))/c),D=Math.ceil((-a-(M-t.center.y))/e),H=Math.floor((a-(M-t.center.y))/e),X=[];for(let y=D;y<=H;y++){const d=M+y*e;for(let u=P;u<=b;u++){const g=l+u*c,R=Math.max(g-c/2,Math.min(t.center.x,g+c/2)),W=Math.max(d-e/2,Math.min(t.center.y,d+e/2)),Y=R-t.center.x,k=W-t.center.y;Y*Y+k*k<=t.radius*t.radius&&X.push({x:g,y:d,width:c,height:e})}}return X}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import type { Wafer } from './wafer.js';
|
|
2
|
+
import type { Die, DieSpec } from './dies.js';
|
|
3
|
+
export interface DataRow {
|
|
4
|
+
[key: string]: string | number;
|
|
5
|
+
}
|
|
6
|
+
export interface MapOptions {
|
|
7
|
+
xField?: string;
|
|
8
|
+
yField?: string;
|
|
9
|
+
iField?: string;
|
|
10
|
+
jField?: string;
|
|
11
|
+
valueField: string;
|
|
12
|
+
/** 'xy' matches by wafer coordinates; 'ij' matches by grid indices (default: 'xy') */
|
|
13
|
+
matchBy?: 'xy' | 'ij';
|
|
14
|
+
}
|
|
15
|
+
export interface TransformOptions {
|
|
16
|
+
/** Additional rotation in degrees, applied on top of existing die coordinates. */
|
|
17
|
+
rotation?: number;
|
|
18
|
+
/** Mirror x around the wafer centre. Applied after rotation. */
|
|
19
|
+
flipX?: boolean;
|
|
20
|
+
/** Mirror y around the wafer centre. Applied after rotation. */
|
|
21
|
+
flipY?: boolean;
|
|
22
|
+
}
|
|
23
|
+
/** Rotate (x, y) clockwise by angleDegrees around (cx, cy). */
|
|
24
|
+
export declare function rotatePoint(x: number, y: number, angleDegrees: number, cx?: number, cy?: number): {
|
|
25
|
+
x: number;
|
|
26
|
+
y: number;
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* A 2D affine transform, applied as:
|
|
30
|
+
* ```
|
|
31
|
+
* x' = a·x + c·y + e
|
|
32
|
+
* y' = b·x + d·y + f
|
|
33
|
+
* ```
|
|
34
|
+
* `From`/`To` are phantom coordinate-frame tags — see {@link CoordFrame}.
|
|
35
|
+
*/
|
|
36
|
+
export interface Affine<From extends CoordFrame = CoordFrame, To extends CoordFrame = CoordFrame> {
|
|
37
|
+
a: number;
|
|
38
|
+
b: number;
|
|
39
|
+
c: number;
|
|
40
|
+
d: number;
|
|
41
|
+
e: number;
|
|
42
|
+
f: number;
|
|
43
|
+
/** Phantom source frame — never present at runtime. */
|
|
44
|
+
readonly __from?: From;
|
|
45
|
+
/** Phantom target frame — never present at runtime. */
|
|
46
|
+
readonly __to?: To;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* The distinct coordinate frames a point can live in. Mixing them up is the
|
|
50
|
+
* single most common source of misplaced-geometry bugs in this library, so the
|
|
51
|
+
* frame travels in the type of the matrix that converts between them.
|
|
52
|
+
*
|
|
53
|
+
* - `physical` — wafer-local mm describing a *physical wafer feature* (the
|
|
54
|
+
* boundary outline, the notch, ring/quadrant region borders). These follow
|
|
55
|
+
* `wafer.orientation` but NOT the data-axis flip: that flip exists to make the
|
|
56
|
+
* rendered image physically correct for a prober convention (e.g.
|
|
57
|
+
* `yAxisDirection: 'down'` puts row 1 at the top), so the physical wafer must
|
|
58
|
+
* stay put while the die grid moves relative to it.
|
|
59
|
+
* - `grid` — wafer-local mm for geometry *aligned to the die grid* (reticle
|
|
60
|
+
* fields, the +X/+Y axis indicator). These DO follow the data-axis flip,
|
|
61
|
+
* because they must stay locked to the dies they describe.
|
|
62
|
+
* - `baked` — what `Die.physX`/`physY` holds after `buildWaferMap` has applied
|
|
63
|
+
* `applyOrientation` then `transformDies`. Public API; do not redefine.
|
|
64
|
+
* - `screen` — final display mm after the interactive rotate/flip. This is what
|
|
65
|
+
* `View.hoverPoints` holds and what hit-testing compares against.
|
|
66
|
+
*/
|
|
67
|
+
export type CoordFrame = 'physical' | 'grid' | 'baked' | 'screen';
|
|
68
|
+
/** The identity transform. */
|
|
69
|
+
export declare function affineIdentity<F extends CoordFrame>(): Affine<F, F>;
|
|
70
|
+
/**
|
|
71
|
+
* Clockwise rotation by `angleDegrees` about (cx, cy) — the same convention as
|
|
72
|
+
* {@link rotatePoint}, which this reproduces exactly.
|
|
73
|
+
*/
|
|
74
|
+
export declare function affineRotation<From extends CoordFrame, To extends CoordFrame>(angleDegrees: number, cx?: number, cy?: number): Affine<From, To>;
|
|
75
|
+
/** Mirror through (cx, cy) on each requested axis. */
|
|
76
|
+
export declare function affineMirror<From extends CoordFrame, To extends CoordFrame>(flipX: boolean, flipY: boolean, cx?: number, cy?: number): Affine<From, To>;
|
|
77
|
+
/**
|
|
78
|
+
* Compose two transforms: `inner` runs first, then `outer`. The frame brands must
|
|
79
|
+
* meet (`inner` ends where `outer` begins), so an out-of-order composition — the
|
|
80
|
+
* exact mistake that mis-drew reticle fields — will not type-check.
|
|
81
|
+
*/
|
|
82
|
+
export declare function affineCompose<A extends CoordFrame, B extends CoordFrame, C extends CoordFrame>(outer: Affine<B, C>, inner: Affine<A, B>): Affine<A, C>;
|
|
83
|
+
/** Invert a transform. Every transform built here is a rotation/mirror, so it is always invertible. */
|
|
84
|
+
export declare function affineInvert<From extends CoordFrame, To extends CoordFrame>(m: Affine<From, To>): Affine<To, From>;
|
|
85
|
+
/** Transform a point (translation applies). */
|
|
86
|
+
export declare function affinePoint<From extends CoordFrame, To extends CoordFrame>(m: Affine<From, To>, x: number, y: number): {
|
|
87
|
+
x: number;
|
|
88
|
+
y: number;
|
|
89
|
+
};
|
|
90
|
+
/**
|
|
91
|
+
* Transform a direction/offset (translation does NOT apply). Use for arrows and
|
|
92
|
+
* any other quantity that is a vector rather than a position.
|
|
93
|
+
*/
|
|
94
|
+
export declare function affineVector<From extends CoordFrame, To extends CoordFrame>(m: Affine<From, To>, dx: number, dy: number): {
|
|
95
|
+
x: number;
|
|
96
|
+
y: number;
|
|
97
|
+
};
|
|
98
|
+
/**
|
|
99
|
+
* True when the transform maps the x-axis onto the y-axis (the 90°/270° class,
|
|
100
|
+
* with or without mirroring) — i.e. when an axis-aligned rectangle's width and
|
|
101
|
+
* height must be exchanged to describe its image.
|
|
102
|
+
*
|
|
103
|
+
* Rotations that are not a multiple of 90° do not map an axis-aligned rectangle
|
|
104
|
+
* to an axis-aligned rectangle at all; those return `false`, matching the
|
|
105
|
+
* library's existing behaviour of drawing dies as unrotated rectangles.
|
|
106
|
+
*/
|
|
107
|
+
export declare function affineSwapsAxes(m: Affine): boolean;
|
|
108
|
+
/**
|
|
109
|
+
* Check whether (x, y) — in wafer-local (pre-rotation) coordinates —
|
|
110
|
+
* lies inside the wafer boundary, including the notch/flat exclusion zone.
|
|
111
|
+
*/
|
|
112
|
+
export declare function isInsideWafer(x: number, y: number, wafer: Wafer): boolean;
|
|
113
|
+
/**
|
|
114
|
+
* Clip dies to the wafer boundary (circle + optional notch/flat).
|
|
115
|
+
* When dieConfig is supplied, all four corners are checked to detect partial dies.
|
|
116
|
+
* Operates on wafer-local coordinates (before applyOrientation).
|
|
117
|
+
*/
|
|
118
|
+
export declare function clipDiesToWafer(dies: Die[], wafer: Wafer, dieConfig?: DieSpec): Die[];
|
|
119
|
+
/**
|
|
120
|
+
* Rotate all die display coordinates by wafer.orientation around wafer.center.
|
|
121
|
+
* Call this after clipping and data mapping so that i/j indices remain intact.
|
|
122
|
+
*/
|
|
123
|
+
export declare function applyOrientation(dies: Die[], wafer: Wafer): Die[];
|
|
124
|
+
/**
|
|
125
|
+
* Apply interactive transforms (rotation + flip) to die display coordinates.
|
|
126
|
+
* Rotation is around wafer.center; flip mirrors through wafer.center.
|
|
127
|
+
* Call this at render time — baseDies already have applyOrientation baked in.
|
|
128
|
+
*/
|
|
129
|
+
export declare function transformDies(dies: Die[], options: TransformOptions, center?: {
|
|
130
|
+
x: number;
|
|
131
|
+
y: number;
|
|
132
|
+
}): Die[];
|
|
133
|
+
/**
|
|
134
|
+
* Attach data values to dies.
|
|
135
|
+
* matchBy='xy' — matches by wafer coordinates (v0.1 behaviour)
|
|
136
|
+
* matchBy='ij' — matches by grid indices
|
|
137
|
+
*
|
|
138
|
+
* Note: duplicate rows with the same coordinate produce last-wins behaviour.
|
|
139
|
+
* Deduplicate `data` upstream if needed.
|
|
140
|
+
*/
|
|
141
|
+
export declare function mapDataToDies(dies: Die[], data: DataRow[], options: MapOptions): Die[];
|
|
142
|
+
//# sourceMappingURL=transforms.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{getDieKey as u}from"./dies.js";export function rotatePoint(t,n,e,o=0,s=0){const c=e*Math.PI/180,a=Math.cos(c),i=Math.sin(c),f=t-o,r=n-s;return{x:o+f*a+r*i,y:s-f*i+r*a}}export function affineIdentity(){return{a:1,b:0,c:0,d:1,e:0,f:0}}export function affineRotation(t,n=0,e=0){const o=t*Math.PI/180,s=Math.cos(o),c=Math.sin(o);return{a:s,c,b:-c,d:s,e:n-(s*n+c*e),f:e-(-c*n+s*e)}}export function affineMirror(t,n,e=0,o=0){return{a:t?-1:1,c:0,b:0,d:n?-1:1,e:t?2*e:0,f:n?2*o:0}}export function affineCompose(t,n){return{a:t.a*n.a+t.c*n.b,b:t.b*n.a+t.d*n.b,c:t.a*n.c+t.c*n.d,d:t.b*n.c+t.d*n.d,e:t.a*n.e+t.c*n.f+t.e,f:t.b*n.e+t.d*n.f+t.f}}export function affineInvert(t){const n=t.a*t.d-t.b*t.c,e=t.d/n,o=-t.b/n,s=-t.c/n,c=t.a/n;return{a:e,b:o,c:s,d:c,e:-(e*t.e+s*t.f),f:-(o*t.e+c*t.f)}}export function affinePoint(t,n,e){return{x:t.a*n+t.c*e+t.e,y:t.b*n+t.d*e+t.f}}export function affineVector(t,n,e){return{x:t.a*n+t.c*e,y:t.b*n+t.d*e}}export function affineSwapsAxes(t){return Math.abs(t.a)<1e-9&&Math.abs(t.d)<1e-9&&Math.abs(t.b)>1e-9}function y(t,n){return Math.sqrt(t*t-(n/2)**2)}export function isInsideWafer(t,n,e){const o=t-e.center.x,s=n-e.center.y;if(o*o+s*s>e.radius*e.radius)return!1;if(e.notch){const c=y(e.radius,e.notch.length);if(e.notch.type==="bottom"&&s<-c||e.notch.type==="top"&&s>c||e.notch.type==="left"&&o<-c||e.notch.type==="right"&&o>c)return!1}return!0}export function clipDiesToWafer(t,n,e){const o=[];for(const s of t){const c=isInsideWafer(s.physX,s.physY,n);if(!e){c&&o.push({...s,insideWafer:!0,partial:!1});continue}const a=e.width/2,i=e.height/2,r=[[s.physX-a,s.physY-i],[s.physX+a,s.physY-i],[s.physX+a,s.physY+i],[s.physX-a,s.physY+i]].filter(([h,p])=>isInsideWafer(h,p,n)).length;!c&&r===0||o.push({...s,insideWafer:!0,partial:r<4})}return o}export function applyOrientation(t,n){if(n.orientation===0)return t;const e=affineRotation(n.orientation,n.center.x,n.center.y);return t.map(o=>{const s=affinePoint(e,o.physX,o.physY);return{...o,physX:s.x,physY:s.y}})}export function transformDies(t,n,e={x:0,y:0}){const{rotation:o=0,flipX:s=!1,flipY:c=!1}=n;if(o===0&&!s&&!c)return t;const a=affineCompose(affineMirror(s,c,e.x,e.y),affineRotation(o,e.x,e.y));return t.map(i=>{const f=affinePoint(a,i.physX,i.physY);return{...i,physX:f.x,physY:f.y}})}export function mapDataToDies(t,n,e){const{valueField:o,matchBy:s="xy"}=e,c=new Map,a=r=>{const h=c.get(u(r));if(h===void 0)return{...r};const p=r.testValues??{};return{...r,testValues:{...p,[Object.keys(p).length]:h}}};if(s==="ij"){const r=e.iField??"x",h=e.jField??"y";for(const p of n)c.set(`${+p[r]},${+p[h]}`,+p[o]);return t.map(a)}const i=e.xField??"x",f=e.yField??"y";for(const r of n)c.set(`${+r[i]},${+r[f]}`,+r[o]);return t.map(a)}
|