@wafertools/wafermap 0.30.3 → 0.30.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +48 -0
- package/README.md +2 -2
- package/dist/packages/canvas-adapter/charts/boxplot.js +1 -1
- package/dist/packages/canvas-adapter/charts/capability.js +1 -1
- package/dist/packages/canvas-adapter/charts/chartShell.d.ts +1 -1
- package/dist/packages/canvas-adapter/charts/chartShell.js +1 -1
- package/dist/packages/canvas-adapter/charts/histogram.js +1 -1
- package/dist/packages/canvas-adapter/charts/scatter.d.ts +5 -1
- package/dist/packages/canvas-adapter/charts/scatter.js +1 -1
- package/dist/packages/canvas-adapter/charts/sweep.js +1 -1
- package/dist/packages/canvas-adapter/charts/testPassRate.js +1 -1
- package/dist/packages/canvas-adapter/charts/trend.js +1 -1
- package/dist/packages/canvas-adapter/insightsTab.d.ts +11 -1
- package/dist/packages/canvas-adapter/insightsTab.js +1 -1
- package/dist/packages/canvas-adapter/maplessSummary.js +1 -1
- package/dist/packages/canvas-adapter/renderWaferGallery.js +1 -1
- package/dist/packages/canvas-adapter/renderWaferMap.js +1 -1
- package/dist/packages/canvas-adapter/summaryPanel.js +3 -3
- package/dist/packages/canvas-adapter/toCanvas.js +1 -1
- package/dist/packages/canvas-adapter/toolbar.js +1 -1
- package/dist/packages/canvas-adapter/userGuideHtml.d.ts +1 -1
- package/dist/packages/canvas-adapter/userGuideHtml.js +38 -36
- package/dist/packages/canvas-adapter/version.d.ts +2 -2
- package/dist/packages/canvas-adapter/version.js +1 -1
- package/dist/packages/canvas-adapter/warnings.js +1 -1
- package/dist/packages/core/dies.d.ts +1 -1
- package/dist/packages/renderer/binColors.js +1 -1
- package/dist/packages/renderer/buildView.js +1 -1
- package/dist/packages/renderer/buildWaferMap.d.ts +40 -12
- package/dist/packages/renderer/buildWaferMap.js +1 -1
- package/dist/packages/renderer/derivedTests/apply.js +1 -1
- package/dist/packages/renderer/derivedTests/parser.js +1 -1
- package/dist/packages/renderer/spec.d.ts +17 -0
- package/dist/packages/renderer/spec.js +1 -1
- package/dist/packages/stats/analyzeWaferMap.js +1 -1
- package/dist/packages/stats/capability.d.ts +4 -2
- package/dist/packages/stats/capability.js +1 -1
- package/dist/packages/stats/correlation.d.ts +1 -1
- package/dist/packages/stats/deprecated.d.ts +1 -1
- package/dist/packages/stats/deprecated.js +1 -1
- package/dist/packages/stats/mergeTestDefs.d.ts +1 -1
- package/dist/packages/stats/mergeTestDefs.js +1 -1
- package/dist/packages/stats/renderSummaryReport.js +11 -11
- package/dist/packages/stats/sweep.d.ts +8 -0
- package/dist/packages/stats/sweep.js +1 -1
- package/dist/packages/stats/types.d.ts +9 -3
- package/package.json +1 -1
|
@@ -72,13 +72,13 @@ 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-
|
|
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>
|
|
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
|
|
79
79
|
or anyone else who may use a wafer map application \u2014 not for developers integrating the wafermap library.</p>
|
|
80
80
|
<p>Some features depend on what data the application has loaded (bin names, test
|
|
81
|
-
definitions,
|
|
81
|
+
definitions, test limits, reticle geometry). Where this applies it is noted.</p>
|
|
82
82
|
<p><strong>Getting oriented.</strong> The wafer map is an interactive viewer. A toolbar is always
|
|
83
83
|
present at the top-right of the map \u2014 use it to change what the colours represent,
|
|
84
84
|
toggle overlays, rotate or flip the wafer, zoom, select dies, and open the summary
|
|
@@ -134,20 +134,20 @@ then failing bins from the most dies to the fewest \u2014 the same order as the
|
|
|
134
134
|
panel and reports.</p>
|
|
135
135
|
<p><strong>Value mode</strong> shows a continuous colorbar: the colour scale runs from the minimum
|
|
136
136
|
to maximum value, with units when available. The colorbar is informational only \u2014
|
|
137
|
-
clicking it has no effect. To switch to a pass/fail view, use the <strong>
|
|
138
|
-
or <strong>Test pass/fail</strong> option in the Overlays menu (available when
|
|
137
|
+
clicking it has no effect. To switch to a pass/fail view, use the <strong>Limit pass/fail</strong>
|
|
138
|
+
or <strong>Test pass/fail</strong> option in the Overlays menu (available when test limits are
|
|
139
139
|
defined, or a recorded verdict exists, for the active test).</p>
|
|
140
|
-
<p><strong>
|
|
140
|
+
<p><strong>Limit pass/fail mode</strong> replaces the colorbar with a small legend: Pass,
|
|
141
141
|
Fail high, and Fail low swatches (only the categories that apply to the test's
|
|
142
|
-
limits) with a die count beside each. This judges dies against the test's
|
|
143
|
-
|
|
142
|
+
limits) with a die count beside each. This judges dies against the test's lower
|
|
143
|
+
and upper test limits.</p>
|
|
144
144
|
<p><strong>Test pass/fail mode</strong> replaces the colorbar with a <strong>Pass / Fail legend</strong> and die
|
|
145
145
|
counts, coloured by the tester's own <strong>recorded</strong> verdict for that test \u2014 not a
|
|
146
|
-
|
|
146
|
+
limit judgement. A test with no measured value (a functional, go/no-go test)
|
|
147
147
|
always displays this way; selecting it switches the map into Test pass/fail
|
|
148
148
|
automatically, since there is nothing to plot on a gradient.</p>
|
|
149
149
|
<p>Every map also shows a short <strong>title</strong> by the colorbar or legend naming what is
|
|
150
|
-
displayed \u2014 the test name (and number, in
|
|
150
|
+
displayed \u2014 the test name (and number, in limit pass/fail mode), the bin type, or the stacked
|
|
151
151
|
wafer count.</p>
|
|
152
152
|
<p>Clicking a bin swatch in the legend filters the display to that bin
|
|
153
153
|
(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>
|
|
@@ -220,33 +220,33 @@ application has loaded.</p>
|
|
|
220
220
|
<p>Stacked modes are only available on lot-stack maps (multiple wafers combined into
|
|
221
221
|
one display). The panel identifies how many wafers were stacked and which
|
|
222
222
|
aggregation method is active.</p>
|
|
223
|
-
<h3 id="test-value-mode-colorbar-range-and-
|
|
224
|
-
<p>When
|
|
223
|
+
<h3 id="test-value-mode-colorbar-range-and-test-limits">Test Value mode \u2014 colorbar range and test limits</h3>
|
|
224
|
+
<p>When test limits are defined for the active test, two additional display options
|
|
225
225
|
become available:</p>
|
|
226
226
|
<ul>
|
|
227
|
-
<li><strong>Colorbar range \u2014 Data /
|
|
228
|
-
actual data extent and the
|
|
229
|
-
range, never how
|
|
230
|
-
coloured by the gradient so you can read the value distribution, and
|
|
231
|
-
|
|
232
|
-
for above
|
|
227
|
+
<li><strong>Colorbar range \u2014 Data / Limits</strong>: switches the colorbar scale between the
|
|
228
|
+
actual data extent and the test limits. This affects only the colorbar's
|
|
229
|
+
range, never how failing dies are shown. In <strong>both</strong> ranges every die is
|
|
230
|
+
coloured by the gradient so you can read the value distribution, and dies
|
|
231
|
+
outside the test limits are marked with a triangle \u2014 pointing <strong>down (\u25BD) for below the low limit</strong>, <strong>up (\u25B3)
|
|
232
|
+
for above the high limit</strong> \u2014 so they stand out without leaving the distribution. The
|
|
233
233
|
triangle is drawn black or white per die for contrast against its own colour, so
|
|
234
234
|
it stays visible under any colour scheme, and its shape (not colour) carries the
|
|
235
235
|
below/above-limit meaning.</li>
|
|
236
|
-
<li><strong>
|
|
237
|
-
and dies outside
|
|
238
|
-
|
|
239
|
-
independently; a die can be flagged on either or both limits. A
|
|
236
|
+
<li><strong>Limit pass/fail</strong>: when active, dies within the test limits are shown in
|
|
237
|
+
a pass colour and dies outside them are highlighted \u2014 <strong>blue for below the
|
|
238
|
+
low limit</strong>, <strong>red for above the high limit</strong>. Both flags apply
|
|
239
|
+
independently; a die can be flagged on either or both limits. A Pass/Fail legend
|
|
240
240
|
replaces the colorbar, listing each applicable category with its die count.</li>
|
|
241
241
|
</ul>
|
|
242
242
|
<p>Some tests have no measured value at all \u2014 a continuity check or any other
|
|
243
243
|
go/no-go test, where the only result is a recorded pass or fail. Selecting one
|
|
244
244
|
of these <strong>functional tests</strong> as the active test switches the map into
|
|
245
245
|
<strong>Test pass/fail</strong> automatically: a Pass/Fail legend by die count, coloured by
|
|
246
|
-
the tester's own recorded verdict rather than a
|
|
246
|
+
the tester's own recorded verdict rather than a limit judgement (there is
|
|
247
247
|
no gradient to fall back to). <strong>Test pass/fail</strong> is also available as an
|
|
248
248
|
Overlays option on an ordinary parametric test when it carries recorded
|
|
249
|
-
verdicts, as an alternative to
|
|
249
|
+
verdicts, as an alternative to limit judgement.</p>
|
|
250
250
|
<div data-wmap-demo="value-heatmap" class="wmap-demo"></div><div data-wmap-demo="spec-passfail" class="wmap-demo"></div><hr>
|
|
251
251
|
<h3 id="derived-tests">Derived tests (\u2020)</h3>
|
|
252
252
|
<p>A test name marked <strong>\u2020</strong> in front is a <em>derived</em> test: its value was calculated
|
|
@@ -284,7 +284,7 @@ shows die tooltips \u2014 a separate thing from the toolbar, which is always pre
|
|
|
284
284
|
<tr>
|
|
285
285
|
<td><img src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgd2lkdGg9IjE2IiBoZWlnaHQ9IjE2IiBmaWxsPSJub25lIiBzdHJva2U9IiMzNzQxNTEiIHN0cm9rZS13aWR0aD0iMS44IiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjxwYXRoIGQ9Ik0yIDEyczMuNi03IDEwLTcgMTAgNyAxMCA3LTMuNiA3LTEwIDctMTAtNy0xMC03eiIvPjxjaXJjbGUgY3g9IjEyIiBjeT0iMTIiIHI9IjMiLz48L3N2Zz4K" width="20" height="20"></td>
|
|
286
286
|
<td>Overlays</td>
|
|
287
|
-
<td>Check-menu of optional display layers: XY axis indicator, ring boundaries, quadrant lines, die coordinate labels, reticle grid (when geometry is configured),
|
|
287
|
+
<td>Check-menu of optional display layers: XY axis indicator, ring boundaries, quadrant lines, die coordinate labels, reticle grid (when geometry is configured), Limit pass/fail (Test Value mode with limits), Test pass/fail (Test Value mode, active test is functional or has recorded verdicts), and <strong>Mark failing dies</strong> (bin modes only). <strong>Clear overlays</strong> at the foot turns off everything in the menu at once, and is greyed when nothing is on \u2014 so the menu also answers "is anything active?" without you auditing every row.</td>
|
|
288
288
|
</tr>
|
|
289
289
|
<tr>
|
|
290
290
|
<td><img src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgd2lkdGg9IjE2IiBoZWlnaHQ9IjE2IiBmaWxsPSJub25lIiBzdHJva2U9IiMzNzQxNTEiIHN0cm9rZS13aWR0aD0iMS44IiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjxwYXRoIGQ9Im0xNC42MjIgMTcuODk3LTEwLjY4LTIuOTEzIi8+PHBhdGggZD0iTTE4LjM3NiAyLjYyMmExIDEgMCAxIDEgMy4wMDIgMy4wMDJMMTcuMzYgOS42NDNhLjUuNSAwIDAgMCAwIC43MDdsLjk0NC45NDRhMi40MSAyLjQxIDAgMCAxIDAgMy40MDhsLS45NDQuOTQ0YS41LjUgMCAwIDEtLjcwNyAwTDguMzU0IDcuMzQ4YS41LjUgMCAwIDEgMC0uNzA3bC45NDQtLjk0NGEyLjQxIDIuNDEgMCAwIDEgMy40MDggMGwuOTQ0Ljk0NGEuNS41IDAgMCAwIC43MDcgMHoiLz48cGF0aCBkPSJNOSA4Yy0xLjgwNCAyLjcxLTMuOTcgMy40Ni02LjU4MyAzLjk0OGEuNTA3LjUwNyAwIDAgMC0uMzAyLjgxOWw3LjMyIDguODgzYTEgMSAwIDAgMCAxLjE4NS4yMDRDMTIuNzM1IDIwLjQwNSAxNiAxNi43OTIgMTYgMTUiLz48L3N2Zz4K" width="20" height="20"></td>
|
|
@@ -299,7 +299,7 @@ shows die tooltips \u2014 a separate thing from the toolbar, which is always pre
|
|
|
299
299
|
<tr>
|
|
300
300
|
<td><img src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgd2lkdGg9IjE2IiBoZWlnaHQ9IjE2IiBmaWxsPSJub25lIiBzdHJva2U9IiMzNzQxNTEiIHN0cm9rZS13aWR0aD0iMS44IiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjxwYXRoIGQ9Ik01IDQgTDUgMjAiLz48cGF0aCBkPSJNNSA0IEw4IDQiLz48cGF0aCBkPSJNNSAyMCBMOCAyMCIvPjxwYXRoIGQ9Ik0xOSA0IEwxOSAyMCIvPjxwYXRoIGQ9Ik0xOSA0IEwxNiA0Ii8+PHBhdGggZD0iTTE5IDIwIEwxNiAyMCIvPjxwYXRoIGQ9Ik01IDEyIEwxOSAxMiIgc3Ryb2tlLWRhc2hhcnJheT0iMyAyIi8+PC9zdmc+Cg==" width="20" height="20"></td>
|
|
301
301
|
<td>Colorbar range</td>
|
|
302
|
-
<td>Test Value mode with
|
|
302
|
+
<td>Test Value mode with test limits only. Toggles the colorbar between the <strong>limit range</strong> (default \u2014 the colours mean the same thing on every wafer, so maps are comparable) and the <strong>data range</strong> (stretches the scale to the values actually present, which shows more contrast but is not comparable between wafers).</td>
|
|
303
303
|
</tr>
|
|
304
304
|
<tr>
|
|
305
305
|
<td><img src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgd2lkdGg9IjE2IiBoZWlnaHQ9IjE2IiBmaWxsPSJub25lIiBzdHJva2U9IiMzNzQxNTEiIHN0cm9rZS13aWR0aD0iMS44IiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjxyZWN0IHdpZHRoPSI3IiBoZWlnaHQ9IjciIHg9IjMiIHk9IjMiIHJ4PSIxIi8+PHJlY3Qgd2lkdGg9IjciIGhlaWdodD0iNyIgeD0iMyIgeT0iMTQiIHJ4PSIxIi8+PHBhdGggZD0iTTE0IDRoNyIvPjxwYXRoIGQ9Ik0xNCA5aDciLz48cGF0aCBkPSJNMTQgMTVoNyIvPjxwYXRoIGQ9Ik0xNCAyMGg3Ii8+PC9zdmc+Cg==" width="20" height="20"></td>
|
|
@@ -404,7 +404,7 @@ highlighting one bin at a time.</p>
|
|
|
404
404
|
<li><strong>Quadrant lines</strong> \u2014 divides the wafer into N, S, E, W quadrants</li>
|
|
405
405
|
<li><strong>Die coordinate labels</strong> \u2014 draws the (x, y) grid position inside each die (useful at high zoom)</li>
|
|
406
406
|
<li><strong>Reticle grid</strong> \u2014 stepper field grid (only shown when reticle geometry is configured)</li>
|
|
407
|
-
<li><strong>
|
|
407
|
+
<li><strong>Limit pass/fail</strong> \u2014 pass/fail colouring for Test Value mode, judged against the test limits, when the active test defines them</li>
|
|
408
408
|
<li><strong>Test pass/fail</strong> \u2014 pass/fail colouring for Test Value mode, coloured by the tester's recorded verdict; always on for a functional (no measured value) active test</li>
|
|
409
409
|
</ul>
|
|
410
410
|
<div data-wmap-demo="overlays" class="wmap-demo"></div><p><em>Ring boundaries, quadrant lines, and XY indicator all active.</em></p>
|
|
@@ -515,7 +515,7 @@ highlighting one bin at a time.</p>
|
|
|
515
515
|
<tr>
|
|
516
516
|
<td><img src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgd2lkdGg9IjE2IiBoZWlnaHQ9IjE2IiBmaWxsPSJub25lIiBzdHJva2U9IiMzNzQxNTEiIHN0cm9rZS13aWR0aD0iMS44IiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjxwYXRoIGQ9Ik01IDQgTDUgMjAiLz48cGF0aCBkPSJNNSA0IEw4IDQiLz48cGF0aCBkPSJNNSAyMCBMOCAyMCIvPjxwYXRoIGQ9Ik0xOSA0IEwxOSAyMCIvPjxwYXRoIGQ9Ik0xOSA0IEwxNiA0Ii8+PHBhdGggZD0iTTE5IDIwIEwxNiAyMCIvPjxwYXRoIGQ9Ik01IDEyIEwxOSAxMiIgc3Ryb2tlLWRhc2hhcnJheT0iMyAyIi8+PC9zdmc+Cg==" width="20" height="20"></td>
|
|
517
517
|
<td>Colorbar range</td>
|
|
518
|
-
<td>Test Value mode with
|
|
518
|
+
<td>Test Value mode with test limits only. Toggles all cards between the limit range and the data range. Leave it on the test limits when comparing wafers \u2014 the data range rescales per view.</td>
|
|
519
519
|
</tr>
|
|
520
520
|
<tr>
|
|
521
521
|
<td><img src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNiIgaGVpZ2h0PSIxNiIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9IiMzNzQxNTEiIHN0cm9rZS13aWR0aD0iMiIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIj48cGF0aCBkPSJNOCAydjQiLz48cGF0aCBkPSJNMTIgMnY0Ii8+PHBhdGggZD0iTTE2IDJ2NCIvPjxyZWN0IHdpZHRoPSIxNiIgaGVpZ2h0PSIxOCIgeD0iNCIgeT0iNCIgcng9IjIiLz48cGF0aCBkPSJNOCAxMGg2Ii8+PHBhdGggZD0iTTggMTRoOCIvPjxwYXRoIGQ9Ik04IDE4aDUiLz48L3N2Zz4K" width="20" height="20"></td>
|
|
@@ -556,7 +556,7 @@ The rest depends on the active plot mode:</p>
|
|
|
556
556
|
<li><strong>Bin modes</strong> \u2014 the die's bin verdict (number and name, if named), plus a
|
|
557
557
|
note of how many test values are recorded for the die</li>
|
|
558
558
|
<li><strong>Test Value mode</strong> \u2014 the active test's value in bold, flagged when it is
|
|
559
|
-
|
|
559
|
+
outside the test limits, with the remaining tests summarised as "+N more tests"</li>
|
|
560
560
|
<li><strong>Stacked modes</strong> \u2014 the single aggregated value or count at that position</li>
|
|
561
561
|
</ul>
|
|
562
562
|
<h3 id="42-zoom-and-pan">4.2 Zoom and pan</h3>
|
|
@@ -594,7 +594,8 @@ wafer otherwise.</p>
|
|
|
594
594
|
<ul>
|
|
595
595
|
<li><strong>Value histogram</strong> \u2014 one test's distribution, opening on the test the map
|
|
596
596
|
is showing.</li>
|
|
597
|
-
<li><strong>Process capability</strong> \u2014 every test normalised to its spec limits,
|
|
597
|
+
<li><strong>Process capability</strong> \u2014 every test normalised to its spec limits, or its
|
|
598
|
+
test limits when it has none, with its
|
|
598
599
|
Ppk. Below 30 dies the chart says each Ppk is a rough estimate: a Ppk from a
|
|
599
600
|
handful of dies can be far from the process's real capability.</li>
|
|
600
601
|
<li><strong>Sweeps</strong> your application has defined (see <a href="#sweep-cards" onclick="(function(e){e.preventDefault();var g=e.target.closest('.wmap-guide');var el=g&&g.querySelector('[id=\\'sweep-cards\\']');if(el)el.scrollIntoView({behavior:'smooth'});})(event)">Sweep cards</a>).</li>
|
|
@@ -712,10 +713,10 @@ with a significance test behind it.</li>
|
|
|
712
713
|
marked. Wafers are in slot order by default, since that is what makes a
|
|
713
714
|
slot-correlated pattern visible; a <strong>Slot / Yield</strong> selector re-sorts. Wafers
|
|
714
715
|
far below the rest are labelled "low outlier".</li>
|
|
715
|
-
<li><strong>Test values</strong> \u2014 per test: mean, <strong>Ppk</strong>, and
|
|
716
|
+
<li><strong>Test values</strong> \u2014 per test: mean, <strong>Ppk</strong>, and limit yield. Ppk (not Cpk)
|
|
716
717
|
because it measures against the <em>overall</em> spread, including wafer-to-wafer
|
|
717
718
|
variation, which is what the dies actually ship against. The full descriptive
|
|
718
|
-
statistics \u2014 min, quartiles, median, max, \u03C3, both
|
|
719
|
+
statistics \u2014 min, quartiles, median, max, \u03C3, both test limits, and all four
|
|
719
720
|
capability indices \u2014 are in the summary report and the CSV export.</li>
|
|
720
721
|
<li><strong>Functional tests</strong> \u2014 pass/fail counts and pass rate per functional test.</li>
|
|
721
722
|
</ul>
|
|
@@ -846,11 +847,12 @@ actually in use and marked with the median, a hard/soft bin pareto, and
|
|
|
846
847
|
ring/quadrant regional yield plus the full per-test statistics table.</p>
|
|
847
848
|
</li>
|
|
848
849
|
<li><p><strong>Distributions</strong> \u2014 process capability (Cp/Cpk/Pp/Ppk for tests with both a
|
|
849
|
-
lower and upper spec
|
|
850
|
+
lower and upper limit \u2014 spec limits where given, otherwise test limits; tests
|
|
851
|
+
without both still appear, normalized
|
|
850
852
|
onto their own range and sorted by variability), a test-value box plot, a
|
|
851
853
|
value histogram, and a <strong>wafer-to-wafer trend</strong> \u2014 one point per wafer at its
|
|
852
854
|
mean, \xB11\u03C3 whiskers, the mean of all wafers dashed across (labelled the "lot
|
|
853
|
-
mean" when they all come from one lot), and
|
|
855
|
+
mean" when they all come from one lot), and test limits where the
|
|
854
856
|
test has them. The trend is always in slot order, deliberately: drift across
|
|
855
857
|
a cassette only reads in the physical sequence, so there is no sort control
|
|
856
858
|
to destroy it. Clicking a capability box drives the box plot, histogram and
|
|
@@ -858,9 +860,9 @@ trend onto that same test automatically.</p>
|
|
|
858
860
|
<p>The box plot, histogram and trend share one row of axis controls, kept in
|
|
859
861
|
sync across all three so switching between them never re-reads the same
|
|
860
862
|
data on a different scale: <strong>Axis includes limits</strong> widens the axis to cover
|
|
861
|
-
|
|
863
|
+
the low and high limits even where the data sits well clear of them (on by default only when
|
|
862
864
|
doing so still leaves the data at least a third of the axis \u2014 otherwise a
|
|
863
|
-
generous
|
|
865
|
+
generous limit window would squash a perfectly capable distribution into a
|
|
864
866
|
sliver), and <strong>Clip outliers</strong> narrows the axis to a robust range (the
|
|
865
867
|
Tukey fence, 1.5\xD7 IQR beyond Q1/Q3) so a handful of extreme values don't
|
|
866
868
|
stretch the axis until the rest of the distribution reads as a flat line.
|
|
@@ -896,7 +898,7 @@ yield/box-plot rows.</em></p>
|
|
|
896
898
|
open that wafer's map), hard/soft bin pareto, ring and quadrant yield, and
|
|
897
899
|
pooled per-test statistics. The "\u2039 Gallery" tab returns to the card grid.</em></p>
|
|
898
900
|
<p><em>Distributions sub-tab: process capability (coloured by Ppk \u2014 green capable,
|
|
899
|
-
orange marginal, red poor; tests without
|
|
901
|
+
orange marginal, red poor; tests without limits are muted and dashed), a
|
|
900
902
|
per-wafer test-value box plot, and a value histogram. Clicking a capability
|
|
901
903
|
box drives the box plot and histogram onto that test.</em></p>
|
|
902
904
|
<p><em>Correlation sub-tab: test-to-test Pearson correlation matrix (blue =
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export declare const WMAP_VERSION = "0.30.
|
|
2
|
-
export declare const WMAP_BUILD_TIME = "2026-09-
|
|
1
|
+
export declare const WMAP_VERSION = "0.30.4";
|
|
2
|
+
export declare const WMAP_BUILD_TIME = "2026-09-25T05:59:59.978Z";
|
|
3
3
|
//# sourceMappingURL=version.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export const WMAP_VERSION="0.30.
|
|
1
|
+
export const WMAP_VERSION="0.30.4",WMAP_BUILD_TIME="2026-09-25T05:59:59.978Z";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{SHADOW as
|
|
1
|
+
import{SHADOW as C,TRACKING as O,SPACE as r,RADIUS as v,FONT as y,CLR as d,Z_BASE as B}from"./toolbar.js";const E={error:0,warning:1,info:2};export function severityOf(t){return t.severity??"warning"}export function collectWarnings(t){const s=[],i=new Set,n=e=>{if(!e?.message)return;const o=`${e.code}\0${e.message}`;i.has(o)||(i.add(o),s.push(e))};for(const e of t.result?.warnings??[])n(e);for(const e of t.statsSummary?.stats.warnings??[])n(e);for(const e of t.lotStatsSummary?.perWafer??[])for(const o of e.summary.stats.warnings??[])n(o);for(const e of t.extra??[])n(e);return s.sort((e,o)=>E[severityOf(e)]-E[severityOf(o)])}function $(t){return t==="error"?{bg:d.errBg,border:d.errBorder,text:d.errText}:{bg:d.warnBg,border:d.warnBorder,text:d.warnText}}function S(t){return t==="error"?"\u26D4":t==="info"?"\u2139":"\u26A0"}export const SHORT_LABEL={"partial-coverage":"Partial wafer coverage","geometry-conflict":"Geometry conflict","non-standard-diameter":"Non-standard wafer diameter","diameter-exceeds-die-extent":"Diameter exceeds die extent","edge-exclusion-exceeds-radius":"Edge exclusion exceeds radius","test-count-capped":"Test analysis skipped","test-def-collision":"Test definitions disagree","analysis-option-corrected":"Analysis option corrected","bin-colors-shared":"Bins share a colour","pass-bins-mixed":"Pass bins differ by wafer","ring-count-mixed":"Ring counts differ by wafer","input-field-removed":"Removed input name ignored","input-values-not-numbers":"Bins or test values are text","input-values-outside-stdf":"Values outside STDF ranges","retests-by-part-id":"Retests matched by part ID","derived-test-invalid":"Derived test not applied"};function w(t){const s=t.code?SHORT_LABEL[t.code]:void 0;if(s)return s;const i=t.message.split(/(?<=\.)\s/)[0]??t.message;return i.length>60?`${i.slice(0,57)}\u2026`:i}export function buildWarningsBanner(t,s=document){const i=s.createElement("div");Object.assign(i.style,{marginBottom:r.lg});for(const n of t){const e=severityOf(n),o=$(e),m=s.createElement("div");Object.assign(m.style,{background:o.bg,border:`1px solid ${o.border}`,borderRadius:v.control,marginBottom:r.sm,fontSize:y.body,color:o.text,lineHeight:"1.5"}),m.setAttribute("role","status");const a=s.createElement("button");a.type="button",Object.assign(a.style,{display:"flex",alignItems:"center",gap:r.sm,width:"100%",background:"none",border:"none",font:"inherit",color:"inherit",textAlign:"left",padding:`${r.sm} ${r.lg}`,cursor:"pointer"});const p=s.createElement("span");Object.assign(p.style,{flex:"1",minWidth:"0"}),p.textContent=`${S(e)} ${w(n)}`;const l=s.createElement("span");Object.assign(l.style,{flexShrink:"0"}),a.append(p,l);const g=s.createElement("div");Object.assign(g.style,{padding:`0 ${r.lg} ${r.sm}`}),g.textContent=n.message;let c=!1;const f=()=>{l.textContent=c?"\u25B4":"\u25BE",g.style.display=c?"block":"none",a.setAttribute("aria-expanded",String(c)),a.setAttribute("aria-label",`${w(n)}. ${c?"Click to collapse.":"Click to expand for detail."}`)};f(),a.addEventListener("click",()=>{c=!c,f()}),a.addEventListener("mouseenter",()=>{l.style.opacity="0.65"}),a.addEventListener("mouseleave",()=>{l.style.opacity="1"}),m.append(a,g),i.appendChild(m)}return i}export function buildWarningsMenuEl(t,s,i=window){const n=i.document,e=n.createElement("div"),o=280,a=t.left+o<=(i.innerWidth??1/0)?t.left:Math.max(4,t.right-o);Object.assign(e.style,{position:"fixed",top:`${t.bottom+4}px`,left:`${a}px`,background:d.menuBg,border:`1px solid ${d.menuBorder}`,borderRadius:v.control,boxShadow:C.menu,zIndex:B,width:`${o}px`,maxHeight:"320px",overflowY:"auto",padding:"4px 0",pointerEvents:"auto"}),e.setAttribute("role","dialog"),e.setAttribute("aria-label","Data warnings");const p=n.createElement("div");Object.assign(p.style,{padding:`${r.sm} ${r.xl} ${r.xs}`,fontSize:y.body,fontWeight:"700",letterSpacing:O,textTransform:"uppercase",color:d.label}),p.textContent=s.length===1?"1 warning":`${s.length} warnings`,e.appendChild(p);for(const l of s){const g=severityOf(l),c=$(g),f=n.createElement("div");Object.assign(f.style,{display:"flex",gap:r.md,padding:`${r.md} ${r.xl}`,fontSize:y.body,lineHeight:"1.5",color:d.text,borderTop:`1px solid ${d.menuBorder}`,alignItems:"flex-start"});const u=n.createElement("span");Object.assign(u.style,{color:c.text,flexShrink:"0"}),u.textContent=S(g),f.appendChild(u);const x=n.createElement("div"),b=n.createElement("div");Object.assign(b.style,{fontWeight:"700",color:c.text,marginBottom:r.xxs}),b.textContent=l.code,x.appendChild(b);const h=n.createElement("div");h.textContent=l.message,x.appendChild(h),f.appendChild(x),e.appendChild(f)}return e}
|
|
@@ -51,7 +51,7 @@ export interface Die {
|
|
|
51
51
|
/** STDF site_num — which parallel test site tested this die. Only meaningful when > 1 distinct value appears per wafer. */
|
|
52
52
|
siteNum?: number;
|
|
53
53
|
/** STDF pir.part_id — tester-assigned identifier for this tested unit. Encodes probe sequence at most fabs. */
|
|
54
|
-
partId?: number;
|
|
54
|
+
partId?: number | string;
|
|
55
55
|
}
|
|
56
56
|
export interface DieEligibilityOptions {
|
|
57
57
|
/** Include dies that straddle the wafer boundary (`die.partial`). Default: excluded. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{diePassStatus as w}from"../core/dies.js";import{INPUT_DEFAULT_PASS_BINS as S,itemPassBins as x}from"../core/passBins.js";import{getBinColorScheme as y}from"./colorSchemes.js";import{arrayEqual as u,mapEqual as p,setEqual as b}from"../core/utils.js";export function resolveBinColors(s,n={}){return resolveBinColorsByWafer([{dies:s,passBins:n.passBins}],n).colors}export function binColorsForMaps(s,n={}){return resolveBinColorsForMaps(Array.isArray(s)?s:[s],n).colors}export function resolveBinColorsForMaps(s,n={}){return resolveBinColorsByWafer(s.map(e=>({dies:e.dies,passBins:x(e)})),{...n,hbinDefs:mergeBinDefs(s.map(e=>e.hbinDefs)),sbinDefs:mergeBinDefs(s.map(e=>e.sbinDefs))})}export function mergeBinDefs(s){const n=new Set,e=[];for(const a of s)for(const r of a??[])n.has(r.bin)||(n.add(r.bin),e.push(r));return e}export function resolveBinColorsByWafer(s,n={}){const e=y(n.binColorScheme),a=n.useDefinedBinColors??!0,r=d(s),f=B(r.hardBins,i=>r.pass.hard.has(i),e,a?n.hbinDefs:void 0,!1),t=B(r.softBins,i=>r.pass.soft.has(i),e,a?n.sbinDefs:void 0,!0);return{colors:{hard:f.colors,soft:t.colors,shared:{hard:f.shared,soft:t.shared},pass:r.pass},mixedHardBins:r.mixedHardBins}}export function binPassSets(s,n){return d([{dies:s,passBins:n}]).pass}export function binPassSetsByWafer(s){return d(s).pass}function d(s){const n=new Map,e=new Map,a=new Set;for(const t of s){const i=new Set(t.passBins??S);for(const l of t.dies){const o=w(l,i)===!0;l.hbin!=null&&(n.set(l.hbin,(n.get(l.hbin)??!0)&&o),o&&a.add(l.hbin)),l.sbin!=null&&e.set(l.sbin,(e.get(l.sbin)??!0)&&o)}}const r=t=>new Set([...t].filter(([,i])=>i).map(([i])=>i)),f={hard:r(n),soft:r(e)};return{hardBins:[...n.keys()],softBins:[...e.keys()],pass:f,mixedHardBins:[...a].filter(t=>!f.hard.has(t)).sort((t,i)=>t-i)}}function g(s){return s.length>8?`${s.slice(0,8).join(", ")} and ${s.length-8} more`:s.join(", ")}export function mixedPassBinsWarning(s){if(!s.length)return null;const n=s.length===1;return{code:"pass-bins-mixed",severity:"warning",message:`Hard ${n?"bin":"bins"} ${g(s)} ${n?"passes":"pass"} on some wafers and ${n?"fails":"fail"} on others: these wafers were built with different pass bins. Each wafer's own yield and die verdicts are correct, but a bin has one colour and one legend row, so ${n?"it is":"they are"} shown as failing there.`}}export function binColorsCover(s,n){if(!s.pass)return!1;for(const e of n)if(e.hbin!=null&&!s.hard.has(e.hbin)||e.sbin!=null&&!s.soft.has(e.sbin))return!1;return!0}export function binColorWarning(s,n){const e=n==="softBin"?"soft":n==="hardBin"?"hard":null;if(!e)return null;const a=s.shared[e];if(!a.length)return null;const r=g(a);return{code:"bin-colors-shared",severity:"warning",message:`${e==="hard"?"Hard":"Soft"} bins ${r} are drawn in a colour another bin also has, so colour alone cannot tell them apart. There are more bins than the colour scheme has distinct colours (or a bin definition repeats one). Use the legend, the tooltip, or highlight one bin at a time.`}}const C=1,P=2;function m(s,n,e,a){const r=s.length,f=a?Math.floor(r/2):0;return s[((n-e+f)%r+r)%r]}function B(s,n,e,a,r){const f=new Map;for(const o of a??[])o.color&&f.set(o.bin,o.color);const t=new Map;for(const o of s)t.set(o,f.get(o)??(n(o)?m(e.pass,o,C,r):m(e.fail,o,P,r)));const i=new Map;for(const[o,h]of t){const c=h.toLowerCase();i.set(c,[...i.get(c)??[],o])}const l=[...i.values()].filter(o=>o.length>1).flat().sort((o,h)=>o-h);return{colors:t,shared:l}}export function binColorsEqual(s,n){return s===n?!0:!s||!n?!1:p(s.hard,n.hard)&&p(s.soft,n.soft)&&u(s.shared.hard,n.shared.hard)&&u(s.shared.soft,n.shared.soft)&&b(s.pass.hard,n.pass.hard)&&b(s.pass.soft,n.pass.soft)}
|
|
1
|
+
import{diePassStatus as w}from"../core/dies.js";import{INPUT_DEFAULT_PASS_BINS as S,itemPassBins as x}from"../core/passBins.js";import{getBinColorScheme as y}from"./colorSchemes.js";import{arrayEqual as u,mapEqual as p,setEqual as b}from"../core/utils.js";export function resolveBinColors(s,n={}){return resolveBinColorsByWafer([{dies:s,passBins:n.passBins}],n).colors}export function binColorsForMaps(s,n={}){return resolveBinColorsForMaps(Array.isArray(s)?s:[s],n).colors}export function resolveBinColorsForMaps(s,n={}){return resolveBinColorsByWafer(s.map(e=>({dies:e.dies,passBins:x(e)})),{...n,hbinDefs:mergeBinDefs(s.map(e=>e.hbinDefs)),sbinDefs:mergeBinDefs(s.map(e=>e.sbinDefs))})}export function mergeBinDefs(s){const n=new Set,e=[];for(const a of s)for(const r of a??[])n.has(r.bin)||(n.add(r.bin),e.push(r));return e}export function resolveBinColorsByWafer(s,n={}){const e=y(n.binColorScheme),a=n.useDefinedBinColors??!0,r=d(s),f=B(r.hardBins,i=>r.pass.hard.has(i),e,a?n.hbinDefs:void 0,!1),t=B(r.softBins,i=>r.pass.soft.has(i),e,a?n.sbinDefs:void 0,!0);return{colors:{hard:f.colors,soft:t.colors,shared:{hard:f.shared,soft:t.shared},pass:r.pass},mixedHardBins:r.mixedHardBins}}export function binPassSets(s,n){return d([{dies:s,passBins:n}]).pass}export function binPassSetsByWafer(s){return d(s).pass}function d(s){const n=new Map,e=new Map,a=new Set;for(const t of s){const i=new Set(t.passBins??S);for(const l of t.dies){const o=w(l,i)===!0;l.hbin!=null&&(n.set(l.hbin,(n.get(l.hbin)??!0)&&o),o&&a.add(l.hbin)),l.sbin!=null&&e.set(l.sbin,(e.get(l.sbin)??!0)&&o)}}const r=t=>new Set([...t].filter(([,i])=>i).map(([i])=>i)),f={hard:r(n),soft:r(e)};return{hardBins:[...n.keys()],softBins:[...e.keys()],pass:f,mixedHardBins:[...a].filter(t=>!f.hard.has(t)).sort((t,i)=>t-i)}}function g(s){return s.length>8?`${s.slice(0,8).join(", ")} and ${s.length-8} more`:s.join(", ")}export function mixedPassBinsWarning(s){if(!s.length)return null;const n=s.length===1;return{code:"pass-bins-mixed",severity:"warning",message:`Hard ${n?"bin":"bins"} ${g(s)} ${n?"passes":"pass"} on some wafers and ${n?"fails":"fail"} on others: these wafers were built with different pass bins. Each wafer's own yield and die verdicts are correct, but a bin has one colour and one legend row, so ${n?"it is":"they are"} shown as failing there.`}}export function binColorsCover(s,n){if(!s.pass)return!1;for(const e of n)if(e.hbin!=null&&!s.hard.has(e.hbin)||e.sbin!=null&&!s.soft.has(e.sbin))return!1;return!0}export function binColorWarning(s,n){const e=n==="softBin"?"soft":n==="hardBin"?"hard":null;if(!e)return null;const a=s.shared[e];if(!a.length)return null;const r=g(a);return{code:"bin-colors-shared",severity:"warning",message:`${e==="hard"?"Hard":"Soft"} bins ${r} are drawn in a colour another bin also has, so colour alone cannot tell them apart. There are more bins than the colour scheme has distinct colours (or a bin definition repeats one). Use the legend, the tooltip, or highlight one bin at a time.`}}const C=1,P=2;function m(s,n,e,a){const r=s.length,f=a?Math.floor(r/2):0;return s[((Math.floor(n)-e+f)%r+r)%r]}function B(s,n,e,a,r){const f=new Map;for(const o of a??[])o.color&&f.set(o.bin,o.color);const t=new Map;for(const o of s)t.set(o,f.get(o)??(n(o)?m(e.pass,o,C,r):m(e.fail,o,P,r)));const i=new Map;for(const[o,h]of t){const c=h.toLowerCase();i.set(c,[...i.get(c)??[],o])}const l=[...i.values()].filter(o=>o.length>1).flat().sort((o,h)=>o-h);return{colors:t,shared:l}}export function binColorsEqual(s,n){return s===n?!0:!s||!n?!1:p(s.hard,n.hard)&&p(s.soft,n.soft)&&u(s.shared.hard,n.shared.hard)&&u(s.shared.soft,n.shared.soft)&&b(s.pass.hard,n.pass.hard)&&b(s.pass.soft,n.pass.soft)}
|
|
@@ -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>(out of spec)</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(" \xB7 "))}}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"?"Spec 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&>[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 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(" \xB7 "))}}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&>[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";
|
|
@@ -57,12 +57,19 @@ export interface DieResult {
|
|
|
57
57
|
*/
|
|
58
58
|
siteNum?: number;
|
|
59
59
|
/**
|
|
60
|
-
* STDF `
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
*
|
|
60
|
+
* STDF PRR `PART_ID` — the tester's part identification, text in STDF (a
|
|
61
|
+
* number is accepted too). Data about the part, not an identifier: a die's
|
|
62
|
+
* identity is its position. Its meaning is fab-specific; at many fabs it
|
|
63
|
+
* encodes the probe sequence.
|
|
64
64
|
*/
|
|
65
|
-
partId?: number;
|
|
65
|
+
partId?: number | string;
|
|
66
|
+
/**
|
|
67
|
+
* The tester marked this record as replacing an earlier one (STDF PRR
|
|
68
|
+
* `PART_FLG` bit 0/1): `'partId'` — the earlier record with the same part ID;
|
|
69
|
+
* `'position'` — the earlier record at the same position. Such a record always
|
|
70
|
+
* wins, whatever `retestPolicy` says.
|
|
71
|
+
*/
|
|
72
|
+
supersedes?: 'partId' | 'position';
|
|
66
73
|
/** Per-die metadata — all fields appear automatically in hover tooltips. See `DieMetadata → §12.4`. */
|
|
67
74
|
metadata?: DieMetadata;
|
|
68
75
|
}
|
|
@@ -214,16 +221,28 @@ export interface TestDef {
|
|
|
214
221
|
*/
|
|
215
222
|
logScale?: boolean;
|
|
216
223
|
/**
|
|
217
|
-
* Lower
|
|
218
|
-
*
|
|
219
|
-
*
|
|
224
|
+
* Lower **test limit** (STDF `LO_LIMIT`) — the pass/fail limit, in the test
|
|
225
|
+
* value's units. A value below it is outside the test limits. Both limits are
|
|
226
|
+
* optional independently — some tests have one-sided limits.
|
|
220
227
|
*/
|
|
221
228
|
limitLow?: number;
|
|
229
|
+
/** Upper test limit (STDF `HI_LIMIT`). A value above it is outside the test limits. */
|
|
230
|
+
limitHigh?: number;
|
|
222
231
|
/**
|
|
223
|
-
*
|
|
224
|
-
*
|
|
232
|
+
* Whether a value exactly equal to `limitLow` passes. Default `true`. STDF
|
|
233
|
+
* states it per test in `PARM_FLG` bit 6, ATDF in the Limit Compare field.
|
|
225
234
|
*/
|
|
226
|
-
|
|
235
|
+
limitLowInclusive?: boolean;
|
|
236
|
+
/** Whether a value exactly equal to `limitHigh` passes. Default `true`; STDF `PARM_FLG` bit 7. */
|
|
237
|
+
limitHighInclusive?: boolean;
|
|
238
|
+
/**
|
|
239
|
+
* Lower **specification limit** (STDF `LO_SPEC`) — separate from the test limits,
|
|
240
|
+
* and what process capability (Cp/Cpk/Pp/Ppk) is judged against when both spec
|
|
241
|
+
* limits are given. Without them, capability uses the test limits and says so.
|
|
242
|
+
*/
|
|
243
|
+
specLow?: number;
|
|
244
|
+
/** Upper specification limit (STDF `HI_SPEC`). See `specLow`. */
|
|
245
|
+
specHigh?: number;
|
|
227
246
|
/**
|
|
228
247
|
* Test kind: `'P'` (parametric — a continuous measured value) or `'F'`
|
|
229
248
|
* (functional — a pass/fail outcome, conventionally recorded as 1 = pass,
|
|
@@ -567,6 +586,15 @@ export interface WaferWarning {
|
|
|
567
586
|
* were given as text (or verdicts as something other than true/false), as a
|
|
568
587
|
* CSV parser produces. They are not converted, so those dies are judged and
|
|
569
588
|
* plotted wrongly — a bin of "1" is not pass bin 1. The message counts them.
|
|
589
|
+
* - `'input-values-outside-stdf'` — raised by `buildWaferMap`: bins, coordinates,
|
|
590
|
+
* 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.
|
|
594
|
+
* - `'retests-by-part-id'` — raised by `buildWaferMap` (severity `'info'`): dies
|
|
595
|
+
* with no position that share a part ID were treated as retests of one die.
|
|
596
|
+
* Blank part IDs never match, and part IDs are not used on a wafer where one
|
|
597
|
+
* value covers more than 20% of the unpositioned records (a default, not an ID).
|
|
570
598
|
* - `'derived-test-invalid'` — raised by `buildWaferMap`: a `derivedTests` entry
|
|
571
599
|
* could not be compiled — a parse or type error in its `expression`, a
|
|
572
600
|
* `testNumber` that collides with a measured test, a `testType` that
|
|
@@ -586,7 +614,7 @@ export interface WaferWarning {
|
|
|
586
614
|
* The union is intentionally open to string so future advisory codes can be
|
|
587
615
|
* added without a breaking change; switch with a `default` branch.
|
|
588
616
|
*/
|
|
589
|
-
code: 'partial-coverage' | 'geometry-conflict' | 'non-standard-diameter' | 'diameter-exceeds-die-extent' | 'test-count-capped' | 'edge-exclusion-exceeds-radius' | 'analysis-option-corrected' | 'bin-colors-shared' | 'pass-bins-mixed' | 'ring-count-mixed' | 'input-field-removed' | 'input-values-not-numbers' | 'derived-test-invalid' | (string & {});
|
|
617
|
+
code: 'partial-coverage' | 'geometry-conflict' | 'non-standard-diameter' | 'diameter-exceeds-die-extent' | 'test-count-capped' | 'edge-exclusion-exceeds-radius' | 'analysis-option-corrected' | 'bin-colors-shared' | 'pass-bins-mixed' | 'ring-count-mixed' | 'input-field-removed' | 'input-values-not-numbers' | 'input-values-outside-stdf' | 'retests-by-part-id' | 'derived-test-invalid' | (string & {});
|
|
590
618
|
/** Human-readable explanation, suitable for direct display. */
|
|
591
619
|
message: string;
|
|
592
620
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{createWafer as j}from"../core/wafer.js";import{isYieldEligibleDie as ge,getDieKey as V,hasPosition as R}from"../core/dies.js";import{applyOrientation as U,transformDies as pe,clipDiesToWafer as ye}from"../core/transforms.js";import{generateDies as be}from"../core/dies.js";import{affineRotation as we,affineMirror as xe,affineCompose as De,affinePoint as Me}from"../core/transforms.js";import{inferWaferFromXY as ve}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 Se}from"../core/reticle.js";import{applyDerivedTests as J}from"./derivedTests/apply.js";import{buildView as Q}from"./buildView.js";import{maxOf as K,minOf as Z,modeOf as ke}from"../core/utils.js";import{aggregateValues as ee,aggregateBinCounts as Ae}from"../core/aggregates.js";export function isParametricTest(t){return t?.testType!=="F"}function Re(t){const n=(c,i)=>({ringCount:c,ringCountWarning:{code:"analysis-option-corrected",severity:"warning",message:`ringCount=${String(t)} ${i} (using ${c}).`}});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 te(t){const n=Array.isArray(t)?{}:t,e=Array.isArray(n.lotStack?.results)?n.lotStack.results:[],c=(Array.isArray(t)?[t]:[n.results,...e]).filter(a=>Array.isArray(a)),i={hbin:0,sbin:0,testValues:0,testPass:0};let o;const u=(a,s)=>{i[a]++,o??=`${a} ${JSON.stringify(s)}`};for(const a of c){const s=new Set,m=new Set;for(let p=0;p<a.length;p+=p<50?1:100){const h=a[p];if(!(h===null||typeof h!="object")){if(h.testValues&&typeof h.testValues=="object")for(const x of Object.keys(h.testValues))s.add(x);if(h.testPass&&typeof h.testPass=="object")for(const x of Object.keys(h.testPass))m.add(x)}}const f=[...s],l=[...m];for(const p of a){if(p===null||typeof p!="object")continue;const h=p;h.hbin!=null&&typeof h.hbin!="number"&&u("hbin",h.hbin),h.sbin!=null&&typeof h.sbin!="number"&&u("sbin",h.sbin);const x=h.testValues;if(x&&typeof x=="object")for(let D=0;D<f.length;D++){const b=x[f[D]];b!=null&&typeof b!="number"&&u("testValues",b)}const C=h.testPass;if(C&&typeof C=="object")for(let D=0;D<l.length;D++){const b=C[l[D]];b!=null&&typeof b!="boolean"&&u("testPass",b)}}}const r=[];if(i.hbin&&r.push(`${i.hbin} hard bin${i.hbin===1?"":"s"}`),i.sbin&&r.push(`${i.sbin} soft bin${i.sbin===1?"":"s"}`),i.testValues&&r.push(`${i.testValues} test value${i.testValues===1?"":"s"}`),i.testPass&&r.push(`${i.testPass} pass/fail verdict${i.testPass===1?"":"s"}`),!r.length)return;const g=`buildWaferMap received ${r.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.`;return console.warn(`[wafermap] ${g}`),{code:"input-values-not-numbers",message:g,severity:"error"}}function ne(t){const n=(a,s)=>a!==null&&typeof a=="object"&&a[s]!==void 0,e=Array.isArray(t)?{}:t,c=[],i=(a,s,m)=>{a&&c.push(`\`${s}\` (now \`${m}\`)`)};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(a=>n(a,"index")&&!n(a,"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(a=>Array.isArray(a));if(i(u.some(a=>a.slice(0,100).some(s=>n(s,"values"))),"values","testValues"),!c.length)return;const r=c.length>1,g=`buildWaferMap ignored ${c.join(", ")}: ${r?"these names were":"that name was"} removed in an earlier release, so what ${r?"they":"it"} described is not on this map. Rename and rebuild.`;return console.warn(`[wafermap] ${g}`),{code:"input-field-removed",message:g,severity:"error"}}function We(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,c=t.dieConfig?.height;if(!(n>0&&e>0&&c>0))throw new Error("buildWaferMap: `layout` needs waferConfig.diameter and dieConfig.width and height, all positive.");const i=j({diameter:n,notch:t.waferConfig?.notch,orientation:0}),o=ye(be(i,{width:e,height:c}),i,{width:e,height:c}).filter(s=>!s.partial),{flipX:u,flipY:r}=se(t.dieConfig,{type:"center"}),{layout:g,...a}=t;return{...a,results:o.map(s=>({x:u?-s.x:s.x,y:r?-s.y:s.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.");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:ne(t),nonNumericWarning:te(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],...Re(t.ringCount),removedFieldWarning:ne(t),nonNumericWarning:te(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 Ee(t,n){return n?.coordinateOrigin?n.coordinateOrigin:{type:"center"}}function Fe(t,n,e){if(n.type==="custom"&&n.offset)return{offsetX:n.offset.x,offsetY:n.offset.y};if(n.type!=="center"){let c=1/0,i=-1/0,o=1/0,u=-1/0;for(const r of t)r.x<c&&(c=r.x),r.x>i&&(i=r.x),r.y<o&&(o=r.y),r.y>u&&(u=r.y);return{offsetX:Math.round((i+c)/2),offsetY:Math.round((u+o)/2)}}return{offsetX:e.offsetX,offsetY:e.offsetY}}function Pe(t,n,e,c,i,o){if(o)return{colMidX:(Math.round(o.x)-n)*c,colMidY:(Math.round(o.y)-e)*i,anchored:!0};if(t.length===0)return{colMidX:0,colMidY:0,anchored:!1};let u=1/0,r=-1/0,g=1/0,a=-1/0;for(const s of t){const m=Math.round(s.x)-n,f=Math.round(s.y)-e;m<u&&(u=m),m>r&&(r=m),f<g&&(g=f),f>a&&(a=f)}return{colMidX:(u+r)/2*c,colMidY:(g+a)/2*i,anchored:!1}}function Te(t,n,e){let c=1/0,i=-1/0,o=1/0,u=-1/0,r=0,g=0;for(const m of t){const f=Math.round(m.x)-n,l=Math.round(m.y)-e;r+=f,g+=l,f<c&&(c=f),f>i&&(i=f),l<o&&(o=l),l>u&&(u=l)}const a=t.length,s=(m,f,l)=>{const p=(l-f)/2;return p<=0?0:Math.abs(m/a-(f+l)/2)/p};return s(r,c,i)>.11||s(g,o,u)>.11}function se(t,n){let e=t?.xAxisDirection==="left",c=t?.yAxisDirection==="down";return(n.type==="UL"||n.type==="UR")&&(c=!0),(n.type==="LR"||n.type==="UR")&&(e=!0),{flipX:e,flipY:c}}function Ie(t,n){const{method:e,targetBin:c}=t,i=t.results.map(o=>o.filter(R));if(e==="mean"||e==="median"||e==="stddev"||e==="min"||e==="max"||e==="count"){const o=new Set((n??[]).filter(s=>!isParametricTest(s)).map(s=>s.testNumber)),u=new Set;for(const s of i)for(const m of s)if(m.testValues)for(const f of Object.keys(m.testValues)){const l=Number(f);o.has(l)||u.add(l)}if(u.size===0)return ee(i,e);const r=[...u],g=r.map(s=>ee(i,e,s)),a=new Map;for(let s=0;s<r.length;s++){const m=r[s];for(const f of g[s]){const l=V(f);a.has(l)||a.set(l,{template:f,testValues:{}});const p=a.get(l),h=f.testValues?.[0];h!==void 0&&(p.testValues[m]=h)}}return[...a.values()].map(({template:s,testValues:m})=>({...s,testValues:Object.keys(m).length>0?m:void 0}))}if(e==="countBin"||e==="percent"){if(c===void 0)return[];const o=Ae(i,c,"hard");if(e==="countBin")return o;const u=i.length;return o.map(r=>({...r,testValues:{0:u>0?(r.testValues?.[0]??0)/u*100:0}}))}if(e==="mode"){const o=new Map;for(const r of i)for(const g of r){const a=V(g);o.has(a)||o.set(a,[]),o.get(a).push(g)}const u=[];for(const[r,g]of o){const a=r.split(","),s=Number(a[0]),m=Number(a[1]),f=g.map(p=>p.hbin).filter(p=>p!==void 0),l=ke(f);l!==null&&u.push({x:s,y:m,hbin:l})}return u}return[]}function ie(t){const n=t.filter(R),e=n.length,c=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:c,unpositionedDies:t.length-n.length,ratio:e>0?i/e:0}}function re(t,n,e="exclude"){const c=new Set(n),o=t.filter(l=>!l.partial).filter(l=>l.edgeExcluded).length,u=t.filter(l=>l.partial).length;let r=0,g=0,a=!1;for(const l of t){if(!ge(l))continue;const p=l.hbin??l.sbin;p!==void 0&&(a=!0,c.has(p)?r++:g++)}const s=r+g,m=a&&s>0?r/s*100:null;let f=null;if(e==="denominator-only"){const l=s+o;f=a&&l>0?r/l*100:null}return{passDies:r,failDies:g,edgeExcludedDies:o,partialDies:u,totalDies:s,yieldPercent:m,yieldPercentGross:f}}function oe(t,n,e,c,i,o=0,u=0,r=0,g=0,a=0,s=!1,m=!1){if(!t)return[];const f=t.anchorDie??{x:0,y:0},l=Se(n,{width:t.width,height:t.height,diePitchX:c,diePitchY:i,anchorDie:{x:f.x-o,y:f.y-u},gridOrigin:{x:-r,y:-g}}),p=De(xe(s,m,n.center.x,n.center.y),we(a,n.center.x,n.center.y));return l.filter(h=>{const x=h.width/2,C=h.height/2,D=[[h.x-x,h.y-C],[h.x+x,h.y-C],[h.x+x,h.y+C],[h.x-x,h.y+C]].map(([w,P])=>Me(p,w,P)),b=Z(D.map(w=>w.x)),S=K(D.map(w=>w.x)),W=Z(D.map(w=>w.y)),$=K(D.map(w=>w.y));return e.some(w=>w.physX>=b&&w.physX<S&&w.physY>=W&&w.physY<$)})}function Ne(t,n,e){const i=Math.max(0,n.radius-e)**2;return t.map(o=>{const u=o.physX-n.center.x,r=o.physY-n.center.y;return u*u+r*r>i?{...o,edgeExcluded:!0}:o})}function Ve(t,n,e){const c=t.filter(R),i=t.filter(s=>!R(s)),o=new Map;for(const s of c){let m=o.get(s.x);m||(m=new Map,o.set(s.x,m)),m.set(s.y,(m.get(s.y)??0)+1)}const u=new Set(e);function r(s,m){const f=s.hbin,l=m.hbin;if(f===void 0||l===void 0)return!1;const p=u.has(f),h=u.has(l);return n==="best"?h!==p?h:l<f:h!==p?p:l>f}const g=new Map;for(const s of c){const m=V(s),f=g.get(m);n==="first"&&f||(n==="best"||n==="worst")&&f&&!r(f,s)||g.set(m,s)}return[...Array.from(g.values()).map(s=>{const f=o.get(s.x)?.get(s.y)??1;return f>1?{...s,retestCount:f}:s}),...i]}export function getDieTestValue(t,n){return t.testValues?.[n]}export function getTestPassStatus(t,n,e){const c=t.testPass?.[n];if(c!==void 0)return c;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 _(t,n){const e={};return n.hbin!==void 0&&(e.hbin=n.hbin),n.sbin!==void 0&&(e.sbin=n.sbin),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 ae(t,n){return n.plotMode?n.plotMode:t.some(dieHasTestData)?"value":"hardBin"}function de(t,n){return t.map(({code:e,message:c})=>({code:e,message:c,severity:e==="non-standard-diameter"||e==="diameter-exceeds-die-extent"?"warning":"error",confidence:n.wafer.confidence}))}const Be="do not fit inside the supplied",_e="Inferred wafer diameter",Ye="The probed dies reach only",Le=.75,Xe=20;export const STANDARD_WAFER_DIAMETERS_MM=[100,125,150,200,300];const je=.02;function fe(t,n){return n.reduce((e,c)=>Math.abs(c-t)<Math.abs(e-t)?c:e)}function Ke(t,n){if(!n.length)return!0;const e=fe(t,n);return Math.abs(e-t)/e<=je}function ze(t){return t===void 0?STANDARD_WAFER_DIAMETERS_MM:t.filter(n=>Number.isFinite(n)&&n>0)}export function buildWaferMap(t,n){const e=$e(!Array.isArray(t)&&t.layout?We(t):t),{debug:c,...i}=n??{},o=[];if(e.derivedTests){const d=new Set(e.passBins);if(e.lotStackOpts){const y=e.lotStackOpts.results.map(v=>J(v,e.testDefs,e.derivedTests,d));e.lotStackOpts={...e.lotStackOpts,results:y.map(v=>v.results)},e.testDefs=y[0]?.testDefs??e.testDefs,o.push(...y[0]?.warnings??[])}else{const y=J(e.results,e.testDefs,e.derivedTests,d);e.results=y.results,e.testDefs=y.testDefs,o.push(...y.warnings)}}const u=e.lotStackOpts?Ie(e.lotStackOpts,e.testDefs):e.results;for(let d=0;d<Math.min(5,u.length);d++){const y=u[d];if(typeof y.x=="string"||typeof y.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 r=Ve(u,e.retestPolicy,e.passBins),g={wafer:{confidence:1,method:"provided"},diePitch:{confidence:1,units:"mm"},grid:{confidence:1}},a=[];if(e.explicitDies){let d=e.explicitDies;const y=r.filter(R),v=r.filter(N=>!R(N));if(y.length>0){const N=new Map(y.map(A=>[V(A),A]));d=d.map(A=>{const H=N.get(V(A));return H?_(A,H):A})}const I=e.waferOpts?.diameter??300,F=j({diameter:I,notch:e.waferOpts?.notch,orientation:e.waferOpts?.orientation??0,metadata:e.waferOpts?.metadata});F.orientation!==0&&(d=U(d,F));const q=oe(e.reticleOpts,F,d,1,1,0,0,0,0,F.orientation),he=i.showReticle??e.reticleOpts!==void 0,L=Q(F,d,{...i,reticles:q,showReticle:he,plotMode:ae(r,i),testDefs:e.testDefs,passBins:e.passBins,ringCount:e.ringCount,isLotStack:!1},{hbinDefs:e.hbinDefs,sbinDefs:e.sbinDefs,metadataFields:e.metadataFields}),me=v.map((N,A)=>_({id:`unpositioned_${A}`,width:d[0]?.width??1,height:d[0]?.height??1},N)),X=[...d,...me];return{wafer:F,dies:X,view:L,reticleConfig:e.reticleOpts,units:"mm",inference:g,warnings:[...e.removedFieldWarning?[e.removedFieldWarning]:[],...e.nonNumericWarning?[e.nonNumericWarning]:[],...de(a,g),...o,...e.ringCountWarning?[e.ringCountWarning]:[]],plotMode:L.plotMode,metadata:L.metadata,isLotStack:!1,dataCoverage:ie(X),passBins:e.passBins,ringCount:e.ringCount,yield:re(X,e.passBins,e.edgeDieYieldMode),reticles:q,hbinDefs:e.hbinDefs,sbinDefs:e.sbinDefs,testDefs:e.testDefs,metadataFields:e.metadataFields}}const s=r.filter(R),m=r.filter(d=>!R(d)),f=s.map(d=>({x:d.x,y:d.y})),l=Ce(f,e.dieOpts,e.waferOpts?.diameter);g.diePitch={confidence:l.confidence,units:l.units};const{pitchX:p,pitchY:h}=l,x=l.units,C=Ee(s,e.dieOpts),D=Oe(f);g.grid={confidence:D.confidence};const{offsetX:b,offsetY:S}=Fe(f,C,D),{flipX:W,flipY:$}=se(e.dieOpts,C),{colMidX:w,colMidY:P,anchored:ce}=Pe(f,b,S,p,h,e.waferOpts?.center);let M=e.waferOpts?.diameter;const B=f.map(d=>({x:(Math.round(d.x)-b)*p-w,y:(Math.round(d.y)-S)*h-P})),E=K(B.map(({x:d,y})=>Math.hypot(Math.abs(d)+p/2,Math.abs(y)+h/2)));if(M===void 0)if(f.length>0)if(x==="mm"){const d=ve(B,{minRadius:E});M=d.diameter,g.wafer={confidence:d.confidence,method:d.method};const y=ze(e.standardDiameters);if(!Ke(M,y)){const v=fe(M,y);a.push({code:"non-standard-diameter",message:`${_e} ${M.toFixed(1)} mm is not a standard wafer size (nearest is ${v} 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=E*2,g.wafer={confidence:l.confidence*.8,method:"extent"};else M=x==="mm"?300:30,g.wafer={confidence:0,method:"default"};if(!ce&&f.length>0&&Te(f,b,S)&&(g.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&&E>0&&f.length>=Xe){const d=E/(M/2);d<Le&&a.push({code:"diameter-exceeds-die-extent",message:`${Ye} ${(d*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 d=e.dieOpts?.width!==void 0&&e.dieOpts?.height!==void 0;if(d&&E>M/2+1e-9){const y=B.filter(({x:v,y:I})=>Math.hypot(Math.abs(v)+p/2,Math.abs(I)+h/2)>M/2+1e-9).length;a.push({code:"geometry-conflict",message:`${y} of ${B.length} probed die positions ${Be} ${M} mm wafer at the supplied die pitch of ${p} \xD7 ${h} 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 ${(E*2).toFixed(1)} mm. Check waferConfig.diameter and dieConfig.width/height against the real device.`})}}const k=j({diameter:M,notch:e.waferOpts?.notch,orientation:e.waferOpts?.orientation??0,metadata:e.waferOpts?.metadata});let O=s.map(d=>{const y=Math.round(d.x)-b,v=Math.round(d.y)-S,I={id:`${y}_${v}`,x:y,y:v,physX:y*p-w,physY:v*h-P,width:p,height:h,insideWafer:!0,partial:!1};return _(I,d)});(b!==0||S!==0)&&(O=O.map(d=>({...d,x:d.x+b,y:d.y+S,id:`${d.x+b}_${d.y+S}`}))),O=U(O,k),(W||$)&&(O=pe(O,{flipX:W,flipY:$},k.center));const z=[];e.waferOpts?.edgeExclusion&&e.waferOpts.edgeExclusion>0&&(O=Ne(O,k,e.waferOpts.edgeExclusion),e.waferOpts.edgeExclusion>k.radius&&z.push({code:"edge-exclusion-exceeds-radius",message:`edgeExclusion (${e.waferOpts.edgeExclusion}mm) exceeds the resolved wafer radius (${k.radius}mm) \u2014 the entire wafer is excluded.`,severity:"warning"}));const G=oe(e.reticleOpts,k,O,p,h,b,S,w,P,k.orientation,W,$),le=i.showReticle??e.reticleOpts!==void 0,T=Q(k,O,{...i,reticles:G,showReticle:le,plotMode:ae(r,i),testDefs:e.testDefs,passBins:e.passBins,ringCount:e.ringCount,dataAxisFlip:{x:W,y:$},isLotStack:e.lotStackOpts!==void 0,aggregationMethod:e.lotStackOpts?.method,lotSize:e.lotStackOpts?.results.length},{hbinDefs:e.hbinDefs,sbinDefs:e.sbinDefs,metadataFields:e.metadataFields}),ue=m.map((d,y)=>_({id:`unpositioned_${y}`,width:p,height:h},d)),Y=[...O,...ue];return{wafer:k,dies:Y,view:T,reticleConfig:e.reticleOpts,units:x,inference:g,warnings:[...e.removedFieldWarning?[e.removedFieldWarning]:[],...e.nonNumericWarning?[e.nonNumericWarning]:[],...de(a,g),...z,...o,...e.ringCountWarning?[e.ringCountWarning]:[]],plotMode:T.plotMode,metadata:T.metadata,isLotStack:e.lotStackOpts!==void 0,dataCoverage:ie(Y),passBins:e.passBins,ringCount:e.ringCount,yield:re(Y,e.passBins,e.edgeDieYieldMode),reticles:G,hbinDefs:e.hbinDefs,sbinDefs:e.sbinDefs,testDefs:e.testDefs,metadataFields:e.metadataFields,aggrMethod:T.aggrMethod,lotSize:T.lotSize}}
|
|
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}}
|