@wafertools/wafermap 0.30.4 → 0.31.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.
Files changed (62) hide show
  1. package/AGENTS.md +9 -19
  2. package/CHANGELOG.md +93 -1
  3. package/README.md +2 -2
  4. package/dist/packages/canvas-adapter/charts/boxplot.js +1 -1
  5. package/dist/packages/canvas-adapter/charts/chartShell.d.ts +89 -6
  6. package/dist/packages/canvas-adapter/charts/chartShell.js +1 -1
  7. package/dist/packages/canvas-adapter/charts/histogram.js +1 -1
  8. package/dist/packages/canvas-adapter/charts/scatter.d.ts +11 -1
  9. package/dist/packages/canvas-adapter/charts/scatter.js +1 -1
  10. package/dist/packages/canvas-adapter/charts/trend.js +1 -1
  11. package/dist/packages/canvas-adapter/dieList.d.ts +1 -1
  12. package/dist/packages/canvas-adapter/exportName.d.ts +1 -12
  13. package/dist/packages/canvas-adapter/exportName.js +1 -1
  14. package/dist/packages/canvas-adapter/index.d.ts +2 -3
  15. package/dist/packages/canvas-adapter/index.js +1 -1
  16. package/dist/packages/canvas-adapter/insightsTab.js +1 -1
  17. package/dist/packages/canvas-adapter/renderWaferGallery.d.ts +4 -7
  18. package/dist/packages/canvas-adapter/renderWaferGallery.js +1 -1
  19. package/dist/packages/canvas-adapter/renderWaferMap.d.ts +15 -30
  20. package/dist/packages/canvas-adapter/renderWaferMap.js +1 -1
  21. package/dist/packages/canvas-adapter/userGuideHtml.d.ts +1 -1
  22. package/dist/packages/canvas-adapter/userGuideHtml.js +14 -10
  23. package/dist/packages/canvas-adapter/version.d.ts +2 -2
  24. package/dist/packages/canvas-adapter/version.js +1 -1
  25. package/dist/packages/core/aggregates.d.ts +0 -6
  26. package/dist/packages/core/aggregates.js +1 -1
  27. package/dist/packages/core/dies.d.ts +0 -2
  28. package/dist/packages/core/dies.js +1 -1
  29. package/dist/packages/core/index.d.ts +8 -10
  30. package/dist/packages/core/index.js +1 -1
  31. package/dist/packages/core/stdf.d.ts +9 -0
  32. package/dist/packages/core/stdf.js +1 -0
  33. package/dist/packages/core/transforms.d.ts +0 -23
  34. package/dist/packages/core/transforms.js +1 -1
  35. package/dist/packages/renderer/buildView.d.ts +1 -9
  36. package/dist/packages/renderer/buildView.js +1 -1
  37. package/dist/packages/renderer/buildWaferMap.d.ts +17 -25
  38. package/dist/packages/renderer/buildWaferMap.js +1 -1
  39. package/dist/packages/renderer/colorMap.d.ts +0 -2
  40. package/dist/packages/renderer/colorMap.js +1 -1
  41. package/dist/packages/renderer/deprecate.d.ts +1 -1
  42. package/dist/packages/renderer/deprecate.js +1 -1
  43. package/dist/packages/renderer/derivedTests/apply.js +1 -1
  44. package/dist/packages/renderer/index.d.ts +6 -5
  45. package/dist/packages/renderer/index.js +1 -1
  46. package/dist/packages/stats/clusterDetection.d.ts +2 -1
  47. package/dist/packages/stats/facets.d.ts +1 -1
  48. package/dist/packages/stats/index.d.ts +6 -16
  49. package/dist/packages/stats/index.js +1 -1
  50. package/dist/packages/stats/patternClassification.d.ts +2 -1
  51. package/dist/packages/stats/regions.d.ts +2 -1
  52. package/dist/packages/stats/renderFindingsReport.d.ts +0 -4
  53. package/dist/packages/stats/renderFindingsReport.js +1 -21
  54. package/package.json +2 -2
  55. package/dist/packages/canvas-adapter/deprecated.d.ts +0 -7
  56. package/dist/packages/canvas-adapter/deprecated.js +0 -1
  57. package/dist/packages/core/deprecated.d.ts +0 -60
  58. package/dist/packages/core/deprecated.js +0 -1
  59. package/dist/packages/renderer/deprecated.d.ts +0 -45
  60. package/dist/packages/renderer/deprecated.js +0 -1
  61. package/dist/packages/stats/deprecated.d.ts +0 -85
  62. package/dist/packages/stats/deprecated.js +0 -1
@@ -72,7 +72,7 @@ export const USER_GUIDE_HTML=`<div class="wmap-guide">
72
72
  .wmap-guide-toc{display:none!important}
73
73
  }
74
74
  </style>
75
- <h1 id="wafer-map-user-guide">Wafer Map \u2014 User Guide<span class="wmap-guide-version" title="Built 2026-09-25T05:58:15.630Z">v0.30.4</span></h1>
75
+ <h1 id="wafer-map-user-guide">Wafer Map \u2014 User Guide<span class="wmap-guide-version" title="Built 2026-09-25T14:31:48.441Z">v0.31.0</span></h1>
76
76
  <p class="wmap-guide-online-link">This is a quick reference. <a href="https://wafertools.github.io/wafermap/user-guide/" target="_blank" rel="noopener">View the full illustrated guide online \u2197</a></p>
77
77
  <p>This guide describes the display and analysis features of the wafer map viewer.
78
78
  It is written for users who may be semiconductor test engineers, device engineers, and yield engineers
@@ -112,10 +112,6 @@ the display to match your convention; die coordinates are unaffected.</p>
112
112
  <td>Active plot value \u2014 bin category or test measurement</td>
113
113
  </tr>
114
114
  <tr>
115
- <td>Muted grey at perimeter</td>
116
- <td>Partial die \u2014 falls outside the wafer circle boundary</td>
117
- </tr>
118
- <tr>
119
115
  <td>Neutral grey (interior)</td>
120
116
  <td>No data \u2014 die has no bin or test result in the loaded dataset</td>
121
117
  </tr>
@@ -124,8 +120,7 @@ the display to match your convention; die coordinates are unaffected.</p>
124
120
  <td>Edge-excluded die \u2014 falls within the edge exclusion band configured for this wafer</td>
125
121
  </tr>
126
122
  </tbody></table>
127
- <p>No-data grey and partial-die grey are visually distinct. A no-data die is not a
128
- fail; it simply has no result recorded. Edge-excluded dies are shown dimmed and are
123
+ <p>A no-data die is not a fail; it simply has no result recorded. Edge-excluded dies are shown dimmed and are
129
124
  not counted in yield calculations.</p>
130
125
  <h3 id="14-legend-and-colorbar">1.4 Legend and colorbar</h3>
131
126
  <p><strong>Bin modes</strong> show a discrete colour legend: one swatch per bin, with bin number
@@ -852,15 +847,24 @@ without both still appear, normalized
852
847
  onto their own range and sorted by variability), a test-value box plot, a
853
848
  value histogram, and a <strong>wafer-to-wafer trend</strong> \u2014 one point per wafer at its
854
849
  mean, \xB11\u03C3 whiskers, the mean of all wafers dashed across (labelled the &quot;lot
855
- mean&quot; when they all come from one lot), and test limits where the
856
- test has them. The trend is always in slot order, deliberately: drift across
850
+ mean&quot; when they all come from one lot), and the test&#39;s limits. The trend is always in slot order, deliberately: drift across
857
851
  a cassette only reads in the physical sequence, so there is no sort control
858
852
  to destroy it. Clicking a capability box drives the box plot, histogram and
859
853
  trend onto that same test automatically.</p>
854
+ <p>The box plot, histogram, trend and the Correlation tab&#39;s scatter draw the
855
+ test&#39;s limits as dashed lines. A test can have two kinds: <strong>test limits</strong>
856
+ (labelled <em>Lo limit</em> / <em>Hi limit</em>, short dashes) \u2014 what each die was judged
857
+ pass/fail by \u2014 and <strong>spec limits</strong> (labelled <em>LSL</em> / <em>USL</em>, long dashes) \u2014
858
+ the process specification that capability is measured against. When a test
859
+ has both, a <strong>Limits</strong> choice appears \u2014 <em>Test + spec</em> (the default), <em>Test
860
+ limits</em>, <em>Spec limits</em> or <em>None</em> \u2014 and it applies to all four charts at
861
+ once. A test that lacks the kind you chose shows the kind it has, so it
862
+ never looks as if it had no limits; a limit that falls outside the plotted
863
+ range is marked at the edge of the chart with an arrow.</p>
860
864
  <p>The box plot, histogram and trend share one row of axis controls, kept in
861
865
  sync across all three so switching between them never re-reads the same
862
866
  data on a different scale: <strong>Axis includes limits</strong> widens the axis to cover
863
- the low and high limits even where the data sits well clear of them (on by default only when
867
+ every limit shown even where the data sits well clear of them (on by default only when
864
868
  doing so still leaves the data at least a third of the axis \u2014 otherwise a
865
869
  generous limit window would squash a perfectly capable distribution into a
866
870
  sliver), and <strong>Clip outliers</strong> narrows the axis to a robust range (the
@@ -1,3 +1,3 @@
1
- export declare const WMAP_VERSION = "0.30.4";
2
- export declare const WMAP_BUILD_TIME = "2026-09-25T05:59:59.978Z";
1
+ export declare const WMAP_VERSION = "0.31.0";
2
+ export declare const WMAP_BUILD_TIME = "2026-09-25T14:33:27.298Z";
3
3
  //# sourceMappingURL=version.d.ts.map
@@ -1 +1 @@
1
- export const WMAP_VERSION="0.30.4",WMAP_BUILD_TIME="2026-09-25T05:59:59.978Z";
1
+ export const WMAP_VERSION="0.31.0",WMAP_BUILD_TIME="2026-09-25T14:33:27.298Z";
@@ -1,4 +1,3 @@
1
- import type { Die } from './dies.js';
2
1
  export type AggregationMethod = 'mean' | 'median' | 'stddev' | 'min' | 'max' | 'count';
3
2
  /** Minimal die shape required by aggregation functions — satisfied by both Die and DieResult. */
4
3
  export interface DieLike {
@@ -29,11 +28,6 @@ export interface DieLike {
29
28
  * ```
30
29
  */
31
30
  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
31
  /**
38
32
  * Aggregate multiple wafers by counting, per die position, how many wafers
39
33
  * had a specific bin value.
@@ -1 +1 @@
1
- import{minOf as p,maxOf as m}from"./utils.js";import{getDieKey as g}from"./dies.js";export function aggregateValues(a,s,l=0){if(!a.length)return[];const n=new Map,t=new Map;for(const d of a)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,u)=>i+u,0)/e.length;else if(s==="median"){const i=[...e].sort((c,h)=>c-h),u=Math.floor(i.length/2);f=i.length%2?i[u]:(i[u-1]+i[u])/2}else if(s==="stddev"){if(e.length<2){r.push({...o,testValues:{0:0},testPass:void 0});continue}const i=e.reduce((u,c)=>u+c,0)/e.length;f=Math.sqrt(e.reduce((u,c)=>u+(c-i)**2,0)/(e.length-1))}else s==="min"?f=p(e):s==="max"?f=m(e):f=e.length;r.push({...o,testValues:{0:f},testPass:void 0})}return r}export function getUniqueBins(a,s="hard"){const l=new Set;for(const n of a){const t=s==="soft"?n.sbin:n.hbin;t!==void 0&&l.add(t)}return[...l].sort((n,t)=>n-t)}export function aggregateBinCounts(a,s,l="hard"){if(!a.length)return[];const n=new Map;for(const t of a)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(a[0]??[]).map(t=>({...t,testValues:{0:n.get(g(t))??0},testPass:void 0,...l==="soft"?{sbin:s}:{hbin:s}}))}
1
+ import{minOf as p,maxOf as m}from"./utils.js";import{getDieKey as h}from"./dies.js";export function aggregateValues(l,n,c=0){if(!l.length)return[];const a=new Map,o=new Map;for(const g of l)for(const t of g){const e=h(t),s=t.testValues?.[c];s!==void 0?(a.has(e)||(a.set(e,[]),o.set(e,t)),a.get(e).push(s)):o.has(e)||o.set(e,t)}const u=[];for(const[g,t]of o){const e=a.get(g);if(!e?.length){u.push({...t,testValues:void 0,testPass:void 0});continue}let s;if(n==="mean")s=e.reduce((f,i)=>f+i,0)/e.length;else if(n==="median"){const f=[...e].sort((r,d)=>r-d),i=Math.floor(f.length/2);s=f.length%2?f[i]:(f[i-1]+f[i])/2}else if(n==="stddev"){if(e.length<2){u.push({...t,testValues:{0:0},testPass:void 0});continue}const f=e.reduce((i,r)=>i+r,0)/e.length;s=Math.sqrt(e.reduce((i,r)=>i+(r-f)**2,0)/(e.length-1))}else n==="min"?s=p(e):n==="max"?s=m(e):s=e.length;u.push({...t,testValues:{0:s},testPass:void 0})}return u}export function aggregateBinCounts(l,n,c="hard"){if(!l.length)return[];const a=new Map;for(const o of l)for(const u of o)if((c==="soft"?u.sbin:u.hbin)===n){const t=h(u);a.set(t,(a.get(t)??0)+1)}return(l[0]??[]).map(o=>({...o,testValues:{0:a.get(h(o))??0},testPass:void 0,...c==="soft"?{sbin:n}:{hbin:n}}))}
@@ -54,8 +54,6 @@ export interface Die {
54
54
  partId?: number | string;
55
55
  }
56
56
  export interface DieEligibilityOptions {
57
- /** Include dies that straddle the wafer boundary (`die.partial`). Default: excluded. */
58
- includePartial?: boolean;
59
57
  /** Include dies inside the edge-exclusion zone (`die.edgeExcluded`). Default: excluded. */
60
58
  includeEdgeExcluded?: boolean;
61
59
  }
@@ -1 +1 @@
1
- export function hasPosition(e){return e.x!=null&&e.y!=null}export function isPositionedDie(e){return e.x!=null&&e.y!=null}export function getDieKey(e){return hasPosition(e)?`${e.x},${e.y}`:`id:${e.id}`}export function isYieldEligibleDie(e,n={}){return!(!n.includePartial&&e.partial||!n.includeEdgeExcluded&&e.edgeExcluded)}export function diePassStatus(e,n){const t=e.hbin??e.sbin;return t===void 0?void 0:n.has(t)}export function generateDies(e,n){const{width:t,height:o,offset:s={x:0,y:0}}=n,u=n.gridSize??Math.ceil(e.radius/Math.min(t,o))+1,l=[];for(let i=-u;i<=u;i++)for(let r=-u;r<=u;r++){const c=e.center.x+r*t+s.x,d=e.center.y+i*o+s.y;l.push({id:`${r}_${i}`,x:r,y:i,physX:c,physY:d,width:t,height:o})}return l}
1
+ export function hasPosition(n){return n.x!=null&&n.y!=null}export function isPositionedDie(n){return n.x!=null&&n.y!=null}export function getDieKey(n){return hasPosition(n)?`${n.x},${n.y}`:`id:${n.id}`}export function isYieldEligibleDie(n,t={}){return!(n.partial||!t.includeEdgeExcluded&&n.edgeExcluded)}export function diePassStatus(n,t){const e=n.hbin??n.sbin;return e===void 0?void 0:t.has(e)}export function generateDies(n,t){const{width:e,height:o,offset:s={x:0,y:0}}=t,u=t.gridSize??Math.ceil(n.radius/Math.min(e,o))+1,l=[];for(let i=-u;i<=u;i++)for(let r=-u;r<=u;r++){const c=n.center.x+r*e+s.x,x=n.center.y+i*o+s.y;l.push({id:`${r}_${i}`,x:r,y:i,physX:c,physY:x,width:e,height:o})}return l}
@@ -1,11 +1,9 @@
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 * from './wafer.js';
9
- export type { PitchResult } from './inference/pitch.js';
10
- export { createWafer, generateDies, isPositionedDie, clipDiesToWafer, applyOrientation, transformDies, mapDataToDies, isInsideWafer, affineIdentity, affineRotation, affineMirror, affineCompose, affineInvert, affinePoint, affineVector, affineSwapsAxes, applyProbeSequence, generateReticleGrid, classifyDie, getRingLabel, aggregateValues, aggregateBinCounts, getUniqueBins, metadataCategoricalValue, resolveGridPitch, } from './deprecated.js';
1
+ export { hasPosition, getDieKey, isYieldEligibleDie, diePassStatus } from './dies.js';
2
+ export type { Die, DieEligibilityOptions } from './dies.js';
3
+ export { metadataDisplayValue } from './metadata.js';
4
+ export type { DieMetadata, WaferMetadata } from './metadata.js';
5
+ export { getReticleCell } from './reticle.js';
6
+ export type { Reticle } from './reticle.js';
7
+ export type { Affine, CoordFrame } from './transforms.js';
8
+ export type { Wafer } from './wafer.js';
11
9
  //# sourceMappingURL=index.d.ts.map
@@ -1 +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*from"./wafer.js";export{createWafer,generateDies,isPositionedDie,clipDiesToWafer,applyOrientation,transformDies,mapDataToDies,isInsideWafer,affineIdentity,affineRotation,affineMirror,affineCompose,affineInvert,affinePoint,affineVector,affineSwapsAxes,applyProbeSequence,generateReticleGrid,classifyDie,getRingLabel,aggregateValues,aggregateBinCounts,getUniqueBins,metadataCategoricalValue,resolveGridPitch}from"./deprecated.js";
1
+ export{hasPosition,getDieKey,isYieldEligibleDie,diePassStatus}from"./dies.js";export{metadataDisplayValue}from"./metadata.js";export{getReticleCell}from"./reticle.js";
@@ -0,0 +1,9 @@
1
+ export declare const STDF_BIN_MAX = 32767;
2
+ export declare const STDF_COORD_MAX = 32767;
3
+ export declare const STDF_TEST_NUM_MAX = 4294967295;
4
+ export declare const STDF_SITE_MAX = 255;
5
+ export declare const isStdfBin: (v: number) => boolean;
6
+ export declare const isStdfCoord: (v: number) => boolean;
7
+ export declare const isStdfTestNumber: (v: number) => boolean;
8
+ export declare const isStdfSite: (v: number) => boolean;
9
+ //# sourceMappingURL=stdf.d.ts.map
@@ -0,0 +1 @@
1
+ export const STDF_BIN_MAX=32767,STDF_COORD_MAX=32767,STDF_TEST_NUM_MAX=4294967295,STDF_SITE_MAX=255,isStdfBin=t=>Number.isInteger(t)&&t>=0&&t<=32767,isStdfCoord=t=>Number.isInteger(t)&&Math.abs(t)<=32767,isStdfTestNumber=t=>Number.isInteger(t)&&t>=0&&t<=4294967295,isStdfSite=t=>Number.isInteger(t)&&t>=0&&t<=255;
@@ -1,17 +1,5 @@
1
1
  import type { Wafer } from './wafer.js';
2
2
  import type { Die, DieSpec, PositionedDie } 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
3
  export interface TransformOptions {
16
4
  /** Additional rotation in degrees, applied on top of existing die coordinates. */
17
5
  rotation?: number;
@@ -65,8 +53,6 @@ export interface Affine<From extends CoordFrame = CoordFrame, To extends CoordFr
65
53
  * `View.hoverPoints` holds and what hit-testing compares against.
66
54
  */
67
55
  export type CoordFrame = 'physical' | 'grid' | 'baked' | 'screen';
68
- /** The identity transform. */
69
- export declare function affineIdentity<F extends CoordFrame>(): Affine<F, F>;
70
56
  /**
71
57
  * Clockwise rotation by `angleDegrees` about (cx, cy) — the same convention as
72
58
  * {@link rotatePoint}, which this reproduces exactly.
@@ -130,13 +116,4 @@ export declare function transformDies(dies: PositionedDie[], options: TransformO
130
116
  x: number;
131
117
  y: number;
132
118
  }): PositionedDie[];
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
119
  //# sourceMappingURL=transforms.d.ts.map
@@ -1 +1 @@
1
- import{getDieKey as l}from"./dies.js";export function rotatePoint(t,n,e,o=0,s=0){const c=e*Math.PI/180,i=Math.cos(c),f=Math.sin(c),a=t-o,r=n-s;return{x:o+a*i+r*f,y:s-a*f+r*i}}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 d(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=d(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=s.physX,i=s.physY,f=isInsideWafer(c,i,n);if(!e){f&&o.push({...s,insideWafer:!0,partial:!1});continue}const a=e.width/2,r=e.height/2,p=[[c-a,i-r],[c+a,i-r],[c+a,i+r],[c-a,i+r]].filter(([h,y])=>isInsideWafer(h,y,n)).length;!f&&p===0||o.push({...s,insideWafer:!0,partial:p<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 i=affineCompose(affineMirror(s,c,e.x,e.y),affineRotation(o,e.x,e.y));return t.map(f=>{const a=affinePoint(i,f.physX,f.physY);return{...f,physX:a.x,physY:a.y}})}export function mapDataToDies(t,n,e){const{valueField:o,matchBy:s="xy"}=e,c=new Map,i=r=>{const u=c.get(l(r));if(u===void 0)return{...r};const p=r.testValues??{};return{...r,testValues:{...p,[Object.keys(p).length]:u}}};if(s==="ij"){const r=e.iField??"x",u=e.jField??"y";for(const p of n)c.set(`${+p[r]},${+p[u]}`,+p[o]);return t.map(i)}const f=e.xField??"x",a=e.yField??"y";for(const r of n)c.set(`${+r[f]},${+r[a]}`,+r[o]);return t.map(i)}
1
+ export function rotatePoint(t,n,o,s=0,c=0){const e=o*Math.PI/180,r=Math.cos(e),i=Math.sin(e),a=t-s,f=n-c;return{x:s+a*r+f*i,y:c-a*i+f*r}}export function affineRotation(t,n=0,o=0){const s=t*Math.PI/180,c=Math.cos(s),e=Math.sin(s);return{a:c,c:e,b:-e,d:c,e:n-(c*n+e*o),f:o-(-e*n+c*o)}}export function affineMirror(t,n,o=0,s=0){return{a:t?-1:1,c:0,b:0,d:n?-1:1,e:t?2*o:0,f:n?2*s: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,o=t.d/n,s=-t.b/n,c=-t.c/n,e=t.a/n;return{a:o,b:s,c,d:e,e:-(o*t.e+c*t.f),f:-(s*t.e+e*t.f)}}export function affinePoint(t,n,o){return{x:t.a*n+t.c*o+t.e,y:t.b*n+t.d*o+t.f}}export function affineVector(t,n,o){return{x:t.a*n+t.c*o,y:t.b*n+t.d*o}}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,o){const s=t-o.center.x,c=n-o.center.y;if(s*s+c*c>o.radius*o.radius)return!1;if(o.notch){const e=y(o.radius,o.notch.length);if(o.notch.type==="bottom"&&c<-e||o.notch.type==="top"&&c>e||o.notch.type==="left"&&s<-e||o.notch.type==="right"&&s>e)return!1}return!0}export function clipDiesToWafer(t,n,o){const s=[];for(const c of t){const e=c.physX,r=c.physY,i=isInsideWafer(e,r,n);if(!o){i&&s.push({...c,insideWafer:!0,partial:!1});continue}const a=o.width/2,f=o.height/2,p=[[e-a,r-f],[e+a,r-f],[e+a,r+f],[e-a,r+f]].filter(([h,u])=>isInsideWafer(h,u,n)).length;!i&&p===0||s.push({...c,insideWafer:!0,partial:p<4})}return s}export function applyOrientation(t,n){if(n.orientation===0)return t;const o=affineRotation(n.orientation,n.center.x,n.center.y);return t.map(s=>{const c=affinePoint(o,s.physX,s.physY);return{...s,physX:c.x,physY:c.y}})}export function transformDies(t,n,o={x:0,y:0}){const{rotation:s=0,flipX:c=!1,flipY:e=!1}=n;if(s===0&&!c&&!e)return t;const r=affineCompose(affineMirror(c,e,o.x,o.y),affineRotation(s,o.x,o.y));return t.map(i=>{const a=affinePoint(r,i.physX,i.physY);return{...i,physX:a.x,physY:a.y}})}
@@ -117,8 +117,7 @@ export interface View {
117
117
  /**
118
118
  * Transformed centre of each die, index-parallel with {@link dies}. Separate
119
119
  * from `rectangles` because a die always has a centre for hit-testing and
120
- * viewport fitting even when it is not drawn (a partial die with
121
- * `showPartialDies: false`).
120
+ * viewport fitting, however many rectangles it draws as.
122
121
  */
123
122
  hoverPoints: ViewHoverPoint[];
124
123
  /** Text to draw — die labels and any axis/indicator strings the view generated. */
@@ -438,13 +437,6 @@ export interface ViewOptions {
438
437
  * regardless of this option — it has no value to plot.
439
438
  */
440
439
  passFailDisplay?: 'off' | 'spec' | 'test';
441
- /**
442
- * When true (default), partial (edge) dies — positions that only partially
443
- * overlap the wafer circle — are rendered in a muted grey.
444
- * Set to false to hide them entirely, matching real prober behaviour where
445
- * edge positions are never tested.
446
- */
447
- showPartialDies?: boolean;
448
440
  }
449
441
  interface ColorFns {
450
442
  forValue: (t: number) => string;
@@ -1 +1 @@
1
- import{hasPosition as de}from"../core/dies.js";import{getReticleCell as he}from"../core/reticle.js";import{metadataCategoricalValue as me,metadataDisplayValue as pe}from"../core/metadata.js";import{affineRotation as bt,affineMirror as Ht,affineCompose as st,affinePoint as G,affineVector as xt,affineSwapsAxes as ye}from"../core/transforms.js";import{contrastTextColor as Q,SPEC_PASS_FILL as Mt,SPEC_FAIL_LOW as ge,SPEC_FAIL_HIGH as St}from"./colorMap.js";import{resolveValueColorFn as be}from"./colorSchemes.js";import{resolveBinColors as xe,binColorsCover as Me}from"./binColors.js";import{NO_DATA_FILL as z}from"./colorMap.js";import{diePassStatus as Se}from"../core/dies.js";import{getDieTestValue as P,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 ve}from"./colorMap.js";import{clamp01 as Kt,compareNatural as ke,escHtml as L,minOf as $e}from"../core/utils.js";import{prettyKey as Wt}from"../core/utils.js";import{testLabel as rt,markedTestLabel as lt,derivedTestNote as jt,isDerivedTest as Be,DERIVED_MARK as Te,DERIVED_KEY as Ie}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(ke)}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 De="#d3d6db",wt="#e8e9ea",Ve="#aab0ba";function ct(t,e){return e?me(t.metadata?.[e]):void 0}function Ce(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 Fe(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 zt(t,e){return t.map(e).join(" / ")}function Pe(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 Re(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=P(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=[L(`Die (${t.x}, ${t.y})`)];if(p&&de(t)){const f=he(t,p);b.push(L(`Reticle (${f.column}, ${f.row})`))}if(e==="stackedValues"){const f=P(t,0);if(f!==void 0){const u=a?.[0],h=u?lt(u,u.testNumber):"Value",v=l?` (${l})`:"";b.push(L(`${h}${v}: ${kt(f,u?.unit,o)}`));const m=jt(u);m&&b.push(`<i>${L(m)}</i>`)}}else if(e==="stackedBins"||e==="stackedSoftBins"){const f=P(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(L(`${v}: ${m}${x}`))}}else{const f=Re(t,a,o);if(e==="value"&&f.length){const{testNumber:u}=resolveTestNumber(g??0,a),h=findTestDef(a,u),v=P(t,u);let m=f.findIndex(A=>A.key===u);m<0&&(m=0);const x=f[m];let V=`<b>${L(`${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>${L(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(L).join(" &nbsp;\xB7&nbsp; "))}}t.retestCount!==void 0&&b.push(L(`Retests: ${t.retestCount}`)),t.siteNum!==void 0&&b.push(L(`Site: ${t.siteNum}`)),t.partId!==void 0&&b.push(L(`Part ID: ${t.partId}`)),t.partial&&b.push("<i>partial die</i>"),t.probeIndex!==void 0&&b.push(L(`Probe: #${t.probeIndex}`));const $={...t.metadata??{}};for(const[f,u]of Object.entries($)){const h=pe(u);if(h===void 0)continue;const v=B?.find(m=>m.key===f)?.label??Wt(f);b.push(L(`${v}: ${h}`))}return b.join("<br>")}function Bt(t){return t?.name?lt(t,t.testNumber):void 0}function Tt(t){return Be(t)?{note:`${Te} ${Ie}`}:{}}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=P(h,u);if(y===void 0)return[];m=zt([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)??z)}else{const y=P(h,0);if(y===void 0)return[];m=zt([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 Ne(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(Fe(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 Oe(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 Ee(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 Ae(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 He(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 _e(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:De,type:"stacked",metadata:e.metadata});return}if(e.edgeExcluded){t.push({x:i,y:a,width:m,height:x,fill:Ve,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:wt,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:wt,type:"metadata",metadata:e.metadata});return}if(s==="value"){if(b==="test"){const j=U(e,p,T),ut=j===void 0?z:j?Mt:St;t.push({x:i,y:a,width:m,height:x,fill:ut,type:"value",metadata:e.metadata});return}const S=P(e,p),N=ot(S,T);let W,J;S===void 0?W=z:b==="spec"?W=N==="failLow"?ge: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):z,W=S!=null&&Se(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)??z:z;t.push({x:i,y:a,width:m,height:x,fill:N,type:"metadata",metadata:e.metadata});return}const y=P(e,0),A=y!==void 0?l.forValue(g(y)):z;t.push({x:i,y:a,width:m,height:x,fill:A,type:"value",metadata:e.metadata})}function Ke(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,showPartialDies:Qt=!0}=i,It=Gt??"off",ft=a?.hbinDefs,dt=a?.sbinDefs,Dt=a?.metadataFields,Ut=Dt?.find(c=>c.key===S),Jt={x:(j?.x??!1)!==(x?.flipX??!1),y:(j?.y??!1)!==(x?.flipY??!1)},Zt=ft?new Map(ft.map(c=>[c.bin,c])):null,te=dt?new Map(dt.map(c=>[c.bin,c])):null,ee=n==="softBin"?te:Zt,ne=new Set(h),ht=u&&Me(u,e)?u:xe(e,{passBins:h,binColorScheme:T,hbinDefs:ft,sbinDefs:dt,useDefinedBinColors:f}),ae=n==="softBin"?ht.soft:ht.hard,Vt={forValue:Pe(be(b,$)),forBin:c=>ae.get(c)??z};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?$e(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),ie=I!==void 0&&(I.limitLow!==void 0||I.limitHigh!==void 0);let K="off";n==="value"&&(Ct?K="test":It==="spec"&&ie?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?P(M,0):P(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?P(k,0):P(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 Pt=Ct?!1:Xt??I?.logScale??!1,Lt=Pt&&O>0&&_>0,se=Lt;let et;if(Lt){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=Ce(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,oe=X!==1/0?Math.min(Nt,X*.12):Nt,Ot=[],pt=[],re=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 w=null;if(!re){w=new Float64Array(e.length*2);for(let c=0;c<e.length;c++){const r=G(C.bakedToScreen,e[c].physX,e[c].physY);w[c*2]=r.x,w[c*2+1]=r.y}}const le=ye(C.gridToScreen),ce=n==="hardBin"||n==="softBin"||n==="stackedBins"||n==="stackedSoftBins",ue=n==="softBin"||n==="stackedSoftBins";let nt=ce?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,Ut?.values?.find(R=>R.value===D)?.color??ve(k)]))}for(let c=0;c<e.length;c++){const r=e[c],M=w?w[c*2]:r.physX,D=w?w[c*2+1]:r.physY;if(pt.push({x:M,y:D}),nt&&!r.partial&&!r.edgeExcluded){const k=ue?r.sbin:r.hbin;k!=null&&nt.set(k,(nt.get(k)??0)+1)}if(yt&&!r.partial&&!r.edgeExcluded){const k=ot(P(r,H),I);k&&yt[k]++}if(gt&&!r.partial&&!r.edgeExcluded){const k=U(r,H,I);k!==void 0&&gt[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)}r.partial&&!Qt||_e(Ot,r,M,D,n,le,oe,Vt,v,et,H,ee,ne,I,K,S,it,m)}const At=o?generateTextOverlay(e,w,{plotMode:n,colorFns:Vt,normalize:et,activeTest:A,valueRange:[O,_],testDefs:y,fallbackFormat:N,passFailDisplay:K,activeMetadataKey:S,metadataColorMap:it,allIntegerValues:tt}):[],Y=Ne(t,C.physicalToScreen);p&&Y.push(...Oe(t,C.physicalToScreen,g)),B&&Y.push(...Ee(t,C.physicalToScreen,t.center.x,t.center.y)),l&&Y.push(...Ae(s,C.gridToScreen)),d&&Y.push(...He(e)),F&&Y.push(...Ke(t,C.gridToScreen,At));const fe=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:se,logScaleRequested:Pt,aggrMethod:W,lotSize:J,axisFlip:Jt,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:fe}}export{getDieKey}from"../core/dies.js";
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(" &nbsp;\xB7&nbsp; "))}}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&&gt[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";
@@ -4,7 +4,7 @@ import type { Wafer } from '../core/wafer.js';
4
4
  import type { Reticle } from '../core/reticle.js';
5
5
  import { type DerivedTestDef } from './derivedTests/apply.js';
6
6
  export type { DerivedTestDef } from './derivedTests/apply.js';
7
- import { type View, type ViewOptions, type PlotMode } from './buildView.js';
7
+ import { type View, type PlotMode } from './buildView.js';
8
8
  /**
9
9
  * Test result for a single die position, as output by the prober.
10
10
  * `x` and `y` are **die grid positions** (prober step coordinates) — integers
@@ -111,9 +111,11 @@ export interface WaferConfig {
111
111
  * clockwise. The notch/flat position is set by `notch.type` and is not
112
112
  * affected by this value — `orientation` rotates the *die grid* on the display.
113
113
  *
114
- * Common values: 0 (default), 90, 180, 270.
114
+ * One of 0 (default), 90, 180 or 270 — the angles STDF can record. An
115
+ * equivalent angle such as −90 is read as 270; any other is treated as
116
+ * missing (built at 0) and reported as `input-values-outside-stdf`.
115
117
  */
116
- orientation?: number;
118
+ orientation?: 0 | 90 | 180 | 270;
117
119
  metadata?: WaferMetadata;
118
120
  /**
119
121
  * Physical edge exclusion zone in mm measured from the wafer edge inward.
@@ -354,12 +356,11 @@ export interface WaferMapInputBase {
354
356
  ringCount?: number;
355
357
  /**
356
358
  * Wafer diameters (mm) treated as standard when sanity-checking an *inferred*
357
- * diameter — see {@link STANDARD_WAFER_DIAMETERS_MM} for the default
358
- * (100/125/150/200/300) and the measurements behind it.
359
+ * diameter. Default `[100, 125, 150, 200, 300]`.
359
360
  *
360
- * **Replaces** the default rather than adding to it, so spread it to extend:
361
- * `standardDiameters: [...STANDARD_WAFER_DIAMETERS_MM, 76.2]` for a line that
362
- * also runs 3-inch. Pass `[]` to disable the check entirely, for genuinely
361
+ * **Replaces** the default rather than adding to it, so list the defaults to
362
+ * extend it: `standardDiameters: [100, 125, 150, 200, 300, 76.2]` for a line
363
+ * that also runs 3-inch. Pass `[]` to disable the check entirely, for genuinely
363
364
  * non-standard substrates (panels, reclaim, odd R&D shapes) — that is the
364
365
  * intended opt-out, rather than suppressing every geometry advisory.
365
366
  *
@@ -494,10 +495,6 @@ export interface WaferMapInputLayout extends WaferMapInputBase {
494
495
  * ({@link WaferMapInputLayout}). Combining them is a type error.
495
496
  */
496
497
  export type WaferMapInput = WaferMapInputSingle | WaferMapInputLotStack | WaferMapInputLayout;
497
- /** Options forwarded to {@link buildView}. */
498
- export interface WaferMapOptions extends ViewOptions {
499
- debug?: boolean;
500
- }
501
498
  export interface YieldSummary {
502
499
  /** Dies with a bin in `passBins`. */
503
500
  passDies: number;
@@ -588,9 +585,10 @@ export interface WaferWarning {
588
585
  * plotted wrongly — a bin of "1" is not pass bin 1. The message counts them.
589
586
  * - `'input-values-outside-stdf'` — raised by `buildWaferMap`: bins, coordinates,
590
587
  * test numbers, site numbers or `waferConfig.orientation` outside the STDF V4
591
- * ranges, or test values that are not finite. Used as given for now; a future
592
- * release treats them as missing. A `NaN` bin is already treated as no bin, and
593
- * is counted here too.
588
+ * ranges, or test values that are not finite. Each is treated as missing: the
589
+ * bin or site is absent, the die has no position, the test is left out, the
590
+ * value is dropped, the map is built at orientation 0. A `NaN` bin is no bin,
591
+ * and is counted here too.
594
592
  * - `'retests-by-part-id'` — raised by `buildWaferMap` (severity `'info'`): dies
595
593
  * with no position that share a part ID were treated as retests of one die.
596
594
  * Blank part IDs never match, and part IDs are not used on a wafer where one
@@ -650,20 +648,14 @@ export interface WaferMapResult {
650
648
  metadata: import('../core/metadata.js').WaferMetadata | null;
651
649
  /** `true` when the result was built from a `lotStack` aggregation. */
652
650
  isLotStack: boolean;
653
- /**
654
- * @internal Renderer-agnostic draw list consumed by `renderWaferMap` and `toCanvas`.
655
- * Not part of the public API — access the named fields on `WaferMapResult` instead.
656
- */
657
651
  /**
658
652
  * @internal The initial draw list. Kept on the result because `analyzeWaferMap`
659
653
  * discriminates its input on `'view' in input`, and because `renderWaferMap`
660
- * reads `dataAxisFlip` from it — not because a host should.
654
+ * reads `dataAxisFlip` from it — not for a host to read.
661
655
  *
662
656
  * Read the promoted top-level fields instead (`plotMode`, `metadata`,
663
- * `isLotStack`, `hbinDefs`, `sbinDefs`, `testDefs`): this one is rebuilt on
664
- * every option change, so anything you cache from it goes stale immediately.
665
- * Hosts driving the low-level pipeline should call `buildView` themselves,
666
- * which is the supported way to hold a {@link View}.
657
+ * `isLotStack`, `hbinDefs`, `sbinDefs`, `testDefs`): the renderers rebuild the
658
+ * draw list on every option change, so anything cached from this goes stale.
667
659
  */
668
660
  view: View;
669
661
  /** Reticle configuration used to generate the overlay and reticle-local groupings. */
@@ -823,5 +815,5 @@ export declare function dieHasTestData(die: Pick<Die, 'testValues' | 'testPass'>
823
815
  * mis-inferring to 50 mm went undetected.
824
816
  */
825
817
  export declare const STANDARD_WAFER_DIAMETERS_MM: readonly number[];
826
- export declare function buildWaferMap(input: DieResult[] | WaferMapInput, options?: WaferMapOptions): WaferMapResult;
818
+ export declare function buildWaferMap(input: DieResult[] | WaferMapInput): WaferMapResult;
827
819
  //# sourceMappingURL=buildWaferMap.d.ts.map
@@ -1 +1 @@
1
- import{createWafer as z}from"../core/wafer.js";import{isYieldEligibleDie as xe,getDieKey as B,hasPosition as E}from"../core/dies.js";import{applyOrientation as ee,transformDies as De,clipDiesToWafer as Me}from"../core/transforms.js";import{generateDies as ve}from"../core/dies.js";import{affineRotation as Ce,affineMirror as Oe,affineCompose as Se,affinePoint as $e}from"../core/transforms.js";import{inferWaferFromXY as ke}from"../core/inference/wafer.js";import{resolveGridPitch as Ae}from"../core/inference/pitch.js";import{assignGridIndices as Ne}from"../core/inference/grid.js";import{generateReticleGrid as Re}from"../core/reticle.js";import{applyDerivedTests as te}from"./derivedTests/apply.js";import{buildView as ne}from"./buildView.js";import{maxOf as G,minOf as se,modeOf as We}from"../core/utils.js";import{aggregateValues as ie,aggregateBinCounts as Ee}from"../core/aggregates.js";export function isParametricTest(t){return t?.testType!=="F"}function Ie(t){const n=(f,i)=>({ringCount:f,ringCountWarning:{code:"analysis-option-corrected",severity:"warning",message:`ringCount=${String(t)} ${i} (using ${f}).`}});if(t===void 0)return{ringCount:4,ringCountWarning:void 0};if(typeof t!="number"||!Number.isFinite(t))return n(4,"is not a finite number");const e=Math.max(1,Math.round(t));return e===t?{ringCount:t,ringCountWarning:void 0}:n(e,"is not a whole number of at least 1")}function re(t){const n=Array.isArray(t)?{}:t,e=Array.isArray(n.lotStack?.results)?n.lotStack.results:[],f=(Array.isArray(t)?[t]:[n.results,...e]).filter(h=>Array.isArray(h)),i={hbin:0,sbin:0,testValues:0,testPass:0};let o;const u=(h,S)=>{i[h]++,o??=`${h} ${JSON.stringify(S)}`},s={bins:0,positions:0,testNumbers:0,values:0,sites:0,orientation:0};let m=0;const c=h=>Number.isInteger(h)&&h>=0&&h<=oe,a=h=>{typeof h=="number"&&(Number.isNaN(h)?m++:c(h)||s.bins++)},g=h=>{typeof h=="number"&&!(Number.isInteger(h)&&Math.abs(h)<=H)&&s.positions++};for(const h of f){const S=new Set,w=new Set;for(let v=0;v<h.length;v+=v<50?1:100){const r=h[v];if(!(r===null||typeof r!="object")){if(r.testValues&&typeof r.testValues=="object")for(const $ of Object.keys(r.testValues))S.add($);if(r.testPass&&typeof r.testPass=="object")for(const $ of Object.keys(r.testPass))w.add($)}}const p=[...S],D=[...w];for(const v of new Set([...p,...D])){const r=Number(v);Number.isInteger(r)&&r>=0&&r<=ae||s.testNumbers++}for(const v of h){if(v===null||typeof v!="object")continue;const r=v;r.hbin!=null&&typeof r.hbin!="number"&&u("hbin",r.hbin),r.sbin!=null&&typeof r.sbin!="number"&&u("sbin",r.sbin),a(r.hbin),a(r.sbin),g(r.x),g(r.y),typeof r.siteNum=="number"&&!(Number.isInteger(r.siteNum)&&r.siteNum>=0&&r.siteNum<=255)&&s.sites++;const $=r.testValues;if($&&typeof $=="object")for(let N=0;N<p.length;N++){const O=$[p[N]];O!=null&&(typeof O!="number"?u("testValues",O):Number.isFinite(O)||s.values++)}const I=r.testPass;if(I&&typeof I=="object")for(let N=0;N<D.length;N++){const O=I[D[N]];O!=null&&typeof O!="boolean"&&u("testPass",O)}}}const y=n.waferConfig?.orientation;typeof y=="number"&&![0,90,180,270].includes((y%360+360)%360)&&s.orientation++;const l=[],b=(h,S,w=`${S}s`)=>`${h} ${h===1?S:w}`,x=[];if(i.hbin&&x.push(b(i.hbin,"hard bin")),i.sbin&&x.push(b(i.sbin,"soft bin")),i.testValues&&x.push(b(i.testValues,"test value")),i.testPass&&x.push(b(i.testPass,"pass/fail verdict")),x.length){const h=`buildWaferMap received ${x.join(", ")} of the wrong type (for example ${o}). Bins and test values must be numbers and verdicts true/false \u2014 a bin of "1" is not pass bin 1, so those dies count as fails and yield is wrong, and a test value given as text is not plotted or analysed correctly. Convert them with Number() (and verdicts to booleans) and rebuild.`;console.warn(`[wafermap] ${h}`),l.push({code:"input-values-not-numbers",message:h,severity:"error"})}const M=[];if(s.bins&&M.push(`${b(s.bins,"bin")} (legal: whole numbers 0\u2013${oe})`),s.positions&&M.push(`${b(s.positions,"coordinate")} (legal: whole numbers \u2212${H} to ${H})`),s.testNumbers&&M.push(`${b(s.testNumbers,"test number")} (legal: whole numbers 0\u2013${ae})`),s.values&&M.push(`${b(s.values,"test value")} that ${s.values===1?"is":"are"} not finite`),s.sites&&M.push(`${b(s.sites,"site number")} (legal: 0\u2013255)`),s.orientation&&M.push("a waferConfig.orientation other than 0, 90, 180 or 270"),M.length||m){const h=[];M.length&&h.push(`buildWaferMap received values outside the STDF V4 ranges: ${M.join("; ")}. They are used as given for now; a future release treats them as missing.`),m&&h.push(`${b(m,"bin was","bins were")} NaN and ${m===1?"is":"are"} treated as no bin.`);const S=h.join(" ");console.warn(`[wafermap] ${S}`),l.push({code:"input-values-outside-stdf",message:S,severity:"warning"})}return l}const oe=32767,H=32767,ae=4294967295;function de(t){const n=(c,a)=>c!==null&&typeof c=="object"&&c[a]!==void 0,e=Array.isArray(t)?{}:t,f=[],i=(c,a,g)=>{c&&f.push(`\`${a}\` (now \`${g}\`)`)};i(n(e,"data"),"data","results"),i(n(e,"die"),"die","dieConfig"),i(n(e,"stack"),"stack","lotStack"),i(n(e.dieConfig,"origin"),"dieConfig.origin","dieConfig.coordinateOrigin"),i(n(e.waferConfig,"flat"),"waferConfig.flat","waferConfig.notch"),i(n(e.reticleConfig,"anchor"),"reticleConfig.anchor","reticleConfig.anchorDie"),i(n(e.lotStack,"aggr"),"lotStack.aggr","lotStack.method"),i(Array.isArray(e.testDefs)&&e.testDefs.some(c=>n(c,"index")&&!n(c,"testNumber")),"TestDef.index","TestDef.testNumber");const o=n(e.lotStack,"results")?e.lotStack.results:[],u=(Array.isArray(t)?[t]:[e.results,...Array.isArray(o)?o:[]]).filter(c=>Array.isArray(c));if(i(u.some(c=>c.slice(0,100).some(a=>n(a,"values"))),"values","testValues"),!f.length)return;const s=f.length>1,m=`buildWaferMap ignored ${f.join(", ")}: ${s?"these names were":"that name was"} removed in an earlier release, so what ${s?"they":"it"} described is not on this map. Rename and rebuild.`;return console.warn(`[wafermap] ${m}`),{code:"input-field-removed",message:m,severity:"error"}}function Te(t){if(t.results!==void 0||t.lotStack!==void 0)throw new Error("buildWaferMap: pass `layout`, `results` or `lotStack` \u2014 only one.");const n=t.waferConfig?.diameter,e=t.dieConfig?.width,f=t.dieConfig?.height;if(!(n>0&&e>0&&f>0))throw new Error("buildWaferMap: `layout` needs waferConfig.diameter and dieConfig.width and height, all positive.");const i=z({diameter:n,notch:t.waferConfig?.notch,orientation:0}),o=Me(ve(i,{width:e,height:f}),i,{width:e,height:f}).filter(a=>!a.partial),{flipX:u,flipY:s}=fe(t.dieConfig,{type:"center"}),{layout:m,...c}=t;return{...c,results:o.map(a=>({x:u?-a.x:a.x,y:s?-a.y:a.y})),waferConfig:{...t.waferConfig,center:{x:0,y:0}},dieConfig:{...t.dieConfig,coordinateOrigin:{type:"center"}}}}function Fe(t){if(!Array.isArray(t)&&"results"in t&&t.results!==void 0&&"lotStack"in t&&t.lotStack!==void 0)throw new Error("buildWaferMap: pass either `results` or `lotStack`, not both.");return Array.isArray(t)?{results:t,waferOpts:void 0,dieOpts:void 0,explicitDies:void 0,reticleOpts:void 0,lotStackOpts:void 0,passBins:[1],ringCount:4,ringCountWarning:void 0,removedFieldWarning:de(t),inputValueWarnings:re(t),standardDiameters:void 0,testDefs:void 0,hbinDefs:void 0,sbinDefs:void 0,metadataFields:void 0,derivedTests:void 0,retestPolicy:"last",edgeDieYieldMode:"exclude"}:{results:t.results??[],waferOpts:t.waferConfig,dieOpts:t.dieConfig,explicitDies:t.dies,reticleOpts:t.reticleConfig,lotStackOpts:t.lotStack,passBins:t.passBins??[1],...Ie(t.ringCount),removedFieldWarning:de(t),inputValueWarnings:re(t),standardDiameters:t.standardDiameters,testDefs:t.testDefs?.length?t.testDefs:void 0,hbinDefs:t.hbinDefs,sbinDefs:t.sbinDefs,metadataFields:t.metadataFields,derivedTests:t.derivedTests?.length?t.derivedTests:void 0,retestPolicy:t.retestPolicy??"last",edgeDieYieldMode:t.edgeDieYieldMode??"exclude"}}function _e(t,n){return n?.coordinateOrigin?n.coordinateOrigin:{type:"center"}}function Pe(t,n,e){if(n.type==="custom"&&n.offset)return{offsetX:n.offset.x,offsetY:n.offset.y};if(n.type!=="center"){let f=1/0,i=-1/0,o=1/0,u=-1/0;for(const s of t)s.x<f&&(f=s.x),s.x>i&&(i=s.x),s.y<o&&(o=s.y),s.y>u&&(u=s.y);return{offsetX:Math.round((i+f)/2),offsetY:Math.round((u+o)/2)}}return{offsetX:e.offsetX,offsetY:e.offsetY}}function Ve(t,n,e,f,i,o){if(o)return{colMidX:(Math.round(o.x)-n)*f,colMidY:(Math.round(o.y)-e)*i,anchored:!0};if(t.length===0)return{colMidX:0,colMidY:0,anchored:!1};let u=1/0,s=-1/0,m=1/0,c=-1/0;for(const a of t){const g=Math.round(a.x)-n,y=Math.round(a.y)-e;g<u&&(u=g),g>s&&(s=g),y<m&&(m=y),y>c&&(c=y)}return{colMidX:(u+s)/2*f,colMidY:(m+c)/2*i,anchored:!1}}function Be(t,n,e){let f=1/0,i=-1/0,o=1/0,u=-1/0,s=0,m=0;for(const g of t){const y=Math.round(g.x)-n,l=Math.round(g.y)-e;s+=y,m+=l,y<f&&(f=y),y>i&&(i=y),l<o&&(o=l),l>u&&(u=l)}const c=t.length,a=(g,y,l)=>{const b=(l-y)/2;return b<=0?0:Math.abs(g/c-(y+l)/2)/b};return a(s,f,i)>.11||a(m,o,u)>.11}function fe(t,n){let e=t?.xAxisDirection==="left",f=t?.yAxisDirection==="down";return(n.type==="UL"||n.type==="UR")&&(f=!0),(n.type==="LR"||n.type==="UR")&&(e=!0),{flipX:e,flipY:f}}function Ye(t,n){const{method:e,targetBin:f}=t,i=t.results.map(o=>o.filter(E));if(e==="mean"||e==="median"||e==="stddev"||e==="min"||e==="max"||e==="count"){const o=new Set((n??[]).filter(a=>!isParametricTest(a)).map(a=>a.testNumber)),u=new Set;for(const a of i)for(const g of a)if(g.testValues)for(const y of Object.keys(g.testValues)){const l=Number(y);o.has(l)||u.add(l)}if(u.size===0)return ie(i,e);const s=[...u],m=s.map(a=>ie(i,e,a)),c=new Map;for(let a=0;a<s.length;a++){const g=s[a];for(const y of m[a]){const l=B(y);c.has(l)||c.set(l,{template:y,testValues:{}});const b=c.get(l),x=y.testValues?.[0];x!==void 0&&(b.testValues[g]=x)}}return[...c.values()].map(({template:a,testValues:g})=>({...a,testValues:Object.keys(g).length>0?g:void 0}))}if(e==="countBin"||e==="percent"){if(f===void 0)return[];const o=Ee(i,f,"hard");if(e==="countBin")return o;const u=i.length;return o.map(s=>({...s,testValues:{0:u>0?(s.testValues?.[0]??0)/u*100:0}}))}if(e==="mode"){const o=new Map;for(const s of i)for(const m of s){const c=B(m);o.has(c)||o.set(c,[]),o.get(c).push(m)}const u=[];for(const[s,m]of o){const c=s.split(","),a=Number(c[0]),g=Number(c[1]),y=m.map(b=>b.hbin).filter(b=>b!==void 0),l=We(y);l!==null&&u.push({x:a,y:g,hbin:l})}return u}return[]}function ce(t){const n=t.filter(E),e=n.length,f=n.filter(o=>o.edgeExcluded).length,i=n.filter(o=>dieHasTestData(o)||o.hbin!==void 0||o.sbin!==void 0).length;return{filledDies:i,totalDies:e,edgeExcludedDies:f,unpositionedDies:t.length-n.length,ratio:e>0?i/e:0}}function le(t,n,e="exclude"){const f=new Set(n),o=t.filter(l=>!l.partial).filter(l=>l.edgeExcluded).length,u=t.filter(l=>l.partial).length;let s=0,m=0,c=!1;for(const l of t){if(!xe(l))continue;const b=l.hbin??l.sbin;b!==void 0&&(c=!0,f.has(b)?s++:m++)}const a=s+m,g=c&&a>0?s/a*100:null;let y=null;if(e==="denominator-only"){const l=a+o;y=c&&l>0?s/l*100:null}return{passDies:s,failDies:m,edgeExcludedDies:o,partialDies:u,totalDies:a,yieldPercent:g,yieldPercentGross:y}}function ue(t,n,e,f,i,o=0,u=0,s=0,m=0,c=0,a=!1,g=!1){if(!t)return[];const y=t.anchorDie??{x:0,y:0},l=Re(n,{width:t.width,height:t.height,diePitchX:f,diePitchY:i,anchorDie:{x:y.x-o,y:y.y-u},gridOrigin:{x:-s,y:-m}}),b=Se(Oe(a,g,n.center.x,n.center.y),Ce(c,n.center.x,n.center.y));return l.filter(x=>{const M=x.width/2,h=x.height/2,S=[[x.x-M,x.y-h],[x.x+M,x.y-h],[x.x+M,x.y+h],[x.x-M,x.y+h]].map(([r,$])=>$e(b,r,$)),w=se(S.map(r=>r.x)),p=G(S.map(r=>r.x)),D=se(S.map(r=>r.y)),v=G(S.map(r=>r.y));return e.some(r=>r.physX>=w&&r.physX<p&&r.physY>=D&&r.physY<v)})}function Le(t,n,e){const i=Math.max(0,n.radius-e)**2;return t.map(o=>{const u=o.physX-n.center.x,s=o.physY-n.center.y;return u*u+s*s>i?{...o,edgeExcluded:!0}:o})}const Xe=.2;function je(t,n,e){const f=new Set(e);function i(w,p){if(n==="last")return!0;if(n==="first")return!1;const D=L(w.hbin),v=L(p.hbin);if(D===void 0||v===void 0)return!1;const r=f.has(D),$=f.has(v);return n==="best"?$!==r?$:v<D:$!==r?r:v>D}const o=(w,p)=>p.supersedes!==void 0||i(w,p),u=w=>{const p=w.partId===void 0?"":String(w.partId).trim();return p===""?void 0:p},s=t.filter(w=>!E(w)),m=new Map;for(const w of s){const p=u(w);p!==void 0&&m.set(p,(m.get(p)??0)+1)}const a=(s.length===0?0:Math.max(0,...m.values())/s.length)<=Xe,g=new Map,y=[],l=new Map;let b=0,x=0;for(const w of t){const p=u(w);let D;if(E(w)?D=`p:${B(w)}`:a&&p!==void 0?D=`i:${p}`:D=`u:${x++}`,w.supersedes==="partId"&&p!==void 0){const r=l.get(p);r!==void 0&&r!==D&&g.has(r)&&g.delete(r)}const v=g.get(D);v===void 0?(g.set(D,{winner:w,count:1}),y.push(D)):(v.count++,D.startsWith("i:")&&b++,o(v.winner,w)&&(v.winner=w)),p!==void 0&&l.set(p,D)}const M=[],h=[...y.filter(w=>w.startsWith("p:")),...y.filter(w=>!w.startsWith("p:"))];for(const w of h){const p=g.get(w);p!==void 0&&M.push(p.count>1?{...p.winner,retestCount:p.count}:p.winner)}const S=b>0?{code:"retests-by-part-id",severity:"info",message:`${b} record${b===1?"":"s"} with no position shared a part ID with an earlier record and ${b===1?"was":"were"} treated as a retest of it.`}:void 0;return{results:M,warning:S}}export function getDieTestValue(t,n){return t.testValues?.[n]}export function getTestPassStatus(t,n,e){const f=t.testPass?.[n];if(f!==void 0)return f;if(e!==void 0&&!isParametricTest(e)){const i=t.testValues?.[n];if(i===0||i===1)return i===1}}export function dieHasTestData(t){return t.testValues!==void 0&&Object.keys(t.testValues).length>0||t.testPass!==void 0&&Object.keys(t.testPass).length>0}function L(t){return t===void 0||Number.isNaN(t)?void 0:t}function X(t,n){const e={},f=L(n.hbin),i=L(n.sbin);return f!==void 0&&(e.hbin=f),i!==void 0&&(e.sbin=i),n.retestCount!==void 0&&(e.retestCount=n.retestCount),n.siteNum!==void 0&&(e.siteNum=n.siteNum),n.partId!==void 0&&(e.partId=n.partId),n.metadata!==void 0&&(e.metadata=n.metadata),n.testPass!==void 0&&(e.testPass=n.testPass),n.testValues!==void 0&&(e.testValues=n.testValues),{...t,...e}}function he(t,n){return n.plotMode?n.plotMode:t.some(dieHasTestData)?"value":"hardBin"}function pe(t,n){return t.map(({code:e,message:f})=>({code:e,message:f,severity:e==="non-standard-diameter"||e==="diameter-exceeds-die-extent"?"warning":"error",confidence:n.wafer.confidence}))}const Ke="do not fit inside the supplied",Ue="Inferred wafer diameter",ze="The probed dies reach only",Ge=.75,He=20;export const STANDARD_WAFER_DIAMETERS_MM=[100,125,150,200,300];const qe=.02;function me(t,n){return n.reduce((e,f)=>Math.abs(f-t)<Math.abs(e-t)?f:e)}function Je(t,n){if(!n.length)return!0;const e=me(t,n);return Math.abs(e-t)/e<=qe}function Qe(t){return t===void 0?STANDARD_WAFER_DIAMETERS_MM:t.filter(n=>Number.isFinite(n)&&n>0)}export function buildWaferMap(t,n){const e=Fe(!Array.isArray(t)&&t.layout?Te(t):t),{debug:f,...i}=n??{},o=[];if(e.derivedTests){const d=new Set(e.passBins);if(e.lotStackOpts){const C=e.lotStackOpts.results.map(k=>te(k,e.testDefs,e.derivedTests,d));e.lotStackOpts={...e.lotStackOpts,results:C.map(k=>k.results)},e.testDefs=C[0]?.testDefs??e.testDefs,o.push(...C[0]?.warnings??[])}else{const C=te(e.results,e.testDefs,e.derivedTests,d);e.results=C.results,e.testDefs=C.testDefs,o.push(...C.warnings)}}const u=e.lotStackOpts?Ye(e.lotStackOpts,e.testDefs):e.results;for(let d=0;d<Math.min(5,u.length);d++){const C=u[d];if(typeof C.x=="string"||typeof C.y=="string")throw new TypeError("buildWaferMap: x and y must be numbers, received strings. Did you forget to cast CSV values? e.g. { x: +row.x, y: +row.y }")}for(const d of u)if(d.x==null!=(d.y==null))throw new TypeError(`buildWaferMap: die has only one of x/y set (x=${String(d.x)}, y=${String(d.y)}). A die must be either fully positioned (both x and y) or fully unpositioned (neither) \u2014 omit both to mark a die as having no reported position.`);const{results:s,warning:m}=je(u,e.retestPolicy,e.passBins),c={wafer:{confidence:1,method:"provided"},diePitch:{confidence:1,units:"mm"},grid:{confidence:1}},a=[];if(e.explicitDies){let d=e.explicitDies;const C=s.filter(E),k=s.filter(V=>!E(V));if(C.length>0){const V=new Map(C.map(W=>[B(W),W]));d=d.map(W=>{const Z=V.get(B(W));return Z?X(W,Z):W})}const P=e.waferOpts?.diameter??300,F=z({diameter:P,notch:e.waferOpts?.notch,orientation:e.waferOpts?.orientation??0,metadata:e.waferOpts?.metadata});F.orientation!==0&&(d=ee(d,F));const Q=ue(e.reticleOpts,F,d,1,1,0,0,0,0,F.orientation),be=i.showReticle??e.reticleOpts!==void 0,K=ne(F,d,{...i,reticles:Q,showReticle:be,plotMode:he(s,i),testDefs:e.testDefs,passBins:e.passBins,ringCount:e.ringCount,isLotStack:!1},{hbinDefs:e.hbinDefs,sbinDefs:e.sbinDefs,metadataFields:e.metadataFields}),we=k.map((V,W)=>X({id:`unpositioned_${W}`,width:d[0]?.width??1,height:d[0]?.height??1},V)),U=[...d,...we];return{wafer:F,dies:U,view:K,reticleConfig:e.reticleOpts,units:"mm",inference:c,warnings:[...e.removedFieldWarning?[e.removedFieldWarning]:[],...e.inputValueWarnings,...m?[m]:[],...pe(a,c),...o,...e.ringCountWarning?[e.ringCountWarning]:[]],plotMode:K.plotMode,metadata:K.metadata,isLotStack:!1,dataCoverage:ce(U),passBins:e.passBins,ringCount:e.ringCount,yield:le(U,e.passBins,e.edgeDieYieldMode),reticles:Q,hbinDefs:e.hbinDefs,sbinDefs:e.sbinDefs,testDefs:e.testDefs,metadataFields:e.metadataFields}}const g=s.filter(E),y=s.filter(d=>!E(d)),l=g.map(d=>({x:d.x,y:d.y})),b=Ae(l,e.dieOpts,e.waferOpts?.diameter);c.diePitch={confidence:b.confidence,units:b.units};const{pitchX:x,pitchY:M}=b,h=b.units,S=_e(g,e.dieOpts),w=Ne(l);c.grid={confidence:w.confidence};const{offsetX:p,offsetY:D}=Pe(l,S,w),{flipX:v,flipY:r}=fe(e.dieOpts,S),{colMidX:$,colMidY:I,anchored:N}=Ve(l,p,D,x,M,e.waferOpts?.center);let O=e.waferOpts?.diameter;const Y=l.map(d=>({x:(Math.round(d.x)-p)*x-$,y:(Math.round(d.y)-D)*M-I})),T=G(Y.map(({x:d,y:C})=>Math.hypot(Math.abs(d)+x/2,Math.abs(C)+M/2)));if(O===void 0)if(l.length>0)if(h==="mm"){const d=ke(Y,{minRadius:T});O=d.diameter,c.wafer={confidence:d.confidence,method:d.method};const C=Qe(e.standardDiameters);if(!Je(O,C)){const k=me(O,C);a.push({code:"non-standard-diameter",message:`${Ue} ${O.toFixed(1)} mm is not a standard wafer size (nearest is ${k} mm). The diameter was derived from the die extent, which assumes the probed grid reaches the wafer edge \u2014 a non-standard result usually means it does not, because the outer dies were never probed. Every die is then placed against a wafer that is too small, which moves dies between rings and changes ring and edge findings. Supply waferConfig.diameter.`})}}else O=T*2,c.wafer={confidence:b.confidence*.8,method:"extent"};else O=h==="mm"?300:30,c.wafer={confidence:0,method:"default"};if(!N&&l.length>0&&Be(l,p,D)&&(c.wafer.method="inferred-partial",a.push({code:"partial-coverage",message:"Wafer geometry was inferred from die positions alone. The data does not span a full symmetric wafer, so the inferred diameter and centre may be wrong and dies may be mis-positioned relative to the true wafer boundary. Supply waferConfig.diameter and waferConfig.center (the prober coordinate of the wafer centre) to position partial data correctly."})),e.waferOpts?.diameter!==void 0&&e.waferOpts.center===void 0&&T>0&&l.length>=He){const d=T/(O/2);d<Ge&&a.push({code:"diameter-exceeds-die-extent",message:`${ze} ${(d*100).toFixed(0)}% of the ${O} mm wafer's radius. Either the diameter is larger than the real one, or this is a partial map covering only part of the wafer. Ring bands are equal-radius, so the outer rings are mostly or entirely empty and ring, quadrant and edge findings describe the assumed wafer rather than the probed area. Check waferConfig.diameter, or drop it and let the diameter be inferred from the die extent.`})}if(e.waferOpts?.diameter!==void 0){const d=e.dieOpts?.width!==void 0&&e.dieOpts?.height!==void 0;if(d&&T>O/2+1e-9){const C=Y.filter(({x:k,y:P})=>Math.hypot(Math.abs(k)+x/2,Math.abs(P)+M/2)>O/2+1e-9).length;a.push({code:"geometry-conflict",message:`${C} of ${Y.length} probed die positions ${Ke} ${O} mm wafer at the supplied die pitch of ${x} \xD7 ${M} mm. A die with test results is a real prober position and is always fully on the wafer, so one of the two supplied values must be wrong: containing these dies at this pitch would need a diameter of at least ${(T*2).toFixed(1)} mm. Check waferConfig.diameter and dieConfig.width/height against the real device.`})}}const R=z({diameter:O,notch:e.waferOpts?.notch,orientation:e.waferOpts?.orientation??0,metadata:e.waferOpts?.metadata});let A=g.map(d=>{const C=Math.round(d.x)-p,k=Math.round(d.y)-D,P={id:`${C}_${k}`,x:C,y:k,physX:C*x-$,physY:k*M-I,width:x,height:M,insideWafer:!0,partial:!1};return X(P,d)});(p!==0||D!==0)&&(A=A.map(d=>({...d,x:d.x+p,y:d.y+D,id:`${d.x+p}_${d.y+D}`}))),A=ee(A,R),(v||r)&&(A=De(A,{flipX:v,flipY:r},R.center));const q=[];e.waferOpts?.edgeExclusion&&e.waferOpts.edgeExclusion>0&&(A=Le(A,R,e.waferOpts.edgeExclusion),e.waferOpts.edgeExclusion>R.radius&&q.push({code:"edge-exclusion-exceeds-radius",message:`edgeExclusion (${e.waferOpts.edgeExclusion}mm) exceeds the resolved wafer radius (${R.radius}mm) \u2014 the entire wafer is excluded.`,severity:"warning"}));const J=ue(e.reticleOpts,R,A,x,M,p,D,$,I,R.orientation,v,r),ge=i.showReticle??e.reticleOpts!==void 0,_=ne(R,A,{...i,reticles:J,showReticle:ge,plotMode:he(s,i),testDefs:e.testDefs,passBins:e.passBins,ringCount:e.ringCount,dataAxisFlip:{x:v,y:r},isLotStack:e.lotStackOpts!==void 0,aggregationMethod:e.lotStackOpts?.method,lotSize:e.lotStackOpts?.results.length},{hbinDefs:e.hbinDefs,sbinDefs:e.sbinDefs,metadataFields:e.metadataFields}),ye=y.map((d,C)=>X({id:`unpositioned_${C}`,width:x,height:M},d)),j=[...A,...ye];return{wafer:R,dies:j,view:_,reticleConfig:e.reticleOpts,units:h,inference:c,warnings:[...e.removedFieldWarning?[e.removedFieldWarning]:[],...e.inputValueWarnings,...m?[m]:[],...pe(a,c),...q,...o,...e.ringCountWarning?[e.ringCountWarning]:[]],plotMode:_.plotMode,metadata:_.metadata,isLotStack:e.lotStackOpts!==void 0,dataCoverage:ce(j),passBins:e.passBins,ringCount:e.ringCount,yield:le(j,e.passBins,e.edgeDieYieldMode),reticles:J,hbinDefs:e.hbinDefs,sbinDefs:e.sbinDefs,testDefs:e.testDefs,metadataFields:e.metadataFields,aggrMethod:_.aggrMethod,lotSize:_.lotSize}}
1
+ import{createWafer as G}from"../core/wafer.js";import{isYieldEligibleDie as ge,getDieKey as j,hasPosition as _}from"../core/dies.js";import{applyOrientation as Q,transformDies as ye,clipDiesToWafer as be}from"../core/transforms.js";import{generateDies as we}from"../core/dies.js";import{affineRotation as De,affineMirror as xe,affineCompose as Me,affinePoint as ve}from"../core/transforms.js";import{inferWaferFromXY as Se}from"../core/inference/wafer.js";import{resolveGridPitch as Ce}from"../core/inference/pitch.js";import{assignGridIndices as Oe}from"../core/inference/grid.js";import{generateReticleGrid as ke}from"../core/reticle.js";import{applyDerivedTests as Z}from"./derivedTests/apply.js";import{buildView as ee}from"./buildView.js";import{isStdfBin as $e,isStdfCoord as te,isStdfTestNumber as ne,isStdfSite as Ae,STDF_BIN_MAX as Re,STDF_COORD_MAX as se,STDF_TEST_NUM_MAX as We,STDF_SITE_MAX as Ne}from"../core/stdf.js";import{maxOf as H,minOf as ie,modeOf as Ee}from"../core/utils.js";import{aggregateValues as re,aggregateBinCounts as Te}from"../core/aggregates.js";export function isParametricTest(t){return t?.testType!=="F"}function Fe(t){const e=(a,r)=>({ringCount:a,ringCountWarning:{code:"analysis-option-corrected",severity:"warning",message:`ringCount=${String(t)} ${r} (using ${a}).`}});if(t===void 0)return{ringCount:4,ringCountWarning:void 0};if(typeof t!="number"||!Number.isFinite(t))return e(4,"is not a finite number");const n=Math.max(1,Math.round(t));return n===t?{ringCount:t,ringCountWarning:void 0}:e(n,"is not a whole number of at least 1")}function Ie(t){const e=Array.isArray(t)?{}:t,n={hbin:0,sbin:0,testValues:0,testPass:0};let a;const r=(d,v)=>{n[d]++,a??=`${d} ${JSON.stringify(v)}`},s={bins:0,positions:0,values:0,sites:0,orientation:0},u=new Set;let o=0;const w=d=>typeof d!="number"?!0:Number.isNaN(d)?(o++,!1):$e(d)?!0:(s.bins++,!1);function h(d){const v=new Set,x=new Set;for(let M=0;M<d.length;M+=M<50?1:100){const g=d[M];if(!(g===null||typeof g!="object")){if(g.testValues&&typeof g.testValues=="object")for(const m of Object.keys(g.testValues))v.add(m);if(g.testPass&&typeof g.testPass=="object")for(const m of Object.keys(g.testPass))x.add(m)}}const O=[...v],y=[...x],b=M=>ne(Number(M));for(const M of new Set([...O,...y]))b(M)||u.add(M);let C;for(let M=0;M<d.length;M++){const g=d[M];if(g===null||typeof g!="object")continue;const m=g;let F;const A=()=>F??={...m};m.hbin!=null&&typeof m.hbin!="number"&&r("hbin",m.hbin),m.sbin!=null&&typeof m.sbin!="number"&&r("sbin",m.sbin),w(m.hbin)||(A().hbin=void 0),w(m.sbin)||(A().sbin=void 0);const N=typeof m.x=="number"&&!te(m.x),$=typeof m.y=="number"&&!te(m.y);if(N||$){s.positions+=(N?1:0)+($?1:0);const k=A();k.x=void 0,k.y=void 0}typeof m.siteNum=="number"&&!Ae(m.siteNum)&&(s.sites++,A().siteNum=void 0);const I=m.testValues;if(I&&typeof I=="object"){let k;for(let R=0;R<O.length;R++){const T=O[R],E=I[T];if(E!=null){if(!b(T)){k??={...I},delete k[T];continue}typeof E!="number"?r("testValues",E):Number.isFinite(E)||(s.values++,k??={...I},delete k[T])}}k&&(A().testValues=k)}const V=m.testPass;if(V&&typeof V=="object"){let k;for(let R=0;R<y.length;R++){const T=y[R],E=V[T];if(E!=null){if(!b(T)){k??={...V},delete k[T];continue}typeof E!="boolean"&&r("testPass",E)}}k&&(A().testPass=k)}F&&((C??=d.slice())[M]=F)}return C}const i={warnings:[]};if(Array.isArray(t))i.results=h(t);else{Array.isArray(e.results)&&(i.results=h(e.results));const d=e.lotStack?.results;if(Array.isArray(d)){const v=d.map(x=>Array.isArray(x)?h(x):void 0);v.some(Boolean)&&(i.stackResults=v.map((x,O)=>x??d[O]))}}if(Array.isArray(e.testDefs)){const d=e.testDefs,v=d.filter(x=>x==null||typeof x.testNumber!="number"||ne(x.testNumber));for(const x of d)v.includes(x)||u.add(String(x.testNumber));v.length!==d.length&&(i.testDefs=v)}const p=e.waferConfig?.orientation;if(typeof p=="number"){const d=Number.isFinite(p)?(p%360+360)%360:NaN;d===0||d===90||d===180||d===270?i.orientation=d:(s.orientation++,i.orientation=0)}const l=(d,v,x=`${v}s`)=>`${d} ${d===1?v:x}`,f=[];if(n.hbin&&f.push(l(n.hbin,"hard bin")),n.sbin&&f.push(l(n.sbin,"soft bin")),n.testValues&&f.push(l(n.testValues,"test value")),n.testPass&&f.push(l(n.testPass,"pass/fail verdict")),f.length){const d=`buildWaferMap received ${f.join(", ")} of the wrong type (for example ${a}). Bins and test values must be numbers and verdicts true/false \u2014 a bin of "1" is not pass bin 1, so those dies count as fails and yield is wrong, and a test value given as text is not plotted or analysed correctly. Convert them with Number() (and verdicts to booleans) and rebuild.`;console.warn(`[wafermap] ${d}`),i.warnings.push({code:"input-values-not-numbers",message:d,severity:"error"})}const D=[];if(s.bins&&D.push(`${l(s.bins,"bin")} (legal: whole numbers 0\u2013${Re}) \u2014 those dies have no bin`),s.positions&&D.push(`${l(s.positions,"coordinate")} (legal: whole numbers \u2212${se} to ${se}) \u2014 those dies have no position`),u.size&&D.push(`${l(u.size,"test number")} (legal: whole numbers 0\u2013${We}) \u2014 those tests are left out`),s.values&&D.push(`${l(s.values,"test value")} that ${s.values===1?"is":"are"} not finite \u2014 left out`),s.sites&&D.push(`${l(s.sites,"site number")} (legal: 0\u2013${Ne}) \u2014 those dies have no site`),s.orientation&&D.push("a waferConfig.orientation other than 0, 90, 180 or 270 \u2014 the map is built at 0"),D.length||o){const d=[];D.length&&d.push(`buildWaferMap received values outside the STDF V4 ranges, treated as missing: ${D.join("; ")}.`),o&&d.push(`${l(o,"bin was","bins were")} NaN and ${o===1?"is":"are"} treated as no bin.`);const v=d.join(" ");console.warn(`[wafermap] ${v}`),i.warnings.push({code:"input-values-outside-stdf",message:v,severity:"warning"})}return i}function oe(t){const e=(h,i)=>h!==null&&typeof h=="object"&&h[i]!==void 0,n=Array.isArray(t)?{}:t,a=[],r=(h,i,p)=>{h&&a.push(`\`${i}\` (now \`${p}\`)`)};r(e(n,"data"),"data","results"),r(e(n,"die"),"die","dieConfig"),r(e(n,"stack"),"stack","lotStack"),r(e(n.dieConfig,"origin"),"dieConfig.origin","dieConfig.coordinateOrigin"),r(e(n.waferConfig,"flat"),"waferConfig.flat","waferConfig.notch"),r(e(n.reticleConfig,"anchor"),"reticleConfig.anchor","reticleConfig.anchorDie"),r(e(n.lotStack,"aggr"),"lotStack.aggr","lotStack.method"),r(Array.isArray(n.testDefs)&&n.testDefs.some(h=>e(h,"index")&&!e(h,"testNumber")),"TestDef.index","TestDef.testNumber");const s=e(n.lotStack,"results")?n.lotStack.results:[],u=(Array.isArray(t)?[t]:[n.results,...Array.isArray(s)?s:[]]).filter(h=>Array.isArray(h));if(r(u.some(h=>h.slice(0,100).some(i=>e(i,"values"))),"values","testValues"),!a.length)return;const o=a.length>1,w=`buildWaferMap ignored ${a.join(", ")}: ${o?"these names were":"that name was"} removed in an earlier release, so what ${o?"they":"it"} described is not on this map. Rename and rebuild.`;return console.warn(`[wafermap] ${w}`),{code:"input-field-removed",message:w,severity:"error"}}function Pe(t){if(t.results!==void 0||t.lotStack!==void 0)throw new Error("buildWaferMap: pass `layout`, `results` or `lotStack` \u2014 only one.");const e=t.waferConfig?.diameter,n=t.dieConfig?.width,a=t.dieConfig?.height;if(!(e>0&&n>0&&a>0))throw new Error("buildWaferMap: `layout` needs waferConfig.diameter and dieConfig.width and height, all positive.");const r=G({diameter:e,notch:t.waferConfig?.notch,orientation:0}),s=be(we(r,{width:n,height:a}),r,{width:n,height:a}).filter(i=>!i.partial),{flipX:u,flipY:o}=ae(t.dieConfig,{type:"center"}),{layout:w,...h}=t;return{...h,results:s.map(i=>({x:u?-i.x:i.x,y:o?-i.y:i.y})),waferConfig:{...t.waferConfig,center:{x:0,y:0}},dieConfig:{...t.dieConfig,coordinateOrigin:{type:"center"}}}}function _e(t){if(!Array.isArray(t)&&"results"in t&&t.results!==void 0&&"lotStack"in t&&t.lotStack!==void 0)throw new Error("buildWaferMap: pass either `results` or `lotStack`, not both.");const e=Ie(t);return Array.isArray(t)?{results:e.results??t,waferOpts:void 0,dieOpts:void 0,explicitDies:void 0,reticleOpts:void 0,lotStackOpts:void 0,passBins:[1],ringCount:4,ringCountWarning:void 0,removedFieldWarning:oe(t),inputValueWarnings:e.warnings,standardDiameters:void 0,testDefs:void 0,hbinDefs:void 0,sbinDefs:void 0,metadataFields:void 0,derivedTests:void 0,retestPolicy:"last",edgeDieYieldMode:"exclude"}:{results:e.results??t.results??[],waferOpts:e.orientation===void 0||t.waferConfig===void 0?t.waferConfig:{...t.waferConfig,orientation:e.orientation},dieOpts:t.dieConfig,explicitDies:t.dies,reticleOpts:t.reticleConfig,lotStackOpts:e.stackResults&&t.lotStack?{...t.lotStack,results:e.stackResults}:t.lotStack,passBins:t.passBins??[1],...Fe(t.ringCount),removedFieldWarning:oe(t),inputValueWarnings:e.warnings,standardDiameters:t.standardDiameters,testDefs:(e.testDefs??t.testDefs)?.length?e.testDefs??t.testDefs:void 0,hbinDefs:t.hbinDefs,sbinDefs:t.sbinDefs,metadataFields:t.metadataFields,derivedTests:t.derivedTests?.length?t.derivedTests:void 0,retestPolicy:t.retestPolicy??"last",edgeDieYieldMode:t.edgeDieYieldMode??"exclude"}}function Ve(t,e){return e?.coordinateOrigin?e.coordinateOrigin:{type:"center"}}function Be(t,e,n){if(e.type==="custom"&&e.offset)return{offsetX:e.offset.x,offsetY:e.offset.y};if(e.type!=="center"){let a=1/0,r=-1/0,s=1/0,u=-1/0;for(const o of t)o.x<a&&(a=o.x),o.x>r&&(r=o.x),o.y<s&&(s=o.y),o.y>u&&(u=o.y);return{offsetX:Math.round((r+a)/2),offsetY:Math.round((u+s)/2)}}return{offsetX:n.offsetX,offsetY:n.offsetY}}function Ye(t,e,n,a,r,s){if(s)return{colMidX:(Math.round(s.x)-e)*a,colMidY:(Math.round(s.y)-n)*r,anchored:!0};if(t.length===0)return{colMidX:0,colMidY:0,anchored:!1};let u=1/0,o=-1/0,w=1/0,h=-1/0;for(const i of t){const p=Math.round(i.x)-e,l=Math.round(i.y)-n;p<u&&(u=p),p>o&&(o=p),l<w&&(w=l),l>h&&(h=l)}return{colMidX:(u+o)/2*a,colMidY:(w+h)/2*r,anchored:!1}}function Xe(t,e,n){let a=1/0,r=-1/0,s=1/0,u=-1/0,o=0,w=0;for(const p of t){const l=Math.round(p.x)-e,f=Math.round(p.y)-n;o+=l,w+=f,l<a&&(a=l),l>r&&(r=l),f<s&&(s=f),f>u&&(u=f)}const h=t.length,i=(p,l,f)=>{const D=(f-l)/2;return D<=0?0:Math.abs(p/h-(l+f)/2)/D};return i(o,a,r)>.11||i(w,s,u)>.11}function ae(t,e){let n=t?.xAxisDirection==="left",a=t?.yAxisDirection==="down";return(e.type==="UL"||e.type==="UR")&&(a=!0),(e.type==="LR"||e.type==="UR")&&(n=!0),{flipX:n,flipY:a}}function je(t,e){const{method:n,targetBin:a}=t,r=t.results.map(s=>s.filter(_));if(n==="mean"||n==="median"||n==="stddev"||n==="min"||n==="max"||n==="count"){const s=new Set((e??[]).filter(i=>!isParametricTest(i)).map(i=>i.testNumber)),u=new Set;for(const i of r)for(const p of i)if(p.testValues)for(const l of Object.keys(p.testValues)){const f=Number(l);s.has(f)||u.add(f)}if(u.size===0)return re(r,n);const o=[...u],w=o.map(i=>re(r,n,i)),h=new Map;for(let i=0;i<o.length;i++){const p=o[i];for(const l of w[i]){const f=j(l);h.has(f)||h.set(f,{template:l,testValues:{}});const D=h.get(f),d=l.testValues?.[0];d!==void 0&&(D.testValues[p]=d)}}return[...h.values()].map(({template:i,testValues:p})=>({...i,testValues:Object.keys(p).length>0?p:void 0}))}if(n==="countBin"||n==="percent"){if(a===void 0)return[];const s=Te(r,a,"hard");if(n==="countBin")return s;const u=r.length;return s.map(o=>({...o,testValues:{0:u>0?(o.testValues?.[0]??0)/u*100:0}}))}if(n==="mode"){const s=new Map;for(const o of r)for(const w of o){const h=j(w);s.has(h)||s.set(h,[]),s.get(h).push(w)}const u=[];for(const[o,w]of s){const h=o.split(","),i=Number(h[0]),p=Number(h[1]),l=w.map(D=>D.hbin).filter(D=>D!==void 0),f=Ee(l);f!==null&&u.push({x:i,y:p,hbin:f})}return u}return[]}function de(t){const e=t.filter(_),n=e.length,a=e.filter(s=>s.edgeExcluded).length,r=e.filter(s=>dieHasTestData(s)||s.hbin!==void 0||s.sbin!==void 0).length;return{filledDies:r,totalDies:n,edgeExcludedDies:a,unpositionedDies:t.length-e.length,ratio:n>0?r/n:0}}function fe(t,e,n="exclude"){const a=new Set(e),s=t.filter(f=>!f.partial).filter(f=>f.edgeExcluded).length,u=t.filter(f=>f.partial).length;let o=0,w=0,h=!1;for(const f of t){if(!ge(f))continue;const D=f.hbin??f.sbin;D!==void 0&&(h=!0,a.has(D)?o++:w++)}const i=o+w,p=h&&i>0?o/i*100:null;let l=null;if(n==="denominator-only"){const f=i+s;l=h&&f>0?o/f*100:null}return{passDies:o,failDies:w,edgeExcludedDies:s,partialDies:u,totalDies:i,yieldPercent:p,yieldPercentGross:l}}function ce(t,e,n,a,r,s=0,u=0,o=0,w=0,h=0,i=!1,p=!1){if(!t)return[];const l=t.anchorDie??{x:0,y:0},f=ke(e,{width:t.width,height:t.height,diePitchX:a,diePitchY:r,anchorDie:{x:l.x-s,y:l.y-u},gridOrigin:{x:-o,y:-w}}),D=Me(xe(i,p,e.center.x,e.center.y),De(h,e.center.x,e.center.y));return f.filter(d=>{const v=d.width/2,x=d.height/2,O=[[d.x-v,d.y-x],[d.x+v,d.y-x],[d.x+v,d.y+x],[d.x-v,d.y+x]].map(([g,m])=>ve(D,g,m)),y=ie(O.map(g=>g.x)),b=H(O.map(g=>g.x)),C=ie(O.map(g=>g.y)),M=H(O.map(g=>g.y));return n.some(g=>g.physX>=y&&g.physX<b&&g.physY>=C&&g.physY<M)})}function Le(t,e,n){const r=Math.max(0,e.radius-n)**2;return t.map(s=>{const u=s.physX-e.center.x,o=s.physY-e.center.y;return u*u+o*o>r?{...s,edgeExcluded:!0}:s})}const Ke=.2;function ze(t,e,n){const a=new Set(n);function r(y,b){if(e==="last")return!0;if(e==="first")return!1;const C=L(y.hbin),M=L(b.hbin);if(C===void 0||M===void 0)return!1;const g=a.has(C),m=a.has(M);return e==="best"?m!==g?m:M<C:m!==g?g:M>C}const s=(y,b)=>b.supersedes!==void 0||r(y,b),u=y=>{const b=y.partId===void 0?"":String(y.partId).trim();return b===""?void 0:b},o=t.filter(y=>!_(y)),w=new Map;for(const y of o){const b=u(y);b!==void 0&&w.set(b,(w.get(b)??0)+1)}const i=(o.length===0?0:Math.max(0,...w.values())/o.length)<=Ke,p=new Map,l=[],f=new Map;let D=0,d=0;for(const y of t){const b=u(y);let C;if(_(y)?C=`p:${j(y)}`:i&&b!==void 0?C=`i:${b}`:C=`u:${d++}`,y.supersedes==="partId"&&b!==void 0){const g=f.get(b);g!==void 0&&g!==C&&p.has(g)&&p.delete(g)}const M=p.get(C);M===void 0?(p.set(C,{winner:y,count:1}),l.push(C)):(M.count++,C.startsWith("i:")&&D++,s(M.winner,y)&&(M.winner=y)),b!==void 0&&f.set(b,C)}const v=[],x=[...l.filter(y=>y.startsWith("p:")),...l.filter(y=>!y.startsWith("p:"))];for(const y of x){const b=p.get(y);b!==void 0&&v.push(b.count>1?{...b.winner,retestCount:b.count}:b.winner)}const O=D>0?{code:"retests-by-part-id",severity:"info",message:`${D} record${D===1?"":"s"} with no position shared a part ID with an earlier record and ${D===1?"was":"were"} treated as a retest of it.`}:void 0;return{results:v,warning:O}}export function getDieTestValue(t,e){return t.testValues?.[e]}export function getTestPassStatus(t,e,n){const a=t.testPass?.[e];if(a!==void 0)return a;if(n!==void 0&&!isParametricTest(n)){const r=t.testValues?.[e];if(r===0||r===1)return r===1}}export function dieHasTestData(t){return t.testValues!==void 0&&Object.keys(t.testValues).length>0||t.testPass!==void 0&&Object.keys(t.testPass).length>0}function L(t){return t===void 0||Number.isNaN(t)?void 0:t}function K(t,e){const n={},a=L(e.hbin),r=L(e.sbin);return a!==void 0&&(n.hbin=a),r!==void 0&&(n.sbin=r),e.retestCount!==void 0&&(n.retestCount=e.retestCount),e.siteNum!==void 0&&(n.siteNum=e.siteNum),e.partId!==void 0&&(n.partId=e.partId),e.metadata!==void 0&&(n.metadata=e.metadata),e.testPass!==void 0&&(n.testPass=e.testPass),e.testValues!==void 0&&(n.testValues=e.testValues),{...t,...n}}function le(t){return t.some(dieHasTestData)?"value":"hardBin"}function ue(t,e){return t.map(({code:n,message:a})=>({code:n,message:a,severity:n==="non-standard-diameter"||n==="diameter-exceeds-die-extent"?"warning":"error",confidence:e.wafer.confidence}))}const Ue="do not fit inside the supplied",Ge="Inferred wafer diameter",He="The probed dies reach only",qe=.75,Je=20;export const STANDARD_WAFER_DIAMETERS_MM=[100,125,150,200,300];const Qe=.02;function he(t,e){return e.reduce((n,a)=>Math.abs(a-t)<Math.abs(n-t)?a:n)}function Ze(t,e){if(!e.length)return!0;const n=he(t,e);return Math.abs(n-t)/n<=Qe}function et(t){return t===void 0?STANDARD_WAFER_DIAMETERS_MM:t.filter(e=>Number.isFinite(e)&&e>0)}export function buildWaferMap(t){const e=_e(!Array.isArray(t)&&t.layout?Pe(t):t),n=[];if(e.derivedTests){const c=new Set(e.passBins);if(e.lotStackOpts){const S=e.lotStackOpts.results.map(W=>Z(W,e.testDefs,e.derivedTests,c));e.lotStackOpts={...e.lotStackOpts,results:S.map(W=>W.results)},e.testDefs=S[0]?.testDefs??e.testDefs,n.push(...S[0]?.warnings??[])}else{const S=Z(e.results,e.testDefs,e.derivedTests,c);e.results=S.results,e.testDefs=S.testDefs,n.push(...S.warnings)}}const a=e.lotStackOpts?je(e.lotStackOpts,e.testDefs):e.results;for(let c=0;c<Math.min(5,a.length);c++){const S=a[c];if(typeof S.x=="string"||typeof S.y=="string")throw new TypeError("buildWaferMap: x and y must be numbers, received strings. Did you forget to cast CSV values? e.g. { x: +row.x, y: +row.y }")}for(const c of a)if(c.x==null!=(c.y==null))throw new TypeError(`buildWaferMap: die has only one of x/y set (x=${String(c.x)}, y=${String(c.y)}). A die must be either fully positioned (both x and y) or fully unpositioned (neither) \u2014 omit both to mark a die as having no reported position.`);const{results:r,warning:s}=ze(a,e.retestPolicy,e.passBins),u={wafer:{confidence:1,method:"provided"},diePitch:{confidence:1,units:"mm"},grid:{confidence:1}},o=[];if(e.explicitDies){let c=e.explicitDies;const S=r.filter(_),W=r.filter(X=>!_(X));if(S.length>0){const X=new Map(S.map(P=>[j(P),P]));c=c.map(P=>{const J=X.get(j(P));return J?K(P,J):P})}const Y=e.waferOpts?.diameter??300,B=G({diameter:Y,notch:e.waferOpts?.notch,orientation:e.waferOpts?.orientation??0,metadata:e.waferOpts?.metadata});B.orientation!==0&&(c=Q(c,B));const q=ce(e.reticleOpts,B,c,1,1,0,0,0,0,B.orientation),pe=e.reticleOpts!==void 0,z=ee(B,c,{reticles:q,showReticle:pe,plotMode:le(r),testDefs:e.testDefs,passBins:e.passBins,ringCount:e.ringCount,isLotStack:!1},{hbinDefs:e.hbinDefs,sbinDefs:e.sbinDefs,metadataFields:e.metadataFields}),me=W.map((X,P)=>K({id:`unpositioned_${P}`,width:c[0]?.width??1,height:c[0]?.height??1},X)),U=[...c,...me];return{wafer:B,dies:U,view:z,reticleConfig:e.reticleOpts,units:"mm",inference:u,warnings:[...e.removedFieldWarning?[e.removedFieldWarning]:[],...e.inputValueWarnings,...s?[s]:[],...ue(o,u),...n,...e.ringCountWarning?[e.ringCountWarning]:[]],plotMode:z.plotMode,metadata:z.metadata,isLotStack:!1,dataCoverage:de(U),passBins:e.passBins,ringCount:e.ringCount,yield:fe(U,e.passBins,e.edgeDieYieldMode),reticles:q,hbinDefs:e.hbinDefs,sbinDefs:e.sbinDefs,testDefs:e.testDefs,metadataFields:e.metadataFields}}const w=r.filter(_),h=r.filter(c=>!_(c)),i=w.map(c=>({x:c.x,y:c.y})),p=Ce(i,e.dieOpts,e.waferOpts?.diameter);u.diePitch={confidence:p.confidence,units:p.units};const{pitchX:l,pitchY:f}=p,D=p.units,d=Ve(w,e.dieOpts),v=Oe(i);u.grid={confidence:v.confidence};const{offsetX:x,offsetY:O}=Be(i,d,v),{flipX:y,flipY:b}=ae(e.dieOpts,d),{colMidX:C,colMidY:M,anchored:g}=Ye(i,x,O,l,f,e.waferOpts?.center);let m=e.waferOpts?.diameter;const F=i.map(c=>({x:(Math.round(c.x)-x)*l-C,y:(Math.round(c.y)-O)*f-M})),A=H(F.map(({x:c,y:S})=>Math.hypot(Math.abs(c)+l/2,Math.abs(S)+f/2)));if(m===void 0)if(i.length>0)if(D==="mm"){const c=Se(F,{minRadius:A});m=c.diameter,u.wafer={confidence:c.confidence,method:c.method};const S=et(e.standardDiameters);if(!Ze(m,S)){const W=he(m,S);o.push({code:"non-standard-diameter",message:`${Ge} ${m.toFixed(1)} mm is not a standard wafer size (nearest is ${W} mm). The diameter was derived from the die extent, which assumes the probed grid reaches the wafer edge \u2014 a non-standard result usually means it does not, because the outer dies were never probed. Every die is then placed against a wafer that is too small, which moves dies between rings and changes ring and edge findings. Supply waferConfig.diameter.`})}}else m=A*2,u.wafer={confidence:p.confidence*.8,method:"extent"};else m=D==="mm"?300:30,u.wafer={confidence:0,method:"default"};if(!g&&i.length>0&&Xe(i,x,O)&&(u.wafer.method="inferred-partial",o.push({code:"partial-coverage",message:"Wafer geometry was inferred from die positions alone. The data does not span a full symmetric wafer, so the inferred diameter and centre may be wrong and dies may be mis-positioned relative to the true wafer boundary. Supply waferConfig.diameter and waferConfig.center (the prober coordinate of the wafer centre) to position partial data correctly."})),e.waferOpts?.diameter!==void 0&&e.waferOpts.center===void 0&&A>0&&i.length>=Je){const c=A/(m/2);c<qe&&o.push({code:"diameter-exceeds-die-extent",message:`${He} ${(c*100).toFixed(0)}% of the ${m} mm wafer's radius. Either the diameter is larger than the real one, or this is a partial map covering only part of the wafer. Ring bands are equal-radius, so the outer rings are mostly or entirely empty and ring, quadrant and edge findings describe the assumed wafer rather than the probed area. Check waferConfig.diameter, or drop it and let the diameter be inferred from the die extent.`})}if(e.waferOpts?.diameter!==void 0){const c=e.dieOpts?.width!==void 0&&e.dieOpts?.height!==void 0;if(c&&A>m/2+1e-9){const S=F.filter(({x:W,y:Y})=>Math.hypot(Math.abs(W)+l/2,Math.abs(Y)+f/2)>m/2+1e-9).length;o.push({code:"geometry-conflict",message:`${S} of ${F.length} probed die positions ${Ue} ${m} mm wafer at the supplied die pitch of ${l} \xD7 ${f} mm. A die with test results is a real prober position and is always fully on the wafer, so one of the two supplied values must be wrong: containing these dies at this pitch would need a diameter of at least ${(A*2).toFixed(1)} mm. Check waferConfig.diameter and dieConfig.width/height against the real device.`})}}const N=G({diameter:m,notch:e.waferOpts?.notch,orientation:e.waferOpts?.orientation??0,metadata:e.waferOpts?.metadata});let $=w.map(c=>{const S=Math.round(c.x)-x,W=Math.round(c.y)-O,Y={id:`${S}_${W}`,x:S,y:W,physX:S*l-C,physY:W*f-M,width:l,height:f,insideWafer:!0,partial:!1};return K(Y,c)});(x!==0||O!==0)&&($=$.map(c=>({...c,x:c.x+x,y:c.y+O,id:`${c.x+x}_${c.y+O}`}))),$=Q($,N),(y||b)&&($=ye($,{flipX:y,flipY:b},N.center));const I=[];e.waferOpts?.edgeExclusion&&e.waferOpts.edgeExclusion>0&&($=Le($,N,e.waferOpts.edgeExclusion),e.waferOpts.edgeExclusion>N.radius&&I.push({code:"edge-exclusion-exceeds-radius",message:`edgeExclusion (${e.waferOpts.edgeExclusion}mm) exceeds the resolved wafer radius (${N.radius}mm) \u2014 the entire wafer is excluded.`,severity:"warning"}));const V=ce(e.reticleOpts,N,$,l,f,x,O,C,M,N.orientation,y,b),k=e.reticleOpts!==void 0,R=ee(N,$,{reticles:V,showReticle:k,plotMode:le(r),testDefs:e.testDefs,passBins:e.passBins,ringCount:e.ringCount,dataAxisFlip:{x:y,y:b},isLotStack:e.lotStackOpts!==void 0,aggregationMethod:e.lotStackOpts?.method,lotSize:e.lotStackOpts?.results.length},{hbinDefs:e.hbinDefs,sbinDefs:e.sbinDefs,metadataFields:e.metadataFields}),T=h.map((c,S)=>K({id:`unpositioned_${S}`,width:l,height:f},c)),E=[...$,...T];return{wafer:N,dies:E,view:R,reticleConfig:e.reticleOpts,units:D,inference:u,warnings:[...e.removedFieldWarning?[e.removedFieldWarning]:[],...e.inputValueWarnings,...s?[s]:[],...ue(o,u),...I,...n,...e.ringCountWarning?[e.ringCountWarning]:[]],plotMode:R.plotMode,metadata:R.metadata,isLotStack:e.lotStackOpts!==void 0,dataCoverage:de(E),passBins:e.passBins,ringCount:e.ringCount,yield:fe(E,e.passBins,e.edgeDieYieldMode),reticles:V,hbinDefs:e.hbinDefs,sbinDefs:e.sbinDefs,testDefs:e.testDefs,metadataFields:e.metadataFields,aggrMethod:R.aggrMethod,lotSize:R.lotSize}}
@@ -28,8 +28,6 @@ export declare function wangHash(n: number): number;
28
28
  /** Linear interpolation across RGB keypoints for t ∈ [0, 1]. */
29
29
  export declare function lerpKp(kp: readonly [number, number, number][], t: number): string;
30
30
  export declare const VIRIDIS: readonly [number, number, number][];
31
- /** Map t ∈ [0, 1] to a Viridis RGB colour string. */
32
- export declare function valueToViridis(t: number): string;
33
31
  /**
34
32
  * Categorical colour for the `index`-th distinct value of an active
35
33
  * `'metadata'` field, where `index` comes from sorting the field's distinct
@@ -1 +1 @@
1
- import{clamp01 as s}from"../core/utils.js";export const SPEC_PASS_FILL="#2ecc71",SPEC_FAIL_LOW="#3498db",SPEC_FAIL_HIGH="#e74c3c",NO_DATA_FILL="#d6d9dd",BIN_PALETTE=["#95a5a6","#1eb84b","#932cdd","#b8a51e","#2cbfdd","#b81e71","#58dd2c","#251eb8","#dd672c","#1eb87f","#ce2cdd","#98b81e","#2c84dd","#b81e3e","#2cdd3b","#581eb8","#dda22c","#1eb8b2","#dd2cb0","#64b81e","#2c49dd","#b8321e","#2cdd76","#8c1eb8","#dcdd2c","#1e8bb8","#dd2c75","#31b81e","#4b2cdd","#b8651e","#2cddb1","#b81eb1","#a1dd2c","#1e57b8","#dd2c3a","#1eb83f","#852cdd","#b8991e","#2ccddd","#b81e7e","#66dd2c","#1e24b8","#dd5a2c","#1eb872","#c02cdd","#a4b81e","#2c92dd","#b81e4a","#2cdd2e","#4c1eb8","#dd942c","#1eb8a6","#dd2cbe","#70b81e","#2c57dd","#b8261e","#2cdd69","#801eb8","#ddcf2c","#1e97b8","#dd2c83","#3db81e","#3d2cdd","#b8591e"];export function wangHash(e){let c=e|0;return c=Math.imul(c^c>>>16,73244475),c=Math.imul(c^c>>>16,73244475),(c^c>>>16)>>>0}const u=BIN_PALETTE.length-1;export function lerpKp(e,c){const n=s(c)*(e.length-1),d=Math.floor(n),t=Math.min(d+1,e.length-1),a=n-d,b=Math.round(e[d][0]+a*(e[t][0]-e[d][0])),o=Math.round(e[d][1]+a*(e[t][1]-e[d][1])),l=Math.round(e[d][2]+a*(e[t][2]-e[d][2]));return`rgb(${b},${o},${l})`}export const VIRIDIS=[[68,1,84],[59,82,139],[33,145,140],[94,201,98],[253,231,37]];export function valueToViridis(e){return lerpKp(VIRIDIS,e)}const f=["#4e79a7","#f28e2b","#e15759","#76b7b2","#59a14f","#edc948","#b07aa1","#ff9da7","#9c755f","#bab0ac"],i=668265263;export function metadataValueColor(e){return e<f.length?f[e]:BIN_PALETTE[wangHash(e^i)%u+1]}export function valueToGreyscale(e){const c=Math.round(s(e)*200+30);return`rgb(${c},${c},${c})`}export function contrastTextColor(e){let c=0,r=0,n=0;const d=e.match(/rgb\((\d+),\s*(\d+),\s*(\d+)\)/);if(d)c=+d[1],r=+d[2],n=+d[3];else{const b=e.replace("#","");c=parseInt(b.slice(0,2),16),r=parseInt(b.slice(2,4),16),n=parseInt(b.slice(4,6),16)}const t=b=>{const o=b/255;return o<=.03928?o/12.92:((o+.055)/1.055)**2.4};return .2126*t(c)+.7152*t(r)+.0722*t(n)>.179?"#000000":"#ffffff"}
1
+ import{clamp01 as s}from"../core/utils.js";export const SPEC_PASS_FILL="#2ecc71",SPEC_FAIL_LOW="#3498db",SPEC_FAIL_HIGH="#e74c3c",NO_DATA_FILL="#d6d9dd",BIN_PALETTE=["#95a5a6","#1eb84b","#932cdd","#b8a51e","#2cbfdd","#b81e71","#58dd2c","#251eb8","#dd672c","#1eb87f","#ce2cdd","#98b81e","#2c84dd","#b81e3e","#2cdd3b","#581eb8","#dda22c","#1eb8b2","#dd2cb0","#64b81e","#2c49dd","#b8321e","#2cdd76","#8c1eb8","#dcdd2c","#1e8bb8","#dd2c75","#31b81e","#4b2cdd","#b8651e","#2cddb1","#b81eb1","#a1dd2c","#1e57b8","#dd2c3a","#1eb83f","#852cdd","#b8991e","#2ccddd","#b81e7e","#66dd2c","#1e24b8","#dd5a2c","#1eb872","#c02cdd","#a4b81e","#2c92dd","#b81e4a","#2cdd2e","#4c1eb8","#dd942c","#1eb8a6","#dd2cbe","#70b81e","#2c57dd","#b8261e","#2cdd69","#801eb8","#ddcf2c","#1e97b8","#dd2c83","#3db81e","#3d2cdd","#b8591e"];export function wangHash(e){let c=e|0;return c=Math.imul(c^c>>>16,73244475),c=Math.imul(c^c>>>16,73244475),(c^c>>>16)>>>0}const u=BIN_PALETTE.length-1;export function lerpKp(e,c){const n=s(c)*(e.length-1),d=Math.floor(n),t=Math.min(d+1,e.length-1),a=n-d,b=Math.round(e[d][0]+a*(e[t][0]-e[d][0])),o=Math.round(e[d][1]+a*(e[t][1]-e[d][1])),l=Math.round(e[d][2]+a*(e[t][2]-e[d][2]));return`rgb(${b},${o},${l})`}export const VIRIDIS=[[68,1,84],[59,82,139],[33,145,140],[94,201,98],[253,231,37]];const f=["#4e79a7","#f28e2b","#e15759","#76b7b2","#59a14f","#edc948","#b07aa1","#ff9da7","#9c755f","#bab0ac"],h=668265263;export function metadataValueColor(e){return e<f.length?f[e]:BIN_PALETTE[wangHash(e^h)%u+1]}export function valueToGreyscale(e){const c=Math.round(s(e)*200+30);return`rgb(${c},${c},${c})`}export function contrastTextColor(e){let c=0,r=0,n=0;const d=e.match(/rgb\((\d+),\s*(\d+),\s*(\d+)\)/);if(d)c=+d[1],r=+d[2],n=+d[3];else{const b=e.replace("#","");c=parseInt(b.slice(0,2),16),r=parseInt(b.slice(2,4),16),n=parseInt(b.slice(4,6),16)}const t=b=>{const o=b/255;return o<=.03928?o/12.92:((o+.055)/1.055)**2.4};return .2126*t(c)+.7152*t(r)+.0722*t(n)>.179?"#000000":"#ffffff"}