@wafertools/wafermap 0.30.4 → 0.32.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 (103) hide show
  1. package/AGENTS.md +21 -31
  2. package/CHANGELOG.md +265 -1
  3. package/README.md +3 -3
  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/dieList.js +7 -7
  13. package/dist/packages/canvas-adapter/drilldown.js +1 -1
  14. package/dist/packages/canvas-adapter/exportName.d.ts +1 -12
  15. package/dist/packages/canvas-adapter/exportName.js +1 -1
  16. package/dist/packages/canvas-adapter/index.d.ts +2 -3
  17. package/dist/packages/canvas-adapter/index.js +1 -1
  18. package/dist/packages/canvas-adapter/insightsTab.js +1 -1
  19. package/dist/packages/canvas-adapter/renderWaferGallery.d.ts +4 -7
  20. package/dist/packages/canvas-adapter/renderWaferGallery.js +1 -1
  21. package/dist/packages/canvas-adapter/renderWaferMap.d.ts +32 -34
  22. package/dist/packages/canvas-adapter/renderWaferMap.js +1 -1
  23. package/dist/packages/canvas-adapter/summaryPanel.js +3 -3
  24. package/dist/packages/canvas-adapter/toCanvas.d.ts +23 -2
  25. package/dist/packages/canvas-adapter/toCanvas.js +1 -1
  26. package/dist/packages/canvas-adapter/toolbar.js +2 -2
  27. package/dist/packages/canvas-adapter/userGuideHtml.d.ts +1 -1
  28. package/dist/packages/canvas-adapter/userGuideHtml.js +38 -24
  29. package/dist/packages/canvas-adapter/version.d.ts +2 -2
  30. package/dist/packages/canvas-adapter/version.js +1 -1
  31. package/dist/packages/core/aggregates.d.ts +0 -6
  32. package/dist/packages/core/aggregates.js +1 -1
  33. package/dist/packages/core/dieTable.d.ts +137 -0
  34. package/dist/packages/core/dieTable.js +1 -0
  35. package/dist/packages/core/dies.d.ts +22 -2
  36. package/dist/packages/core/dies.js +1 -1
  37. package/dist/packages/core/index.d.ts +8 -10
  38. package/dist/packages/core/index.js +1 -1
  39. package/dist/packages/core/stdf.d.ts +9 -0
  40. package/dist/packages/core/stdf.js +1 -0
  41. package/dist/packages/core/transforms.d.ts +0 -23
  42. package/dist/packages/core/transforms.js +1 -1
  43. package/dist/packages/core/utils.d.ts +14 -0
  44. package/dist/packages/core/utils.js +1 -1
  45. package/dist/packages/renderer/buildView.d.ts +4 -12
  46. package/dist/packages/renderer/buildView.js +1 -1
  47. package/dist/packages/renderer/buildWaferMap.d.ts +42 -32
  48. package/dist/packages/renderer/buildWaferMap.js +1 -1
  49. package/dist/packages/renderer/colorMap.d.ts +0 -2
  50. package/dist/packages/renderer/colorMap.js +1 -1
  51. package/dist/packages/renderer/columnarInput.d.ts +87 -0
  52. package/dist/packages/renderer/columnarInput.js +1 -0
  53. package/dist/packages/renderer/deprecate.d.ts +1 -1
  54. package/dist/packages/renderer/deprecate.js +1 -1
  55. package/dist/packages/renderer/derivedTests/apply.d.ts +8 -6
  56. package/dist/packages/renderer/derivedTests/apply.js +1 -1
  57. package/dist/packages/renderer/index.d.ts +7 -5
  58. package/dist/packages/renderer/index.js +1 -1
  59. package/dist/packages/renderer/spec.d.ts +3 -7
  60. package/dist/packages/renderer/spec.js +1 -1
  61. package/dist/packages/stats/analyzeWaferLot.js +1 -1
  62. package/dist/packages/stats/analyzeWaferMap.d.ts +134 -2
  63. package/dist/packages/stats/analyzeWaferMap.js +1 -1
  64. package/dist/packages/stats/boxplot.js +1 -1
  65. package/dist/packages/stats/capability.d.ts +7 -7
  66. package/dist/packages/stats/capability.js +1 -1
  67. package/dist/packages/stats/clusterDetection.d.ts +2 -1
  68. package/dist/packages/stats/clusterDetection.js +1 -1
  69. package/dist/packages/stats/connectedComponents.js +1 -1
  70. package/dist/packages/stats/correlation.js +1 -1
  71. package/dist/packages/stats/facets.d.ts +1 -1
  72. package/dist/packages/stats/filterFindings.d.ts +5 -0
  73. package/dist/packages/stats/filterFindings.js +1 -1
  74. package/dist/packages/stats/histogram.js +1 -1
  75. package/dist/packages/stats/index.d.ts +6 -16
  76. package/dist/packages/stats/index.js +1 -1
  77. package/dist/packages/stats/math.d.ts +22 -6
  78. package/dist/packages/stats/math.js +1 -1
  79. package/dist/packages/stats/normalizeInput.js +1 -1
  80. package/dist/packages/stats/patternClassification.d.ts +5 -4
  81. package/dist/packages/stats/patternClassification.js +1 -1
  82. package/dist/packages/stats/regions.d.ts +9 -1
  83. package/dist/packages/stats/regions.js +1 -1
  84. package/dist/packages/stats/renderFindingsReport.d.ts +0 -4
  85. package/dist/packages/stats/renderFindingsReport.js +1 -21
  86. package/dist/packages/stats/renderSummaryReport.js +18 -18
  87. package/dist/packages/stats/scatter.js +1 -1
  88. package/dist/packages/stats/sweep.js +1 -1
  89. package/dist/packages/stats/testPassRate.js +1 -1
  90. package/dist/packages/stats/trend.js +1 -1
  91. package/dist/packages/stats/types.d.ts +5 -0
  92. package/dist/packages/worker/index.js +1 -1
  93. package/dist/packages/worker/wafermap.worker.d.ts +3 -0
  94. package/dist/packages/worker/wafermap.worker.js +1 -1
  95. package/package.json +2 -2
  96. package/dist/packages/canvas-adapter/deprecated.d.ts +0 -7
  97. package/dist/packages/canvas-adapter/deprecated.js +0 -1
  98. package/dist/packages/core/deprecated.d.ts +0 -60
  99. package/dist/packages/core/deprecated.js +0 -1
  100. package/dist/packages/renderer/deprecated.d.ts +0 -45
  101. package/dist/packages/renderer/deprecated.js +0 -1
  102. package/dist/packages/stats/deprecated.d.ts +0 -85
  103. 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-27T14:44:40.685Z">v0.32.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
@@ -149,7 +144,9 @@ automatically, since there is nothing to plot on a gradient.</p>
149
144
  <p>Every map also shows a short <strong>title</strong> by the colorbar or legend naming what is
150
145
  displayed \u2014 the test name (and number, in limit pass/fail mode), the bin type, or the stacked
151
146
  wafer count.</p>
152
- <p>Clicking a bin swatch in the legend filters the display to that bin
147
+ <p>Clicking a bin swatch in the legend filters the display to that bin: every other bin is
148
+ greyed out. <strong>Ctrl / Cmd + click</strong> adds or removes further bins, so several can be shown
149
+ together. Clicking the only bin shown again clears the filter
153
150
  (see <a href="#3-toolbar-controls" onclick="(function(e){e.preventDefault();var g=e.target.closest('.wmap-guide');var el=g&&g.querySelector('[id=\\'3-toolbar-controls\\']');if(el)el.scrollIntoView({behavior:'smooth'});})(event)">Highlight bin</a>).</p>
154
151
  <h3 id="15-wafers-and-dies-with-no-position-data">1.5 Wafers and dies with no position data</h3>
155
152
  <p>Not every die necessarily has a reported grid position \u2014 some data sources supply bin or test
@@ -266,7 +263,7 @@ appears in a <strong>Derived from</strong> column instead.</p>
266
263
  <p>The toolbar sits at the top-right of the map and is always visible. Controls that
267
264
  are not applicable to the current mode are hidden automatically. (Hovering the map
268
265
  shows die tooltips \u2014 a separate thing from the toolbar, which is always present.)</p>
269
- <p><img src="data:image/png;base64,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" alt="Single-map toolbar"></p>
266
+ <p><img src="data:image/png;base64,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" alt="Single-map toolbar"></p>
270
267
  <h3 id="single-map">Single map</h3>
271
268
  <table>
272
269
  <thead>
@@ -339,7 +336,7 @@ shows die tooltips \u2014 a separate thing from the toolbar, which is always pre
339
336
  <tr>
340
337
  <td><img src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgd2lkdGg9IjE2IiBoZWlnaHQ9IjE2IiBmaWxsPSJub25lIiBzdHJva2U9IiMzNzQxNTEiIHN0cm9rZS13aWR0aD0iMS44IiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjxwYXRoIGQ9Ik01IDNhMiAyIDAgMCAwLTIgMiIvPjxwYXRoIGQ9Ik0xOSAzYTIgMiAwIDAgMSAyIDIiLz48cGF0aCBkPSJNMjEgMTlhMiAyIDAgMCAxLTIgMiIvPjxwYXRoIGQ9Ik01IDIxYTIgMiAwIDAgMS0yLTIiLz48cGF0aCBkPSJNOSAzaDEiLz48cGF0aCBkPSJNOSAyMWgxIi8+PHBhdGggZD0iTTE0IDNoMSIvPjxwYXRoIGQ9Ik0xNCAyMWgxIi8+PHBhdGggZD0iTTMgOXYxIi8+PHBhdGggZD0iTTIxIDl2MSIvPjxwYXRoIGQ9Ik0zIDE0djEiLz48cGF0aCBkPSJNMjEgMTR2MSIvPjwvc3ZnPgo=" width="20" height="20"></td>
341
338
  <td>Box select</td>
342
- <td>Click and drag to select a rectangular group of dies (see <a href="#43-box-select" onclick="(function(e){e.preventDefault();var g=e.target.closest('.wmap-guide');var el=g&&g.querySelector('[id=\\'43-box-select\\']');if(el)el.scrollIntoView({behavior:'smooth'});})(event)">Section 4.3</a>).</td>
339
+ <td>Click and drag to select a rectangular group of dies (see <a href="#43-box-select" onclick="(function(e){e.preventDefault();var g=e.target.closest('.wmap-guide');var el=g&&g.querySelector('[id=\\'43-box-select\\']');if(el)el.scrollIntoView({behavior:'smooth'});})(event)">Section 4.3</a>). This is the mode a map opens in.</td>
343
340
  </tr>
344
341
  <tr>
345
342
  <td><img src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNiIgaGVpZ2h0PSIxNiIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9IiMzNzQxNTEiIHN0cm9rZS13aWR0aD0iMiIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIj48cGF0aCBkPSJNMyAzdjE4aDE4Ii8+PHBhdGggZD0iTTcgMTZjMy0xIDQtOSA3LTlzMyA1IDYgNSIvPjwvc3ZnPgo=" width="20" height="20"></td>
@@ -560,18 +557,24 @@ outside the test limits, with the remaining tests summarised as &quot;+N more te
560
557
  <li><strong>Stacked modes</strong> \u2014 the single aggregated value or count at that position</li>
561
558
  </ul>
562
559
  <h3 id="42-zoom-and-pan">4.2 Zoom and pan</h3>
563
- <p>Scroll to zoom in and out. Click and drag to pan when in Pan mode. The toolbar
564
- also provides dedicated <strong>Zoom mode</strong> (drag to draw a zoom region), <strong>Zoom in</strong>,
560
+ <p>Scroll to zoom in and out. To pan, hold <strong>Space</strong> and drag, use the arrow keys,
561
+ or choose Pan mode in the toolbar so that a plain drag pans. The toolbar also
562
+ provides dedicated <strong>Zoom mode</strong> (drag to draw a zoom region), <strong>Zoom in</strong>,
565
563
  <strong>Zoom out</strong>, and <strong>Reset zoom</strong> buttons. Tooltips and die selection remain
566
564
  accurate at all zoom levels.</p>
567
565
  <h3 id="43-box-select">4.3 Box select</h3>
568
- <p>Switch to Box select mode in the toolbar, then click and drag to draw a selection
569
- rectangle. The application may display statistics or details for the selected
570
- dies. This is useful for comparing a sub-region against the full wafer.
571
- Use <strong>Ctrl / Cmd + click</strong> to add individual dies to the current selection.
572
- Press <strong>Esc</strong> to clear.</p>
573
- <div data-wmap-demo="box-select" class="wmap-demo"></div><p><em>A block of dies near the centre is shown pre-selected. Choose Box select in the
574
- toolbar and drag to make your own selection, or Ctrl/Cmd + click individual dies.</em></p>
566
+ <p>Click and drag on the map to draw a selection rectangle \u2014 Box select is the mode
567
+ a map opens in. Click a die to select just that die, and click it again to clear
568
+ it. The application may display statistics or details for the selected dies.
569
+ This is useful for comparing a sub-region against the full wafer.</p>
570
+ <p>Selected dies keep their full colour and every other die is faded, with an
571
+ outline round the selection, so it is clear which dies are selected whatever
572
+ their shape \u2014 a block, a ring or scattered dies. A finding highlighted from the
573
+ Summary panel is shown the same way.</p>
574
+ <p>Use <strong>Ctrl / Cmd + click</strong> to add or remove individual dies. Press <strong>Esc</strong>, or
575
+ click outside the wafer, to clear the selection.</p>
576
+ <div data-wmap-demo="box-select" class="wmap-demo"></div><p><em>A block of dies near the centre is shown pre-selected. Drag on the map to make
577
+ your own selection, or Ctrl/Cmd + click individual dies.</em></p>
575
578
  <h3 id="44-charting-dies-and-wafers">4.4 Charting dies and wafers</h3>
576
579
  <p><strong>Right-click</strong> a population to open a menu of charts drawn from just those
577
580
  dies. Picking one opens it in a window over the map. This is how you look at a
@@ -623,8 +626,10 @@ detected patterns on the wafer. Open it from the toolbar.</p>
623
626
  <ul>
624
627
  <li><strong>Severity</strong> \u2014 Unusual, Notable, or Info (ordered most to least significant)</li>
625
628
  <li><strong>Description</strong> \u2014 plain-language summary of what was detected and where</li>
626
- <li><strong>Click to highlight</strong> \u2014 clicking a finding highlights the affected dies on
627
- the map with a black-and-white outline, visible against every colour scheme</li>
629
+ <li><strong>Click to highlight</strong> \u2014 clicking a finding fades the rest of the wafer and outlines the
630
+ affected dies. A finding about a bin also filters the legend to that bin, so the legend
631
+ always names what the finding describes; a finding about yield or a test clears the
632
+ filter. Changing the selection or the legend filter yourself releases the finding.</li>
628
633
  </ul>
629
634
  <p><strong>Severity</strong> reflects how strong the statistical evidence is. <em>Unusual</em> findings
630
635
  have both a very low adjusted p-value and a large effect size \u2014 they are reliably
@@ -852,15 +857,24 @@ without both still appear, normalized
852
857
  onto their own range and sorted by variability), a test-value box plot, a
853
858
  value histogram, and a <strong>wafer-to-wafer trend</strong> \u2014 one point per wafer at its
854
859
  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
860
+ 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
861
  a cassette only reads in the physical sequence, so there is no sort control
858
862
  to destroy it. Clicking a capability box drives the box plot, histogram and
859
863
  trend onto that same test automatically.</p>
864
+ <p>The box plot, histogram, trend and the Correlation tab&#39;s scatter draw the
865
+ test&#39;s limits as dashed lines. A test can have two kinds: <strong>test limits</strong>
866
+ (labelled <em>Lo limit</em> / <em>Hi limit</em>, short dashes) \u2014 what each die was judged
867
+ pass/fail by \u2014 and <strong>spec limits</strong> (labelled <em>LSL</em> / <em>USL</em>, long dashes) \u2014
868
+ the process specification that capability is measured against. When a test
869
+ has both, a <strong>Limits</strong> choice appears \u2014 <em>Test + spec</em> (the default), <em>Test
870
+ limits</em>, <em>Spec limits</em> or <em>None</em> \u2014 and it applies to all four charts at
871
+ once. A test that lacks the kind you chose shows the kind it has, so it
872
+ never looks as if it had no limits; a limit that falls outside the plotted
873
+ range is marked at the edge of the chart with an arrow.</p>
860
874
  <p>The box plot, histogram and trend share one row of axis controls, kept in
861
875
  sync across all three so switching between them never re-reads the same
862
876
  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
877
+ every limit shown even where the data sits well clear of them (on by default only when
864
878
  doing so still leaves the data at least a third of the axis \u2014 otherwise a
865
879
  generous limit window would squash a perfectly capable distribution into a
866
880
  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.32.0";
2
+ export declare const WMAP_BUILD_TIME = "2026-09-27T14:45:45.383Z";
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.32.0",WMAP_BUILD_TIME="2026-09-27T14:45:45.383Z";
@@ -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{copyDie as p,testValue as m}from"./dieTable.js";import{minOf as M,maxOf as V}from"./utils.js";import{positionKey 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=m(t,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(p(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(p(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=M(e):n==="max"?s=V(e):s=e.length;u.push(p(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=>p(o,{testValues:{0:a.get(h(o))??0},testPass:void 0,...c==="soft"?{sbin:n}:{hbin:n}}))}
@@ -0,0 +1,137 @@
1
+ import type { Die } from './dies.js';
2
+ /** Verdict column entries: no verdict / fail / pass. */
3
+ export declare const VERDICT_NONE = -1;
4
+ export declare const VERDICT_FAIL = 0;
5
+ export declare const VERDICT_PASS = 1;
6
+ /** `DieTable.fields` bits: the record carried a `testValues` / `testPass` object. */
7
+ export declare const HAS_VALUES = 1;
8
+ export declare const HAS_VERDICTS = 2;
9
+ export interface DieTable {
10
+ /** Number of rows. A die's row is an index below this. */
11
+ readonly rows: number;
12
+ /** Test number → value per row; `NaN` = no value. Stored as supplied, never narrowed. */
13
+ readonly values: ReadonlyMap<number, Float32Array | Float64Array>;
14
+ /** Test number → verdict per row: −1 none, 0 fail, 1 pass. */
15
+ readonly verdicts: ReadonlyMap<number, Int8Array>;
16
+ /**
17
+ * Per row, which of `testValues` / `testPass` the die exposes (bits
18
+ * `HAS_VALUES`, `HAS_VERDICTS`): the fields its record carried, so a die
19
+ * with no verdicts has no `testPass`, exactly as before.
20
+ */
21
+ readonly fields: Uint8Array;
22
+ }
23
+ /** What the readers need of a die. A `DieResult` qualifies. */
24
+ export type DieData = Pick<Die, 'testValues' | 'testPass'>;
25
+ /** Marks a die being built as `row` of `table`, in place. `finaliseDie` completes it. */
26
+ export declare function linkDie<D extends object>(die: D, table: DieTable, row: number): D;
27
+ /** The table and row `die` is linked to, or `undefined` for a die with its own objects. */
28
+ export declare function dieLink(die: DieData): {
29
+ table: DieTable;
30
+ row: number;
31
+ } | undefined;
32
+ type Row = {
33
+ testValues?: Record<number, number>;
34
+ testPass?: Record<number, boolean>;
35
+ };
36
+ /**
37
+ * A table built from row-shaped records (`DieResult`s): row `i` holds
38
+ * `rows[i]`'s values and verdicts. Values are copied as-is (`Float64Array`,
39
+ * the precision a JS number has); a missing entry is `NaN` / no verdict.
40
+ */
41
+ export declare function tableFromRows(rows: readonly Row[]): DieTable;
42
+ /**
43
+ * A copy of `table` with more columns: `values` / `verdicts` are added (a test
44
+ * number already present is replaced), and `fields` is copied, so the caller
45
+ * can mark rows that gained data. The original table is never changed.
46
+ */
47
+ export declare function extendTable(table: DieTable, values: ReadonlyMap<number, Float32Array | Float64Array>, verdicts: ReadonlyMap<number, Int8Array>): DieTable & {
48
+ fields: Uint8Array;
49
+ };
50
+ /**
51
+ * One reusable die-shaped view over `table`: `at(row)` points it at a row and
52
+ * returns it, so a rule that takes a die (`getTestPassStatus`) can be applied
53
+ * row by row without an object per row. Valid until the next `at`.
54
+ */
55
+ export declare function rowView(table: DieTable): (row: number) => DieData;
56
+ /**
57
+ * The die a host receives, from a die built with `linkDie`: a fresh object with
58
+ * the same fields, a non-enumerable link, and `testValues` / `testPass` getters
59
+ * for the fields its record carried. A die with no link is returned as is.
60
+ *
61
+ * A die that already carries a value object its record does not replace (a
62
+ * host-supplied layout die with its own `testPass`, say, and a record with
63
+ * only values) is not linked: it gets its record's objects built onto it now,
64
+ * beside its own, which is exactly what it had before tables existed.
65
+ */
66
+ export declare function finaliseDie<D extends Die>(die: D): D;
67
+ /**
68
+ * A copy of `die` with `patch` applied. For a linked die this copies the
69
+ * fields and the link without calling the getters: an object spread would
70
+ * call them and build the value objects of every die it copies (3 s and the
71
+ * lot's full memory on 266k dies). A patch that sets `testValues` or
72
+ * `testPass` gives an own-objects die (the other field built from the row).
73
+ */
74
+ export declare function copyDie<D extends DieData>(die: D, patch: Partial<D>): D;
75
+ /** What `detachTables` sends beside the dies: their tables, and each die's table and row. */
76
+ export interface DetachedTables {
77
+ tables: DieTable[];
78
+ /** Per die, in order across every die array: its table's index in `tables` (−1 unlinked), then its row. */
79
+ links: Int32Array;
80
+ }
81
+ /**
82
+ * Prepares linked dies to be posted from a worker without their values being
83
+ * built and copied: takes their `testValues` / `testPass` getters off (a
84
+ * structured clone would call them), and returns their tables and links. Post
85
+ * the dies, and the result with `transfer` as the transfer list, which moves
86
+ * the columns without a copy; the other side calls `attachTables`.
87
+ *
88
+ * **Changes the dies in place**, and transferring empties the columns: only
89
+ * for dies the sender discards once they are posted.
90
+ */
91
+ export declare function detachTables(dieArrays: ReadonlyArray<readonly Die[]>): DetachedTables & {
92
+ transfer: ArrayBuffer[];
93
+ };
94
+ /** The receiving half of `detachTables`: links the posted dies to their tables again, in place. */
95
+ export declare function attachTables(dieArrays: ReadonlyArray<readonly Die[]>, detached: DetachedTables): void;
96
+ /** `die`'s value for test `testNumber`, or `undefined` when it has none. */
97
+ export declare function testValue(die: DieData, testNumber: number): number | undefined;
98
+ /**
99
+ * `die`'s recorded verdict for test `testNumber` (true = pass), or `undefined`.
100
+ * The raw record only: `getTestPassStatus` owns the functional-test fallback
101
+ * and is what callers use.
102
+ */
103
+ export declare function recordedVerdict(die: DieData, testNumber: number): boolean | undefined;
104
+ /** True when `die` has any measured value or recorded verdict. */
105
+ export declare function dieHasAnyTestData(die: DieData): boolean;
106
+ /** True when `die` has at least one measured value. */
107
+ export declare function dieHasValues(die: DieData): boolean;
108
+ /** True when `die` has at least one recorded verdict. */
109
+ export declare function dieHasVerdicts(die: DieData): boolean;
110
+ /** `die`'s values as `[testNumber, value]`, test numbers ascending. */
111
+ export declare function dieValueEntries(die: DieData): Array<[number, number]>;
112
+ /** `die`'s recorded verdicts as `[testNumber, pass]`, test numbers ascending. */
113
+ export declare function dieVerdictEntries(die: DieData): Array<[number, boolean]>;
114
+ /**
115
+ * A reader of tests `testNumbers` for die after die: `read(die, out)` writes
116
+ * test `testNumbers[k]`'s value to `out[k]` (`NaN` when none) and returns
117
+ * `out`. For a linked die it is one indexed read per test: the columns are
118
+ * looked up once per table, not once per die. The bulk path for statistics
119
+ * that walk many dies × many tests.
120
+ */
121
+ export declare function testValuesReader(testNumbers: readonly number[]): (die: DieData, out: Float64Array) => Float64Array;
122
+ /**
123
+ * {@link testValuesReader} for recorded verdicts: `read(die, out)` writes test
124
+ * `testNumbers[k]`'s recorded verdict to `out[k]` — `VERDICT_PASS`,
125
+ * `VERDICT_FAIL` or `VERDICT_NONE` — and returns `out`. The raw record only, as
126
+ * {@link recordedVerdict}: `testPassStatusReader` adds the functional-test
127
+ * fallback and is what callers use.
128
+ */
129
+ export declare function recordedVerdictsReader(testNumbers: readonly number[]): (die: DieData, out: Int8Array) => Int8Array;
130
+ /**
131
+ * Every test number with at least one value or verdict among `dies` (only
132
+ * values, with `which: 'values'`), ascending. Linked dies are checked column
133
+ * by column per table, not die by die, and each column stops at its first hit.
134
+ */
135
+ export declare function testsPresent(dies: readonly DieData[], which?: 'all' | 'values'): number[];
136
+ export {};
137
+ //# sourceMappingURL=dieTable.d.ts.map
@@ -0,0 +1 @@
1
+ import{hasAnyKey as O}from"./utils.js";const k=NaN;export const VERDICT_NONE=-1,VERDICT_FAIL=0,VERDICT_PASS=1,HAS_VALUES=1,HAS_VERDICTS=2;const a=Symbol("wmap.dieTable"),u=Symbol("wmap.dieRow");export function linkDie(e,t,s){return e[a]=t,e[u]=s,e}export function dieLink(e){const t=e[a];return t===void 0?void 0:{table:t,row:e[u]}}export function tableFromRows(e){const t=e.length,s=new Map,r=new Map,o=new Uint8Array(t),n=g(s,()=>new Float64Array(t).fill(k)),i=g(r,()=>new Int8Array(t).fill(VERDICT_NONE));for(let f=0;f<t;f++){const c=e[f].testValues;if(c){o[f]|=HAS_VALUES;const d=n(c),w=Object.values(c);for(let b=0;b<d.length;b++)d[b][f]=w[b]}const l=e[f].testPass;if(l){o[f]|=HAS_VERDICTS;const d=i(l),w=Object.values(l);for(let b=0;b<d.length;b++)d[b][f]=w[b]?VERDICT_PASS:VERDICT_FAIL}}return{rows:t,values:m(s),verdicts:m(r),fields:o}}function g(e,t){let s=[],r=[];return o=>{const n=Object.keys(o);let i=n.length===s.length;for(let f=0;i&&f<n.length;f++)i=n[f]===s[f];return i||(s=n,r=n.map(f=>{let c=e.get(f);return c===void 0&&e.set(f,c=t()),c})),r}}function m(e){return new Map([...e].map(([t,s])=>[Number(t),s]).sort((t,s)=>t[0]-s[0]))}export function extendTable(e,t,s){const r=new Map(e.values),o=new Map(e.verdicts);for(const[n,i]of t)r.set(n,i);for(const[n,i]of s)o.set(n,i);return{rows:e.rows,values:new Map([...r].sort((n,i)=>n[0]-i[0])),verdicts:new Map([...o].sort((n,i)=>n[0]-i[0])),fields:e.fields.slice()}}export function rowView(e){const t=linkDie({},e,0);return s=>(t[u]=s,t)}function v(e,t){const s={},r=[...e.values];for(let o=r.length-1;o>=0;o--){const n=r[o][1][t];Number.isNaN(n)||(s[r[o][0]]=n)}return s}function V(e,t){const s={},r=[...e.verdicts];for(let o=r.length-1;o>=0;o--){const n=r[o][1][t];n!==VERDICT_NONE&&(s[r[o][0]]=n===VERDICT_PASS)}return s}function x(){return Object.freeze(v(this[a],this[u]))}function N(){return Object.freeze(V(this[a],this[u]))}function j(e){return function(){throw new TypeError(`wafermap: die.${e} is read-only on a die built by buildWaferMap (die ${this.id}). Pass the values you want to buildWaferMap, or copy the die: { ...die, ${e}: yours }.`)}}const y={get:x,set:j("testValues"),enumerable:!0,configurable:!0},P={get:N,set:j("testPass"),enumerable:!0,configurable:!0},p=e=>({value:e,writable:!0,enumerable:!1,configurable:!0});export function finaliseDie(e){const t=e[a];if(t===void 0)return e;const s=e[u],r=t.fields[s],o=e.testValues!==void 0&&!(r&HAS_VALUES)||e.testPass!==void 0&&!(r&HAS_VERDICTS),n={};for(const i of Object.keys(e))i==="testValues"&&r&HAS_VALUES||i==="testPass"&&r&HAS_VERDICTS||(n[i]=e[i]);return o?(r&HAS_VALUES&&(n.testValues=v(t,s)),r&HAS_VERDICTS&&(n.testPass=V(t,s)),n):(Object.defineProperty(n,a,p(t)),Object.defineProperty(n,u,p(s)),r&HAS_VALUES&&Object.defineProperty(n,"testValues",y),r&HAS_VERDICTS&&Object.defineProperty(n,"testPass",P),n)}export function copyDie(e,t){const s=e[a];if(s===void 0)return{...e,...t};const r=e[u],o=s.fields[r],n={};for(const i of Object.keys(e))i!=="testValues"&&i!=="testPass"&&(n[i]=e[i]);return"testValues"in t||"testPass"in t?(o&HAS_VALUES&&!("testValues"in t)&&(n.testValues=v(s,r)),o&HAS_VERDICTS&&!("testPass"in t)&&(n.testPass=V(s,r)),Object.assign(n,t)):(Object.assign(n,t),Object.defineProperty(n,a,p(s)),Object.defineProperty(n,u,p(r)),o&HAS_VALUES&&Object.defineProperty(n,"testValues",y),o&HAS_VERDICTS&&Object.defineProperty(n,"testPass",P),n)}export function detachTables(e){const t=[],s=new Map,r=e.reduce((f,c)=>f+c.length,0),o=new Int32Array(r*2);let n=0;for(const f of e)for(const c of f){const l=dieLink(c);if(l===void 0){o[n++]=-1,o[n++]=0;continue}let d=s.get(l.table);d===void 0&&(s.set(l.table,d=t.length),t.push(l.table)),o[n++]=d,o[n++]=l.row,delete c.testValues,delete c.testPass}const i=new Set;for(const f of t){for(const c of f.values.values())i.add(c.buffer);for(const c of f.verdicts.values())i.add(c.buffer);i.add(f.fields.buffer)}return{tables:t,links:o,transfer:[...i]}}export function attachTables(e,t){let s=0;for(const r of e)for(const o of r){const n=t.links[s++],i=t.links[s++];if(n<0)continue;const f=t.tables[n],c=f.fields[i];Object.defineProperty(o,a,p(f)),Object.defineProperty(o,u,p(i)),c&HAS_VALUES&&Object.defineProperty(o,"testValues",y),c&HAS_VERDICTS&&Object.defineProperty(o,"testPass",P)}}export function testValue(e,t){const s=e[a];if(s===void 0)return e.testValues?.[t];const r=s.values.get(t)?.[e[u]];return r===void 0||Number.isNaN(r)?void 0:r}export function recordedVerdict(e,t){const s=e[a];if(s===void 0)return e.testPass?.[t];const r=s.verdicts.get(t)?.[e[u]];return r===void 0||r===VERDICT_NONE?void 0:r===VERDICT_PASS}export function dieHasAnyTestData(e){return dieHasValues(e)||dieHasVerdicts(e)}export function dieHasValues(e){const t=dieLink(e);if(t===void 0)return O(e.testValues);for(const s of t.table.values.values())if(!Number.isNaN(s[t.row]))return!0;return!1}export function dieHasVerdicts(e){const t=dieLink(e);if(t===void 0)return O(e.testPass);for(const s of t.table.verdicts.values())if(s[t.row]!==VERDICT_NONE)return!0;return!1}export function dieValueEntries(e){const t=dieLink(e);if(t===void 0){const r=e.testValues;return r===void 0?[]:Object.keys(r).map(o=>[Number(o),r[Number(o)]])}const s=[];for(const[r,o]of t.table.values){const n=o[t.row];Number.isNaN(n)||s.push([r,n])}return s}export function dieVerdictEntries(e){const t=dieLink(e);if(t===void 0){const r=e.testPass;return r===void 0?[]:Object.keys(r).map(o=>[Number(o),r[Number(o)]])}const s=[];for(const[r,o]of t.table.verdicts){const n=o[t.row];n!==VERDICT_NONE&&s.push([r,n===VERDICT_PASS])}return s}export function testValuesReader(e){let t,s=[];return function(o,n){const i=dieLink(o);if(i===void 0){const f=o.testValues;for(let c=0;c<e.length;c++){const l=f?.[e[c]];n[c]=l===void 0?NaN:l}return n}i.table!==t&&(t=i.table,s=e.map(f=>i.table.values.get(f)));for(let f=0;f<s.length;f++){const c=s[f];n[f]=c===void 0?NaN:c[i.row]}return n}}export function recordedVerdictsReader(e){let t,s=[];return function(o,n){const i=dieLink(o);if(i===void 0){const f=o.testPass;for(let c=0;c<e.length;c++){const l=f?.[e[c]];n[c]=l===void 0?VERDICT_NONE:l?VERDICT_PASS:VERDICT_FAIL}return n}i.table!==t&&(t=i.table,s=e.map(f=>i.table.verdicts.get(f)));for(let f=0;f<s.length;f++){const c=s[f];n[f]=c===void 0?VERDICT_NONE:c[i.row]}return n}}export function testsPresent(e,t="all"){const s=new Set,r=new Map;for(const o of e){const n=dieLink(o);if(n===void 0){if(o.testValues)for(const f in o.testValues)s.add(Number(f));if(t==="all"&&o.testPass)for(const f in o.testPass)s.add(Number(f));continue}let i=r.get(n.table);i===void 0&&r.set(n.table,i=[]),i.push(n.row)}for(const[o,n]of r){for(const[i,f]of o.values)!s.has(i)&&n.some(c=>!Number.isNaN(f[c]))&&s.add(i);if(t!=="values")for(const[i,f]of o.verdicts)!s.has(i)&&n.some(c=>f[c]!==VERDICT_NONE)&&s.add(i)}return[...s].sort((o,n)=>o-n)}
@@ -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
  }
@@ -143,6 +141,28 @@ export declare function getDieKey(die: {
143
141
  y?: number;
144
142
  id?: string;
145
143
  }): string;
144
+ /**
145
+ * A number identifying grid position (`x`, `y`), for neighbour lookups inside
146
+ * the library: cluster detection and connected components probe up to 25 cells
147
+ * per die, and building a `getDieKey` string for each probe was most of their
148
+ * cost. Exact and collision-free for integer coordinates with |x| < 2^20 and
149
+ * |y| < 2^31, which covers STDF's ±32767 many times over. Never shown and
150
+ * never stored — findings carry `getDieKey` strings.
151
+ */
152
+ export declare function gridKey(x: number, y: number): number;
153
+ /**
154
+ * A die's `getDieKey` identity as a value that is cheap to hash: two dies get
155
+ * equal position keys exactly when their `getDieKey` strings are equal. Integer
156
+ * coordinates in STDF's range map one-to-one to {@link gridKey}; anything else
157
+ * keeps its `getDieKey` string. For sets and maps over many dies (stacked cards,
158
+ * analysis), where building a string per die was most of the work. Never shown
159
+ * and never stored — findings carry `getDieKey` strings.
160
+ */
161
+ export declare function positionKey(die: {
162
+ x?: number;
163
+ y?: number;
164
+ id?: string;
165
+ }): number | string;
146
166
  export declare function isYieldEligibleDie(die: Die, options?: DieEligibilityOptions): boolean;
147
167
  /**
148
168
  * Whether a die passed: its hard bin (else its soft bin) is in `passBins`.
@@ -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(t){return t.x!=null&&t.y!=null}export function isPositionedDie(t){return t.x!=null&&t.y!=null}export function getDieKey(t){return hasPosition(t)?`${t.x},${t.y}`:`id:${t.id}`}export function gridKey(t,e){return t*4294967296+e}export function positionKey(t){const{x:e,y:n}=t;return Number.isInteger(e)&&Number.isInteger(n)&&Math.abs(e)<=32767&&Math.abs(n)<=32767?gridKey(e,n):getDieKey(t)}export function isYieldEligibleDie(t,e={}){return!(t.partial||!e.includeEdgeExcluded&&t.edgeExcluded)}export function diePassStatus(t,e){const n=t.hbin??t.sbin;return n===void 0?void 0:e.has(n)}export function generateDies(t,e){const{width:n,height:o,offset:s={x:0,y:0}}=e,u=e.gridSize??Math.ceil(t.radius/Math.min(n,o))+1,c=[];for(let r=-u;r<=u;r++)for(let i=-u;i<=u;i++){const l=t.center.x+i*n+s.x,x=t.center.y+r*o+s.y;c.push({id:`${i}_${r}`,x:i,y:r,physX:l,physY:x,width:n,height:o})}return c}
@@ -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}})}
@@ -25,6 +25,12 @@ export declare function compareNatural(a: string, b: string): number;
25
25
  */
26
26
  export declare function prettyKey(key: string): string;
27
27
  /** Clamp `v` into [0, 1] — the normalization range every colour scale expects. */
28
+ /**
29
+ * Whether `obj` has an own key. The per-die form of `Object.keys(obj).length > 0`
30
+ * without building the key array: on a 266k-die lot that allocation, once per
31
+ * die, cost seconds in WebKit's engine (the desktop app on Linux and macOS).
32
+ */
33
+ export declare function hasAnyKey<T extends object>(obj: T | null | undefined): obj is T;
28
34
  /**
29
35
  * Smallest / largest of `values`, by iteration rather than `Math.min(...values)`.
30
36
  *
@@ -135,4 +141,12 @@ export type Chunked<T> = Generator<void, T, void>;
135
141
  /** Run a {@link Chunked} computation to completion without yielding, and
136
142
  * return its result — the synchronous entry point for any chunked builder. */
137
143
  export declare function drain<T>(work: Chunked<T>): T;
144
+ /** A highlight option (one value or several) as a list; `undefined` is none. */
145
+ export declare function asList<T>(value: T | readonly T[] | undefined): readonly T[];
146
+ /**
147
+ * The highlight after a click on `value`. A plain click shows only that value,
148
+ * or clears the highlight when it was the only one; an additive click
149
+ * (Ctrl/Cmd) adds or removes it. One value stays a plain value.
150
+ */
151
+ export declare function toggleHighlight<T>(current: T | readonly T[] | undefined, value: T, additive: boolean): T | T[] | undefined;
138
152
  //# sourceMappingURL=utils.d.ts.map