@wafertools/wafermap 0.30.3 → 0.31.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +9 -19
- package/CHANGELOG.md +140 -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 +89 -6
- 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 +16 -2
- 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/dieList.d.ts +1 -1
- package/dist/packages/canvas-adapter/exportName.d.ts +1 -12
- package/dist/packages/canvas-adapter/exportName.js +1 -1
- package/dist/packages/canvas-adapter/index.d.ts +2 -3
- package/dist/packages/canvas-adapter/index.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.d.ts +4 -7
- package/dist/packages/canvas-adapter/renderWaferGallery.js +1 -1
- package/dist/packages/canvas-adapter/renderWaferMap.d.ts +15 -30
- 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 +49 -43
- 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/aggregates.d.ts +0 -6
- package/dist/packages/core/aggregates.js +1 -1
- package/dist/packages/core/dies.d.ts +1 -3
- package/dist/packages/core/dies.js +1 -1
- package/dist/packages/core/index.d.ts +8 -10
- package/dist/packages/core/index.js +1 -1
- package/dist/packages/core/stdf.d.ts +9 -0
- package/dist/packages/core/stdf.js +1 -0
- package/dist/packages/core/transforms.d.ts +0 -23
- package/dist/packages/core/transforms.js +1 -1
- package/dist/packages/renderer/binColors.js +1 -1
- package/dist/packages/renderer/buildView.d.ts +1 -9
- package/dist/packages/renderer/buildView.js +1 -1
- package/dist/packages/renderer/buildWaferMap.d.ts +54 -34
- package/dist/packages/renderer/buildWaferMap.js +1 -1
- package/dist/packages/renderer/colorMap.d.ts +0 -2
- package/dist/packages/renderer/colorMap.js +1 -1
- package/dist/packages/renderer/deprecate.d.ts +1 -1
- package/dist/packages/renderer/deprecate.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/index.d.ts +6 -5
- package/dist/packages/renderer/index.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/clusterDetection.d.ts +2 -1
- package/dist/packages/stats/correlation.d.ts +1 -1
- package/dist/packages/stats/facets.d.ts +1 -1
- package/dist/packages/stats/index.d.ts +6 -16
- package/dist/packages/stats/index.js +1 -1
- package/dist/packages/stats/mergeTestDefs.d.ts +1 -1
- package/dist/packages/stats/mergeTestDefs.js +1 -1
- package/dist/packages/stats/patternClassification.d.ts +2 -1
- package/dist/packages/stats/regions.d.ts +2 -1
- package/dist/packages/stats/renderFindingsReport.d.ts +0 -4
- package/dist/packages/stats/renderFindingsReport.js +1 -21
- 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 +2 -2
- package/dist/packages/canvas-adapter/deprecated.d.ts +0 -7
- package/dist/packages/canvas-adapter/deprecated.js +0 -1
- package/dist/packages/core/deprecated.d.ts +0 -60
- package/dist/packages/core/deprecated.js +0 -1
- package/dist/packages/renderer/deprecated.d.ts +0 -45
- package/dist/packages/renderer/deprecated.js +0 -1
- package/dist/packages/stats/deprecated.d.ts +0 -85
- package/dist/packages/stats/deprecated.js +0 -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-25T14:31:48.441Z">v0.31.0</span></h1>
|
|
76
76
|
<p class="wmap-guide-online-link">This is a quick reference. <a href="https://wafertools.github.io/wafermap/user-guide/" target="_blank" rel="noopener">View the full illustrated guide online \u2197</a></p>
|
|
77
77
|
<p>This guide describes the display and analysis features of the wafer map viewer.
|
|
78
78
|
It is written for users who may be semiconductor test engineers, device engineers, and yield engineers
|
|
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
|
|
@@ -112,10 +112,6 @@ the display to match your convention; die coordinates are unaffected.</p>
|
|
|
112
112
|
<td>Active plot value \u2014 bin category or test measurement</td>
|
|
113
113
|
</tr>
|
|
114
114
|
<tr>
|
|
115
|
-
<td>Muted grey at perimeter</td>
|
|
116
|
-
<td>Partial die \u2014 falls outside the wafer circle boundary</td>
|
|
117
|
-
</tr>
|
|
118
|
-
<tr>
|
|
119
115
|
<td>Neutral grey (interior)</td>
|
|
120
116
|
<td>No data \u2014 die has no bin or test result in the loaded dataset</td>
|
|
121
117
|
</tr>
|
|
@@ -124,8 +120,7 @@ the display to match your convention; die coordinates are unaffected.</p>
|
|
|
124
120
|
<td>Edge-excluded die \u2014 falls within the edge exclusion band configured for this wafer</td>
|
|
125
121
|
</tr>
|
|
126
122
|
</tbody></table>
|
|
127
|
-
<p>
|
|
128
|
-
fail; it simply has no result recorded. Edge-excluded dies are shown dimmed and are
|
|
123
|
+
<p>A no-data die is not a fail; it simply has no result recorded. Edge-excluded dies are shown dimmed and are
|
|
129
124
|
not counted in yield calculations.</p>
|
|
130
125
|
<h3 id="14-legend-and-colorbar">1.4 Legend and colorbar</h3>
|
|
131
126
|
<p><strong>Bin modes</strong> show a discrete colour legend: one swatch per bin, with bin number
|
|
@@ -134,20 +129,20 @@ then failing bins from the most dies to the fewest \u2014 the same order as the
|
|
|
134
129
|
panel and reports.</p>
|
|
135
130
|
<p><strong>Value mode</strong> shows a continuous colorbar: the colour scale runs from the minimum
|
|
136
131
|
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
|
|
132
|
+
clicking it has no effect. To switch to a pass/fail view, use the <strong>Limit pass/fail</strong>
|
|
133
|
+
or <strong>Test pass/fail</strong> option in the Overlays menu (available when test limits are
|
|
139
134
|
defined, or a recorded verdict exists, for the active test).</p>
|
|
140
|
-
<p><strong>
|
|
135
|
+
<p><strong>Limit pass/fail mode</strong> replaces the colorbar with a small legend: Pass,
|
|
141
136
|
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
|
-
|
|
137
|
+
limits) with a die count beside each. This judges dies against the test's lower
|
|
138
|
+
and upper test limits.</p>
|
|
144
139
|
<p><strong>Test pass/fail mode</strong> replaces the colorbar with a <strong>Pass / Fail legend</strong> and die
|
|
145
140
|
counts, coloured by the tester's own <strong>recorded</strong> verdict for that test \u2014 not a
|
|
146
|
-
|
|
141
|
+
limit judgement. A test with no measured value (a functional, go/no-go test)
|
|
147
142
|
always displays this way; selecting it switches the map into Test pass/fail
|
|
148
143
|
automatically, since there is nothing to plot on a gradient.</p>
|
|
149
144
|
<p>Every map also shows a short <strong>title</strong> by the colorbar or legend naming what is
|
|
150
|
-
displayed \u2014 the test name (and number, in
|
|
145
|
+
displayed \u2014 the test name (and number, in limit pass/fail mode), the bin type, or the stacked
|
|
151
146
|
wafer count.</p>
|
|
152
147
|
<p>Clicking a bin swatch in the legend filters the display to that bin
|
|
153
148
|
(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 +215,33 @@ application has loaded.</p>
|
|
|
220
215
|
<p>Stacked modes are only available on lot-stack maps (multiple wafers combined into
|
|
221
216
|
one display). The panel identifies how many wafers were stacked and which
|
|
222
217
|
aggregation method is active.</p>
|
|
223
|
-
<h3 id="test-value-mode-colorbar-range-and-
|
|
224
|
-
<p>When
|
|
218
|
+
<h3 id="test-value-mode-colorbar-range-and-test-limits">Test Value mode \u2014 colorbar range and test limits</h3>
|
|
219
|
+
<p>When test limits are defined for the active test, two additional display options
|
|
225
220
|
become available:</p>
|
|
226
221
|
<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
|
|
222
|
+
<li><strong>Colorbar range \u2014 Data / Limits</strong>: switches the colorbar scale between the
|
|
223
|
+
actual data extent and the test limits. This affects only the colorbar's
|
|
224
|
+
range, never how failing dies are shown. In <strong>both</strong> ranges every die is
|
|
225
|
+
coloured by the gradient so you can read the value distribution, and dies
|
|
226
|
+
outside the test limits are marked with a triangle \u2014 pointing <strong>down (\u25BD) for below the low limit</strong>, <strong>up (\u25B3)
|
|
227
|
+
for above the high limit</strong> \u2014 so they stand out without leaving the distribution. The
|
|
233
228
|
triangle is drawn black or white per die for contrast against its own colour, so
|
|
234
229
|
it stays visible under any colour scheme, and its shape (not colour) carries the
|
|
235
230
|
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
|
|
231
|
+
<li><strong>Limit pass/fail</strong>: when active, dies within the test limits are shown in
|
|
232
|
+
a pass colour and dies outside them are highlighted \u2014 <strong>blue for below the
|
|
233
|
+
low limit</strong>, <strong>red for above the high limit</strong>. Both flags apply
|
|
234
|
+
independently; a die can be flagged on either or both limits. A Pass/Fail legend
|
|
240
235
|
replaces the colorbar, listing each applicable category with its die count.</li>
|
|
241
236
|
</ul>
|
|
242
237
|
<p>Some tests have no measured value at all \u2014 a continuity check or any other
|
|
243
238
|
go/no-go test, where the only result is a recorded pass or fail. Selecting one
|
|
244
239
|
of these <strong>functional tests</strong> as the active test switches the map into
|
|
245
240
|
<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
|
|
241
|
+
the tester's own recorded verdict rather than a limit judgement (there is
|
|
247
242
|
no gradient to fall back to). <strong>Test pass/fail</strong> is also available as an
|
|
248
243
|
Overlays option on an ordinary parametric test when it carries recorded
|
|
249
|
-
verdicts, as an alternative to
|
|
244
|
+
verdicts, as an alternative to limit judgement.</p>
|
|
250
245
|
<div data-wmap-demo="value-heatmap" class="wmap-demo"></div><div data-wmap-demo="spec-passfail" class="wmap-demo"></div><hr>
|
|
251
246
|
<h3 id="derived-tests">Derived tests (\u2020)</h3>
|
|
252
247
|
<p>A test name marked <strong>\u2020</strong> in front is a <em>derived</em> test: its value was calculated
|
|
@@ -284,7 +279,7 @@ shows die tooltips \u2014 a separate thing from the toolbar, which is always pre
|
|
|
284
279
|
<tr>
|
|
285
280
|
<td><img src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgd2lkdGg9IjE2IiBoZWlnaHQ9IjE2IiBmaWxsPSJub25lIiBzdHJva2U9IiMzNzQxNTEiIHN0cm9rZS13aWR0aD0iMS44IiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjxwYXRoIGQ9Ik0yIDEyczMuNi03IDEwLTcgMTAgNyAxMCA3LTMuNiA3LTEwIDctMTAtNy0xMC03eiIvPjxjaXJjbGUgY3g9IjEyIiBjeT0iMTIiIHI9IjMiLz48L3N2Zz4K" width="20" height="20"></td>
|
|
286
281
|
<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),
|
|
282
|
+
<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
283
|
</tr>
|
|
289
284
|
<tr>
|
|
290
285
|
<td><img src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgd2lkdGg9IjE2IiBoZWlnaHQ9IjE2IiBmaWxsPSJub25lIiBzdHJva2U9IiMzNzQxNTEiIHN0cm9rZS13aWR0aD0iMS44IiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjxwYXRoIGQ9Im0xNC42MjIgMTcuODk3LTEwLjY4LTIuOTEzIi8+PHBhdGggZD0iTTE4LjM3NiAyLjYyMmExIDEgMCAxIDEgMy4wMDIgMy4wMDJMMTcuMzYgOS42NDNhLjUuNSAwIDAgMCAwIC43MDdsLjk0NC45NDRhMi40MSAyLjQxIDAgMCAxIDAgMy40MDhsLS45NDQuOTQ0YS41LjUgMCAwIDEtLjcwNyAwTDguMzU0IDcuMzQ4YS41LjUgMCAwIDEgMC0uNzA3bC45NDQtLjk0NGEyLjQxIDIuNDEgMCAwIDEgMy40MDggMGwuOTQ0Ljk0NGEuNS41IDAgMCAwIC43MDcgMHoiLz48cGF0aCBkPSJNOSA4Yy0xLjgwNCAyLjcxLTMuOTcgMy40Ni02LjU4MyAzLjk0OGEuNTA3LjUwNyAwIDAgMC0uMzAyLjgxOWw3LjMyIDguODgzYTEgMSAwIDAgMCAxLjE4NS4yMDRDMTIuNzM1IDIwLjQwNSAxNiAxNi43OTIgMTYgMTUiLz48L3N2Zz4K" width="20" height="20"></td>
|
|
@@ -299,7 +294,7 @@ shows die tooltips \u2014 a separate thing from the toolbar, which is always pre
|
|
|
299
294
|
<tr>
|
|
300
295
|
<td><img src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgd2lkdGg9IjE2IiBoZWlnaHQ9IjE2IiBmaWxsPSJub25lIiBzdHJva2U9IiMzNzQxNTEiIHN0cm9rZS13aWR0aD0iMS44IiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjxwYXRoIGQ9Ik01IDQgTDUgMjAiLz48cGF0aCBkPSJNNSA0IEw4IDQiLz48cGF0aCBkPSJNNSAyMCBMOCAyMCIvPjxwYXRoIGQ9Ik0xOSA0IEwxOSAyMCIvPjxwYXRoIGQ9Ik0xOSA0IEwxNiA0Ii8+PHBhdGggZD0iTTE5IDIwIEwxNiAyMCIvPjxwYXRoIGQ9Ik01IDEyIEwxOSAxMiIgc3Ryb2tlLWRhc2hhcnJheT0iMyAyIi8+PC9zdmc+Cg==" width="20" height="20"></td>
|
|
301
296
|
<td>Colorbar range</td>
|
|
302
|
-
<td>Test Value mode with
|
|
297
|
+
<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
298
|
</tr>
|
|
304
299
|
<tr>
|
|
305
300
|
<td><img src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgd2lkdGg9IjE2IiBoZWlnaHQ9IjE2IiBmaWxsPSJub25lIiBzdHJva2U9IiMzNzQxNTEiIHN0cm9rZS13aWR0aD0iMS44IiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjxyZWN0IHdpZHRoPSI3IiBoZWlnaHQ9IjciIHg9IjMiIHk9IjMiIHJ4PSIxIi8+PHJlY3Qgd2lkdGg9IjciIGhlaWdodD0iNyIgeD0iMyIgeT0iMTQiIHJ4PSIxIi8+PHBhdGggZD0iTTE0IDRoNyIvPjxwYXRoIGQ9Ik0xNCA5aDciLz48cGF0aCBkPSJNMTQgMTVoNyIvPjxwYXRoIGQ9Ik0xNCAyMGg3Ii8+PC9zdmc+Cg==" width="20" height="20"></td>
|
|
@@ -404,7 +399,7 @@ highlighting one bin at a time.</p>
|
|
|
404
399
|
<li><strong>Quadrant lines</strong> \u2014 divides the wafer into N, S, E, W quadrants</li>
|
|
405
400
|
<li><strong>Die coordinate labels</strong> \u2014 draws the (x, y) grid position inside each die (useful at high zoom)</li>
|
|
406
401
|
<li><strong>Reticle grid</strong> \u2014 stepper field grid (only shown when reticle geometry is configured)</li>
|
|
407
|
-
<li><strong>
|
|
402
|
+
<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
403
|
<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
404
|
</ul>
|
|
410
405
|
<div data-wmap-demo="overlays" class="wmap-demo"></div><p><em>Ring boundaries, quadrant lines, and XY indicator all active.</em></p>
|
|
@@ -515,7 +510,7 @@ highlighting one bin at a time.</p>
|
|
|
515
510
|
<tr>
|
|
516
511
|
<td><img src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgd2lkdGg9IjE2IiBoZWlnaHQ9IjE2IiBmaWxsPSJub25lIiBzdHJva2U9IiMzNzQxNTEiIHN0cm9rZS13aWR0aD0iMS44IiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjxwYXRoIGQ9Ik01IDQgTDUgMjAiLz48cGF0aCBkPSJNNSA0IEw4IDQiLz48cGF0aCBkPSJNNSAyMCBMOCAyMCIvPjxwYXRoIGQ9Ik0xOSA0IEwxOSAyMCIvPjxwYXRoIGQ9Ik0xOSA0IEwxNiA0Ii8+PHBhdGggZD0iTTE5IDIwIEwxNiAyMCIvPjxwYXRoIGQ9Ik01IDEyIEwxOSAxMiIgc3Ryb2tlLWRhc2hhcnJheT0iMyAyIi8+PC9zdmc+Cg==" width="20" height="20"></td>
|
|
517
512
|
<td>Colorbar range</td>
|
|
518
|
-
<td>Test Value mode with
|
|
513
|
+
<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
514
|
</tr>
|
|
520
515
|
<tr>
|
|
521
516
|
<td><img src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNiIgaGVpZ2h0PSIxNiIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9IiMzNzQxNTEiIHN0cm9rZS13aWR0aD0iMiIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIj48cGF0aCBkPSJNOCAydjQiLz48cGF0aCBkPSJNMTIgMnY0Ii8+PHBhdGggZD0iTTE2IDJ2NCIvPjxyZWN0IHdpZHRoPSIxNiIgaGVpZ2h0PSIxOCIgeD0iNCIgeT0iNCIgcng9IjIiLz48cGF0aCBkPSJNOCAxMGg2Ii8+PHBhdGggZD0iTTggMTRoOCIvPjxwYXRoIGQ9Ik04IDE4aDUiLz48L3N2Zz4K" width="20" height="20"></td>
|
|
@@ -556,7 +551,7 @@ The rest depends on the active plot mode:</p>
|
|
|
556
551
|
<li><strong>Bin modes</strong> \u2014 the die's bin verdict (number and name, if named), plus a
|
|
557
552
|
note of how many test values are recorded for the die</li>
|
|
558
553
|
<li><strong>Test Value mode</strong> \u2014 the active test's value in bold, flagged when it is
|
|
559
|
-
|
|
554
|
+
outside the test limits, with the remaining tests summarised as "+N more tests"</li>
|
|
560
555
|
<li><strong>Stacked modes</strong> \u2014 the single aggregated value or count at that position</li>
|
|
561
556
|
</ul>
|
|
562
557
|
<h3 id="42-zoom-and-pan">4.2 Zoom and pan</h3>
|
|
@@ -594,7 +589,8 @@ wafer otherwise.</p>
|
|
|
594
589
|
<ul>
|
|
595
590
|
<li><strong>Value histogram</strong> \u2014 one test's distribution, opening on the test the map
|
|
596
591
|
is showing.</li>
|
|
597
|
-
<li><strong>Process capability</strong> \u2014 every test normalised to its spec limits,
|
|
592
|
+
<li><strong>Process capability</strong> \u2014 every test normalised to its spec limits, or its
|
|
593
|
+
test limits when it has none, with its
|
|
598
594
|
Ppk. Below 30 dies the chart says each Ppk is a rough estimate: a Ppk from a
|
|
599
595
|
handful of dies can be far from the process's real capability.</li>
|
|
600
596
|
<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 +708,10 @@ with a significance test behind it.</li>
|
|
|
712
708
|
marked. Wafers are in slot order by default, since that is what makes a
|
|
713
709
|
slot-correlated pattern visible; a <strong>Slot / Yield</strong> selector re-sorts. Wafers
|
|
714
710
|
far below the rest are labelled "low outlier".</li>
|
|
715
|
-
<li><strong>Test values</strong> \u2014 per test: mean, <strong>Ppk</strong>, and
|
|
711
|
+
<li><strong>Test values</strong> \u2014 per test: mean, <strong>Ppk</strong>, and limit yield. Ppk (not Cpk)
|
|
716
712
|
because it measures against the <em>overall</em> spread, including wafer-to-wafer
|
|
717
713
|
variation, which is what the dies actually ship against. The full descriptive
|
|
718
|
-
statistics \u2014 min, quartiles, median, max, \u03C3, both
|
|
714
|
+
statistics \u2014 min, quartiles, median, max, \u03C3, both test limits, and all four
|
|
719
715
|
capability indices \u2014 are in the summary report and the CSV export.</li>
|
|
720
716
|
<li><strong>Functional tests</strong> \u2014 pass/fail counts and pass rate per functional test.</li>
|
|
721
717
|
</ul>
|
|
@@ -846,21 +842,31 @@ actually in use and marked with the median, a hard/soft bin pareto, and
|
|
|
846
842
|
ring/quadrant regional yield plus the full per-test statistics table.</p>
|
|
847
843
|
</li>
|
|
848
844
|
<li><p><strong>Distributions</strong> \u2014 process capability (Cp/Cpk/Pp/Ppk for tests with both a
|
|
849
|
-
lower and upper spec
|
|
845
|
+
lower and upper limit \u2014 spec limits where given, otherwise test limits; tests
|
|
846
|
+
without both still appear, normalized
|
|
850
847
|
onto their own range and sorted by variability), a test-value box plot, a
|
|
851
848
|
value histogram, and a <strong>wafer-to-wafer trend</strong> \u2014 one point per wafer at its
|
|
852
849
|
mean, \xB11\u03C3 whiskers, the mean of all wafers dashed across (labelled the "lot
|
|
853
|
-
mean" when they all come from one lot), and
|
|
854
|
-
test has them. The trend is always in slot order, deliberately: drift across
|
|
850
|
+
mean" when they all come from one lot), and the test's limits. The trend is always in slot order, deliberately: drift across
|
|
855
851
|
a cassette only reads in the physical sequence, so there is no sort control
|
|
856
852
|
to destroy it. Clicking a capability box drives the box plot, histogram and
|
|
857
853
|
trend onto that same test automatically.</p>
|
|
854
|
+
<p>The box plot, histogram, trend and the Correlation tab's scatter draw the
|
|
855
|
+
test's limits as dashed lines. A test can have two kinds: <strong>test limits</strong>
|
|
856
|
+
(labelled <em>Lo limit</em> / <em>Hi limit</em>, short dashes) \u2014 what each die was judged
|
|
857
|
+
pass/fail by \u2014 and <strong>spec limits</strong> (labelled <em>LSL</em> / <em>USL</em>, long dashes) \u2014
|
|
858
|
+
the process specification that capability is measured against. When a test
|
|
859
|
+
has both, a <strong>Limits</strong> choice appears \u2014 <em>Test + spec</em> (the default), <em>Test
|
|
860
|
+
limits</em>, <em>Spec limits</em> or <em>None</em> \u2014 and it applies to all four charts at
|
|
861
|
+
once. A test that lacks the kind you chose shows the kind it has, so it
|
|
862
|
+
never looks as if it had no limits; a limit that falls outside the plotted
|
|
863
|
+
range is marked at the edge of the chart with an arrow.</p>
|
|
858
864
|
<p>The box plot, histogram and trend share one row of axis controls, kept in
|
|
859
865
|
sync across all three so switching between them never re-reads the same
|
|
860
866
|
data on a different scale: <strong>Axis includes limits</strong> widens the axis to cover
|
|
861
|
-
|
|
867
|
+
every limit shown even where the data sits well clear of them (on by default only when
|
|
862
868
|
doing so still leaves the data at least a third of the axis \u2014 otherwise a
|
|
863
|
-
generous
|
|
869
|
+
generous limit window would squash a perfectly capable distribution into a
|
|
864
870
|
sliver), and <strong>Clip outliers</strong> narrows the axis to a robust range (the
|
|
865
871
|
Tukey fence, 1.5\xD7 IQR beyond Q1/Q3) so a handful of extreme values don't
|
|
866
872
|
stretch the axis until the rest of the distribution reads as a flat line.
|
|
@@ -896,7 +902,7 @@ yield/box-plot rows.</em></p>
|
|
|
896
902
|
open that wafer's map), hard/soft bin pareto, ring and quadrant yield, and
|
|
897
903
|
pooled per-test statistics. The "\u2039 Gallery" tab returns to the card grid.</em></p>
|
|
898
904
|
<p><em>Distributions sub-tab: process capability (coloured by Ppk \u2014 green capable,
|
|
899
|
-
orange marginal, red poor; tests without
|
|
905
|
+
orange marginal, red poor; tests without limits are muted and dashed), a
|
|
900
906
|
per-wafer test-value box plot, and a value histogram. Clicking a capability
|
|
901
907
|
box drives the box plot and histogram onto that test.</em></p>
|
|
902
908
|
<p><em>Correlation sub-tab: test-to-test Pearson correlation matrix (blue =
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export declare const WMAP_VERSION = "0.
|
|
2
|
-
export declare const WMAP_BUILD_TIME = "2026-09-
|
|
1
|
+
export declare const WMAP_VERSION = "0.31.0";
|
|
2
|
+
export declare const WMAP_BUILD_TIME = "2026-09-25T14:33:27.298Z";
|
|
3
3
|
//# sourceMappingURL=version.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export const WMAP_VERSION="0.
|
|
1
|
+
export const WMAP_VERSION="0.31.0",WMAP_BUILD_TIME="2026-09-25T14:33:27.298Z";
|
|
@@ -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}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import type { Die } from './dies.js';
|
|
2
1
|
export type AggregationMethod = 'mean' | 'median' | 'stddev' | 'min' | 'max' | 'count';
|
|
3
2
|
/** Minimal die shape required by aggregation functions — satisfied by both Die and DieResult. */
|
|
4
3
|
export interface DieLike {
|
|
@@ -29,11 +28,6 @@ export interface DieLike {
|
|
|
29
28
|
* ```
|
|
30
29
|
*/
|
|
31
30
|
export declare function aggregateValues(diesByWafer: DieLike[][], method: AggregationMethod, paramIndex?: number): DieLike[];
|
|
32
|
-
/**
|
|
33
|
-
* Return all unique bin values present in dies, sorted ascending.
|
|
34
|
-
* `binSpace` selects whether to inspect `die.hbin` (default) or `die.sbin`.
|
|
35
|
-
*/
|
|
36
|
-
export declare function getUniqueBins(dies: Die[], binSpace?: 'hard' | 'soft'): number[];
|
|
37
31
|
/**
|
|
38
32
|
* Aggregate multiple wafers by counting, per die position, how many wafers
|
|
39
33
|
* had a specific bin value.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{minOf as p,maxOf as m}from"./utils.js";import{getDieKey as
|
|
1
|
+
import{minOf as p,maxOf as m}from"./utils.js";import{getDieKey as h}from"./dies.js";export function aggregateValues(l,n,c=0){if(!l.length)return[];const a=new Map,o=new Map;for(const g of l)for(const t of g){const e=h(t),s=t.testValues?.[c];s!==void 0?(a.has(e)||(a.set(e,[]),o.set(e,t)),a.get(e).push(s)):o.has(e)||o.set(e,t)}const u=[];for(const[g,t]of o){const e=a.get(g);if(!e?.length){u.push({...t,testValues:void 0,testPass:void 0});continue}let s;if(n==="mean")s=e.reduce((f,i)=>f+i,0)/e.length;else if(n==="median"){const f=[...e].sort((r,d)=>r-d),i=Math.floor(f.length/2);s=f.length%2?f[i]:(f[i-1]+f[i])/2}else if(n==="stddev"){if(e.length<2){u.push({...t,testValues:{0:0},testPass:void 0});continue}const f=e.reduce((i,r)=>i+r,0)/e.length;s=Math.sqrt(e.reduce((i,r)=>i+(r-f)**2,0)/(e.length-1))}else n==="min"?s=p(e):n==="max"?s=m(e):s=e.length;u.push({...t,testValues:{0:s},testPass:void 0})}return u}export function aggregateBinCounts(l,n,c="hard"){if(!l.length)return[];const a=new Map;for(const o of l)for(const u of o)if((c==="soft"?u.sbin:u.hbin)===n){const t=h(u);a.set(t,(a.get(t)??0)+1)}return(l[0]??[]).map(o=>({...o,testValues:{0:a.get(h(o))??0},testPass:void 0,...c==="soft"?{sbin:n}:{hbin:n}}))}
|
|
@@ -51,11 +51,9 @@ 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
|
-
/** Include dies that straddle the wafer boundary (`die.partial`). Default: excluded. */
|
|
58
|
-
includePartial?: boolean;
|
|
59
57
|
/** Include dies inside the edge-exclusion zone (`die.edgeExcluded`). Default: excluded. */
|
|
60
58
|
includeEdgeExcluded?: boolean;
|
|
61
59
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export function hasPosition(
|
|
1
|
+
export function hasPosition(n){return n.x!=null&&n.y!=null}export function isPositionedDie(n){return n.x!=null&&n.y!=null}export function getDieKey(n){return hasPosition(n)?`${n.x},${n.y}`:`id:${n.id}`}export function isYieldEligibleDie(n,t={}){return!(n.partial||!t.includeEdgeExcluded&&n.edgeExcluded)}export function diePassStatus(n,t){const e=n.hbin??n.sbin;return e===void 0?void 0:t.has(e)}export function generateDies(n,t){const{width:e,height:o,offset:s={x:0,y:0}}=t,u=t.gridSize??Math.ceil(n.radius/Math.min(e,o))+1,l=[];for(let i=-u;i<=u;i++)for(let r=-u;r<=u;r++){const c=n.center.x+r*e+s.x,x=n.center.y+i*o+s.y;l.push({id:`${r}_${i}`,x:r,y:i,physX:c,physY:x,width:e,height:o})}return l}
|
|
@@ -1,11 +1,9 @@
|
|
|
1
|
-
export
|
|
2
|
-
export
|
|
3
|
-
export
|
|
4
|
-
export
|
|
5
|
-
export
|
|
6
|
-
export
|
|
7
|
-
export type {
|
|
8
|
-
export
|
|
9
|
-
export type { PitchResult } from './inference/pitch.js';
|
|
10
|
-
export { createWafer, generateDies, isPositionedDie, clipDiesToWafer, applyOrientation, transformDies, mapDataToDies, isInsideWafer, affineIdentity, affineRotation, affineMirror, affineCompose, affineInvert, affinePoint, affineVector, affineSwapsAxes, applyProbeSequence, generateReticleGrid, classifyDie, getRingLabel, aggregateValues, aggregateBinCounts, getUniqueBins, metadataCategoricalValue, resolveGridPitch, } from './deprecated.js';
|
|
1
|
+
export { hasPosition, getDieKey, isYieldEligibleDie, diePassStatus } from './dies.js';
|
|
2
|
+
export type { Die, DieEligibilityOptions } from './dies.js';
|
|
3
|
+
export { metadataDisplayValue } from './metadata.js';
|
|
4
|
+
export type { DieMetadata, WaferMetadata } from './metadata.js';
|
|
5
|
+
export { getReticleCell } from './reticle.js';
|
|
6
|
+
export type { Reticle } from './reticle.js';
|
|
7
|
+
export type { Affine, CoordFrame } from './transforms.js';
|
|
8
|
+
export type { Wafer } from './wafer.js';
|
|
11
9
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export
|
|
1
|
+
export{hasPosition,getDieKey,isYieldEligibleDie,diePassStatus}from"./dies.js";export{metadataDisplayValue}from"./metadata.js";export{getReticleCell}from"./reticle.js";
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export declare const STDF_BIN_MAX = 32767;
|
|
2
|
+
export declare const STDF_COORD_MAX = 32767;
|
|
3
|
+
export declare const STDF_TEST_NUM_MAX = 4294967295;
|
|
4
|
+
export declare const STDF_SITE_MAX = 255;
|
|
5
|
+
export declare const isStdfBin: (v: number) => boolean;
|
|
6
|
+
export declare const isStdfCoord: (v: number) => boolean;
|
|
7
|
+
export declare const isStdfTestNumber: (v: number) => boolean;
|
|
8
|
+
export declare const isStdfSite: (v: number) => boolean;
|
|
9
|
+
//# sourceMappingURL=stdf.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const STDF_BIN_MAX=32767,STDF_COORD_MAX=32767,STDF_TEST_NUM_MAX=4294967295,STDF_SITE_MAX=255,isStdfBin=t=>Number.isInteger(t)&&t>=0&&t<=32767,isStdfCoord=t=>Number.isInteger(t)&&Math.abs(t)<=32767,isStdfTestNumber=t=>Number.isInteger(t)&&t>=0&&t<=4294967295,isStdfSite=t=>Number.isInteger(t)&&t>=0&&t<=255;
|
|
@@ -1,17 +1,5 @@
|
|
|
1
1
|
import type { Wafer } from './wafer.js';
|
|
2
2
|
import type { Die, DieSpec, PositionedDie } from './dies.js';
|
|
3
|
-
export interface DataRow {
|
|
4
|
-
[key: string]: string | number;
|
|
5
|
-
}
|
|
6
|
-
export interface MapOptions {
|
|
7
|
-
xField?: string;
|
|
8
|
-
yField?: string;
|
|
9
|
-
iField?: string;
|
|
10
|
-
jField?: string;
|
|
11
|
-
valueField: string;
|
|
12
|
-
/** 'xy' matches by wafer coordinates; 'ij' matches by grid indices (default: 'xy') */
|
|
13
|
-
matchBy?: 'xy' | 'ij';
|
|
14
|
-
}
|
|
15
3
|
export interface TransformOptions {
|
|
16
4
|
/** Additional rotation in degrees, applied on top of existing die coordinates. */
|
|
17
5
|
rotation?: number;
|
|
@@ -65,8 +53,6 @@ export interface Affine<From extends CoordFrame = CoordFrame, To extends CoordFr
|
|
|
65
53
|
* `View.hoverPoints` holds and what hit-testing compares against.
|
|
66
54
|
*/
|
|
67
55
|
export type CoordFrame = 'physical' | 'grid' | 'baked' | 'screen';
|
|
68
|
-
/** The identity transform. */
|
|
69
|
-
export declare function affineIdentity<F extends CoordFrame>(): Affine<F, F>;
|
|
70
56
|
/**
|
|
71
57
|
* Clockwise rotation by `angleDegrees` about (cx, cy) — the same convention as
|
|
72
58
|
* {@link rotatePoint}, which this reproduces exactly.
|
|
@@ -130,13 +116,4 @@ export declare function transformDies(dies: PositionedDie[], options: TransformO
|
|
|
130
116
|
x: number;
|
|
131
117
|
y: number;
|
|
132
118
|
}): PositionedDie[];
|
|
133
|
-
/**
|
|
134
|
-
* Attach data values to dies.
|
|
135
|
-
* matchBy='xy' — matches by wafer coordinates (v0.1 behaviour)
|
|
136
|
-
* matchBy='ij' — matches by grid indices
|
|
137
|
-
*
|
|
138
|
-
* Note: duplicate rows with the same coordinate produce last-wins behaviour.
|
|
139
|
-
* Deduplicate `data` upstream if needed.
|
|
140
|
-
*/
|
|
141
|
-
export declare function mapDataToDies(dies: Die[], data: DataRow[], options: MapOptions): Die[];
|
|
142
119
|
//# sourceMappingURL=transforms.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
export function rotatePoint(t,n,o,s=0,c=0){const e=o*Math.PI/180,r=Math.cos(e),i=Math.sin(e),a=t-s,f=n-c;return{x:s+a*r+f*i,y:c-a*i+f*r}}export function affineRotation(t,n=0,o=0){const s=t*Math.PI/180,c=Math.cos(s),e=Math.sin(s);return{a:c,c:e,b:-e,d:c,e:n-(c*n+e*o),f:o-(-e*n+c*o)}}export function affineMirror(t,n,o=0,s=0){return{a:t?-1:1,c:0,b:0,d:n?-1:1,e:t?2*o:0,f:n?2*s:0}}export function affineCompose(t,n){return{a:t.a*n.a+t.c*n.b,b:t.b*n.a+t.d*n.b,c:t.a*n.c+t.c*n.d,d:t.b*n.c+t.d*n.d,e:t.a*n.e+t.c*n.f+t.e,f:t.b*n.e+t.d*n.f+t.f}}export function affineInvert(t){const n=t.a*t.d-t.b*t.c,o=t.d/n,s=-t.b/n,c=-t.c/n,e=t.a/n;return{a:o,b:s,c,d:e,e:-(o*t.e+c*t.f),f:-(s*t.e+e*t.f)}}export function affinePoint(t,n,o){return{x:t.a*n+t.c*o+t.e,y:t.b*n+t.d*o+t.f}}export function affineVector(t,n,o){return{x:t.a*n+t.c*o,y:t.b*n+t.d*o}}export function affineSwapsAxes(t){return Math.abs(t.a)<1e-9&&Math.abs(t.d)<1e-9&&Math.abs(t.b)>1e-9}function y(t,n){return Math.sqrt(t*t-(n/2)**2)}export function isInsideWafer(t,n,o){const s=t-o.center.x,c=n-o.center.y;if(s*s+c*c>o.radius*o.radius)return!1;if(o.notch){const e=y(o.radius,o.notch.length);if(o.notch.type==="bottom"&&c<-e||o.notch.type==="top"&&c>e||o.notch.type==="left"&&s<-e||o.notch.type==="right"&&s>e)return!1}return!0}export function clipDiesToWafer(t,n,o){const s=[];for(const c of t){const e=c.physX,r=c.physY,i=isInsideWafer(e,r,n);if(!o){i&&s.push({...c,insideWafer:!0,partial:!1});continue}const a=o.width/2,f=o.height/2,p=[[e-a,r-f],[e+a,r-f],[e+a,r+f],[e-a,r+f]].filter(([h,u])=>isInsideWafer(h,u,n)).length;!i&&p===0||s.push({...c,insideWafer:!0,partial:p<4})}return s}export function applyOrientation(t,n){if(n.orientation===0)return t;const o=affineRotation(n.orientation,n.center.x,n.center.y);return t.map(s=>{const c=affinePoint(o,s.physX,s.physY);return{...s,physX:c.x,physY:c.y}})}export function transformDies(t,n,o={x:0,y:0}){const{rotation:s=0,flipX:c=!1,flipY:e=!1}=n;if(s===0&&!c&&!e)return t;const r=affineCompose(affineMirror(c,e,o.x,o.y),affineRotation(s,o.x,o.y));return t.map(i=>{const a=affinePoint(r,i.physX,i.physY);return{...i,physX:a.x,physY:a.y}})}
|
|
@@ -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)}
|
|
@@ -117,8 +117,7 @@ export interface View {
|
|
|
117
117
|
/**
|
|
118
118
|
* Transformed centre of each die, index-parallel with {@link dies}. Separate
|
|
119
119
|
* from `rectangles` because a die always has a centre for hit-testing and
|
|
120
|
-
* viewport fitting
|
|
121
|
-
* `showPartialDies: false`).
|
|
120
|
+
* viewport fitting, however many rectangles it draws as.
|
|
122
121
|
*/
|
|
123
122
|
hoverPoints: ViewHoverPoint[];
|
|
124
123
|
/** Text to draw — die labels and any axis/indicator strings the view generated. */
|
|
@@ -438,13 +437,6 @@ export interface ViewOptions {
|
|
|
438
437
|
* regardless of this option — it has no value to plot.
|
|
439
438
|
*/
|
|
440
439
|
passFailDisplay?: 'off' | 'spec' | 'test';
|
|
441
|
-
/**
|
|
442
|
-
* When true (default), partial (edge) dies — positions that only partially
|
|
443
|
-
* overlap the wafer circle — are rendered in a muted grey.
|
|
444
|
-
* Set to false to hide them entirely, matching real prober behaviour where
|
|
445
|
-
* edge positions are never tested.
|
|
446
|
-
*/
|
|
447
|
-
showPartialDies?: boolean;
|
|
448
440
|
}
|
|
449
441
|
interface ColorFns {
|
|
450
442
|
forValue: (t: number) => string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{hasPosition as de}from"../core/dies.js";import{getReticleCell as he}from"../core/reticle.js";import{metadataCategoricalValue as me,metadataDisplayValue as pe}from"../core/metadata.js";import{affineRotation as bt,affineMirror as Ht,affineCompose as st,affinePoint as G,affineVector as xt,affineSwapsAxes as ye}from"../core/transforms.js";import{contrastTextColor as Q,SPEC_PASS_FILL as Mt,SPEC_FAIL_LOW as ge,SPEC_FAIL_HIGH as St}from"./colorMap.js";import{resolveValueColorFn as be}from"./colorSchemes.js";import{resolveBinColors as xe,binColorsCover as Me}from"./binColors.js";import{NO_DATA_FILL as z}from"./colorMap.js";import{diePassStatus as Se}from"../core/dies.js";import{getDieTestValue as P,getTestPassStatus as U,isParametricTest as vt}from"./buildWaferMap.js";import{classifySpec as ot}from"./spec.js";export{ot as classifySpec};import{fmt as kt,fmtColorbarAxis as $t,fmtAggregationMethod as _t}from"./fmt.js";import{metadataValueColor as ve}from"./colorMap.js";import{clamp01 as Kt,compareNatural as ke,escHtml as L,minOf as $e}from"../core/utils.js";import{prettyKey as Wt}from"../core/utils.js";import{testLabel as rt,markedTestLabel as lt,derivedTestNote as jt,isDerivedTest as Be,DERIVED_MARK as Te,DERIVED_KEY as Ie}from"./testLabel.js";export function resolveTestNumber(t,e){return e?.length?{testNumber:(findTestDef(e,t)??e[0]).testNumber}:{testNumber:t}}export function collectMetadataValues(t,e){const i=new Set;for(const a of t){if(a.partial||a.edgeExcluded)continue;const s=ct(a,e);s!==void 0&&i.add(s)}return[...i].sort(ke)}export function findTestDef(t,e){return t?.find(i=>i.testNumber===e)}export function getUniqueTestNumbers(t){return[...new Set(t.flatMap(e=>e.testValues?Object.keys(e.testValues).map(Number):[]))].sort((e,i)=>e-i)}const De="#d3d6db",wt="#e8e9ea",Ve="#aab0ba";function ct(t,e){return e?me(t.metadata?.[e]):void 0}function Ce(t,e,i){const{x:a,y:s}=t.center,n=bt(t.orientation,a,s),o=st(Ht(e?.x??!1,e?.y??!1,a,s),bt(t.orientation,a,s)),l=st(Ht(i?.flipX??!1,i?.flipY??!1,a,s),bt(i?.rotation??0,a,s));return{bakedToScreen:l,physicalToScreen:st(l,n),gridToScreen:st(l,o)}}function q(t,e=!1){return{points:[t],closed:e}}function Fe(t,e){const{center:i,radius:a,notch:s}=t;let n=i.x+a*Math.cos(e),o=i.y+a*Math.sin(e);if(!s)return{x:n,y:o};const l=Math.sqrt(a**2-(s.length/2)**2),d=s.length/2,g=n-i.x,p=o-i.y;return s.type==="bottom"&&p<-l?(o=i.y-l,n=i.x+Math.max(-d,Math.min(d,g))):s.type==="top"&&p>l?(o=i.y+l,n=i.x+Math.max(-d,Math.min(d,g))):s.type==="left"&&g<-l?(n=i.x-l,o=i.y+Math.max(-d,Math.min(d,p))):s.type==="right"&&g>l&&(n=i.x+l,o=i.y+Math.max(-d,Math.min(d,p))),{x:n,y:o}}function zt(t,e){return t.map(e).join(" / ")}function Pe(t,e=256){const i=Array.from({length:e},(a,s)=>t(s/(e-1)));return a=>i[Math.min(e-1,Math.max(0,Math.round(a*(e-1))))]}function Le(t,e){const i=Math.max(1,Math.min(t.width,t.height)),a=t.width/Math.max(e.length,1);return Math.max(8,Math.min(16,Math.round(Math.min(i*.55,a*1.8))))}function Re(t,e,i){if(e?.length){const a=e.flatMap(s=>{const n=s.testNumber;if(!vt(s)){const l=U(t,n,s);return l===void 0?[]:[{key:n,label:lt(s,n),value:l?"Pass":"Fail"}]}const o=P(t,n);return o===void 0?[]:[{key:n,label:lt(s,n),value:kt(o,s.unit,i),recordedFail:t.testPass?.[n]===!1}]});if(a.length)return a}return t.testValues&&Object.keys(t.testValues).length>0?Object.entries(t.testValues).map(([a,s])=>({key:Number(a),label:rt(void 0,Number(a)),value:kt(s,void 0,i)})):t.testPass&&Object.keys(t.testPass).length>0?Object.entries(t.testPass).map(([a,s])=>({key:Number(a),label:rt(void 0,Number(a)),value:s?"Pass":"Fail"})):[]}export function buildHoverText(t,e,i={}){const{testDefs:a,hbinDefs:s,sbinDefs:n,fallbackFormat:o,aggrMethod:l,lotSize:d,activeTest:g,reticleConfig:p,metadataFields:B}=i,F=s?new Map(s.map(f=>[f.bin,f])):null,T=n?new Map(n.map(f=>[f.bin,f])):null,b=[L(`Die (${t.x}, ${t.y})`)];if(p&&de(t)){const f=he(t,p);b.push(L(`Reticle (${f.column}, ${f.row})`))}if(e==="stackedValues"){const f=P(t,0);if(f!==void 0){const u=a?.[0],h=u?lt(u,u.testNumber):"Value",v=l?` (${l})`:"";b.push(L(`${h}${v}: ${kt(f,u?.unit,o)}`));const m=jt(u);m&&b.push(`<i>${L(m)}</i>`)}}else if(e==="stackedBins"||e==="stackedSoftBins"){const f=P(t,0),u=e==="stackedSoftBins"?t.sbin:t.hbin;if(f!==void 0){const h=e==="stackedSoftBins"?T:F,v=u!==void 0?h?.get(u)?.name?`${u} \xB7 ${h.get(u).name}`:`Bin ${u}`:"Bin";let m;if(l==="percent")m=`${f.toFixed(0)}%`;else{const V=d?` (${(f/d*100).toFixed(0)}% of stacked wafers)`:"";m=`${f}${V}`}const x=l?` [${_t(l)}]`:"";b.push(L(`${v}: ${m}${x}`))}}else{const f=Re(t,a,o);if(e==="value"&&f.length){const{testNumber:u}=resolveTestNumber(g??0,a),h=findTestDef(a,u),v=P(t,u);let m=f.findIndex(A=>A.key===u);m<0&&(m=0);const x=f[m];let V=`<b>${L(`${x.label}: ${x.value}`)}${x.recordedFail?" <i>(recorded fail)</i>":""}</b>`;if(x.key===u&&h){const A=ot(v,h);(A==="failLow"||A==="failHigh")&&(V+=" <i>(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 fe}from"../core/dies.js";import{getReticleCell as de}from"../core/reticle.js";import{metadataCategoricalValue as he,metadataDisplayValue as me}from"../core/metadata.js";import{affineRotation as bt,affineMirror as Ht,affineCompose as st,affinePoint as G,affineVector as xt,affineSwapsAxes as pe}from"../core/transforms.js";import{contrastTextColor as Q,SPEC_PASS_FILL as Mt,SPEC_FAIL_LOW as ye,SPEC_FAIL_HIGH as St}from"./colorMap.js";import{resolveValueColorFn as ge}from"./colorSchemes.js";import{resolveBinColors as be,binColorsCover as xe}from"./binColors.js";import{NO_DATA_FILL as w}from"./colorMap.js";import{diePassStatus as Me}from"../core/dies.js";import{getDieTestValue as L,getTestPassStatus as U,isParametricTest as vt}from"./buildWaferMap.js";import{classifySpec as ot}from"./spec.js";export{ot as classifySpec};import{fmt as kt,fmtColorbarAxis as $t,fmtAggregationMethod as _t}from"./fmt.js";import{metadataValueColor as Se}from"./colorMap.js";import{clamp01 as Kt,compareNatural as ve,escHtml as P,minOf as ke}from"../core/utils.js";import{prettyKey as Wt}from"../core/utils.js";import{testLabel as rt,markedTestLabel as lt,derivedTestNote as jt,isDerivedTest as $e,DERIVED_MARK as Be,DERIVED_KEY as Te}from"./testLabel.js";export function resolveTestNumber(t,e){return e?.length?{testNumber:(findTestDef(e,t)??e[0]).testNumber}:{testNumber:t}}export function collectMetadataValues(t,e){const i=new Set;for(const a of t){if(a.partial||a.edgeExcluded)continue;const s=ct(a,e);s!==void 0&&i.add(s)}return[...i].sort(ve)}export function findTestDef(t,e){return t?.find(i=>i.testNumber===e)}export function getUniqueTestNumbers(t){return[...new Set(t.flatMap(e=>e.testValues?Object.keys(e.testValues).map(Number):[]))].sort((e,i)=>e-i)}const Ie="#d3d6db",zt="#e8e9ea",De="#aab0ba";function ct(t,e){return e?he(t.metadata?.[e]):void 0}function Ve(t,e,i){const{x:a,y:s}=t.center,n=bt(t.orientation,a,s),o=st(Ht(e?.x??!1,e?.y??!1,a,s),bt(t.orientation,a,s)),l=st(Ht(i?.flipX??!1,i?.flipY??!1,a,s),bt(i?.rotation??0,a,s));return{bakedToScreen:l,physicalToScreen:st(l,n),gridToScreen:st(l,o)}}function q(t,e=!1){return{points:[t],closed:e}}function Ce(t,e){const{center:i,radius:a,notch:s}=t;let n=i.x+a*Math.cos(e),o=i.y+a*Math.sin(e);if(!s)return{x:n,y:o};const l=Math.sqrt(a**2-(s.length/2)**2),d=s.length/2,g=n-i.x,p=o-i.y;return s.type==="bottom"&&p<-l?(o=i.y-l,n=i.x+Math.max(-d,Math.min(d,g))):s.type==="top"&&p>l?(o=i.y+l,n=i.x+Math.max(-d,Math.min(d,g))):s.type==="left"&&g<-l?(n=i.x-l,o=i.y+Math.max(-d,Math.min(d,p))):s.type==="right"&&g>l&&(n=i.x+l,o=i.y+Math.max(-d,Math.min(d,p))),{x:n,y:o}}function wt(t,e){return t.map(e).join(" / ")}function Fe(t,e=256){const i=Array.from({length:e},(a,s)=>t(s/(e-1)));return a=>i[Math.min(e-1,Math.max(0,Math.round(a*(e-1))))]}function Le(t,e){const i=Math.max(1,Math.min(t.width,t.height)),a=t.width/Math.max(e.length,1);return Math.max(8,Math.min(16,Math.round(Math.min(i*.55,a*1.8))))}function Pe(t,e,i){if(e?.length){const a=e.flatMap(s=>{const n=s.testNumber;if(!vt(s)){const l=U(t,n,s);return l===void 0?[]:[{key:n,label:lt(s,n),value:l?"Pass":"Fail"}]}const o=L(t,n);return o===void 0?[]:[{key:n,label:lt(s,n),value:kt(o,s.unit,i),recordedFail:t.testPass?.[n]===!1}]});if(a.length)return a}return t.testValues&&Object.keys(t.testValues).length>0?Object.entries(t.testValues).map(([a,s])=>({key:Number(a),label:rt(void 0,Number(a)),value:kt(s,void 0,i)})):t.testPass&&Object.keys(t.testPass).length>0?Object.entries(t.testPass).map(([a,s])=>({key:Number(a),label:rt(void 0,Number(a)),value:s?"Pass":"Fail"})):[]}export function buildHoverText(t,e,i={}){const{testDefs:a,hbinDefs:s,sbinDefs:n,fallbackFormat:o,aggrMethod:l,lotSize:d,activeTest:g,reticleConfig:p,metadataFields:B}=i,F=s?new Map(s.map(f=>[f.bin,f])):null,T=n?new Map(n.map(f=>[f.bin,f])):null,b=[P(`Die (${t.x}, ${t.y})`)];if(p&&fe(t)){const f=de(t,p);b.push(P(`Reticle (${f.column}, ${f.row})`))}if(e==="stackedValues"){const f=L(t,0);if(f!==void 0){const u=a?.[0],h=u?lt(u,u.testNumber):"Value",v=l?` (${l})`:"";b.push(P(`${h}${v}: ${kt(f,u?.unit,o)}`));const m=jt(u);m&&b.push(`<i>${P(m)}</i>`)}}else if(e==="stackedBins"||e==="stackedSoftBins"){const f=L(t,0),u=e==="stackedSoftBins"?t.sbin:t.hbin;if(f!==void 0){const h=e==="stackedSoftBins"?T:F,v=u!==void 0?h?.get(u)?.name?`${u} \xB7 ${h.get(u).name}`:`Bin ${u}`:"Bin";let m;if(l==="percent")m=`${f.toFixed(0)}%`;else{const V=d?` (${(f/d*100).toFixed(0)}% of stacked wafers)`:"";m=`${f}${V}`}const x=l?` [${_t(l)}]`:"";b.push(P(`${v}: ${m}${x}`))}}else{const f=Pe(t,a,o);if(e==="value"&&f.length){const{testNumber:u}=resolveTestNumber(g??0,a),h=findTestDef(a,u),v=L(t,u);let m=f.findIndex(A=>A.key===u);m<0&&(m=0);const x=f[m];let V=`<b>${P(`${x.label}: ${x.value}`)}${x.recordedFail?" <i>(recorded fail)</i>":""}</b>`;if(x.key===u&&h){const A=ot(v,h);(A==="failLow"||A==="failHigh")&&(V+=" <i>(outside test limits)</i>")}b.push(V);const E=jt(findTestDef(a,x.key));E&&b.push(`<i>${P(E)}</i>`);const y=f.length-1;y>0&&b.push(`<i>+${y} more test${y===1?"":"s"}</i>`)}else if(f.length){const u=f.length;b.push(`<i>${u} test result${u===1?"":"s"} recorded</i>`)}if(t.hbin!==void 0||t.sbin!==void 0){const u=[];if(t.hbin!==void 0){const h=F?.get(t.hbin)?.name,v=h?`${t.hbin} \xB7 ${h}`:String(t.hbin);u.push(`HBin: ${v}`)}if(t.sbin!==void 0){const h=T?.get(t.sbin)?.name,v=h?`${t.sbin} \xB7 ${h}`:String(t.sbin);u.push(`SBin: ${v}`)}b.push(u.map(P).join(" \xB7 "))}}t.retestCount!==void 0&&b.push(P(`Retests: ${t.retestCount}`)),t.siteNum!==void 0&&b.push(P(`Site: ${t.siteNum}`)),t.partId!==void 0&&b.push(P(`Part ID: ${t.partId}`)),t.partial&&b.push("<i>partial die</i>"),t.probeIndex!==void 0&&b.push(P(`Probe: #${t.probeIndex}`));const $={...t.metadata??{}};for(const[f,u]of Object.entries($)){const h=me(u);if(h===void 0)continue;const v=B?.find(m=>m.key===f)?.label??Wt(f);b.push(P(`${v}: ${h}`))}return b.join("<br>")}function Bt(t){return t?.name?lt(t,t.testNumber):void 0}function Tt(t){return $e(t)?{note:`${Be} ${Te}`}:{}}export function buildMapTitle(t,e="engineering",i){const a=t.isLotStack&&t.lotSize!==void 0?`stacked (${t.lotSize} ${t.lotSize===1?"wafer":"wafers"})`:"stacked",s=n=>{const o=i?.length===1?i[0]:void 0;return o===void 0?`${n} Bin`:o.name?`${n} Bin ${o.bin} \xB7 ${o.name}`:`${n} Bin ${o.bin}`};switch(t.plotMode){case"hardBin":return{primary:"Hard Bin",secondary:""};case"softBin":return{primary:"Soft Bin",secondary:""};case"stackedBins":return{primary:s("Hard"),secondary:a};case"stackedSoftBins":return{primary:s("Soft"),secondary:a};case"metadata":{const n=t.metadataFields?.find(l=>l.key===t.activeMetadataKey),o=t.activeMetadataKey?Wt(t.activeMetadataKey):"Metadata";return{primary:n?.label??o,secondary:""}}case"stackedValues":{const n=t.testDefs?.[0],o=t.valueRange[1]||t.valueRange[0]||0,{axisLabel:l}=$t(o,Bt(n),n?.unit,e),d=l||"Value",g=t.aggrMethod?` \xB7 ${_t(t.aggrMethod)}`:"";return{primary:`${d}${g}`,secondary:a,...Tt(n)}}default:{const n=findTestDef(t.testDefs,t.activeTest),o=t.valueRange[1]||t.valueRange[0]||0,{axisLabel:l}=$t(o,Bt(n),n?.unit,e),d=l||Bt(n);if(t.passFailDisplay!=="off"){const g=n?.testNumber??t.activeTest,p=d?`${d} \xB7 #${g}`:rt(void 0,g),B=t.passFailDisplay==="spec"?"Limit pass/fail":n!==void 0&&!vt(n)?"Functional pass/fail":"Tester pass/fail";return{primary:p,secondary:B,...Tt(n)}}return{primary:d??rt(void 0,t.activeTest),secondary:"",...Tt(n)}}}}export function generateTextOverlay(t,e,i){const{plotMode:a,colorFns:s,normalize:n,activeTest:o,valueRange:l,testDefs:d,fallbackFormat:g,passFailDisplay:p="off",activeMetadataKey:B,metadataColorMap:F,allIntegerValues:T}=i,b=findTestDef(d,o),{tickFmt:$}=$t(l[1],b?.name,b?.unit,g),f=T?h=>String(Math.round(h)):$,{testNumber:u}=resolveTestNumber(o,d);return t.flatMap((h,v)=>{let m="",x="#111111";if(a==="value"&&p==="test"){const y=U(h,u,b);if(y===void 0)return[];m=y?"P":"F",x=Q(y?Mt:St)}else if(a==="value"){const y=L(h,u);if(y===void 0)return[];m=wt([y],f),x=Q(s.forValue(n(y)))}else if(a==="hardBin"||a==="softBin"){const y=a==="softBin"?h.sbin:h.hbin;if(y===void 0)return[];m=String(y),x=Q(s.forBin(y))}else if(a==="metadata"){const y=ct(h,B);if(y===void 0)return[];m=y,x=Q(F?.get(m)??w)}else{const y=L(h,0);if(y===void 0)return[];m=wt([y],f),x=Q(s.forValue(n(y)))}const V=e?e[v*2]:h.physX,E=e?e[v*2+1]:h.physY;return[{x:V,y:E,text:m,fontSize:Le(h,m),color:x,align:"center"}]})}function qt(t){return t==="top"?Math.PI/2:t==="bottom"?-Math.PI/2:t==="left"?Math.PI:0}function Re(t,e,i=720){const{center:a,radius:s,notch:n}=t,o=$=>G(e,$.x,$.y);if(!(n!==void 0&&t.diameter>150)){const $=[];for(let f=0;f<=i;f++){const u=2*Math.PI*f/i;$.push(o(Ce(t,u)))}return[{kind:"wafer-boundary",...q($,!0),lineColor:"#888888",lineWidth:1}]}const d=1.25,g=n.length,p=qt(n.type),B=Math.atan2(g,s),F=s-d,T=[];let b=!1;for(let $=0;$<=i;$++){const f=2*Math.PI*$/i;let u=f-p;for(;u>Math.PI;)u-=2*Math.PI;for(;u<-Math.PI;)u+=2*Math.PI;if(Math.abs(u)<=B){if(!b){b=!0;const h={x:a.x+s*Math.cos(p-B),y:a.y+s*Math.sin(p-B)},v={x:a.x+F*Math.cos(p),y:a.y+F*Math.sin(p)},m={x:a.x+s*Math.cos(p+B),y:a.y+s*Math.sin(p+B)};T.push(o(h),o(v),o(m))}continue}T.push(o({x:a.x+s*Math.cos(f),y:a.y+s*Math.sin(f)}))}return[{kind:"wafer-boundary",...q(T,!0),lineColor:"#888888",lineWidth:1}]}function Ne(t,e,i,a=360){const s=[],n=Math.max(1,Math.floor(i));for(let o=1;o<n;o++){const l=t.radius*o/n,d=[];for(let g=0;g<=a;g++){const p=2*Math.PI*g/a,B={x:t.center.x+l*Math.cos(p),y:t.center.y+l*Math.sin(p)};d.push(G(e,B.x,B.y))}s.push({kind:"ring-boundary",...q(d,!0),lineColor:"rgba(255,255,255,0.7)",lineWidth:1.5})}return s}function Oe(t,e,i,a){const{x:s,y:n}=t.center,o=t.radius,l=(f,u)=>G(e,f,u),d=i-s,g=Math.sqrt(Math.max(0,o*o-d*d)),p=l(i,n-g),B=l(i,n+g),F=a-n,T=Math.sqrt(Math.max(0,o*o-F*F)),b=l(s-T,a),$=l(s+T,a);return[{kind:"quadrant-boundary",...q([p,B]),lineColor:"rgba(255,255,255,0.7)",lineWidth:1.5},{kind:"quadrant-boundary",...q([b,$]),lineColor:"rgba(255,255,255,0.7)",lineWidth:1.5}]}function Ee(t,e){return t.map(i=>{const a=i.width/2,s=i.height/2;return{kind:"reticle",points:[[[i.x-a,i.y-s],[i.x+a,i.y-s],[i.x+a,i.y+s],[i.x-a,i.y+s]].map(([o,l])=>G(e,o,l))],closed:!0,lineColor:"rgba(255,255,255,0.7)",lineWidth:1.5,fill:"rgba(0,0,0,0)"}})}function Ae(t){const e=t.filter(i=>i.probeIndex!==void 0).sort((i,a)=>(i.probeIndex??0)-(a.probeIndex??0));return e.length?[{kind:"probe-path",...q(e.map(i=>({x:i.physX,y:i.physY}))),lineColor:"rgba(220,80,0,0.55)",lineWidth:1}]:[]}function He(t,e,i,a,s,n,o,l,d,g,p,B,F,T,b="off",$,f,u){const h=e.width-o,v=e.height-o,m=n?v:h,x=n?h:v,V=S=>s==="softBin"?S.sbin:S.hbin,E=S=>ct(S,$);if(e.partial){t.push({x:i,y:a,width:m,height:x,fill:Ie,type:"stacked",metadata:e.metadata});return}if(e.edgeExcluded){t.push({x:i,y:a,width:m,height:x,fill:De,type:"stacked",metadata:e.metadata});return}if(d!==void 0&&(s==="hardBin"||s==="softBin")&&V(e)!==d){t.push({x:i,y:a,width:m,height:x,fill:zt,type:"hardBin",metadata:e.metadata});return}if(u!==void 0&&s==="metadata"&&E(e)!==u){t.push({x:i,y:a,width:m,height:x,fill:zt,type:"metadata",metadata:e.metadata});return}if(s==="value"){if(b==="test"){const j=U(e,p,T),ut=j===void 0?w:j?Mt:St;t.push({x:i,y:a,width:m,height:x,fill:ut,type:"value",metadata:e.metadata});return}const S=L(e,p),N=ot(S,T);let W,J;S===void 0?W=w:b==="spec"?W=N==="failLow"?ye:N==="failHigh"?St:Mt:(W=l.forValue(g(S)),(N==="failLow"||N==="failHigh")&&(J=N)),t.push({x:i,y:a,width:m,height:x,fill:W,type:"value",specMark:J,metadata:e.metadata});return}if(s==="hardBin"||s==="softBin"){const S=V(e),N=S!=null?l.forBin(S):w,W=S!=null&&Me(e,F)===!1?!0:void 0;t.push({x:i,y:a,width:m,height:x,fill:N,type:s,binFail:W,metadata:e.metadata});return}if(s==="metadata"){const S=E(e),N=S!==void 0?f?.get(S)??w:w;t.push({x:i,y:a,width:m,height:x,fill:N,type:"metadata",metadata:e.metadata});return}const y=L(e,0),A=y!==void 0?l.forValue(g(y)):w;t.push({x:i,y:a,width:m,height:x,fill:A,type:"value",metadata:e.metadata})}function _e(t,e,i){const a=t.radius*.15,s=xt(e,a,0),n=xt(e,0,a),o=s.x+n.x>=0?-1:1,l=s.y+n.y>=0?-1:1,d={x:t.center.x+o*t.radius*.9,y:t.center.y+l*t.radius*.9},g={x:d.x+s.x,y:d.y+s.y},p={x:d.x+n.x,y:d.y+n.y};return i.push({x:g.x+s.x*.35,y:g.y+s.y*.35,text:"+X",fontSize:10,color:"#cc3300",align:"center",role:"indicator"},{x:p.x+n.x*.35,y:p.y+n.y*.35,text:"+Y",fontSize:10,color:"#0044cc",align:"center",role:"indicator"}),[{kind:"xy-indicator",...q([d,g]),lineColor:"#cc3300",lineWidth:2},{kind:"xy-indicator",...q([d,p]),lineColor:"#0044cc",lineWidth:2}]}export function buildView(t,e,i={},a){const s=i.reticles??[],{plotMode:n="value",showDieLabels:o=!1,showReticle:l=!1,showProbePath:d=!1,ringCount:g=4,showRingBoundaries:p=!1,showQuadrantBoundaries:B=!1,showXYIndicator:F=!1,binColorScheme:T="default",valueColorScheme:b="default",reverseValueScheme:$=!1,useDefinedBinColors:f=!0,binColors:u,passBins:h=[1],highlightBin:v,highlightMetadataValue:m,interactiveTransform:x,valueRange:V,metadataValueOrder:E,testDefs:y,activeTest:A=0,activeMetadataKey:S,fallbackFormat:N="engineering",aggregationMethod:W,lotSize:J,dataAxisFlip:j,isLotStack:ut=!1,logScale:Xt,colorbarRangeMode:Yt="spec",passFailDisplay:Gt}=i,It=Gt??"off",ft=a?.hbinDefs,dt=a?.sbinDefs,Dt=a?.metadataFields,Qt=Dt?.find(c=>c.key===S),Ut={x:(j?.x??!1)!==(x?.flipX??!1),y:(j?.y??!1)!==(x?.flipY??!1)},Jt=ft?new Map(ft.map(c=>[c.bin,c])):null,Zt=dt?new Map(dt.map(c=>[c.bin,c])):null,te=n==="softBin"?Zt:Jt,ee=new Set(h),ht=u&&xe(u,e)?u:be(e,{passBins:h,binColorScheme:T,hbinDefs:ft,sbinDefs:dt,useDefinedBinColors:f}),ne=n==="softBin"?ht.soft:ht.hard,Vt={forValue:Fe(ge(b,$)),forBin:c=>ne.get(c)??w};let{testNumber:H}=resolveTestNumber(A,y);if(!y?.length&&n==="value"&&!e.some(r=>r.testValues&&H in r.testValues)){const r=e.reduce((M,D)=>{if(!D.testValues)return M;const k=Object.keys(D.testValues).map(Number),R=k.length?ke(k):void 0;return R!==void 0&&(M===void 0||R<M)?R:M},void 0);r!==void 0&&(H=r)}let Z;if(Array.isArray(V))Z=V;else if(V){const{testNumber:c}=resolveTestNumber(V.test,y);Z=c===H?V.range:void 0}const I=findTestDef(y,H),Ct=n==="value"&&I!==void 0&&!vt(I),ae=I!==void 0&&(I.limitLow!==void 0||I.limitHigh!==void 0);let K="off";n==="value"&&(Ct?K="test":It==="spec"&&ae?K="spec":It==="test"&&e.some(c=>U(c,H,I)!==void 0)&&(K="test"));const Ft=K==="spec"?"spec":Yt,mt=n==="stackedValues"||n==="stackedBins"||n==="stackedSoftBins";let O,_,tt=!1;if(Z){[O,_]=Z;let c=!0,r=!1;for(const M of e){const D=mt?L(M,0):L(M,H);if(D!==void 0&&(r=!0,!Number.isInteger(D))){c=!1;break}}tt=r&&c}else{const c=!mt&&n==="value"&&Ft==="spec"&&I&&(I.limitLow!==void 0||I.limitHigh!==void 0);let r=1/0,M=-1/0,D=!0;for(const k of e){const R=mt?L(k,0):L(k,H);R!==void 0&&(R<r&&(r=R),R>M&&(M=R),Number.isInteger(R)||(D=!1))}isFinite(r)||(D=!1),tt=D,c?(O=I.limitLow!==void 0?I.limitLow:isFinite(r)?r:0,_=I.limitHigh!==void 0?I.limitHigh:isFinite(M)?M:1):(O=isFinite(r)?r:0,_=isFinite(M)?M:1)}O===_&&(_=O+1);const Lt=Ct?!1:Xt??I?.logScale??!1,Pt=Lt&&O>0&&_>0,ie=Pt;let et;if(Pt){const c=Math.log10(O),r=Math.log10(_)-c;et=M=>M<=0?0:Kt((Math.log10(M)-c)/r)}else et=c=>Kt((c-O)/(_-O));const C=Ve(t,j,x);let Rt=null;if(t.notch){const c=qt(t.notch.type),r=xt(C.physicalToScreen,Math.cos(c)*t.radius,Math.sin(c)*t.radius),M=Math.hypot(r.x,r.y);M>0&&(Rt={x:r.x/M,y:r.y/M})}let X=1/0;for(let c=0;c<e.length;c++){const r=e[c];r.width<X&&(X=r.width),r.height<X&&(X=r.height)}const Nt=1,se=X!==1/0?Math.min(Nt,X*.12):Nt,Ot=[],pt=[],oe=C.bakedToScreen.a===1&&C.bakedToScreen.b===0&&C.bakedToScreen.c===0&&C.bakedToScreen.d===1&&C.bakedToScreen.e===0&&C.bakedToScreen.f===0;let z=null;if(!oe){z=new Float64Array(e.length*2);for(let c=0;c<e.length;c++){const r=G(C.bakedToScreen,e[c].physX,e[c].physY);z[c*2]=r.x,z[c*2+1]=r.y}}const re=pe(C.gridToScreen),le=n==="hardBin"||n==="softBin"||n==="stackedBins"||n==="stackedSoftBins",ce=n==="softBin"||n==="stackedSoftBins";let nt=le?new Map:void 0;const yt=n==="value"&&K==="spec"?{pass:0,failHigh:0,failLow:0}:void 0,gt=n==="value"&&K==="test"?{pass:0,fail:0}:void 0,Et=n==="metadata";let at=Et?new Map:void 0,it=null;if(Et&&S){const c=collectMetadataValues(e,S),r=E?.key===S?E.values:void 0,M=r?[...r,...c.filter(D=>!r.includes(D))]:c;it=new Map(M.map((D,k)=>[D,Qt?.values?.find(R=>R.value===D)?.color??Se(k)]))}for(let c=0;c<e.length;c++){const r=e[c],M=z?z[c*2]:r.physX,D=z?z[c*2+1]:r.physY;if(pt.push({x:M,y:D}),nt&&!r.partial&&!r.edgeExcluded){const k=ce?r.sbin:r.hbin;k!=null&&nt.set(k,(nt.get(k)??0)+1)}if(yt&&!r.partial&&!r.edgeExcluded){const k=ot(L(r,H),I);k&&yt[k]++}if(gt&&!r.partial&&!r.edgeExcluded){const k=U(r,H,I);k!==void 0&>[k?"pass":"fail"]++}if(at&&!r.partial&&!r.edgeExcluded){const k=ct(r,S);k!==void 0&&at.set(k,(at.get(k)??0)+1)}He(Ot,r,M,D,n,re,se,Vt,v,et,H,te,ee,I,K,S,it,m)}const At=o?generateTextOverlay(e,z,{plotMode:n,colorFns:Vt,normalize:et,activeTest:A,valueRange:[O,_],testDefs:y,fallbackFormat:N,passFailDisplay:K,activeMetadataKey:S,metadataColorMap:it,allIntegerValues:tt}):[],Y=Re(t,C.physicalToScreen);p&&Y.push(...Ne(t,C.physicalToScreen,g)),B&&Y.push(...Oe(t,C.physicalToScreen,t.center.x,t.center.y)),l&&Y.push(...Ee(s,C.gridToScreen)),d&&Y.push(...Ae(e)),F&&Y.push(..._e(t,C.gridToScreen,At));const ue=pt.length>0?{minX:t.center.x-t.radius,maxX:t.center.x+t.radius,minY:t.center.y-t.radius,maxY:t.center.y+t.radius}:null;return{rectangles:Ot,hoverPoints:pt,texts:At,overlays:Y,plotMode:n,binColorScheme:T,valueColorScheme:b,reverseValueScheme:$,binColors:ht,metadata:t.metadata??null,dies:e,valueRange:[O,_],allIntegerValues:tt,testDefs:y,activeTest:A,logScale:ie,logScaleRequested:Lt,aggrMethod:W,lotSize:J,axisFlip:Ut,dataAxisFlip:{x:j?.x??!1,y:j?.y??!1},gridToScreen:C.gridToScreen,rotation:((t.orientation+(x?.rotation??0))%360+360)%360,hasReticle:s.length>0,isLotStack:ut,passFailDisplay:K,colorbarRangeMode:Ft,waferCenter:t.center,waferRadius:t.radius,notchDir:Rt,binCounts:nt,metadataCounts:at,metadataColorMap:it,activeMetadataKey:S,metadataFields:Dt,specCounts:yt,passFailCounts:gt,dieBounds:ue}}export{getDieKey}from"../core/dies.js";
|