@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.
Files changed (138) hide show
  1. package/CHANGELOG.md +751 -0
  2. package/LICENSE +21 -0
  3. package/README.md +79 -0
  4. package/dist/index.d.ts +4 -0
  5. package/dist/index.js +1 -0
  6. package/dist/packages/canvas-adapter/canvasTheme.d.ts +28 -0
  7. package/dist/packages/canvas-adapter/canvasTheme.js +1 -0
  8. package/dist/packages/canvas-adapter/charts/barPanel.d.ts +52 -0
  9. package/dist/packages/canvas-adapter/charts/barPanel.js +1 -0
  10. package/dist/packages/canvas-adapter/charts/binCluster.d.ts +17 -0
  11. package/dist/packages/canvas-adapter/charts/binCluster.js +1 -0
  12. package/dist/packages/canvas-adapter/charts/boxplot.d.ts +45 -0
  13. package/dist/packages/canvas-adapter/charts/boxplot.js +1 -0
  14. package/dist/packages/canvas-adapter/charts/capability.d.ts +41 -0
  15. package/dist/packages/canvas-adapter/charts/capability.js +1 -0
  16. package/dist/packages/canvas-adapter/charts/chartShell.d.ts +156 -0
  17. package/dist/packages/canvas-adapter/charts/chartShell.js +1 -0
  18. package/dist/packages/canvas-adapter/charts/correlation.d.ts +35 -0
  19. package/dist/packages/canvas-adapter/charts/correlation.js +1 -0
  20. package/dist/packages/canvas-adapter/charts/histogram.d.ts +30 -0
  21. package/dist/packages/canvas-adapter/charts/histogram.js +1 -0
  22. package/dist/packages/canvas-adapter/charts/index.d.ts +16 -0
  23. package/dist/packages/canvas-adapter/charts/index.js +1 -0
  24. package/dist/packages/canvas-adapter/charts/palette.d.ts +26 -0
  25. package/dist/packages/canvas-adapter/charts/palette.js +1 -0
  26. package/dist/packages/canvas-adapter/charts/regionYieldDiagram.d.ts +18 -0
  27. package/dist/packages/canvas-adapter/charts/regionYieldDiagram.js +1 -0
  28. package/dist/packages/canvas-adapter/charts/scatter.d.ts +37 -0
  29. package/dist/packages/canvas-adapter/charts/scatter.js +1 -0
  30. package/dist/packages/canvas-adapter/icons.d.ts +2 -0
  31. package/dist/packages/canvas-adapter/icons.js +42 -0
  32. package/dist/packages/canvas-adapter/index.d.ts +12 -0
  33. package/dist/packages/canvas-adapter/index.js +1 -0
  34. package/dist/packages/canvas-adapter/insightsTab.d.ts +76 -0
  35. package/dist/packages/canvas-adapter/insightsTab.js +1 -0
  36. package/dist/packages/canvas-adapter/metadataBadge.d.ts +26 -0
  37. package/dist/packages/canvas-adapter/metadataBadge.js +1 -0
  38. package/dist/packages/canvas-adapter/renderWaferGallery.d.ts +181 -0
  39. package/dist/packages/canvas-adapter/renderWaferGallery.js +20 -0
  40. package/dist/packages/canvas-adapter/renderWaferMap.d.ts +316 -0
  41. package/dist/packages/canvas-adapter/renderWaferMap.js +1 -0
  42. package/dist/packages/canvas-adapter/summaryPanel.d.ts +262 -0
  43. package/dist/packages/canvas-adapter/summaryPanel.js +3 -0
  44. package/dist/packages/canvas-adapter/toCanvas.d.ts +106 -0
  45. package/dist/packages/canvas-adapter/toCanvas.js +1 -0
  46. package/dist/packages/canvas-adapter/toolbar.d.ts +496 -0
  47. package/dist/packages/canvas-adapter/toolbar.js +9 -0
  48. package/dist/packages/canvas-adapter/userGuideHtml.d.ts +2 -0
  49. package/dist/packages/canvas-adapter/userGuideHtml.js +968 -0
  50. package/dist/packages/canvas-adapter/version.d.ts +3 -0
  51. package/dist/packages/canvas-adapter/version.js +1 -0
  52. package/dist/packages/core/aggregates.d.ts +54 -0
  53. package/dist/packages/core/aggregates.js +1 -0
  54. package/dist/packages/core/classify.d.ts +24 -0
  55. package/dist/packages/core/classify.js +1 -0
  56. package/dist/packages/core/dies.d.ts +92 -0
  57. package/dist/packages/core/dies.js +1 -0
  58. package/dist/packages/core/index.d.ts +13 -0
  59. package/dist/packages/core/index.js +1 -0
  60. package/dist/packages/core/inference/grid.d.ts +35 -0
  61. package/dist/packages/core/inference/grid.js +1 -0
  62. package/dist/packages/core/inference/index.d.ts +7 -0
  63. package/dist/packages/core/inference/index.js +1 -0
  64. package/dist/packages/core/inference/pitch.d.ts +26 -0
  65. package/dist/packages/core/inference/pitch.js +1 -0
  66. package/dist/packages/core/inference/wafer.d.ts +30 -0
  67. package/dist/packages/core/inference/wafer.js +1 -0
  68. package/dist/packages/core/metadata.d.ts +31 -0
  69. package/dist/packages/core/metadata.js +1 -0
  70. package/dist/packages/core/probe.d.ts +16 -0
  71. package/dist/packages/core/probe.js +1 -0
  72. package/dist/packages/core/reticle.d.ts +68 -0
  73. package/dist/packages/core/reticle.js +1 -0
  74. package/dist/packages/core/transforms.d.ts +142 -0
  75. package/dist/packages/core/transforms.js +1 -0
  76. package/dist/packages/core/utils.d.ts +60 -0
  77. package/dist/packages/core/utils.js +1 -0
  78. package/dist/packages/core/wafer.d.ts +45 -0
  79. package/dist/packages/core/wafer.js +1 -0
  80. package/dist/packages/renderer/buildView.d.ts +420 -0
  81. package/dist/packages/renderer/buildView.js +1 -0
  82. package/dist/packages/renderer/buildWaferMap.d.ts +543 -0
  83. package/dist/packages/renderer/buildWaferMap.js +1 -0
  84. package/dist/packages/renderer/colorMap.d.ts +46 -0
  85. package/dist/packages/renderer/colorMap.js +1 -0
  86. package/dist/packages/renderer/colorSchemes.d.ts +40 -0
  87. package/dist/packages/renderer/colorSchemes.js +1 -0
  88. package/dist/packages/renderer/fmt.d.ts +30 -0
  89. package/dist/packages/renderer/fmt.js +1 -0
  90. package/dist/packages/renderer/index.d.ts +7 -0
  91. package/dist/packages/renderer/index.js +1 -0
  92. package/dist/packages/stats/analyzeWaferLot.d.ts +5 -0
  93. package/dist/packages/stats/analyzeWaferLot.js +1 -0
  94. package/dist/packages/stats/analyzeWaferMap.d.ts +13 -0
  95. package/dist/packages/stats/analyzeWaferMap.js +1 -0
  96. package/dist/packages/stats/binPareto.d.ts +49 -0
  97. package/dist/packages/stats/binPareto.js +1 -0
  98. package/dist/packages/stats/boxplot.d.ts +39 -0
  99. package/dist/packages/stats/boxplot.js +1 -0
  100. package/dist/packages/stats/capability.d.ts +55 -0
  101. package/dist/packages/stats/capability.js +1 -0
  102. package/dist/packages/stats/clusterDetection.d.ts +18 -0
  103. package/dist/packages/stats/clusterDetection.js +1 -0
  104. package/dist/packages/stats/correlation.d.ts +55 -0
  105. package/dist/packages/stats/correlation.js +1 -0
  106. package/dist/packages/stats/facets.d.ts +74 -0
  107. package/dist/packages/stats/facets.js +1 -0
  108. package/dist/packages/stats/filterFindings.d.ts +9 -0
  109. package/dist/packages/stats/filterFindings.js +1 -0
  110. package/dist/packages/stats/findingsNarrative.d.ts +3 -0
  111. package/dist/packages/stats/findingsNarrative.js +1 -0
  112. package/dist/packages/stats/histogram.d.ts +42 -0
  113. package/dist/packages/stats/histogram.js +1 -0
  114. package/dist/packages/stats/index.d.ts +27 -0
  115. package/dist/packages/stats/index.js +1 -0
  116. package/dist/packages/stats/math.d.ts +10 -0
  117. package/dist/packages/stats/math.js +1 -0
  118. package/dist/packages/stats/patternClassification.d.ts +107 -0
  119. package/dist/packages/stats/patternClassification.js +1 -0
  120. package/dist/packages/stats/regions.d.ts +57 -0
  121. package/dist/packages/stats/regions.js +1 -0
  122. package/dist/packages/stats/renderFindingsReport.d.ts +7 -0
  123. package/dist/packages/stats/renderFindingsReport.js +41 -0
  124. package/dist/packages/stats/renderSummaryReport.d.ts +60 -0
  125. package/dist/packages/stats/renderSummaryReport.js +68 -0
  126. package/dist/packages/stats/reportHtml.d.ts +42 -0
  127. package/dist/packages/stats/reportHtml.js +301 -0
  128. package/dist/packages/stats/scatter.d.ts +24 -0
  129. package/dist/packages/stats/scatter.js +1 -0
  130. package/dist/packages/stats/types.d.ts +269 -0
  131. package/dist/packages/stats/types.js +1 -0
  132. package/dist/packages/stats/yield.d.ts +56 -0
  133. package/dist/packages/stats/yield.js +1 -0
  134. package/dist/packages/worker/index.d.ts +44 -0
  135. package/dist/packages/worker/index.js +1 -0
  136. package/dist/packages/worker/wafermap.worker.d.ts +44 -0
  137. package/dist/packages/worker/wafermap.worker.js +1 -0
  138. package/package.json +122 -0
@@ -0,0 +1,3 @@
1
+ export declare const WMAP_VERSION = "0.21.1";
2
+ export declare const WMAP_BUILD_TIME = "2026-08-01T11:07:58.032Z";
3
+ //# sourceMappingURL=version.d.ts.map
@@ -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)}