@wafertools/wafermap 0.21.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (138) hide show
  1. package/CHANGELOG.md +751 -0
  2. package/LICENSE +21 -0
  3. package/README.md +79 -0
  4. package/dist/index.d.ts +4 -0
  5. package/dist/index.js +1 -0
  6. package/dist/packages/canvas-adapter/canvasTheme.d.ts +28 -0
  7. package/dist/packages/canvas-adapter/canvasTheme.js +1 -0
  8. package/dist/packages/canvas-adapter/charts/barPanel.d.ts +52 -0
  9. package/dist/packages/canvas-adapter/charts/barPanel.js +1 -0
  10. package/dist/packages/canvas-adapter/charts/binCluster.d.ts +17 -0
  11. package/dist/packages/canvas-adapter/charts/binCluster.js +1 -0
  12. package/dist/packages/canvas-adapter/charts/boxplot.d.ts +45 -0
  13. package/dist/packages/canvas-adapter/charts/boxplot.js +1 -0
  14. package/dist/packages/canvas-adapter/charts/capability.d.ts +41 -0
  15. package/dist/packages/canvas-adapter/charts/capability.js +1 -0
  16. package/dist/packages/canvas-adapter/charts/chartShell.d.ts +156 -0
  17. package/dist/packages/canvas-adapter/charts/chartShell.js +1 -0
  18. package/dist/packages/canvas-adapter/charts/correlation.d.ts +35 -0
  19. package/dist/packages/canvas-adapter/charts/correlation.js +1 -0
  20. package/dist/packages/canvas-adapter/charts/histogram.d.ts +30 -0
  21. package/dist/packages/canvas-adapter/charts/histogram.js +1 -0
  22. package/dist/packages/canvas-adapter/charts/index.d.ts +16 -0
  23. package/dist/packages/canvas-adapter/charts/index.js +1 -0
  24. package/dist/packages/canvas-adapter/charts/palette.d.ts +26 -0
  25. package/dist/packages/canvas-adapter/charts/palette.js +1 -0
  26. package/dist/packages/canvas-adapter/charts/regionYieldDiagram.d.ts +18 -0
  27. package/dist/packages/canvas-adapter/charts/regionYieldDiagram.js +1 -0
  28. package/dist/packages/canvas-adapter/charts/scatter.d.ts +37 -0
  29. package/dist/packages/canvas-adapter/charts/scatter.js +1 -0
  30. package/dist/packages/canvas-adapter/icons.d.ts +2 -0
  31. package/dist/packages/canvas-adapter/icons.js +42 -0
  32. package/dist/packages/canvas-adapter/index.d.ts +12 -0
  33. package/dist/packages/canvas-adapter/index.js +1 -0
  34. package/dist/packages/canvas-adapter/insightsTab.d.ts +76 -0
  35. package/dist/packages/canvas-adapter/insightsTab.js +1 -0
  36. package/dist/packages/canvas-adapter/metadataBadge.d.ts +26 -0
  37. package/dist/packages/canvas-adapter/metadataBadge.js +1 -0
  38. package/dist/packages/canvas-adapter/renderWaferGallery.d.ts +181 -0
  39. package/dist/packages/canvas-adapter/renderWaferGallery.js +20 -0
  40. package/dist/packages/canvas-adapter/renderWaferMap.d.ts +316 -0
  41. package/dist/packages/canvas-adapter/renderWaferMap.js +1 -0
  42. package/dist/packages/canvas-adapter/summaryPanel.d.ts +262 -0
  43. package/dist/packages/canvas-adapter/summaryPanel.js +3 -0
  44. package/dist/packages/canvas-adapter/toCanvas.d.ts +106 -0
  45. package/dist/packages/canvas-adapter/toCanvas.js +1 -0
  46. package/dist/packages/canvas-adapter/toolbar.d.ts +496 -0
  47. package/dist/packages/canvas-adapter/toolbar.js +9 -0
  48. package/dist/packages/canvas-adapter/userGuideHtml.d.ts +2 -0
  49. package/dist/packages/canvas-adapter/userGuideHtml.js +968 -0
  50. package/dist/packages/canvas-adapter/version.d.ts +3 -0
  51. package/dist/packages/canvas-adapter/version.js +1 -0
  52. package/dist/packages/core/aggregates.d.ts +54 -0
  53. package/dist/packages/core/aggregates.js +1 -0
  54. package/dist/packages/core/classify.d.ts +24 -0
  55. package/dist/packages/core/classify.js +1 -0
  56. package/dist/packages/core/dies.d.ts +92 -0
  57. package/dist/packages/core/dies.js +1 -0
  58. package/dist/packages/core/index.d.ts +13 -0
  59. package/dist/packages/core/index.js +1 -0
  60. package/dist/packages/core/inference/grid.d.ts +35 -0
  61. package/dist/packages/core/inference/grid.js +1 -0
  62. package/dist/packages/core/inference/index.d.ts +7 -0
  63. package/dist/packages/core/inference/index.js +1 -0
  64. package/dist/packages/core/inference/pitch.d.ts +26 -0
  65. package/dist/packages/core/inference/pitch.js +1 -0
  66. package/dist/packages/core/inference/wafer.d.ts +30 -0
  67. package/dist/packages/core/inference/wafer.js +1 -0
  68. package/dist/packages/core/metadata.d.ts +31 -0
  69. package/dist/packages/core/metadata.js +1 -0
  70. package/dist/packages/core/probe.d.ts +16 -0
  71. package/dist/packages/core/probe.js +1 -0
  72. package/dist/packages/core/reticle.d.ts +68 -0
  73. package/dist/packages/core/reticle.js +1 -0
  74. package/dist/packages/core/transforms.d.ts +142 -0
  75. package/dist/packages/core/transforms.js +1 -0
  76. package/dist/packages/core/utils.d.ts +60 -0
  77. package/dist/packages/core/utils.js +1 -0
  78. package/dist/packages/core/wafer.d.ts +45 -0
  79. package/dist/packages/core/wafer.js +1 -0
  80. package/dist/packages/renderer/buildView.d.ts +420 -0
  81. package/dist/packages/renderer/buildView.js +1 -0
  82. package/dist/packages/renderer/buildWaferMap.d.ts +543 -0
  83. package/dist/packages/renderer/buildWaferMap.js +1 -0
  84. package/dist/packages/renderer/colorMap.d.ts +46 -0
  85. package/dist/packages/renderer/colorMap.js +1 -0
  86. package/dist/packages/renderer/colorSchemes.d.ts +40 -0
  87. package/dist/packages/renderer/colorSchemes.js +1 -0
  88. package/dist/packages/renderer/fmt.d.ts +30 -0
  89. package/dist/packages/renderer/fmt.js +1 -0
  90. package/dist/packages/renderer/index.d.ts +7 -0
  91. package/dist/packages/renderer/index.js +1 -0
  92. package/dist/packages/stats/analyzeWaferLot.d.ts +5 -0
  93. package/dist/packages/stats/analyzeWaferLot.js +1 -0
  94. package/dist/packages/stats/analyzeWaferMap.d.ts +13 -0
  95. package/dist/packages/stats/analyzeWaferMap.js +1 -0
  96. package/dist/packages/stats/binPareto.d.ts +49 -0
  97. package/dist/packages/stats/binPareto.js +1 -0
  98. package/dist/packages/stats/boxplot.d.ts +39 -0
  99. package/dist/packages/stats/boxplot.js +1 -0
  100. package/dist/packages/stats/capability.d.ts +55 -0
  101. package/dist/packages/stats/capability.js +1 -0
  102. package/dist/packages/stats/clusterDetection.d.ts +18 -0
  103. package/dist/packages/stats/clusterDetection.js +1 -0
  104. package/dist/packages/stats/correlation.d.ts +55 -0
  105. package/dist/packages/stats/correlation.js +1 -0
  106. package/dist/packages/stats/facets.d.ts +74 -0
  107. package/dist/packages/stats/facets.js +1 -0
  108. package/dist/packages/stats/filterFindings.d.ts +9 -0
  109. package/dist/packages/stats/filterFindings.js +1 -0
  110. package/dist/packages/stats/findingsNarrative.d.ts +3 -0
  111. package/dist/packages/stats/findingsNarrative.js +1 -0
  112. package/dist/packages/stats/histogram.d.ts +42 -0
  113. package/dist/packages/stats/histogram.js +1 -0
  114. package/dist/packages/stats/index.d.ts +27 -0
  115. package/dist/packages/stats/index.js +1 -0
  116. package/dist/packages/stats/math.d.ts +10 -0
  117. package/dist/packages/stats/math.js +1 -0
  118. package/dist/packages/stats/patternClassification.d.ts +107 -0
  119. package/dist/packages/stats/patternClassification.js +1 -0
  120. package/dist/packages/stats/regions.d.ts +57 -0
  121. package/dist/packages/stats/regions.js +1 -0
  122. package/dist/packages/stats/renderFindingsReport.d.ts +7 -0
  123. package/dist/packages/stats/renderFindingsReport.js +41 -0
  124. package/dist/packages/stats/renderSummaryReport.d.ts +60 -0
  125. package/dist/packages/stats/renderSummaryReport.js +68 -0
  126. package/dist/packages/stats/reportHtml.d.ts +42 -0
  127. package/dist/packages/stats/reportHtml.js +301 -0
  128. package/dist/packages/stats/scatter.d.ts +24 -0
  129. package/dist/packages/stats/scatter.js +1 -0
  130. package/dist/packages/stats/types.d.ts +269 -0
  131. package/dist/packages/stats/types.js +1 -0
  132. package/dist/packages/stats/yield.d.ts +56 -0
  133. package/dist/packages/stats/yield.js +1 -0
  134. package/dist/packages/worker/index.d.ts +44 -0
  135. package/dist/packages/worker/index.js +1 -0
  136. package/dist/packages/worker/wafermap.worker.d.ts +44 -0
  137. package/dist/packages/worker/wafermap.worker.js +1 -0
  138. package/package.json +122 -0
@@ -0,0 +1,543 @@
1
+ import type { Die } from '../core/dies.js';
2
+ import type { DieMetadata, WaferMetadata } from '../core/metadata.js';
3
+ import type { Wafer } from '../core/wafer.js';
4
+ import type { Reticle } from '../core/reticle.js';
5
+ import { type View, type ViewOptions, type PlotMode } from './buildView.js';
6
+ /**
7
+ * Test result for a single die position, as output by the prober.
8
+ * `x` and `y` are **die grid positions** (prober step coordinates) — integers
9
+ * such as −7, 0, 5. They are NOT millimetre values.
10
+ */
11
+ export interface DieResult {
12
+ /** Die grid X position (prober step coordinate). */
13
+ x: number;
14
+ /** Die grid Y position (prober step coordinate). */
15
+ y: number;
16
+ /**
17
+ * Test values keyed by test number — a stable per-test identity such as
18
+ * STDF TEST_NUM or an equivalent application-defined integer. Keying by test
19
+ * number is unaffected by test ordering changes in the test program.
20
+ * Example: `{ 1050: 1.42e-3, 1060: 0.487, 1070: 8.3e-12 }`
21
+ */
22
+ testValues?: Record<number, number>;
23
+ /**
24
+ * Recorded per-test pass/fail verdicts keyed by test number (true = pass),
25
+ * parallel to `testValues`. Parametric tests carry a value in `testValues`
26
+ * and may optionally carry the tester's recorded verdict here (e.g. STDF
27
+ * PTR TEST_FLG); functional tests (`testType: 'F'` in `testDefs`) carry a
28
+ * verdict here ONLY — they have no measured value.
29
+ * Example: `{ 2001: true, 2002: false }`
30
+ */
31
+ testPass?: Record<number, boolean>;
32
+ /** Hard bin assignment (physical sort result). */
33
+ hbin?: number;
34
+ /** Soft bin assignment (test-program failure category). */
35
+ sbin?: number;
36
+ /**
37
+ * Number of times this die position appeared in the input results array.
38
+ * Populated automatically by `buildWaferMap` — do not set manually.
39
+ * Only present when the die was tested more than once.
40
+ */
41
+ retestCount?: number;
42
+ /**
43
+ * STDF `site_num` — which parallel test site tested this die.
44
+ * Only meaningful when more than one distinct value appears across the wafer
45
+ * (i.e. the wafer was tested with a multi-site probe card).
46
+ */
47
+ siteNum?: number;
48
+ /**
49
+ * STDF `pir.part_id` — tester-assigned identifier for this tested unit.
50
+ * At most fabs this encodes the probe sequence (the order in which the prober
51
+ * stepped across the wafer), but the field is semantically neutral — its
52
+ * meaning is fab-specific.
53
+ */
54
+ partId?: number;
55
+ /** Per-die metadata — all fields appear automatically in hover tooltips. See `DieMetadata → §12.4`. */
56
+ metadata?: DieMetadata;
57
+ }
58
+ /** Wafer geometry parameters — all optional; any omitted fields are inferred. */
59
+ export interface WaferConfig {
60
+ /** Wafer diameter in mm. Inferred from grid extent × pitch when omitted. */
61
+ diameter?: number;
62
+ /**
63
+ * The prober coordinate `(x, y)` that lies at the physical centre of the
64
+ * wafer. Supply this for **partial or sparse** data (a half wafer, a single
65
+ * quadrant, an edge ring, a small cluster) or whenever the prober origin is
66
+ * not the wafer centre.
67
+ *
68
+ * When omitted, the centre is inferred as the midpoint of the observed die
69
+ * positions — correct only when the data spans a full, roughly symmetric
70
+ * wafer. For partial data that assumption is wrong: the data midpoint is not
71
+ * the wafer centre, so dies would be mis-positioned relative to the true
72
+ * boundary and notch. Setting this anchors placement to the real centre.
73
+ *
74
+ * Note: this does not change the public `die.x` / `die.y` labels — those are
75
+ * always the original prober coordinates. It only fixes physical placement.
76
+ */
77
+ center?: {
78
+ x: number;
79
+ y: number;
80
+ };
81
+ /**
82
+ * Orientation mark direction. Standard dimensions are derived automatically
83
+ * from the wafer diameter:
84
+ * - ≤ 100 mm → 32.5 mm orientation flat (SEMI M1)
85
+ * - ≤ 150 mm → 57.5 mm orientation flat (SEMI M1)
86
+ * - > 150 mm → V-notch (~3.5 mm wide, 1.25 mm deep — SEMI M1)
87
+ */
88
+ notch?: {
89
+ type: 'top' | 'bottom' | 'left' | 'right';
90
+ };
91
+ /**
92
+ * Wafer orientation in degrees. Positive values rotate the map
93
+ * clockwise. The notch/flat position is set by `notch.type` and is not
94
+ * affected by this value — `orientation` rotates the *die grid* on the display.
95
+ *
96
+ * Common values: 0 (default), 90, 180, 270.
97
+ */
98
+ orientation?: number;
99
+ metadata?: WaferMetadata;
100
+ /**
101
+ * Physical edge exclusion zone in mm measured from the wafer edge inward.
102
+ * Dies whose centres fall inside this band are rendered dimmed and marked
103
+ * `edgeExcluded: true` on the returned Die objects.
104
+ */
105
+ edgeExclusion?: number;
106
+ }
107
+ /**
108
+ * Die geometry and coordinate-system parameters — all optional.
109
+ * When omitted, dimensions are estimated from the grid layout.
110
+ */
111
+ export interface DieConfig {
112
+ /** Die width in mm (= X pitch). */
113
+ width?: number;
114
+ /** Die height in mm (= Y pitch). */
115
+ height?: number;
116
+ /**
117
+ * Where the prober places coordinate (0,0) on the wafer grid.
118
+ *
119
+ * - `'center'` (default) — grid already near (0,0); centroid offset applied.
120
+ * - `'LL'` — (0,0) at lower-left; positive x right, positive y up.
121
+ * - `'UL'` — (0,0) at upper-left; positive x right, positive y **down**.
122
+ * - `'LR'` — (0,0) at lower-right; positive x **left**, positive y up.
123
+ * - `'UR'` — (0,0) at upper-right; positive x left, positive y down.
124
+ * - `'custom'` — apply explicit `offset` (in grid steps) to centre the grid.
125
+ *
126
+ */
127
+ coordinateOrigin?: {
128
+ type: 'center' | 'LL' | 'UL' | 'LR' | 'UR' | 'custom';
129
+ /** Grid-step offset to the true centre. Used only when type is `'custom'`. */
130
+ offset?: {
131
+ x: number;
132
+ y: number;
133
+ };
134
+ };
135
+ /**
136
+ * Direction in which the prober Y axis increases.
137
+ * `'up'` (default) is standard Cartesian; `'down'` is row/matrix convention
138
+ * (row 1 at top). The library flips the display Y axis so the map renders
139
+ * with +Y pointing up regardless of the prober convention.
140
+ */
141
+ yAxisDirection?: 'up' | 'down';
142
+ /**
143
+ * Direction in which the prober X axis increases.
144
+ * `'right'` (default) is standard; `'left'` is used for backside probing or
145
+ * mirrored coordinate systems.
146
+ */
147
+ xAxisDirection?: 'right' | 'left';
148
+ }
149
+ /**
150
+ * Reticle (stepper field) overlay configuration.
151
+ * Dimensions are in die counts; `anchorDie` pins a specific die index to the
152
+ * reticle's min-x/min-y corner (bottom-left, since +Y is up).
153
+ */
154
+ export interface ReticleConfig {
155
+ /** Field width in number of dies. */
156
+ width: number;
157
+ /** Field height in number of dies. */
158
+ height: number;
159
+ /**
160
+ * Die grid index (x, y) — in original die coordinates (`die.x`/`die.y`) — that
161
+ * sits at the reticle field's min-x/min-y corner (bottom-left, since +Y is up).
162
+ * That die becomes the leftmost, bottom-most die of the field it belongs to;
163
+ * every field boundary is placed relative to it. Controls the phase
164
+ * (alignment) of the reticle grid. Defaults to `{x: 0, y: 0}`.
165
+ */
166
+ anchorDie?: {
167
+ x: number;
168
+ y: number;
169
+ };
170
+ }
171
+ /**
172
+ * Lot-level stacking — collapse results from several wafers into a single map.
173
+ * The aggregated result is used as the `results` for this map; any top-level
174
+ * `results` field is ignored when `lotStack` is present.
175
+ */
176
+ export interface LotStackConfig {
177
+ /** One `DieResult[]` per wafer in the lot. */
178
+ results: DieResult[][];
179
+ /** Aggregation method applied per die position across all wafers. */
180
+ method: 'mean' | 'median' | 'stddev' | 'min' | 'max' | 'count' | 'countBin' | 'mode' | 'percent';
181
+ /** Required when `method` is `'countBin'` or `'percent'`. */
182
+ targetBin?: number;
183
+ }
184
+ /**
185
+ * Metadata for one test measurement.
186
+ * Provides a human-readable name and optional unit for display in tooltips,
187
+ * the colorbar, and the mode selector.
188
+ */
189
+ export interface TestDef {
190
+ /**
191
+ * Stable per-test identity — an application-defined integer that uniquely
192
+ * identifies this test within a test program (for example, STDF TEST_NUM).
193
+ * Must match the key used in `DieResult.testValues` / `DieResult.testPass`.
194
+ */
195
+ testNumber: number;
196
+ /** Human-readable test name, e.g. `"Idsat"` or `"Vth"`. */
197
+ name: string;
198
+ /** Physical unit string, e.g. `"A"`, `"V"`, `"Ω"`. Shown in tooltip and colorbar. */
199
+ unit?: string;
200
+ /**
201
+ * When true, value normalization and the colorbar use log₁₀ scale for this test.
202
+ * Silently falls back to linear when any die value is ≤ 0. Default: false.
203
+ */
204
+ logScale?: boolean;
205
+ /**
206
+ * Lower specification limit in the same units as the test value.
207
+ * When set, values below this limit are considered out-of-spec.
208
+ * Both limits are optional independently — some tests have one-sided limits.
209
+ */
210
+ limitLow?: number;
211
+ /**
212
+ * Upper specification limit in the same units as the test value.
213
+ * When set, values above this limit are considered out-of-spec.
214
+ */
215
+ limitHigh?: number;
216
+ /**
217
+ * Test kind: `'P'` (parametric — a continuous measured value) or `'F'`
218
+ * (functional — a pass/fail outcome, conventionally recorded as 1 = pass,
219
+ * 0 = fail, e.g. an STDF FTR). Functional tests render on the wafer map
220
+ * like any other test value, but are excluded from parametric statistics —
221
+ * per-test descriptive stats, capability, correlation, distribution charts,
222
+ * and regional value findings — where a mean or Cpk of a binary outcome
223
+ * would be meaningless. Default: `'P'`.
224
+ */
225
+ testType?: 'P' | 'F';
226
+ }
227
+ /**
228
+ * True when `def` describes a parametric (continuous-value) test — i.e. its
229
+ * values are valid input for parametric statistics (mean, quartiles, Cpk,
230
+ * correlation). Functional tests (`testType: 'F'`) record pass/fail outcomes,
231
+ * not measurements. An undefined def or undefined `testType` defaults to
232
+ * parametric, so untyped callers keep today's behaviour.
233
+ */
234
+ export declare function isParametricTest(def: TestDef | undefined): boolean;
235
+ /**
236
+ * Metadata for one bin number (hard bin or soft bin).
237
+ * Hard bins and soft bins have independent number spaces (both 0–32767 per STDF V4)
238
+ * so separate `hbinDefs` and `sbinDefs` arrays are used — never mixed.
239
+ */
240
+ export interface BinDef {
241
+ /** Numeric bin value this definition describes. */
242
+ bin: number;
243
+ /** Human-readable bin name, e.g. `"Pass"` or `"Contact Open"`. */
244
+ name: string;
245
+ /**
246
+ * Optional CSS color override for this bin.
247
+ * When set, overrides the active colour scheme for this bin value.
248
+ */
249
+ color?: string;
250
+ }
251
+ /**
252
+ * Named definition for one `die.metadata` key, opting it into the `'metadata'`
253
+ * plot mode's toolbar entry, color fill, and legend. Presence in
254
+ * `metadataFields` is what makes a key selectable — wmap never guesses which
255
+ * metadata keys are "categorical enough" to plot.
256
+ */
257
+ export interface MetadataFieldDef {
258
+ /** The `die.metadata` key this definition applies to. */
259
+ key: string;
260
+ /** Display name for the toolbar entry and map title. Defaults to a Title-Cased version of `key`. */
261
+ label?: string;
262
+ /**
263
+ * Optional per-value name/color overrides. Distinct values not listed here
264
+ * are still shown — auto-labeled with the raw (stringified) value and
265
+ * auto-colored from an ordered palette.
266
+ */
267
+ values?: Array<{
268
+ value: string;
269
+ label?: string;
270
+ color?: string;
271
+ }>;
272
+ }
273
+ /** Input accepted by {@link buildWaferMap}. All fields are optional. */
274
+ /** Fields common to both single-wafer and lot-stack inputs. */
275
+ export interface WaferMapInputBase {
276
+ /** Wafer geometry — diameter, notch direction, orientation, edge exclusion. */
277
+ waferConfig?: WaferConfig;
278
+ /** Die size and coordinate-system conventions. */
279
+ dieConfig?: DieConfig;
280
+ /** Pre-built die array. When supplied, geometry generation is skipped. */
281
+ dies?: Die[];
282
+ /**
283
+ * Reticle (stepper field) overlay.
284
+ * When provided, `showReticle` defaults to `true` in the scene options.
285
+ */
286
+ reticleConfig?: ReticleConfig;
287
+ /**
288
+ * Bin values that count as pass for yield calculation.
289
+ * Defaults to `[1]` (industry convention: bin 1 = pass).
290
+ * Set to an empty array to suppress yield calculation.
291
+ */
292
+ passBins?: number[];
293
+ /**
294
+ * How to handle multiple `DieResult` entries for the same die position (retests).
295
+ *
296
+ * - `'last'` (default) — keep the most recent result. Matches pre-existing behaviour
297
+ * and is appropriate when records are in probe order and the final touch
298
+ * is the authoritative result.
299
+ * - `'first'` — keep the earliest result. Useful when the first touch is canonical
300
+ * or when the array is already sorted best-first.
301
+ * - `'best'` — keep the result with the best hard bin outcome. Pass beats fail
302
+ * (determined by `passBins`); within the same category, the lower hbin
303
+ * number wins. Requires `hbin` on each result — if either record in a
304
+ * comparison has no `hbin`, the existing record is kept. `sbin` and
305
+ * `testValues` are not used as ordering criteria.
306
+ * - `'worst'` — inverse of `'best'`: fail beats pass; higher hbin number wins within
307
+ * each category. Same `hbin` requirement applies.
308
+ *
309
+ * Regardless of policy, `die.retestCount` is set on any die that was tested more than
310
+ * once, so retest hotspots are always visible.
311
+ */
312
+ retestPolicy?: 'last' | 'first' | 'best' | 'worst';
313
+ /**
314
+ * Named test definitions — one per entry in `die.values[]`.
315
+ * When provided, tooltips show `"Idsat: 1.23 A"` instead of `"Values: 1.23"`,
316
+ * and the mode selector offers a per-test dropdown entry.
317
+ */
318
+ testDefs?: TestDef[];
319
+ /**
320
+ * Named hard bin definitions — one per distinct `hbin` value.
321
+ * Hard bins and soft bins have independent number spaces (STDF V4: both 0–32767),
322
+ * so they are defined separately.
323
+ * When provided, the bin legend and tooltips show names like `"Pass"` instead of `"Bin 1"`.
324
+ * A `color` on a `BinDef` overrides the active colour scheme for that bin.
325
+ */
326
+ hbinDefs?: BinDef[];
327
+ /**
328
+ * Named soft bin definitions — one per distinct `sbin` value.
329
+ * Soft bins are the logical/test-program classification; hard bins are the physical sort result.
330
+ * Both spaces range 0–32767 and may overlap — define them separately.
331
+ */
332
+ sbinDefs?: BinDef[];
333
+ /**
334
+ * Named definitions for `die.metadata` keys that should be selectable as the
335
+ * `'metadata'` plot mode — a generic categorical view driven by whatever
336
+ * per-die classification a host already has in `metadata` (project, vendor,
337
+ * test site, wafer zone, …), distinct from test/bin results. A key only
338
+ * appears in the toolbar when it's listed here (opt-in, never auto-detected)
339
+ * and at least one die actually has that key set.
340
+ */
341
+ metadataFields?: MetadataFieldDef[];
342
+ /**
343
+ * Controls how edge-excluded dies (dies within the edge exclusion zone) are counted in yield.
344
+ *
345
+ * - `'exclude'` (default) — edge dies are excluded from both numerator and denominator.
346
+ * `yieldPercent` = `passDies / (passDies + failDies)` counting only non-edge dies.
347
+ * - `'denominator-only'` — edge dies are counted in `totalDies` denominator but never
348
+ * as pass. This gives gross die yield: pass count vs. total populated area.
349
+ * `yieldPercentGross` is also set on `YieldSummary` for unambiguous reference.
350
+ */
351
+ edgeDieYieldMode?: 'exclude' | 'denominator-only';
352
+ }
353
+ /** Single-wafer input — pass one wafer's die results directly. */
354
+ export interface WaferMapInputSingle extends WaferMapInputBase {
355
+ /** Per-die test results from the prober. */
356
+ results?: DieResult[];
357
+ lotStack?: never;
358
+ }
359
+ /** Lot-stack input — collapse results from multiple wafers into a single aggregated map. */
360
+ export interface WaferMapInputLotStack extends WaferMapInputBase {
361
+ /** Lot-level stacking — collapse results from several wafers into a single map. */
362
+ lotStack: LotStackConfig;
363
+ results?: never;
364
+ }
365
+ /**
366
+ * Input accepted by {@link buildWaferMap}.
367
+ * Use {@link WaferMapInputSingle} for a single wafer or {@link WaferMapInputLotStack}
368
+ * for an aggregated lot-stack map. Passing both `results` and `lotStack` is a type error.
369
+ */
370
+ export type WaferMapInput = WaferMapInputSingle | WaferMapInputLotStack;
371
+ /** Options forwarded to {@link buildView}. */
372
+ export interface WaferMapOptions extends ViewOptions {
373
+ debug?: boolean;
374
+ }
375
+ export interface YieldSummary {
376
+ /** Dies with a bin in `passBins`. */
377
+ passDies: number;
378
+ /** Full dies inside wafer boundary with a bin not in `passBins`. */
379
+ failDies: number;
380
+ /** Full dies whose centres fall within the edge exclusion zone. */
381
+ edgeExcludedDies: number;
382
+ /** Dies that straddle the wafer boundary. */
383
+ partialDies: number;
384
+ /** Total full dies inside wafer boundary used for yield (excludes edge and partial). */
385
+ totalDies: number;
386
+ /** `(passDies / totalDies) × 100` in [0, 100], or `null` when no bin data is present. */
387
+ yieldPercent: number | null;
388
+ /**
389
+ * Gross die yield: `passDies / (passDies + failDies + edgeExcludedDies) × 100` in [0, 100].
390
+ * Set when `edgeDieYieldMode: 'denominator-only'` was passed; `null` otherwise.
391
+ */
392
+ yieldPercentGross?: number | null;
393
+ }
394
+ /**
395
+ * A non-fatal advisory raised while `buildWaferMap` inferred geometry from data.
396
+ * Surface these to the user (a panel, a log, a status line) instead of letting
397
+ * silent inference mask questionable input — an engineer reading a wafer map
398
+ * built on guessed geometry must be told the geometry was guessed.
399
+ */
400
+ export interface WaferWarning {
401
+ /**
402
+ * Stable machine-readable key for the advisory. Branch on this, not on
403
+ * `message` (which is prose and may be reworded). Known codes:
404
+ * - `'partial-coverage'` — data does not span a full symmetric wafer; the
405
+ * inferred diameter and centre may be wrong and dies may be mis-positioned.
406
+ * - `'geometry-conflict'` — `waferConfig.diameter` and `dieConfig.width`/`height`
407
+ * were BOTH supplied, and the wafer is too small to contain the probed dies.
408
+ * Since a die with test results is a real prober position and is always fully
409
+ * on the wafer, the two supplied values contradict each other and the die
410
+ * positions are the trustworthy side. wmap does not silently resize a diameter
411
+ * you asserted — it reports this so you can correct one of them. Only raised
412
+ * when the pitch was supplied: pitch is a free scaling parameter, so with an
413
+ * inferred pitch "the dies don't fit" is not a statement about your data.
414
+ * - `'inferred-pitch'` — `waferConfig.diameter` was supplied without a die pitch,
415
+ * so the pitch was derived as `diameter ÷ grid span`, assuming the grid spans
416
+ * the full wafer. That assumption fails whenever edge dies are absent, which
417
+ * silently scales every die position. Supply `dieConfig.width`/`height`.
418
+ *
419
+ * The union is intentionally open to string so future advisory codes can be
420
+ * added without a breaking change; switch with a `default` branch.
421
+ */
422
+ code: 'partial-coverage' | 'geometry-conflict' | 'inferred-pitch' | (string & {});
423
+ /** Human-readable explanation, suitable for direct display. */
424
+ message: string;
425
+ /** Inference confidence in [0, 1] for the related quantity, when one exists. */
426
+ confidence?: number;
427
+ }
428
+ export interface WaferMapResult {
429
+ wafer: Wafer;
430
+ dies: Die[];
431
+ /**
432
+ * The initial plot mode selected by `buildWaferMap` — `'value'` when test values are
433
+ * present, `'hardBin'` otherwise. Used to initialise the toolbar to the most useful mode.
434
+ */
435
+ plotMode: PlotMode;
436
+ /** Wafer metadata copied from `waferConfig.metadata`, or `null` if none was provided. */
437
+ metadata: import('../core/metadata.js').WaferMetadata | null;
438
+ /** `true` when the result was built from a `lotStack` aggregation. */
439
+ isLotStack: boolean;
440
+ /**
441
+ * @internal Renderer-agnostic draw list consumed by `renderWaferMap` and `toCanvas`.
442
+ * Not part of the public API — access the named fields on `WaferMapResult` instead.
443
+ */
444
+ view: View;
445
+ /** Reticle configuration used to generate the overlay and reticle-local groupings. */
446
+ reticleConfig?: ReticleConfig;
447
+ /**
448
+ * Coordinate space of `die.physX` / `die.physY` and wafer dimensions:
449
+ * - **'mm'** — at least one physical dimension was provided or could
450
+ * be inferred; all spatial values are in real millimetres.
451
+ * - **'normalized'** — only grid positions were supplied; coordinates are
452
+ * proportionally correct but not in physical mm.
453
+ */
454
+ units: 'mm' | 'normalized';
455
+ inference: {
456
+ wafer: {
457
+ confidence: number;
458
+ method: string;
459
+ };
460
+ diePitch: {
461
+ confidence: number;
462
+ units: 'mm' | 'normalized';
463
+ };
464
+ grid: {
465
+ confidence: number;
466
+ };
467
+ /**
468
+ * @deprecated Use the promoted top-level `WaferMapResult.warnings` instead —
469
+ * it is always present (empty when none) and carries structured
470
+ * `{ code, message, confidence? }` entries you can branch on. This raw
471
+ * string array is retained for backward compatibility and mirrors the
472
+ * `message` of each structured warning.
473
+ */
474
+ warnings?: string[];
475
+ };
476
+ /**
477
+ * Structured non-fatal advisories raised while inferring geometry from data.
478
+ * Always present; empty when geometry was supplied or confidently inferred.
479
+ * Branch on `warning.code` and display `warning.message`. The most important
480
+ * case is `'partial-coverage'`: data that does not span a full symmetric
481
+ * wafer, where the inferred diameter and centre may be wrong. Read this
482
+ * programmatically rather than relying on console output.
483
+ */
484
+ warnings: WaferWarning[];
485
+ /** Die population statistics. */
486
+ dataCoverage: {
487
+ /** Dies inside the wafer boundary that have at least one value or bin. */
488
+ filledDies: number;
489
+ /** Total dies inside the wafer boundary (including partial). */
490
+ totalDies: number;
491
+ /** Dies falling within the edge exclusion zone. */
492
+ edgeExcludedDies: number;
493
+ /** `filledDies / totalDies` in [0, 1]. */
494
+ ratio: number;
495
+ };
496
+ /** Yield statistics computed against `passBins`. */
497
+ yield: YieldSummary;
498
+ /** Generated reticle geometry — pass as `viewOptions.reticles` to `renderWaferMap` to show the reticle overlay. */
499
+ reticles: Reticle[];
500
+ /** Named hard bin definitions passed to `buildWaferMap`. Consumed automatically by the renderer — no need to pass again to `renderWaferMap`. */
501
+ hbinDefs?: BinDef[];
502
+ /** Named soft bin definitions passed to `buildWaferMap`. Consumed automatically by the renderer — no need to pass again to `renderWaferMap`. */
503
+ sbinDefs?: BinDef[];
504
+ /** Named test definitions passed to `buildWaferMap`. Consumed automatically by the renderer — no need to pass again to `renderWaferMap`. */
505
+ testDefs?: TestDef[];
506
+ /** Named metadata-field definitions passed to `buildWaferMap`. Consumed automatically by the renderer — no need to pass again to `renderWaferMap`. */
507
+ metadataFields?: MetadataFieldDef[];
508
+ /**
509
+ * Lot-stack aggregation method used when `lotStack` was passed to `buildWaferMap`.
510
+ * `undefined` for single-wafer results.
511
+ */
512
+ aggrMethod?: string;
513
+ /**
514
+ * Number of wafers aggregated when `lotStack` was passed to `buildWaferMap`.
515
+ * `undefined` for single-wafer results.
516
+ */
517
+ lotSize?: number;
518
+ }
519
+ /** Read a test value from a die by test number. */
520
+ export declare function getDieTestValue(die: Die, testNumber: number): number | undefined;
521
+ /**
522
+ * Single read-path for "did this die pass test `testNumber`".
523
+ *
524
+ * Primary source: `die.testPass[testNumber]` (the tester's recorded verdict).
525
+ * Migration fallback — the ONLY place this rule exists: a functional test
526
+ * (`testType: 'F'`) with no `testPass` entry but a `testValues` entry of
527
+ * exactly 0 or 1 is legacy encoding (1 = pass, 0 = fail) from callers that
528
+ * predate `testPass`. The fallback is never applied to parametric tests,
529
+ * whose values are measurements.
530
+ *
531
+ * Returns `undefined` when no verdict is recorded — callers must treat that
532
+ * as no-data, never as a fail.
533
+ */
534
+ export declare function getTestPassStatus(die: Pick<Die, 'testValues' | 'testPass'>, testNumber: number, testDef?: TestDef): boolean | undefined;
535
+ /**
536
+ * True when the die carries any per-test data — a measured test value or a
537
+ * recorded pass/fail verdict. The single source for "does this die have test
538
+ * data", used by coverage, plot-mode inference, and the toolbar's value-mode
539
+ * availability checks.
540
+ */
541
+ export declare function dieHasTestData(die: Pick<Die, 'testValues' | 'testPass'>): boolean;
542
+ export declare function buildWaferMap(input: DieResult[] | WaferMapInput, options?: WaferMapOptions): WaferMapResult;
543
+ //# sourceMappingURL=buildWaferMap.d.ts.map
@@ -0,0 +1 @@
1
+ import{createWafer as $}from"../core/wafer.js";import{isYieldEligibleDie as Q,getDieKey as I}from"../core/dies.js";import{applyOrientation as A,transformDies as Z}from"../core/transforms.js";import{affineRotation as ee,affineMirror as te,affineCompose as ne,affinePoint as ie}from"../core/transforms.js";import{inferWaferFromXY as se}from"../core/inference/wafer.js";import{resolveGridPitch as oe}from"../core/inference/pitch.js";import{assignGridIndices as re}from"../core/inference/grid.js";import{generateReticleGrid as ae}from"../core/reticle.js";import{buildView as T}from"./buildView.js";import{modeOf as fe}from"../core/utils.js";import{aggregateValues as L,aggregateBinCounts as de}from"../core/aggregates.js";export function isParametricTest(t){return t?.testType!=="F"}function ce(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],testDefs:void 0,hbinDefs:void 0,sbinDefs:void 0,metadataFields: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],testDefs:t.testDefs,hbinDefs:t.hbinDefs,sbinDefs:t.sbinDefs,metadataFields:t.metadataFields,retestPolicy:t.retestPolicy??"last",edgeDieYieldMode:t.edgeDieYieldMode??"exclude"}}function le(t,n){return n?.coordinateOrigin?n.coordinateOrigin:{type:"center"}}function ue(t,n,e){if(n.type==="custom"&&n.offset)return{offsetX:n.offset.x,offsetY:n.offset.y};if(n.type!=="center"){let o=1/0,r=-1/0,h=1/0,f=-1/0;for(const i of t)i.x<o&&(o=i.x),i.x>r&&(r=i.x),i.y<h&&(h=i.y),i.y>f&&(f=i.y);return{offsetX:Math.round((r+o)/2),offsetY:Math.round((f+h)/2)}}return{offsetX:e.offsetX,offsetY:e.offsetY}}function he(t,n,e,o,r,h){if(h)return{colMidX:(Math.round(h.x)-n)*o,colMidY:(Math.round(h.y)-e)*r,anchored:!0};if(t.length===0)return{colMidX:0,colMidY:0,anchored:!1};let f=1/0,i=-1/0,d=1/0,a=-1/0;for(const s of t){const c=Math.round(s.x)-n,p=Math.round(s.y)-e;c<f&&(f=c),c>i&&(i=c),p<d&&(d=p),p>a&&(a=p)}return{colMidX:(f+i)/2*o,colMidY:(d+a)/2*r,anchored:!1}}function pe(t,n,e){let o=1/0,r=-1/0,h=1/0,f=-1/0,i=0,d=0;for(const c of t){const p=Math.round(c.x)-n,u=Math.round(c.y)-e;i+=p,d+=u,p<o&&(o=p),p>r&&(r=p),u<h&&(h=u),u>f&&(f=u)}const a=t.length,s=(c,p,u)=>{const g=(u-p)/2;return g<=0?0:Math.abs(c/a-(p+u)/2)/g};return s(i,o,r)>.11||s(d,h,f)>.11}function me(t,n){let e=t?.xAxisDirection==="left",o=t?.yAxisDirection==="down";return(n.type==="UL"||n.type==="UR")&&(o=!0),(n.type==="LR"||n.type==="UR")&&(e=!0),{flipX:e,flipY:o}}function ye(t,n){const{results:e,method:o,targetBin:r}=t;if(o==="mean"||o==="median"||o==="stddev"||o==="min"||o==="max"||o==="count"){const h=new Set((n??[]).filter(s=>!isParametricTest(s)).map(s=>s.testNumber)),f=new Set;for(const s of e)for(const c of s)if(c.testValues)for(const p of Object.keys(c.testValues)){const u=Number(p);h.has(u)||f.add(u)}if(f.size===0)return L(e,o);const i=[...f],d=i.map(s=>L(e,o,s)),a=new Map;for(let s=0;s<i.length;s++){const c=i[s];for(const p of d[s]){const u=I(p);a.has(u)||a.set(u,{template:p,testValues:{}});const g=a.get(u),m=p.testValues?.[0];m!==void 0&&(g.testValues[c]=m)}}return[...a.values()].map(({template:s,testValues:c})=>({...s,testValues:Object.keys(c).length>0?c:void 0}))}if(o==="countBin"||o==="percent"){if(r===void 0)return[];const h=de(e,r,"hard");if(o==="countBin")return h;const f=e.length;return h.map(i=>({...i,testValues:{0:f>0?(i.testValues?.[0]??0)/f*100:0}}))}if(o==="mode"){const h=new Map;for(const i of e)for(const d of i){const a=I(d);h.has(a)||h.set(a,[]),h.get(a).push(d)}const f=[];for(const[i,d]of h){const a=i.split(","),s=Number(a[0]),c=Number(a[1]),p=d.map(g=>g.hbin).filter(g=>g!==void 0),u=fe(p);u!==null&&f.push({x:s,y:c,hbin:u})}return f}return[]}function W(t){const n=t.length,e=t.filter(r=>r.edgeExcluded).length,o=t.filter(r=>dieHasTestData(r)||r.hbin!==void 0||r.sbin!==void 0).length;return{filledDies:o,totalDies:n,edgeExcludedDies:e,ratio:n>0?o/n:0}}function N(t,n,e="exclude"){const o=new Set(n),h=t.filter(u=>!u.partial).filter(u=>u.edgeExcluded).length,f=t.filter(u=>u.partial).length;let i=0,d=0,a=!1;for(const u of t){if(!Q(u))continue;const g=u.hbin??u.sbin;g!==void 0&&(a=!0,o.has(g)?i++:d++)}const s=i+d,c=a&&s>0?i/s*100:null;let p=null;if(e==="denominator-only"){const u=s+h;p=a&&u>0?i/u*100:null}return{passDies:i,failDies:d,edgeExcludedDies:h,partialDies:f,totalDies:s,yieldPercent:c,yieldPercentGross:p}}function _(t,n,e,o,r,h=0,f=0,i=0,d=0,a=0,s=!1,c=!1){if(!t)return[];const p=t.anchorDie??{x:0,y:0},u=ae(n,{width:t.width,height:t.height,diePitchX:o,diePitchY:r,anchorDie:{x:p.x-h,y:p.y-f},gridOrigin:{x:-i,y:-d}}),g=ne(te(s,c,n.center.x,n.center.y),ee(a,n.center.x,n.center.y));return u.filter(m=>{const x=m.width/2,O=m.height/2,S=[[m.x-x,m.y-O],[m.x+x,m.y-O],[m.x+x,m.y+O],[m.x-x,m.y+O]].map(([y,C])=>ie(g,y,C)),R=Math.min(...S.map(y=>y.x)),Y=Math.max(...S.map(y=>y.x)),E=Math.min(...S.map(y=>y.y)),D=Math.max(...S.map(y=>y.y));return e.some(y=>y.physX>=R&&y.physX<Y&&y.physY>=E&&y.physY<D)})}function ge(t,n,e){const o=(n.radius-e)**2;return t.map(r=>{const h=r.physX-n.center.x,f=r.physY-n.center.y;return h*h+f*f>o?{...r,edgeExcluded:!0}:r})}function xe(t,n,e){const o=new Map;for(const i of t){let d=o.get(i.x);d||(d=new Map,o.set(i.x,d)),d.set(i.y,(d.get(i.y)??0)+1)}const r=new Set(e);function h(i,d){const a=i.hbin,s=d.hbin;if(a===void 0||s===void 0)return!1;const c=r.has(a),p=r.has(s);return n==="best"?p!==c?p:s<a:p!==c?c:s>a}const f=new Map;for(const i of t){const d=I(i),a=f.get(d);n==="first"&&a||(n==="best"||n==="worst")&&a&&!h(a,i)||f.set(d,i)}return Array.from(f.values()).map(i=>{const a=o.get(i.x)?.get(i.y)??1;return a>1?{...i,retestCount:a}:i})}export function getDieTestValue(t,n){return t.testValues?.[n]}export function getTestPassStatus(t,n,e){const o=t.testPass?.[n];if(o!==void 0)return o;if(e!==void 0&&!isParametricTest(e)){const r=t.testValues?.[n];if(r===0||r===1)return r===1}}export function dieHasTestData(t){return t.testValues!==void 0&&Object.keys(t.testValues).length>0||t.testPass!==void 0&&Object.keys(t.testPass).length>0}function G(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 K(t,n){return n.plotMode?n.plotMode:t.some(dieHasTestData)?"value":"hardBin"}function z(t){return(t.warnings??[]).map(n=>({code:we(n),message:n,confidence:t.wafer.confidence}))}function we(t){return t.includes(j)?"geometry-conflict":t.includes(H)?"inferred-pitch":"partial-coverage"}const j="do not fit inside the supplied",H="The die pitch was inferred as";export function buildWaferMap(t,n){const e=ce(t),{debug:o,...r}=n??{},h=e.lotStackOpts?ye(e.lotStackOpts,e.testDefs):e.results;for(let l=0;l<Math.min(5,h.length);l++){const M=h[l];if(typeof M.x=="string"||typeof M.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 }")}const f=xe(h,e.retestPolicy,e.passBins),i={wafer:{confidence:1,method:"provided"},diePitch:{confidence:1,units:"mm"},grid:{confidence:1}};if(e.explicitDies){let l=e.explicitDies;if(f.length>0){const J=new Map(f.map(P=>[I(P),P]));l=l.map(P=>{const X=J.get(I(P));return X?G(P,X):P})}const M=e.waferOpts?.diameter??300,b=$({diameter:M,notch:e.waferOpts?.notch,orientation:e.waferOpts?.orientation??0,metadata:e.waferOpts?.metadata});b.orientation!==0&&(l=A(l,b));const v=_(e.reticleOpts,b,l,1,1,0,0,0,0,b.orientation),q=r.showReticle??e.reticleOpts!==void 0,V=T(b,l,{...r,reticles:v,showReticle:q,plotMode:K(f,r),testDefs:e.testDefs,isLotStack:!1},{hbinDefs:e.hbinDefs,sbinDefs:e.sbinDefs,metadataFields:e.metadataFields});return{wafer:b,dies:l,view:V,reticleConfig:e.reticleOpts,units:"mm",inference:i,warnings:z(i),plotMode:V.plotMode,metadata:V.metadata,isLotStack:!1,dataCoverage:W(l),yield:N(l,e.passBins,e.edgeDieYieldMode),reticles:v,hbinDefs:e.hbinDefs,sbinDefs:e.sbinDefs,testDefs:e.testDefs,metadataFields:e.metadataFields}}const d=f.map(l=>({x:l.x,y:l.y})),a=oe(d,e.dieOpts,e.waferOpts?.diameter);i.diePitch={confidence:a.confidence,units:a.units};const{pitchX:s,pitchY:c}=a,p=a.units,u=le(f,e.dieOpts),g=re(d);i.grid={confidence:g.confidence};const{offsetX:m,offsetY:x}=ue(d,u,g),{flipX:O,flipY:S}=me(e.dieOpts,u),{colMidX:R,colMidY:Y,anchored:E}=he(d,m,x,s,c,e.waferOpts?.center);let D=e.waferOpts?.diameter;const y=d.map(l=>({x:(Math.round(l.x)-m)*s-R,y:(Math.round(l.y)-x)*c-Y})),C=y.length>0?Math.max(...y.map(({x:l,y:M})=>Math.hypot(Math.abs(l)+s/2,Math.abs(M)+c/2))):0;if(D===void 0)if(d.length>0)if(p==="mm"){const l=se(y,{minRadius:C});D=l.diameter,i.wafer={confidence:l.confidence,method:l.method}}else D=C*2,i.wafer={confidence:a.confidence*.8,method:"extent"};else D=p==="mm"?300:30,i.wafer={confidence:0,method:"default"};if(!E&&d.length>0&&pe(d,m,x)&&(i.wafer.method="inferred-partial",(i.warnings??=[]).push("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){const l=e.dieOpts?.width!==void 0&&e.dieOpts?.height!==void 0;if(l&&C>D/2+1e-9){const M=y.filter(({x:b,y:v})=>Math.hypot(Math.abs(b)+s/2,Math.abs(v)+c/2)>D/2+1e-9).length;(i.warnings??=[]).push(`${M} of ${y.length} probed die positions ${j} ${D} mm wafer at the supplied die pitch of ${s} \xD7 ${c} 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 ${(C*2).toFixed(1)} mm. Check waferConfig.diameter and dieConfig.width/height against the real device.`)}else l||(i.warnings??=[]).push(`${H} ${s.toFixed(3)} \xD7 ${c.toFixed(3)} mm by assuming the die grid fits within the ${D} mm wafer. The assumed aspect ratio may not match the true die shape whenever edge dies are absent from the data (a reticle-complete map, or partial dies filtered out upstream). Supply dieConfig.width and dieConfig.height \u2014 the die pitch, not the diameter, is what fixes placement.`)}const k=$({diameter:D,notch:e.waferOpts?.notch,orientation:e.waferOpts?.orientation??0,metadata:e.waferOpts?.metadata}),Me={width:s,height:c};let w=f.map(l=>{const M=Math.round(l.x)-m,b=Math.round(l.y)-x,v={id:`${M}_${b}`,x:M,y:b,physX:M*s-R,physY:b*c-Y,width:s,height:c,insideWafer:!0,partial:!1};return G(v,l)});(m!==0||x!==0)&&(w=w.map(l=>({...l,x:l.x+m,y:l.y+x,id:`${l.x+m}_${l.y+x}`}))),w=A(w,k),(O||S)&&(w=Z(w,{flipX:O,flipY:S},k.center)),e.waferOpts?.edgeExclusion&&e.waferOpts.edgeExclusion>0&&(w=ge(w,k,e.waferOpts.edgeExclusion));const B=_(e.reticleOpts,k,w,s,c,m,x,R,Y,k.orientation,O,S),U=r.showReticle??e.reticleOpts!==void 0,F=T(k,w,{...r,reticles:B,showReticle:U,plotMode:K(f,r),testDefs:e.testDefs,dataAxisFlip:{x:O,y:S},isLotStack:e.lotStackOpts!==void 0,aggregationMethod:e.lotStackOpts?.method,lotSize:e.lotStackOpts?.results.length},{hbinDefs:e.hbinDefs,sbinDefs:e.sbinDefs,metadataFields:e.metadataFields});return{wafer:k,dies:w,view:F,reticleConfig:e.reticleOpts,units:p,inference:i,warnings:z(i),plotMode:F.plotMode,metadata:F.metadata,isLotStack:e.lotStackOpts!==void 0,dataCoverage:W(w),yield:N(w,e.passBins,e.edgeDieYieldMode),reticles:B,hbinDefs:e.hbinDefs,sbinDefs:e.sbinDefs,testDefs:e.testDefs,metadataFields:e.metadataFields,aggrMethod:F.aggrMethod,lotSize:F.lotSize}}
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Spec pass/fail die colours, shared by the value-map renderer and the spec legend so both use one
3
+ * definition. Pass = green, fail-low (below limitLow) = blue, fail-high (above limitHigh) = red.
4
+ */
5
+ export declare const SPEC_PASS_FILL = "#2ecc71";
6
+ export declare const SPEC_FAIL_LOW = "#3498db";
7
+ export declare const SPEC_FAIL_HIGH = "#e74c3c";
8
+ /**
9
+ * Shared categorical palette for hard and soft bin colouring.
10
+ * Index 0 is the no-data grey sentinel. Indices 1–63 are perceptually
11
+ * spread colours generated via golden-angle HSL stepping.
12
+ * Hard and soft bins use different hash salts so the same bin number
13
+ * maps to different colours in each scheme.
14
+ */
15
+ export declare const BIN_PALETTE: readonly string[];
16
+ /** Wang hash — maps any integer to a well-distributed unsigned 32-bit value. Shared with colorSchemes. */
17
+ export declare function wangHash(n: number): number;
18
+ /** Categorical colour for a hard bin. No-data handling is the caller's responsibility. */
19
+ export declare function hardBinColor(bin: number): string;
20
+ /** Linear interpolation across RGB keypoints for t ∈ [0, 1]. */
21
+ export declare function lerpKp(kp: readonly [number, number, number][], t: number): string;
22
+ export declare const VIRIDIS: readonly [number, number, number][];
23
+ /** Map t ∈ [0, 1] to a Viridis RGB colour string. */
24
+ export declare function valueToViridis(t: number): string;
25
+ /** Categorical colour for a soft bin. No-data handling is the caller's responsibility. */
26
+ export declare function softBinColor(bin: number): string;
27
+ /**
28
+ * Categorical colour for the `index`-th distinct value of an active
29
+ * `'metadata'` field, where `index` comes from sorting the field's distinct
30
+ * values alphabetically (deterministic — never dependent on die array
31
+ * iteration order). Ordered assignment, not hashing, for the first
32
+ * `METADATA_PALETTE.length` slots — this maximizes distinctness for the
33
+ * common case of a handful of categories, unlike a hash which doesn't
34
+ * optimize for a *known* small set. Falls back to the same
35
+ * hash+`BIN_PALETTE` mechanism `hardBinColor`/`softBinColor` use (a new
36
+ * salt) for wafers with an unusually large category count.
37
+ */
38
+ export declare function metadataValueColor(index: number): string;
39
+ /** Categorical greyscale shades for hard bins. Index 0 = no data. */
40
+ export declare const HARD_BIN_GREY: readonly string[];
41
+ export declare function hardBinGreyscale(bin: number): string;
42
+ /** Map t ∈ [0, 1] to a greyscale rgb string (range 30–230 to avoid pure black/white). */
43
+ export declare function valueToGreyscale(t: number): string;
44
+ /** Return '#000000' or '#ffffff' for maximum contrast against the given colour. */
45
+ export declare function contrastTextColor(cssColor: string): '#000000' | '#ffffff';
46
+ //# sourceMappingURL=colorMap.d.ts.map
@@ -0,0 +1 @@
1
+ import{clamp01 as f}from"../core/utils.js";export const SPEC_PASS_FILL="#2ecc71",SPEC_FAIL_LOW="#3498db",SPEC_FAIL_HIGH="#e74c3c",BIN_PALETTE=["#95a5a6","#1eb84b","#932cdd","#b8a51e","#2cbfdd","#b81e71","#58dd2c","#251eb8","#dd672c","#1eb87f","#ce2cdd","#98b81e","#2c84dd","#b81e3e","#2cdd3b","#581eb8","#dda22c","#1eb8b2","#dd2cb0","#64b81e","#2c49dd","#b8321e","#2cdd76","#8c1eb8","#dcdd2c","#1e8bb8","#dd2c75","#31b81e","#4b2cdd","#b8651e","#2cddb1","#b81eb1","#a1dd2c","#1e57b8","#dd2c3a","#1eb83f","#852cdd","#b8991e","#2ccddd","#b81e7e","#66dd2c","#1e24b8","#dd5a2c","#1eb872","#c02cdd","#a4b81e","#2c92dd","#b81e4a","#2cdd2e","#4c1eb8","#dd942c","#1eb8a6","#dd2cbe","#70b81e","#2c57dd","#b8261e","#2cdd69","#801eb8","#ddcf2c","#1e97b8","#dd2c83","#3db81e","#3d2cdd","#b8591e"];export function wangHash(e){let c=e|0;return c=Math.imul(c^c>>>16,73244475),c=Math.imul(c^c>>>16,73244475),(c^c>>>16)>>>0}const s=BIN_PALETTE.length-1,i=2654435769,h=1818371886,x={1:"#2ecc71",2:"#e74c3c",3:"#f39c12",4:"#9b59b6",5:"#3498db",6:"#1abc9c",7:"#e67e22",8:"#2c3e50",9:"#c0392b",10:"#8e44ad",11:"#2980b9",12:"#27ae60",13:"#d35400",14:"#16a085"};export function hardBinColor(e){return x[e]??BIN_PALETTE[wangHash(e^i)%s+1]}export function lerpKp(e,c){const b=f(c)*(e.length-1),t=Math.floor(b),d=Math.min(t+1,e.length-1),a=b-t,n=Math.round(e[t][0]+a*(e[d][0]-e[t][0])),o=Math.round(e[t][1]+a*(e[d][1]-e[t][1])),l=Math.round(e[t][2]+a*(e[d][2]-e[t][2]));return`rgb(${n},${o},${l})`}export const VIRIDIS=[[68,1,84],[59,82,139],[33,145,140],[94,201,98],[253,231,37]];export function valueToViridis(e){return lerpKp(VIRIDIS,e)}export function softBinColor(e){return BIN_PALETTE[wangHash(e^h)%s+1]}const u=["#4e79a7","#f28e2b","#e15759","#76b7b2","#59a14f","#edc948","#b07aa1","#ff9da7","#9c755f","#bab0ac"],A=668265263;export function metadataValueColor(e){return e<u.length?u[e]:BIN_PALETTE[wangHash(e^A)%s+1]}export const HARD_BIN_GREY=["#aaaaaa","#f7f7f7","#303030","#888888","#bbbbbb","#666666","#999999","#555555","#444444","#222222","#cccccc","#777777","#eeeeee","#333333","#888888"];export function hardBinGreyscale(e){return HARD_BIN_GREY[Math.max(0,Math.min(e,HARD_BIN_GREY.length-1))]}export function valueToGreyscale(e){const c=Math.round(f(e)*200+30);return`rgb(${c},${c},${c})`}export function contrastTextColor(e){let c=0,r=0,b=0;const t=e.match(/rgb\((\d+),\s*(\d+),\s*(\d+)\)/);if(t)c=+t[1],r=+t[2],b=+t[3];else{const n=e.replace("#","");c=parseInt(n.slice(0,2),16),r=parseInt(n.slice(2,4),16),b=parseInt(n.slice(4,6),16)}const d=n=>{const o=n/255;return o<=.03928?o/12.92:((o+.055)/1.055)**2.4};return .2126*d(c)+.7152*d(r)+.0722*d(b)>.179?"#000000":"#ffffff"}
@@ -0,0 +1,40 @@
1
+ export interface ColorScheme {
2
+ /** Human-readable display name */
3
+ label: string;
4
+ /**
5
+ * Return a CSS colour string for a categorical bin number.
6
+ * Index 0 conventionally means "no data / unknown".
7
+ */
8
+ forBin: (bin: number) => string;
9
+ /**
10
+ * Return a CSS colour string for a continuous value t ∈ [0, 1].
11
+ * Values are pre-normalized by buildView before this is called.
12
+ */
13
+ forValue: (t: number) => string;
14
+ }
15
+ /**
16
+ * Register a named colour scheme, making it available to buildView via the
17
+ * colorScheme option. Call this once at app startup before rendering.
18
+ *
19
+ * `forBin` and `forValue` must return valid CSS color strings — invalid values
20
+ * produce silent rendering artifacts (blank or black rectangles).
21
+ *
22
+ * @example
23
+ * registerColorScheme('my-brand', {
24
+ * label: 'My Brand',
25
+ * forBin: (bin) => MY_BRAND_BINS[bin] ?? '#ccc',
26
+ * forValue: (t) => `hsl(${200 + t * 60}, 70%, ${30 + t * 40}%)`,
27
+ * });
28
+ */
29
+ export declare function registerColorScheme(name: string, scheme: ColorScheme): void;
30
+ /**
31
+ * Retrieve a registered scheme by name. Falls back to 'default' if the name
32
+ * is not found, so callers never receive undefined.
33
+ */
34
+ export declare function getColorScheme(name?: string): ColorScheme;
35
+ /** Return all registered schemes as { name, label } pairs, in insertion order. */
36
+ export declare function listColorSchemes(): Array<{
37
+ name: string;
38
+ label: string;
39
+ }>;
40
+ //# sourceMappingURL=colorSchemes.d.ts.map