@wafertools/wafermap 0.30.4 → 0.32.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +21 -31
- package/CHANGELOG.md +265 -1
- package/README.md +3 -3
- package/dist/packages/canvas-adapter/charts/boxplot.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 +11 -1
- package/dist/packages/canvas-adapter/charts/scatter.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/dieList.js +7 -7
- package/dist/packages/canvas-adapter/drilldown.js +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.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 +32 -34
- package/dist/packages/canvas-adapter/renderWaferMap.js +1 -1
- package/dist/packages/canvas-adapter/summaryPanel.js +3 -3
- package/dist/packages/canvas-adapter/toCanvas.d.ts +23 -2
- package/dist/packages/canvas-adapter/toCanvas.js +1 -1
- package/dist/packages/canvas-adapter/toolbar.js +2 -2
- package/dist/packages/canvas-adapter/userGuideHtml.d.ts +1 -1
- package/dist/packages/canvas-adapter/userGuideHtml.js +38 -24
- package/dist/packages/canvas-adapter/version.d.ts +2 -2
- package/dist/packages/canvas-adapter/version.js +1 -1
- package/dist/packages/core/aggregates.d.ts +0 -6
- package/dist/packages/core/aggregates.js +1 -1
- package/dist/packages/core/dieTable.d.ts +137 -0
- package/dist/packages/core/dieTable.js +1 -0
- package/dist/packages/core/dies.d.ts +22 -2
- 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/core/utils.d.ts +14 -0
- package/dist/packages/core/utils.js +1 -1
- package/dist/packages/renderer/buildView.d.ts +4 -12
- package/dist/packages/renderer/buildView.js +1 -1
- package/dist/packages/renderer/buildWaferMap.d.ts +42 -32
- 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/columnarInput.d.ts +87 -0
- package/dist/packages/renderer/columnarInput.js +1 -0
- package/dist/packages/renderer/deprecate.d.ts +1 -1
- package/dist/packages/renderer/deprecate.js +1 -1
- package/dist/packages/renderer/derivedTests/apply.d.ts +8 -6
- package/dist/packages/renderer/derivedTests/apply.js +1 -1
- package/dist/packages/renderer/index.d.ts +7 -5
- package/dist/packages/renderer/index.js +1 -1
- package/dist/packages/renderer/spec.d.ts +3 -7
- package/dist/packages/renderer/spec.js +1 -1
- package/dist/packages/stats/analyzeWaferLot.js +1 -1
- package/dist/packages/stats/analyzeWaferMap.d.ts +134 -2
- package/dist/packages/stats/analyzeWaferMap.js +1 -1
- package/dist/packages/stats/boxplot.js +1 -1
- package/dist/packages/stats/capability.d.ts +7 -7
- package/dist/packages/stats/capability.js +1 -1
- package/dist/packages/stats/clusterDetection.d.ts +2 -1
- package/dist/packages/stats/clusterDetection.js +1 -1
- package/dist/packages/stats/connectedComponents.js +1 -1
- package/dist/packages/stats/correlation.js +1 -1
- package/dist/packages/stats/facets.d.ts +1 -1
- package/dist/packages/stats/filterFindings.d.ts +5 -0
- package/dist/packages/stats/filterFindings.js +1 -1
- package/dist/packages/stats/histogram.js +1 -1
- package/dist/packages/stats/index.d.ts +6 -16
- package/dist/packages/stats/index.js +1 -1
- package/dist/packages/stats/math.d.ts +22 -6
- package/dist/packages/stats/math.js +1 -1
- package/dist/packages/stats/normalizeInput.js +1 -1
- package/dist/packages/stats/patternClassification.d.ts +5 -4
- package/dist/packages/stats/patternClassification.js +1 -1
- package/dist/packages/stats/regions.d.ts +9 -1
- package/dist/packages/stats/regions.js +1 -1
- package/dist/packages/stats/renderFindingsReport.d.ts +0 -4
- package/dist/packages/stats/renderFindingsReport.js +1 -21
- package/dist/packages/stats/renderSummaryReport.js +18 -18
- package/dist/packages/stats/scatter.js +1 -1
- package/dist/packages/stats/sweep.js +1 -1
- package/dist/packages/stats/testPassRate.js +1 -1
- package/dist/packages/stats/trend.js +1 -1
- package/dist/packages/stats/types.d.ts +5 -0
- package/dist/packages/worker/index.js +1 -1
- package/dist/packages/worker/wafermap.worker.d.ts +3 -0
- package/dist/packages/worker/wafermap.worker.js +1 -1
- 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
|
@@ -1 +1 @@
|
|
|
1
|
-
export function compareNatural(t,
|
|
1
|
+
export function compareNatural(t,r){return t.localeCompare(r,"en",{numeric:!0})}export function prettyKey(t){return t.replace(/([A-Z])/g," $1").replace(/_/g," ").trim().replace(/^./,r=>r.toUpperCase())}export function hasAnyKey(t){if(t==null)return!1;for(const r in t)if(Object.hasOwn(t,r))return!0;return!1}export function minOf(t){if(t.length===0)return 0;let r=t[0];for(let e=1;e<t.length;e++)t[e]<r&&(r=t[e]);return r}export function maxOf(t){if(t.length===0)return 0;let r=t[0];for(let e=1;e<t.length;e++)t[e]>r&&(r=t[e]);return r}export function clamp01(t){return Math.max(0,Math.min(1,t))}export function mean(t){if(t.length===0)return NaN;let r=0;for(const e of t)r+=e;return r/t.length}export function medianOfSorted(t){const r=t.length;if(r===0)return 0;const e=Math.floor(r/2);return r%2===1?t[e]:(t[e-1]+t[e])/2}export function median(t){return medianOfSorted([...t].sort((r,e)=>r-e))}export function percentile98(t){if(t.length===0)return 0;const r=Float64Array.from(t);r.sort();const e=Math.floor(r.length*.98);return r[Math.min(e,r.length-1)]}export function modeOf(t){if(!t.length)return null;const r=new Map;for(const o of t)r.set(o,(r.get(o)??0)+1);let e=0,n=t[0];for(const[o,f]of r)f>e&&(e=f,n=o);return n}const i=/^[=+\-@]/;export function csvField(t){const r=i.test(t)&&Number.isNaN(Number(t))?`'${t}`:t;return/[",\n\r]/.test(r)?`"${r.replace(/"/g,'""')}"`:r}export function escHtml(t){return t.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""").replace(/'/g,"'")}export function arrayEqual(t,r){if(t===r)return!0;if(!t||!r||t.length!==r.length)return!1;for(let e=0;e<t.length;e++)if(t[e]!==r[e])return!1;return!0}export function mapEqual(t,r){if(t===r)return!0;if(!t||!r||t.size!==r.size)return!1;for(const[e,n]of t)if(!r.has(e)||r.get(e)!==n)return!1;return!0}export function setEqual(t,r){if(t===r)return!0;if(!t||!r||t.size!==r.size)return!1;for(const e of t)if(!r.has(e))return!1;return!0}export function drain(t){let r=t.next();for(;!r.done;)r=t.next();return r.value}export function asList(t){return t===void 0?[]:Array.isArray(t)?t:[t]}export function toggleHighlight(t,r,e){const n=asList(t),o=e?n.includes(r)?n.filter(f=>f!==r):[...n,r]:n.length===1&&n[0]===r?[]:[r];return o.length===0?void 0:o.length===1?o[0]:o}
|
|
@@ -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. */
|
|
@@ -309,9 +308,9 @@ export interface ViewOptions {
|
|
|
309
308
|
* bin present here: a stale assignment must never leave a real bin uncoloured.
|
|
310
309
|
*/
|
|
311
310
|
binColors?: BinColors;
|
|
312
|
-
highlightBin?: number;
|
|
313
|
-
/** Dim every die except
|
|
314
|
-
highlightMetadataValue?: string;
|
|
311
|
+
highlightBin?: number | number[];
|
|
312
|
+
/** Dim every die except these metadata values, `'metadata'` mode's analogue of `highlightBin`. */
|
|
313
|
+
highlightMetadataValue?: string | string[];
|
|
315
314
|
interactiveTransform?: {
|
|
316
315
|
rotation?: number;
|
|
317
316
|
flipX?: boolean;
|
|
@@ -438,13 +437,6 @@ export interface ViewOptions {
|
|
|
438
437
|
* regardless of this option — it has no value to plot.
|
|
439
438
|
*/
|
|
440
439
|
passFailDisplay?: 'off' | 'spec' | 'test';
|
|
441
|
-
/**
|
|
442
|
-
* When true (default), partial (edge) dies — positions that only partially
|
|
443
|
-
* overlap the wafer circle — are rendered in a muted grey.
|
|
444
|
-
* Set to false to hide them entirely, matching real prober behaviour where
|
|
445
|
-
* edge positions are never tested.
|
|
446
|
-
*/
|
|
447
|
-
showPartialDies?: boolean;
|
|
448
440
|
}
|
|
449
441
|
interface ColorFns {
|
|
450
442
|
forValue: (t: number) => string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{hasPosition as de}from"../core/dies.js";import{getReticleCell as he}from"../core/reticle.js";import{metadataCategoricalValue as me,metadataDisplayValue as pe}from"../core/metadata.js";import{affineRotation as bt,affineMirror as Ht,affineCompose as st,affinePoint as G,affineVector as xt,affineSwapsAxes as ye}from"../core/transforms.js";import{contrastTextColor as Q,SPEC_PASS_FILL as Mt,SPEC_FAIL_LOW as ge,SPEC_FAIL_HIGH as St}from"./colorMap.js";import{resolveValueColorFn as be}from"./colorSchemes.js";import{resolveBinColors as xe,binColorsCover as Me}from"./binColors.js";import{NO_DATA_FILL as z}from"./colorMap.js";import{diePassStatus as Se}from"../core/dies.js";import{getDieTestValue as P,getTestPassStatus as U,isParametricTest as vt}from"./buildWaferMap.js";import{classifySpec as ot}from"./spec.js";export{ot as classifySpec};import{fmt as kt,fmtColorbarAxis as $t,fmtAggregationMethod as _t}from"./fmt.js";import{metadataValueColor as ve}from"./colorMap.js";import{clamp01 as Kt,compareNatural as ke,escHtml as L,minOf as $e}from"../core/utils.js";import{prettyKey as Wt}from"../core/utils.js";import{testLabel as rt,markedTestLabel as lt,derivedTestNote as jt,isDerivedTest as Be,DERIVED_MARK as Te,DERIVED_KEY as Ie}from"./testLabel.js";export function resolveTestNumber(t,e){return e?.length?{testNumber:(findTestDef(e,t)??e[0]).testNumber}:{testNumber:t}}export function collectMetadataValues(t,e){const i=new Set;for(const a of t){if(a.partial||a.edgeExcluded)continue;const s=ct(a,e);s!==void 0&&i.add(s)}return[...i].sort(ke)}export function findTestDef(t,e){return t?.find(i=>i.testNumber===e)}export function getUniqueTestNumbers(t){return[...new Set(t.flatMap(e=>e.testValues?Object.keys(e.testValues).map(Number):[]))].sort((e,i)=>e-i)}const De="#d3d6db",wt="#e8e9ea",Ve="#aab0ba";function ct(t,e){return e?me(t.metadata?.[e]):void 0}function Ce(t,e,i){const{x:a,y:s}=t.center,n=bt(t.orientation,a,s),o=st(Ht(e?.x??!1,e?.y??!1,a,s),bt(t.orientation,a,s)),l=st(Ht(i?.flipX??!1,i?.flipY??!1,a,s),bt(i?.rotation??0,a,s));return{bakedToScreen:l,physicalToScreen:st(l,n),gridToScreen:st(l,o)}}function q(t,e=!1){return{points:[t],closed:e}}function Fe(t,e){const{center:i,radius:a,notch:s}=t;let n=i.x+a*Math.cos(e),o=i.y+a*Math.sin(e);if(!s)return{x:n,y:o};const l=Math.sqrt(a**2-(s.length/2)**2),d=s.length/2,g=n-i.x,p=o-i.y;return s.type==="bottom"&&p<-l?(o=i.y-l,n=i.x+Math.max(-d,Math.min(d,g))):s.type==="top"&&p>l?(o=i.y+l,n=i.x+Math.max(-d,Math.min(d,g))):s.type==="left"&&g<-l?(n=i.x-l,o=i.y+Math.max(-d,Math.min(d,p))):s.type==="right"&&g>l&&(n=i.x+l,o=i.y+Math.max(-d,Math.min(d,p))),{x:n,y:o}}function zt(t,e){return t.map(e).join(" / ")}function Pe(t,e=256){const i=Array.from({length:e},(a,s)=>t(s/(e-1)));return a=>i[Math.min(e-1,Math.max(0,Math.round(a*(e-1))))]}function Le(t,e){const i=Math.max(1,Math.min(t.width,t.height)),a=t.width/Math.max(e.length,1);return Math.max(8,Math.min(16,Math.round(Math.min(i*.55,a*1.8))))}function Re(t,e,i){if(e?.length){const a=e.flatMap(s=>{const n=s.testNumber;if(!vt(s)){const l=U(t,n,s);return l===void 0?[]:[{key:n,label:lt(s,n),value:l?"Pass":"Fail"}]}const o=P(t,n);return o===void 0?[]:[{key:n,label:lt(s,n),value:kt(o,s.unit,i),recordedFail:t.testPass?.[n]===!1}]});if(a.length)return a}return t.testValues&&Object.keys(t.testValues).length>0?Object.entries(t.testValues).map(([a,s])=>({key:Number(a),label:rt(void 0,Number(a)),value:kt(s,void 0,i)})):t.testPass&&Object.keys(t.testPass).length>0?Object.entries(t.testPass).map(([a,s])=>({key:Number(a),label:rt(void 0,Number(a)),value:s?"Pass":"Fail"})):[]}export function buildHoverText(t,e,i={}){const{testDefs:a,hbinDefs:s,sbinDefs:n,fallbackFormat:o,aggrMethod:l,lotSize:d,activeTest:g,reticleConfig:p,metadataFields:B}=i,F=s?new Map(s.map(f=>[f.bin,f])):null,T=n?new Map(n.map(f=>[f.bin,f])):null,b=[L(`Die (${t.x}, ${t.y})`)];if(p&&de(t)){const f=he(t,p);b.push(L(`Reticle (${f.column}, ${f.row})`))}if(e==="stackedValues"){const f=P(t,0);if(f!==void 0){const u=a?.[0],h=u?lt(u,u.testNumber):"Value",v=l?` (${l})`:"";b.push(L(`${h}${v}: ${kt(f,u?.unit,o)}`));const m=jt(u);m&&b.push(`<i>${L(m)}</i>`)}}else if(e==="stackedBins"||e==="stackedSoftBins"){const f=P(t,0),u=e==="stackedSoftBins"?t.sbin:t.hbin;if(f!==void 0){const h=e==="stackedSoftBins"?T:F,v=u!==void 0?h?.get(u)?.name?`${u} \xB7 ${h.get(u).name}`:`Bin ${u}`:"Bin";let m;if(l==="percent")m=`${f.toFixed(0)}%`;else{const V=d?` (${(f/d*100).toFixed(0)}% of stacked wafers)`:"";m=`${f}${V}`}const x=l?` [${_t(l)}]`:"";b.push(L(`${v}: ${m}${x}`))}}else{const f=Re(t,a,o);if(e==="value"&&f.length){const{testNumber:u}=resolveTestNumber(g??0,a),h=findTestDef(a,u),v=P(t,u);let m=f.findIndex(A=>A.key===u);m<0&&(m=0);const x=f[m];let V=`<b>${L(`${x.label}: ${x.value}`)}${x.recordedFail?" <i>(recorded fail)</i>":""}</b>`;if(x.key===u&&h){const A=ot(v,h);(A==="failLow"||A==="failHigh")&&(V+=" <i>(outside test limits)</i>")}b.push(V);const E=jt(findTestDef(a,x.key));E&&b.push(`<i>${L(E)}</i>`);const y=f.length-1;y>0&&b.push(`<i>+${y} more test${y===1?"":"s"}</i>`)}else if(f.length){const u=f.length;b.push(`<i>${u} test result${u===1?"":"s"} recorded</i>`)}if(t.hbin!==void 0||t.sbin!==void 0){const u=[];if(t.hbin!==void 0){const h=F?.get(t.hbin)?.name,v=h?`${t.hbin} \xB7 ${h}`:String(t.hbin);u.push(`HBin: ${v}`)}if(t.sbin!==void 0){const h=T?.get(t.sbin)?.name,v=h?`${t.sbin} \xB7 ${h}`:String(t.sbin);u.push(`SBin: ${v}`)}b.push(u.map(L).join(" \xB7 "))}}t.retestCount!==void 0&&b.push(L(`Retests: ${t.retestCount}`)),t.siteNum!==void 0&&b.push(L(`Site: ${t.siteNum}`)),t.partId!==void 0&&b.push(L(`Part ID: ${t.partId}`)),t.partial&&b.push("<i>partial die</i>"),t.probeIndex!==void 0&&b.push(L(`Probe: #${t.probeIndex}`));const $={...t.metadata??{}};for(const[f,u]of Object.entries($)){const h=pe(u);if(h===void 0)continue;const v=B?.find(m=>m.key===f)?.label??Wt(f);b.push(L(`${v}: ${h}`))}return b.join("<br>")}function Bt(t){return t?.name?lt(t,t.testNumber):void 0}function Tt(t){return Be(t)?{note:`${Te} ${Ie}`}:{}}export function buildMapTitle(t,e="engineering",i){const a=t.isLotStack&&t.lotSize!==void 0?`stacked (${t.lotSize} ${t.lotSize===1?"wafer":"wafers"})`:"stacked",s=n=>{const o=i?.length===1?i[0]:void 0;return o===void 0?`${n} Bin`:o.name?`${n} Bin ${o.bin} \xB7 ${o.name}`:`${n} Bin ${o.bin}`};switch(t.plotMode){case"hardBin":return{primary:"Hard Bin",secondary:""};case"softBin":return{primary:"Soft Bin",secondary:""};case"stackedBins":return{primary:s("Hard"),secondary:a};case"stackedSoftBins":return{primary:s("Soft"),secondary:a};case"metadata":{const n=t.metadataFields?.find(l=>l.key===t.activeMetadataKey),o=t.activeMetadataKey?Wt(t.activeMetadataKey):"Metadata";return{primary:n?.label??o,secondary:""}}case"stackedValues":{const n=t.testDefs?.[0],o=t.valueRange[1]||t.valueRange[0]||0,{axisLabel:l}=$t(o,Bt(n),n?.unit,e),d=l||"Value",g=t.aggrMethod?` \xB7 ${_t(t.aggrMethod)}`:"";return{primary:`${d}${g}`,secondary:a,...Tt(n)}}default:{const n=findTestDef(t.testDefs,t.activeTest),o=t.valueRange[1]||t.valueRange[0]||0,{axisLabel:l}=$t(o,Bt(n),n?.unit,e),d=l||Bt(n);if(t.passFailDisplay!=="off"){const g=n?.testNumber??t.activeTest,p=d?`${d} \xB7 #${g}`:rt(void 0,g),B=t.passFailDisplay==="spec"?"Limit pass/fail":n!==void 0&&!vt(n)?"Functional pass/fail":"Tester pass/fail";return{primary:p,secondary:B,...Tt(n)}}return{primary:d??rt(void 0,t.activeTest),secondary:"",...Tt(n)}}}}export function generateTextOverlay(t,e,i){const{plotMode:a,colorFns:s,normalize:n,activeTest:o,valueRange:l,testDefs:d,fallbackFormat:g,passFailDisplay:p="off",activeMetadataKey:B,metadataColorMap:F,allIntegerValues:T}=i,b=findTestDef(d,o),{tickFmt:$}=$t(l[1],b?.name,b?.unit,g),f=T?h=>String(Math.round(h)):$,{testNumber:u}=resolveTestNumber(o,d);return t.flatMap((h,v)=>{let m="",x="#111111";if(a==="value"&&p==="test"){const y=U(h,u,b);if(y===void 0)return[];m=y?"P":"F",x=Q(y?Mt:St)}else if(a==="value"){const y=P(h,u);if(y===void 0)return[];m=zt([y],f),x=Q(s.forValue(n(y)))}else if(a==="hardBin"||a==="softBin"){const y=a==="softBin"?h.sbin:h.hbin;if(y===void 0)return[];m=String(y),x=Q(s.forBin(y))}else if(a==="metadata"){const y=ct(h,B);if(y===void 0)return[];m=y,x=Q(F?.get(m)??z)}else{const y=P(h,0);if(y===void 0)return[];m=zt([y],f),x=Q(s.forValue(n(y)))}const V=e?e[v*2]:h.physX,E=e?e[v*2+1]:h.physY;return[{x:V,y:E,text:m,fontSize:Le(h,m),color:x,align:"center"}]})}function qt(t){return t==="top"?Math.PI/2:t==="bottom"?-Math.PI/2:t==="left"?Math.PI:0}function Ne(t,e,i=720){const{center:a,radius:s,notch:n}=t,o=$=>G(e,$.x,$.y);if(!(n!==void 0&&t.diameter>150)){const $=[];for(let f=0;f<=i;f++){const u=2*Math.PI*f/i;$.push(o(Fe(t,u)))}return[{kind:"wafer-boundary",...q($,!0),lineColor:"#888888",lineWidth:1}]}const d=1.25,g=n.length,p=qt(n.type),B=Math.atan2(g,s),F=s-d,T=[];let b=!1;for(let $=0;$<=i;$++){const f=2*Math.PI*$/i;let u=f-p;for(;u>Math.PI;)u-=2*Math.PI;for(;u<-Math.PI;)u+=2*Math.PI;if(Math.abs(u)<=B){if(!b){b=!0;const h={x:a.x+s*Math.cos(p-B),y:a.y+s*Math.sin(p-B)},v={x:a.x+F*Math.cos(p),y:a.y+F*Math.sin(p)},m={x:a.x+s*Math.cos(p+B),y:a.y+s*Math.sin(p+B)};T.push(o(h),o(v),o(m))}continue}T.push(o({x:a.x+s*Math.cos(f),y:a.y+s*Math.sin(f)}))}return[{kind:"wafer-boundary",...q(T,!0),lineColor:"#888888",lineWidth:1}]}function Oe(t,e,i,a=360){const s=[],n=Math.max(1,Math.floor(i));for(let o=1;o<n;o++){const l=t.radius*o/n,d=[];for(let g=0;g<=a;g++){const p=2*Math.PI*g/a,B={x:t.center.x+l*Math.cos(p),y:t.center.y+l*Math.sin(p)};d.push(G(e,B.x,B.y))}s.push({kind:"ring-boundary",...q(d,!0),lineColor:"rgba(255,255,255,0.7)",lineWidth:1.5})}return s}function Ee(t,e,i,a){const{x:s,y:n}=t.center,o=t.radius,l=(f,u)=>G(e,f,u),d=i-s,g=Math.sqrt(Math.max(0,o*o-d*d)),p=l(i,n-g),B=l(i,n+g),F=a-n,T=Math.sqrt(Math.max(0,o*o-F*F)),b=l(s-T,a),$=l(s+T,a);return[{kind:"quadrant-boundary",...q([p,B]),lineColor:"rgba(255,255,255,0.7)",lineWidth:1.5},{kind:"quadrant-boundary",...q([b,$]),lineColor:"rgba(255,255,255,0.7)",lineWidth:1.5}]}function Ae(t,e){return t.map(i=>{const a=i.width/2,s=i.height/2;return{kind:"reticle",points:[[[i.x-a,i.y-s],[i.x+a,i.y-s],[i.x+a,i.y+s],[i.x-a,i.y+s]].map(([o,l])=>G(e,o,l))],closed:!0,lineColor:"rgba(255,255,255,0.7)",lineWidth:1.5,fill:"rgba(0,0,0,0)"}})}function He(t){const e=t.filter(i=>i.probeIndex!==void 0).sort((i,a)=>(i.probeIndex??0)-(a.probeIndex??0));return e.length?[{kind:"probe-path",...q(e.map(i=>({x:i.physX,y:i.physY}))),lineColor:"rgba(220,80,0,0.55)",lineWidth:1}]:[]}function _e(t,e,i,a,s,n,o,l,d,g,p,B,F,T,b="off",$,f,u){const h=e.width-o,v=e.height-o,m=n?v:h,x=n?h:v,V=S=>s==="softBin"?S.sbin:S.hbin,E=S=>ct(S,$);if(e.partial){t.push({x:i,y:a,width:m,height:x,fill:De,type:"stacked",metadata:e.metadata});return}if(e.edgeExcluded){t.push({x:i,y:a,width:m,height:x,fill:Ve,type:"stacked",metadata:e.metadata});return}if(d!==void 0&&(s==="hardBin"||s==="softBin")&&V(e)!==d){t.push({x:i,y:a,width:m,height:x,fill:wt,type:"hardBin",metadata:e.metadata});return}if(u!==void 0&&s==="metadata"&&E(e)!==u){t.push({x:i,y:a,width:m,height:x,fill:wt,type:"metadata",metadata:e.metadata});return}if(s==="value"){if(b==="test"){const j=U(e,p,T),ut=j===void 0?z:j?Mt:St;t.push({x:i,y:a,width:m,height:x,fill:ut,type:"value",metadata:e.metadata});return}const S=P(e,p),N=ot(S,T);let W,J;S===void 0?W=z:b==="spec"?W=N==="failLow"?ge:N==="failHigh"?St:Mt:(W=l.forValue(g(S)),(N==="failLow"||N==="failHigh")&&(J=N)),t.push({x:i,y:a,width:m,height:x,fill:W,type:"value",specMark:J,metadata:e.metadata});return}if(s==="hardBin"||s==="softBin"){const S=V(e),N=S!=null?l.forBin(S):z,W=S!=null&&Se(e,F)===!1?!0:void 0;t.push({x:i,y:a,width:m,height:x,fill:N,type:s,binFail:W,metadata:e.metadata});return}if(s==="metadata"){const S=E(e),N=S!==void 0?f?.get(S)??z:z;t.push({x:i,y:a,width:m,height:x,fill:N,type:"metadata",metadata:e.metadata});return}const y=P(e,0),A=y!==void 0?l.forValue(g(y)):z;t.push({x:i,y:a,width:m,height:x,fill:A,type:"value",metadata:e.metadata})}function Ke(t,e,i){const a=t.radius*.15,s=xt(e,a,0),n=xt(e,0,a),o=s.x+n.x>=0?-1:1,l=s.y+n.y>=0?-1:1,d={x:t.center.x+o*t.radius*.9,y:t.center.y+l*t.radius*.9},g={x:d.x+s.x,y:d.y+s.y},p={x:d.x+n.x,y:d.y+n.y};return i.push({x:g.x+s.x*.35,y:g.y+s.y*.35,text:"+X",fontSize:10,color:"#cc3300",align:"center",role:"indicator"},{x:p.x+n.x*.35,y:p.y+n.y*.35,text:"+Y",fontSize:10,color:"#0044cc",align:"center",role:"indicator"}),[{kind:"xy-indicator",...q([d,g]),lineColor:"#cc3300",lineWidth:2},{kind:"xy-indicator",...q([d,p]),lineColor:"#0044cc",lineWidth:2}]}export function buildView(t,e,i={},a){const s=i.reticles??[],{plotMode:n="value",showDieLabels:o=!1,showReticle:l=!1,showProbePath:d=!1,ringCount:g=4,showRingBoundaries:p=!1,showQuadrantBoundaries:B=!1,showXYIndicator:F=!1,binColorScheme:T="default",valueColorScheme:b="default",reverseValueScheme:$=!1,useDefinedBinColors:f=!0,binColors:u,passBins:h=[1],highlightBin:v,highlightMetadataValue:m,interactiveTransform:x,valueRange:V,metadataValueOrder:E,testDefs:y,activeTest:A=0,activeMetadataKey:S,fallbackFormat:N="engineering",aggregationMethod:W,lotSize:J,dataAxisFlip:j,isLotStack:ut=!1,logScale:Xt,colorbarRangeMode:Yt="spec",passFailDisplay:Gt,showPartialDies:Qt=!0}=i,It=Gt??"off",ft=a?.hbinDefs,dt=a?.sbinDefs,Dt=a?.metadataFields,Ut=Dt?.find(c=>c.key===S),Jt={x:(j?.x??!1)!==(x?.flipX??!1),y:(j?.y??!1)!==(x?.flipY??!1)},Zt=ft?new Map(ft.map(c=>[c.bin,c])):null,te=dt?new Map(dt.map(c=>[c.bin,c])):null,ee=n==="softBin"?te:Zt,ne=new Set(h),ht=u&&Me(u,e)?u:xe(e,{passBins:h,binColorScheme:T,hbinDefs:ft,sbinDefs:dt,useDefinedBinColors:f}),ae=n==="softBin"?ht.soft:ht.hard,Vt={forValue:Pe(be(b,$)),forBin:c=>ae.get(c)??z};let{testNumber:H}=resolveTestNumber(A,y);if(!y?.length&&n==="value"&&!e.some(r=>r.testValues&&H in r.testValues)){const r=e.reduce((M,D)=>{if(!D.testValues)return M;const k=Object.keys(D.testValues).map(Number),R=k.length?$e(k):void 0;return R!==void 0&&(M===void 0||R<M)?R:M},void 0);r!==void 0&&(H=r)}let Z;if(Array.isArray(V))Z=V;else if(V){const{testNumber:c}=resolveTestNumber(V.test,y);Z=c===H?V.range:void 0}const I=findTestDef(y,H),Ct=n==="value"&&I!==void 0&&!vt(I),ie=I!==void 0&&(I.limitLow!==void 0||I.limitHigh!==void 0);let K="off";n==="value"&&(Ct?K="test":It==="spec"&&ie?K="spec":It==="test"&&e.some(c=>U(c,H,I)!==void 0)&&(K="test"));const Ft=K==="spec"?"spec":Yt,mt=n==="stackedValues"||n==="stackedBins"||n==="stackedSoftBins";let O,_,tt=!1;if(Z){[O,_]=Z;let c=!0,r=!1;for(const M of e){const D=mt?P(M,0):P(M,H);if(D!==void 0&&(r=!0,!Number.isInteger(D))){c=!1;break}}tt=r&&c}else{const c=!mt&&n==="value"&&Ft==="spec"&&I&&(I.limitLow!==void 0||I.limitHigh!==void 0);let r=1/0,M=-1/0,D=!0;for(const k of e){const R=mt?P(k,0):P(k,H);R!==void 0&&(R<r&&(r=R),R>M&&(M=R),Number.isInteger(R)||(D=!1))}isFinite(r)||(D=!1),tt=D,c?(O=I.limitLow!==void 0?I.limitLow:isFinite(r)?r:0,_=I.limitHigh!==void 0?I.limitHigh:isFinite(M)?M:1):(O=isFinite(r)?r:0,_=isFinite(M)?M:1)}O===_&&(_=O+1);const Pt=Ct?!1:Xt??I?.logScale??!1,Lt=Pt&&O>0&&_>0,se=Lt;let et;if(Lt){const c=Math.log10(O),r=Math.log10(_)-c;et=M=>M<=0?0:Kt((Math.log10(M)-c)/r)}else et=c=>Kt((c-O)/(_-O));const C=Ce(t,j,x);let Rt=null;if(t.notch){const c=qt(t.notch.type),r=xt(C.physicalToScreen,Math.cos(c)*t.radius,Math.sin(c)*t.radius),M=Math.hypot(r.x,r.y);M>0&&(Rt={x:r.x/M,y:r.y/M})}let X=1/0;for(let c=0;c<e.length;c++){const r=e[c];r.width<X&&(X=r.width),r.height<X&&(X=r.height)}const Nt=1,oe=X!==1/0?Math.min(Nt,X*.12):Nt,Ot=[],pt=[],re=C.bakedToScreen.a===1&&C.bakedToScreen.b===0&&C.bakedToScreen.c===0&&C.bakedToScreen.d===1&&C.bakedToScreen.e===0&&C.bakedToScreen.f===0;let w=null;if(!re){w=new Float64Array(e.length*2);for(let c=0;c<e.length;c++){const r=G(C.bakedToScreen,e[c].physX,e[c].physY);w[c*2]=r.x,w[c*2+1]=r.y}}const le=ye(C.gridToScreen),ce=n==="hardBin"||n==="softBin"||n==="stackedBins"||n==="stackedSoftBins",ue=n==="softBin"||n==="stackedSoftBins";let nt=ce?new Map:void 0;const yt=n==="value"&&K==="spec"?{pass:0,failHigh:0,failLow:0}:void 0,gt=n==="value"&&K==="test"?{pass:0,fail:0}:void 0,Et=n==="metadata";let at=Et?new Map:void 0,it=null;if(Et&&S){const c=collectMetadataValues(e,S),r=E?.key===S?E.values:void 0,M=r?[...r,...c.filter(D=>!r.includes(D))]:c;it=new Map(M.map((D,k)=>[D,Ut?.values?.find(R=>R.value===D)?.color??ve(k)]))}for(let c=0;c<e.length;c++){const r=e[c],M=w?w[c*2]:r.physX,D=w?w[c*2+1]:r.physY;if(pt.push({x:M,y:D}),nt&&!r.partial&&!r.edgeExcluded){const k=ue?r.sbin:r.hbin;k!=null&&nt.set(k,(nt.get(k)??0)+1)}if(yt&&!r.partial&&!r.edgeExcluded){const k=ot(P(r,H),I);k&&yt[k]++}if(gt&&!r.partial&&!r.edgeExcluded){const k=U(r,H,I);k!==void 0&>[k?"pass":"fail"]++}if(at&&!r.partial&&!r.edgeExcluded){const k=ct(r,S);k!==void 0&&at.set(k,(at.get(k)??0)+1)}r.partial&&!Qt||_e(Ot,r,M,D,n,le,oe,Vt,v,et,H,ee,ne,I,K,S,it,m)}const At=o?generateTextOverlay(e,w,{plotMode:n,colorFns:Vt,normalize:et,activeTest:A,valueRange:[O,_],testDefs:y,fallbackFormat:N,passFailDisplay:K,activeMetadataKey:S,metadataColorMap:it,allIntegerValues:tt}):[],Y=Ne(t,C.physicalToScreen);p&&Y.push(...Oe(t,C.physicalToScreen,g)),B&&Y.push(...Ee(t,C.physicalToScreen,t.center.x,t.center.y)),l&&Y.push(...Ae(s,C.gridToScreen)),d&&Y.push(...He(e)),F&&Y.push(...Ke(t,C.gridToScreen,At));const fe=pt.length>0?{minX:t.center.x-t.radius,maxX:t.center.x+t.radius,minY:t.center.y-t.radius,maxY:t.center.y+t.radius}:null;return{rectangles:Ot,hoverPoints:pt,texts:At,overlays:Y,plotMode:n,binColorScheme:T,valueColorScheme:b,reverseValueScheme:$,binColors:ht,metadata:t.metadata??null,dies:e,valueRange:[O,_],allIntegerValues:tt,testDefs:y,activeTest:A,logScale:se,logScaleRequested:Pt,aggrMethod:W,lotSize:J,axisFlip:Jt,dataAxisFlip:{x:j?.x??!1,y:j?.y??!1},gridToScreen:C.gridToScreen,rotation:((t.orientation+(x?.rotation??0))%360+360)%360,hasReticle:s.length>0,isLotStack:ut,passFailDisplay:K,colorbarRangeMode:Ft,waferCenter:t.center,waferRadius:t.radius,notchDir:Rt,binCounts:nt,metadataCounts:at,metadataColorMap:it,activeMetadataKey:S,metadataFields:Dt,specCounts:yt,passFailCounts:gt,dieBounds:fe}}export{getDieKey}from"../core/dies.js";
|
|
1
|
+
import{hasPosition as pe}from"../core/dies.js";import{dieValueEntries as ye,dieVerdictEntries as ge,recordedVerdict as be,testsPresent as _t}from"../core/dieTable.js";import{getReticleCell as xe}from"../core/reticle.js";import{metadataCategoricalValue as Me,metadataDisplayValue as Se}from"../core/metadata.js";import{affineRotation as bt,affineMirror as Ot,affineCompose as st,affinePoint as G,affineVector as xt,affineSwapsAxes as ve}from"../core/transforms.js";import{contrastTextColor as Q,SPEC_PASS_FILL as Mt,SPEC_FAIL_LOW as ke,SPEC_FAIL_HIGH as St}from"./colorMap.js";import{resolveValueColorFn as Be}from"./colorSchemes.js";import{resolveBinColors as $e,binColorsCover as Te}from"./binColors.js";import{NO_DATA_FILL as q}from"./colorMap.js";import{diePassStatus as Ie}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 Bt,fmtAggregationMethod as Wt}from"./fmt.js";import{metadataValueColor as De}from"./colorMap.js";import{asList as wt,clamp01 as zt,compareNatural as Ce,escHtml as R}from"../core/utils.js";import{prettyKey as Kt}from"../core/utils.js";import{testLabel as rt,markedTestLabel as lt,derivedTestNote as qt,isDerivedTest as Fe,DERIVED_MARK as Ve,DERIVED_KEY as Le}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 a=new Set;for(const i of t){if(i.partial||i.edgeExcluded)continue;const s=ct(i,e);s!==void 0&&a.add(s)}return[...a].sort(Ce)}export function findTestDef(t,e){return t?.find(a=>a.testNumber===e)}export function getUniqueTestNumbers(t){return _t(t,"values")}const Re="#d3d6db",Xt="#e8e9ea",Pe="#aab0ba";function ct(t,e){return e?Me(t.metadata?.[e]):void 0}function Ne(t,e,a){const{x:i,y:s}=t.center,n=bt(t.orientation,i,s),o=st(Ot(e?.x??!1,e?.y??!1,i,s),bt(t.orientation,i,s)),r=st(Ot(a?.flipX??!1,a?.flipY??!1,i,s),bt(a?.rotation??0,i,s));return{bakedToScreen:r,physicalToScreen:st(r,n),gridToScreen:st(r,o)}}function X(t,e=!1){return{points:[t],closed:e}}function Ee(t,e){const{center:a,radius:i,notch:s}=t;let n=a.x+i*Math.cos(e),o=a.y+i*Math.sin(e);if(!s)return{x:n,y:o};const r=Math.sqrt(i**2-(s.length/2)**2),f=s.length/2,p=n-a.x,y=o-a.y;return s.type==="bottom"&&y<-r?(o=a.y-r,n=a.x+Math.max(-f,Math.min(f,p))):s.type==="top"&&y>r?(o=a.y+r,n=a.x+Math.max(-f,Math.min(f,p))):s.type==="left"&&p<-r?(n=a.x-r,o=a.y+Math.max(-f,Math.min(f,y))):s.type==="right"&&p>r&&(n=a.x+r,o=a.y+Math.max(-f,Math.min(f,y))),{x:n,y:o}}function Yt(t,e){return t.map(e).join(" / ")}function Ae(t,e=256){const a=Array.from({length:e},(i,s)=>t(s/(e-1)));return i=>a[Math.min(e-1,Math.max(0,Math.round(i*(e-1))))]}function He(t,e){const a=Math.max(1,Math.min(t.width,t.height)),i=t.width/Math.max(e.length,1);return Math.max(8,Math.min(16,Math.round(Math.min(a*.55,i*1.8))))}function _e(t,e,a){if(e?.length){const n=e.flatMap(o=>{const r=o.testNumber;if(!vt(o)){const p=U(t,r,o);return p===void 0?[]:[{key:r,label:lt(o,r),value:p?"Pass":"Fail"}]}const f=L(t,r);return f===void 0?[]:[{key:r,label:lt(o,r),value:kt(f,o.unit,a),recordedFail:be(t,r)===!1}]});if(n.length)return n}const i=ye(t);if(i.length)return i.map(([n,o])=>({key:n,label:rt(void 0,n),value:kt(o,void 0,a)}));const s=ge(t);return s.length?s.map(([n,o])=>({key:n,label:rt(void 0,n),value:o?"Pass":"Fail"})):[]}export function buildHoverText(t,e,a={}){const{testDefs:i,hbinDefs:s,sbinDefs:n,fallbackFormat:o,aggrMethod:r,lotSize:f,activeTest:p,reticleConfig:y,metadataFields:B}=a,F=s?new Map(s.map(d=>[d.bin,d])):null,T=n?new Map(n.map(d=>[d.bin,d])):null,b=[R(`Die (${t.x}, ${t.y})`)];if(y&&pe(t)){const d=xe(t,y);b.push(R(`Reticle (${d.column}, ${d.row})`))}if(e==="stackedValues"){const d=L(t,0);if(d!==void 0){const u=i?.[0],h=u?lt(u,u.testNumber):"Value",S=r?` (${r})`:"";b.push(R(`${h}${S}: ${kt(d,u?.unit,o)}`));const m=qt(u);m&&b.push(`<i>${R(m)}</i>`)}}else if(e==="stackedBins"||e==="stackedSoftBins"){const d=L(t,0),u=e==="stackedSoftBins"?t.sbin:t.hbin;if(d!==void 0){const h=e==="stackedSoftBins"?T:F,S=u!==void 0?h?.get(u)?.name?`${u} \xB7 ${h.get(u).name}`:`Bin ${u}`:"Bin";let m;if(r==="percent")m=`${d.toFixed(0)}%`;else{const D=f?` (${(d/f*100).toFixed(0)}% of stacked wafers)`:"";m=`${d}${D}`}const x=r?` [${Wt(r)}]`:"";b.push(R(`${S}: ${m}${x}`))}}else{const d=_e(t,i,o);if(e==="value"&&d.length){const{testNumber:u}=resolveTestNumber(p??0,i),h=findTestDef(i,u),S=L(t,u);let m=d.findIndex(A=>A.key===u);m<0&&(m=0);const x=d[m];let D=`<b>${R(`${x.label}: ${x.value}`)}${x.recordedFail?" <i>(recorded fail)</i>":""}</b>`;if(x.key===u&&h){const A=ot(S,h);(A==="failLow"||A==="failHigh")&&(D+=" <i>(outside test limits)</i>")}b.push(D);const E=qt(findTestDef(i,x.key));E&&b.push(`<i>${R(E)}</i>`);const g=d.length-1;g>0&&b.push(`<i>+${g} more test${g===1?"":"s"}</i>`)}else if(d.length){const u=d.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,S=h?`${t.hbin} \xB7 ${h}`:String(t.hbin);u.push(`HBin: ${S}`)}if(t.sbin!==void 0){const h=T?.get(t.sbin)?.name,S=h?`${t.sbin} \xB7 ${h}`:String(t.sbin);u.push(`SBin: ${S}`)}b.push(u.map(R).join(" \xB7 "))}}t.retestCount!==void 0&&b.push(R(`Retests: ${t.retestCount}`)),t.siteNum!==void 0&&b.push(R(`Site: ${t.siteNum}`)),t.partId!==void 0&&b.push(R(`Part ID: ${t.partId}`)),t.partial&&b.push("<i>partial die</i>"),t.probeIndex!==void 0&&b.push(R(`Probe: #${t.probeIndex}`));const k={...t.metadata??{}};for(const[d,u]of Object.entries(k)){const h=Se(u);if(h===void 0)continue;const S=B?.find(m=>m.key===d)?.label??Kt(d);b.push(R(`${S}: ${h}`))}return b.join("<br>")}function $t(t){return t?.name?lt(t,t.testNumber):void 0}function Tt(t){return Fe(t)?{note:`${Ve} ${Le}`}:{}}export function buildMapTitle(t,e="engineering",a){const i=t.isLotStack&&t.lotSize!==void 0?`stacked (${t.lotSize} ${t.lotSize===1?"wafer":"wafers"})`:"stacked",s=n=>{const o=a?.length===1?a[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:i};case"stackedSoftBins":return{primary:s("Soft"),secondary:i};case"metadata":{const n=t.metadataFields?.find(r=>r.key===t.activeMetadataKey),o=t.activeMetadataKey?Kt(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:r}=Bt(o,$t(n),n?.unit,e),f=r||"Value",p=t.aggrMethod?` \xB7 ${Wt(t.aggrMethod)}`:"";return{primary:`${f}${p}`,secondary:i,...Tt(n)}}default:{const n=findTestDef(t.testDefs,t.activeTest),o=t.valueRange[1]||t.valueRange[0]||0,{axisLabel:r}=Bt(o,$t(n),n?.unit,e),f=r||$t(n);if(t.passFailDisplay!=="off"){const p=n?.testNumber??t.activeTest,y=f?`${f} \xB7 #${p}`:rt(void 0,p),B=t.passFailDisplay==="spec"?"Limit pass/fail":n!==void 0&&!vt(n)?"Functional pass/fail":"Tester pass/fail";return{primary:y,secondary:B,...Tt(n)}}return{primary:f??rt(void 0,t.activeTest),secondary:"",...Tt(n)}}}}export function generateTextOverlay(t,e,a){const{plotMode:i,colorFns:s,normalize:n,activeTest:o,valueRange:r,testDefs:f,fallbackFormat:p,passFailDisplay:y="off",activeMetadataKey:B,metadataColorMap:F,allIntegerValues:T}=a,b=findTestDef(f,o),{tickFmt:k}=Bt(r[1],b?.name,b?.unit,p),d=T?h=>String(Math.round(h)):k,{testNumber:u}=resolveTestNumber(o,f);return t.flatMap((h,S)=>{let m="",x="#111111";if(i==="value"&&y==="test"){const g=U(h,u,b);if(g===void 0)return[];m=g?"P":"F",x=Q(g?Mt:St)}else if(i==="value"){const g=L(h,u);if(g===void 0)return[];m=Yt([g],d),x=Q(s.forValue(n(g)))}else if(i==="hardBin"||i==="softBin"){const g=i==="softBin"?h.sbin:h.hbin;if(g===void 0)return[];m=String(g),x=Q(s.forBin(g))}else if(i==="metadata"){const g=ct(h,B);if(g===void 0)return[];m=g,x=Q(F?.get(m)??q)}else{const g=L(h,0);if(g===void 0)return[];m=Yt([g],d),x=Q(s.forValue(n(g)))}const D=e?e[S*2]:h.physX,E=e?e[S*2+1]:h.physY;return[{x:D,y:E,text:m,fontSize:He(h,m),color:x,align:"center"}]})}function jt(t){return t==="top"?Math.PI/2:t==="bottom"?-Math.PI/2:t==="left"?Math.PI:0}function Oe(t,e,a=720){const{center:i,radius:s,notch:n}=t,o=k=>G(e,k.x,k.y);if(!(n!==void 0&&t.diameter>150)){const k=[];for(let d=0;d<=a;d++){const u=2*Math.PI*d/a;k.push(o(Ee(t,u)))}return[{kind:"wafer-boundary",...X(k,!0),lineColor:"#888888",lineWidth:1}]}const f=1.25,p=n.length,y=jt(n.type),B=Math.atan2(p,s),F=s-f,T=[];let b=!1;for(let k=0;k<=a;k++){const d=2*Math.PI*k/a;let u=d-y;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:i.x+s*Math.cos(y-B),y:i.y+s*Math.sin(y-B)},S={x:i.x+F*Math.cos(y),y:i.y+F*Math.sin(y)},m={x:i.x+s*Math.cos(y+B),y:i.y+s*Math.sin(y+B)};T.push(o(h),o(S),o(m))}continue}T.push(o({x:i.x+s*Math.cos(d),y:i.y+s*Math.sin(d)}))}return[{kind:"wafer-boundary",...X(T,!0),lineColor:"#888888",lineWidth:1}]}function We(t,e,a,i=360){const s=[],n=Math.max(1,Math.floor(a));for(let o=1;o<n;o++){const r=t.radius*o/n,f=[];for(let p=0;p<=i;p++){const y=2*Math.PI*p/i,B={x:t.center.x+r*Math.cos(y),y:t.center.y+r*Math.sin(y)};f.push(G(e,B.x,B.y))}s.push({kind:"ring-boundary",...X(f,!0),lineColor:"rgba(255,255,255,0.7)",lineWidth:1.5})}return s}function we(t,e,a,i){const{x:s,y:n}=t.center,o=t.radius,r=(d,u)=>G(e,d,u),f=a-s,p=Math.sqrt(Math.max(0,o*o-f*f)),y=r(a,n-p),B=r(a,n+p),F=i-n,T=Math.sqrt(Math.max(0,o*o-F*F)),b=r(s-T,i),k=r(s+T,i);return[{kind:"quadrant-boundary",...X([y,B]),lineColor:"rgba(255,255,255,0.7)",lineWidth:1.5},{kind:"quadrant-boundary",...X([b,k]),lineColor:"rgba(255,255,255,0.7)",lineWidth:1.5}]}function ze(t,e){return t.map(a=>{const i=a.width/2,s=a.height/2;return{kind:"reticle",points:[[[a.x-i,a.y-s],[a.x+i,a.y-s],[a.x+i,a.y+s],[a.x-i,a.y+s]].map(([o,r])=>G(e,o,r))],closed:!0,lineColor:"rgba(255,255,255,0.7)",lineWidth:1.5,fill:"rgba(0,0,0,0)"}})}function Ke(t){const e=t.filter(a=>a.probeIndex!==void 0).sort((a,i)=>(a.probeIndex??0)-(i.probeIndex??0));return e.length?[{kind:"probe-path",...X(e.map(a=>({x:a.physX,y:a.physY}))),lineColor:"rgba(220,80,0,0.55)",lineWidth:1}]:[]}function qe(t,e,a,i,s,n,o,r,f,p,y,B,F,T,b="off",k,d,u){const h=e.width-o,S=e.height-o,m=n?S:h,x=n?h:S,D=M=>s==="softBin"?M.sbin:M.hbin,E=M=>ct(M,k);if(e.partial){t.push({x:a,y:i,width:m,height:x,fill:Re,type:"stacked",metadata:e.metadata});return}if(e.edgeExcluded){t.push({x:a,y:i,width:m,height:x,fill:Pe,type:"stacked",metadata:e.metadata});return}if(f!==void 0&&(s==="hardBin"||s==="softBin")&&!f.has(D(e))){t.push({x:a,y:i,width:m,height:x,fill:Xt,type:"hardBin",metadata:e.metadata});return}if(u!==void 0&&s==="metadata"&&!u.has(E(e))){t.push({x:a,y:i,width:m,height:x,fill:Xt,type:"metadata",metadata:e.metadata});return}if(s==="value"){if(b==="test"){const w=U(e,y,T),ut=w===void 0?q:w?Mt:St;t.push({x:a,y:i,width:m,height:x,fill:ut,type:"value",metadata:e.metadata});return}const M=L(e,y),P=ot(M,T);let W,J;M===void 0?W=q:b==="spec"?W=P==="failLow"?ke:P==="failHigh"?St:Mt:(W=r.forValue(p(M)),(P==="failLow"||P==="failHigh")&&(J=P)),t.push({x:a,y:i,width:m,height:x,fill:W,type:"value",specMark:J,metadata:e.metadata});return}if(s==="hardBin"||s==="softBin"){const M=D(e),P=M!=null?r.forBin(M):q,W=M!=null&&Ie(e,F)===!1?!0:void 0;t.push({x:a,y:i,width:m,height:x,fill:P,type:s,binFail:W,metadata:e.metadata});return}if(s==="metadata"){const M=E(e),P=M!==void 0?d?.get(M)??q:q;t.push({x:a,y:i,width:m,height:x,fill:P,type:"metadata",metadata:e.metadata});return}const g=L(e,0),A=g!==void 0?r.forValue(p(g)):q;t.push({x:a,y:i,width:m,height:x,fill:A,type:"value",metadata:e.metadata})}function Xe(t,e,a){const i=t.radius*.15,s=xt(e,i,0),n=xt(e,0,i),o=s.x+n.x>=0?-1:1,r=s.y+n.y>=0?-1:1,f={x:t.center.x+o*t.radius*.9,y:t.center.y+r*t.radius*.9},p={x:f.x+s.x,y:f.y+s.y},y={x:f.x+n.x,y:f.y+n.y};return a.push({x:p.x+s.x*.35,y:p.y+s.y*.35,text:"+X",fontSize:10,color:"#cc3300",align:"center",role:"indicator"},{x:y.x+n.x*.35,y:y.y+n.y*.35,text:"+Y",fontSize:10,color:"#0044cc",align:"center",role:"indicator"}),[{kind:"xy-indicator",...X([f,p]),lineColor:"#cc3300",lineWidth:2},{kind:"xy-indicator",...X([f,y]),lineColor:"#0044cc",lineWidth:2}]}export function buildView(t,e,a={},i){const s=a.reticles??[],{plotMode:n="value",showDieLabels:o=!1,showReticle:r=!1,showProbePath:f=!1,ringCount:p=4,showRingBoundaries:y=!1,showQuadrantBoundaries:B=!1,showXYIndicator:F=!1,binColorScheme:T="default",valueColorScheme:b="default",reverseValueScheme:k=!1,useDefinedBinColors:d=!0,binColors:u,passBins:h=[1],highlightBin:S,highlightMetadataValue:m,interactiveTransform:x,valueRange:D,metadataValueOrder:E,testDefs:g,activeTest:A=0,activeMetadataKey:M,fallbackFormat:P="engineering",aggregationMethod:W,lotSize:J,dataAxisFlip:w,isLotStack:ut=!1,logScale:Gt,colorbarRangeMode:Qt="spec",passFailDisplay:Ut}=a,It=Ut??"off",ft=i?.hbinDefs,dt=i?.sbinDefs,Dt=i?.metadataFields,Jt=Dt?.find(l=>l.key===M),Zt={x:(w?.x??!1)!==(x?.flipX??!1),y:(w?.y??!1)!==(x?.flipY??!1)},te=ft?new Map(ft.map(l=>[l.bin,l])):null,ee=dt?new Map(dt.map(l=>[l.bin,l])):null,ne=n==="softBin"?ee:te,ae=new Set(h),ht=u&&Te(u,e)?u:$e(e,{passBins:h,binColorScheme:T,hbinDefs:ft,sbinDefs:dt,useDefinedBinColors:d}),ie=n==="softBin"?ht.soft:ht.hard,Ct={forValue:Ae(Be(b,k)),forBin:l=>ie.get(l)??q};let{testNumber:H}=resolveTestNumber(A,g);if(!g?.length&&n==="value"){const l=_t(e,"values");!l.includes(H)&&l.length&&(H=l[0])}let Z;if(Array.isArray(D))Z=D;else if(D){const{testNumber:l}=resolveTestNumber(D.test,g);Z=l===H?D.range:void 0}const I=findTestDef(g,H),Ft=n==="value"&&I!==void 0&&!vt(I),se=I!==void 0&&(I.limitLow!==void 0||I.limitHigh!==void 0);let O="off";n==="value"&&(Ft?O="test":It==="spec"&&se?O="spec":It==="test"&&e.some(l=>U(l,H,I)!==void 0)&&(O="test"));const Vt=O==="spec"?"spec":Qt,mt=n==="stackedValues"||n==="stackedBins"||n==="stackedSoftBins";let N,_,tt=!1;if(Z){[N,_]=Z;let l=!0,c=!1;for(const v of e){const V=mt?L(v,0):L(v,H);if(V!==void 0&&(c=!0,!Number.isInteger(V))){l=!1;break}}tt=c&&l}else{const l=!mt&&n==="value"&&Vt==="spec"&&I&&(I.limitLow!==void 0||I.limitHigh!==void 0);let c=1/0,v=-1/0,V=!0;for(const $ of e){const K=mt?L($,0):L($,H);K!==void 0&&(K<c&&(c=K),K>v&&(v=K),Number.isInteger(K)||(V=!1))}isFinite(c)||(V=!1),tt=V,l?(N=I.limitLow!==void 0?I.limitLow:isFinite(c)?c:0,_=I.limitHigh!==void 0?I.limitHigh:isFinite(v)?v:1):(N=isFinite(c)?c:0,_=isFinite(v)?v:1)}N===_&&(_=N+1);const Lt=Ft?!1:Gt??I?.logScale??!1,Rt=Lt&&N>0&&_>0,oe=Rt;let et;if(Rt){const l=Math.log10(N),c=Math.log10(_)-l;et=v=>v<=0?0:zt((Math.log10(v)-l)/c)}else et=l=>zt((l-N)/(_-N));const C=Ne(t,w,x);let Pt=null;if(t.notch){const l=jt(t.notch.type),c=xt(C.physicalToScreen,Math.cos(l)*t.radius,Math.sin(l)*t.radius),v=Math.hypot(c.x,c.y);v>0&&(Pt={x:c.x/v,y:c.y/v})}let Y=1/0;for(let l=0;l<e.length;l++){const c=e[l];c.width<Y&&(Y=c.width),c.height<Y&&(Y=c.height)}const Nt=1,re=Y!==1/0?Math.min(Nt,Y*.12):Nt,Et=[],pt=[],le=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(!le){z=new Float64Array(e.length*2);for(let l=0;l<e.length;l++){const c=G(C.bakedToScreen,e[l].physX,e[l].physY);z[l*2]=c.x,z[l*2+1]=c.y}}const ce=ve(C.gridToScreen),ue=n==="hardBin"||n==="softBin"||n==="stackedBins"||n==="stackedSoftBins",fe=n==="softBin"||n==="stackedSoftBins";let nt=ue?new Map:void 0;const yt=n==="value"&&O==="spec"?{pass:0,failHigh:0,failLow:0}:void 0,gt=n==="value"&&O==="test"?{pass:0,fail:0}:void 0,At=n==="metadata";let at=At?new Map:void 0,it=null;if(At&&M){const l=collectMetadataValues(e,M),c=E?.key===M?E.values:void 0,v=c?[...c,...l.filter(V=>!c.includes(V))]:l;it=new Map(v.map((V,$)=>[V,Jt?.values?.find(K=>K.value===V)?.color??De($)]))}const de=S===void 0?void 0:new Set(wt(S)),he=m===void 0?void 0:new Set(wt(m));for(let l=0;l<e.length;l++){const c=e[l],v=z?z[l*2]:c.physX,V=z?z[l*2+1]:c.physY;if(pt.push({x:v,y:V}),nt&&!c.partial&&!c.edgeExcluded){const $=fe?c.sbin:c.hbin;$!=null&&nt.set($,(nt.get($)??0)+1)}if(yt&&!c.partial&&!c.edgeExcluded){const $=ot(L(c,H),I);$&&yt[$]++}if(gt&&!c.partial&&!c.edgeExcluded){const $=U(c,H,I);$!==void 0&>[$?"pass":"fail"]++}if(at&&!c.partial&&!c.edgeExcluded){const $=ct(c,M);$!==void 0&&at.set($,(at.get($)??0)+1)}qe(Et,c,v,V,n,ce,re,Ct,de,et,H,ne,ae,I,O,M,it,he)}const Ht=o?generateTextOverlay(e,z,{plotMode:n,colorFns:Ct,normalize:et,activeTest:A,valueRange:[N,_],testDefs:g,fallbackFormat:P,passFailDisplay:O,activeMetadataKey:M,metadataColorMap:it,allIntegerValues:tt}):[],j=Oe(t,C.physicalToScreen);y&&j.push(...We(t,C.physicalToScreen,p)),B&&j.push(...we(t,C.physicalToScreen,t.center.x,t.center.y)),r&&j.push(...ze(s,C.gridToScreen)),f&&j.push(...Ke(e)),F&&j.push(...Xe(t,C.gridToScreen,Ht));const me=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:Et,hoverPoints:pt,texts:Ht,overlays:j,plotMode:n,binColorScheme:T,valueColorScheme:b,reverseValueScheme:k,binColors:ht,metadata:t.metadata??null,dies:e,valueRange:[N,_],allIntegerValues:tt,testDefs:g,activeTest:A,logScale:oe,logScaleRequested:Lt,aggrMethod:W,lotSize:J,axisFlip:Zt,dataAxisFlip:{x:w?.x??!1,y:w?.y??!1},gridToScreen:C.gridToScreen,rotation:((t.orientation+(x?.rotation??0))%360+360)%360,hasReticle:s.length>0,isLotStack:ut,passFailDisplay:O,colorbarRangeMode:Vt,waferCenter:t.center,waferRadius:t.radius,notchDir:Pt,binCounts:nt,metadataCounts:at,metadataColorMap:it,activeMetadataKey:M,metadataFields:Dt,specCounts:yt,passFailCounts:gt,dieBounds:me}}export{getDieKey}from"../core/dies.js";
|
|
@@ -2,9 +2,10 @@ import type { Die } from '../core/dies.js';
|
|
|
2
2
|
import type { DieMetadata, WaferMetadata } from '../core/metadata.js';
|
|
3
3
|
import type { Wafer } from '../core/wafer.js';
|
|
4
4
|
import type { Reticle } from '../core/reticle.js';
|
|
5
|
+
import type { DieColumns } from './columnarInput.js';
|
|
5
6
|
import { type DerivedTestDef } from './derivedTests/apply.js';
|
|
6
7
|
export type { DerivedTestDef } from './derivedTests/apply.js';
|
|
7
|
-
import
|
|
8
|
+
import type { PlotMode } from './buildView.js';
|
|
8
9
|
/**
|
|
9
10
|
* Test result for a single die position, as output by the prober.
|
|
10
11
|
* `x` and `y` are **die grid positions** (prober step coordinates) — integers
|
|
@@ -111,9 +112,11 @@ export interface WaferConfig {
|
|
|
111
112
|
* clockwise. The notch/flat position is set by `notch.type` and is not
|
|
112
113
|
* affected by this value — `orientation` rotates the *die grid* on the display.
|
|
113
114
|
*
|
|
114
|
-
*
|
|
115
|
+
* One of 0 (default), 90, 180 or 270 — the angles STDF can record. An
|
|
116
|
+
* equivalent angle such as −90 is read as 270; any other is treated as
|
|
117
|
+
* missing (built at 0) and reported as `input-values-outside-stdf`.
|
|
115
118
|
*/
|
|
116
|
-
orientation?:
|
|
119
|
+
orientation?: 0 | 90 | 180 | 270;
|
|
117
120
|
metadata?: WaferMetadata;
|
|
118
121
|
/**
|
|
119
122
|
* Physical edge exclusion zone in mm measured from the wafer edge inward.
|
|
@@ -354,12 +357,11 @@ export interface WaferMapInputBase {
|
|
|
354
357
|
ringCount?: number;
|
|
355
358
|
/**
|
|
356
359
|
* Wafer diameters (mm) treated as standard when sanity-checking an *inferred*
|
|
357
|
-
* diameter
|
|
358
|
-
* (100/125/150/200/300) and the measurements behind it.
|
|
360
|
+
* diameter. Default `[100, 125, 150, 200, 300]`.
|
|
359
361
|
*
|
|
360
|
-
* **Replaces** the default rather than adding to it, so
|
|
361
|
-
* `standardDiameters: [
|
|
362
|
-
* also runs 3-inch. Pass `[]` to disable the check entirely, for genuinely
|
|
362
|
+
* **Replaces** the default rather than adding to it, so list the defaults to
|
|
363
|
+
* extend it: `standardDiameters: [100, 125, 150, 200, 300, 76.2]` for a line
|
|
364
|
+
* that also runs 3-inch. Pass `[]` to disable the check entirely, for genuinely
|
|
363
365
|
* non-standard substrates (panels, reclaim, odd R&D shapes) — that is the
|
|
364
366
|
* intended opt-out, rather than suppressing every geometry advisory.
|
|
365
367
|
*
|
|
@@ -457,8 +459,11 @@ export interface WaferMapInputBase {
|
|
|
457
459
|
}
|
|
458
460
|
/** Single-wafer input — pass one wafer's die results directly. */
|
|
459
461
|
export interface WaferMapInputSingle extends WaferMapInputBase {
|
|
460
|
-
/**
|
|
461
|
-
|
|
462
|
+
/**
|
|
463
|
+
* Per-die test results from the prober: one object per record, or the same
|
|
464
|
+
* records as columns (`DieColumns`), for a host that already holds columns.
|
|
465
|
+
*/
|
|
466
|
+
results?: DieResult[] | DieColumns;
|
|
462
467
|
lotStack?: never;
|
|
463
468
|
layout?: never;
|
|
464
469
|
}
|
|
@@ -494,10 +499,6 @@ export interface WaferMapInputLayout extends WaferMapInputBase {
|
|
|
494
499
|
* ({@link WaferMapInputLayout}). Combining them is a type error.
|
|
495
500
|
*/
|
|
496
501
|
export type WaferMapInput = WaferMapInputSingle | WaferMapInputLotStack | WaferMapInputLayout;
|
|
497
|
-
/** Options forwarded to {@link buildView}. */
|
|
498
|
-
export interface WaferMapOptions extends ViewOptions {
|
|
499
|
-
debug?: boolean;
|
|
500
|
-
}
|
|
501
502
|
export interface YieldSummary {
|
|
502
503
|
/** Dies with a bin in `passBins`. */
|
|
503
504
|
passDies: number;
|
|
@@ -588,9 +589,10 @@ export interface WaferWarning {
|
|
|
588
589
|
* plotted wrongly — a bin of "1" is not pass bin 1. The message counts them.
|
|
589
590
|
* - `'input-values-outside-stdf'` — raised by `buildWaferMap`: bins, coordinates,
|
|
590
591
|
* test numbers, site numbers or `waferConfig.orientation` outside the STDF V4
|
|
591
|
-
* ranges, or test values that are not finite.
|
|
592
|
-
*
|
|
593
|
-
* is
|
|
592
|
+
* ranges, or test values that are not finite. Each is treated as missing: the
|
|
593
|
+
* bin or site is absent, the die has no position, the test is left out, the
|
|
594
|
+
* value is dropped, the map is built at orientation 0. A `NaN` bin is no bin,
|
|
595
|
+
* and is counted here too.
|
|
594
596
|
* - `'retests-by-part-id'` — raised by `buildWaferMap` (severity `'info'`): dies
|
|
595
597
|
* with no position that share a part ID were treated as retests of one die.
|
|
596
598
|
* Blank part IDs never match, and part IDs are not used on a wafer where one
|
|
@@ -651,21 +653,14 @@ export interface WaferMapResult {
|
|
|
651
653
|
/** `true` when the result was built from a `lotStack` aggregation. */
|
|
652
654
|
isLotStack: boolean;
|
|
653
655
|
/**
|
|
654
|
-
* @internal
|
|
655
|
-
*
|
|
656
|
-
|
|
657
|
-
/**
|
|
658
|
-
* @internal The initial draw list. Kept on the result because `analyzeWaferMap`
|
|
659
|
-
* discriminates its input on `'view' in input`, and because `renderWaferMap`
|
|
660
|
-
* reads `dataAxisFlip` from it — not because a host should.
|
|
661
|
-
*
|
|
662
|
-
* Read the promoted top-level fields instead (`plotMode`, `metadata`,
|
|
663
|
-
* `isLotStack`, `hbinDefs`, `sbinDefs`, `testDefs`): this one is rebuilt on
|
|
664
|
-
* every option change, so anything you cache from it goes stale immediately.
|
|
665
|
-
* Hosts driving the low-level pipeline should call `buildView` themselves,
|
|
666
|
-
* which is the supported way to hold a {@link View}.
|
|
656
|
+
* @internal The data's own axis flip (`dieConfig.xAxisDirection` / `yAxisDirection` /
|
|
657
|
+
* `coordinateOrigin`), which the renderers feed back into every view they build.
|
|
658
|
+
* Its presence also marks a map as built by `buildWaferMap` (`isBuiltMap`).
|
|
667
659
|
*/
|
|
668
|
-
|
|
660
|
+
dataAxisFlip: {
|
|
661
|
+
x: boolean;
|
|
662
|
+
y: boolean;
|
|
663
|
+
};
|
|
669
664
|
/** Reticle configuration used to generate the overlay and reticle-local groupings. */
|
|
670
665
|
reticleConfig?: ReticleConfig;
|
|
671
666
|
/**
|
|
@@ -766,6 +761,12 @@ export interface WaferMapResult {
|
|
|
766
761
|
*/
|
|
767
762
|
lotSize?: number;
|
|
768
763
|
}
|
|
764
|
+
/**
|
|
765
|
+
* @internal Whether `map` was built by `buildWaferMap`, rather than being an input
|
|
766
|
+
* or a map a host assembled from its own pieces. Survives a spread and a worker's
|
|
767
|
+
* structured clone, which is why it tests a field and not object identity.
|
|
768
|
+
*/
|
|
769
|
+
export declare function isBuiltMap(map: object): map is WaferMapResult;
|
|
769
770
|
/** Read a test value from a die by test number. */
|
|
770
771
|
export declare function getDieTestValue(die: Die, testNumber: number): number | undefined;
|
|
771
772
|
/**
|
|
@@ -782,6 +783,15 @@ export declare function getDieTestValue(die: Die, testNumber: number): number |
|
|
|
782
783
|
* as no-data, never as a fail.
|
|
783
784
|
*/
|
|
784
785
|
export declare function getTestPassStatus(die: Pick<Die, 'testValues' | 'testPass'>, testNumber: number, testDef?: TestDef): boolean | undefined;
|
|
786
|
+
/**
|
|
787
|
+
* @internal {@link getTestPassStatus} for many dies × tests: `read(die, out)`
|
|
788
|
+
* writes each of `testDefs`' verdicts for `die` to `out` — `1` pass, `0` fail,
|
|
789
|
+
* `-1` none — and returns `out`. The same rule, the same fallback: the recorded
|
|
790
|
+
* verdicts come through a bulk column reader (looked up once per table, not once
|
|
791
|
+
* per die and test), which is what a lot-wide chart walking every die needs.
|
|
792
|
+
* `tests/testPassStatusReader.test.mjs` holds the two to the same answers.
|
|
793
|
+
*/
|
|
794
|
+
export declare function testPassStatusReader(testDefs: readonly TestDef[]): (die: Die, out: Int8Array) => Int8Array;
|
|
785
795
|
/**
|
|
786
796
|
* True when the die carries any per-test data — a measured test value or a
|
|
787
797
|
* recorded pass/fail verdict. The single source for "does this die have test
|
|
@@ -823,5 +833,5 @@ export declare function dieHasTestData(die: Pick<Die, 'testValues' | 'testPass'>
|
|
|
823
833
|
* mis-inferring to 50 mm went undetected.
|
|
824
834
|
*/
|
|
825
835
|
export declare const STANDARD_WAFER_DIAMETERS_MM: readonly number[];
|
|
826
|
-
export declare function buildWaferMap(input: DieResult[] | WaferMapInput
|
|
836
|
+
export declare function buildWaferMap(input: DieResult[] | WaferMapInput): WaferMapResult;
|
|
827
837
|
//# sourceMappingURL=buildWaferMap.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{createWafer as z}from"../core/wafer.js";import{isYieldEligibleDie as xe,getDieKey as B,hasPosition as E}from"../core/dies.js";import{applyOrientation as ee,transformDies as De,clipDiesToWafer as Me}from"../core/transforms.js";import{generateDies as ve}from"../core/dies.js";import{affineRotation as Ce,affineMirror as Oe,affineCompose as Se,affinePoint as $e}from"../core/transforms.js";import{inferWaferFromXY as ke}from"../core/inference/wafer.js";import{resolveGridPitch as Ae}from"../core/inference/pitch.js";import{assignGridIndices as Ne}from"../core/inference/grid.js";import{generateReticleGrid as Re}from"../core/reticle.js";import{applyDerivedTests as te}from"./derivedTests/apply.js";import{buildView as ne}from"./buildView.js";import{maxOf as G,minOf as se,modeOf as We}from"../core/utils.js";import{aggregateValues as ie,aggregateBinCounts as Ee}from"../core/aggregates.js";export function isParametricTest(t){return t?.testType!=="F"}function Ie(t){const n=(f,i)=>({ringCount:f,ringCountWarning:{code:"analysis-option-corrected",severity:"warning",message:`ringCount=${String(t)} ${i} (using ${f}).`}});if(t===void 0)return{ringCount:4,ringCountWarning:void 0};if(typeof t!="number"||!Number.isFinite(t))return n(4,"is not a finite number");const e=Math.max(1,Math.round(t));return e===t?{ringCount:t,ringCountWarning:void 0}:n(e,"is not a whole number of at least 1")}function re(t){const n=Array.isArray(t)?{}:t,e=Array.isArray(n.lotStack?.results)?n.lotStack.results:[],f=(Array.isArray(t)?[t]:[n.results,...e]).filter(h=>Array.isArray(h)),i={hbin:0,sbin:0,testValues:0,testPass:0};let o;const u=(h,S)=>{i[h]++,o??=`${h} ${JSON.stringify(S)}`},s={bins:0,positions:0,testNumbers:0,values:0,sites:0,orientation:0};let m=0;const c=h=>Number.isInteger(h)&&h>=0&&h<=oe,a=h=>{typeof h=="number"&&(Number.isNaN(h)?m++:c(h)||s.bins++)},g=h=>{typeof h=="number"&&!(Number.isInteger(h)&&Math.abs(h)<=H)&&s.positions++};for(const h of f){const S=new Set,w=new Set;for(let v=0;v<h.length;v+=v<50?1:100){const r=h[v];if(!(r===null||typeof r!="object")){if(r.testValues&&typeof r.testValues=="object")for(const $ of Object.keys(r.testValues))S.add($);if(r.testPass&&typeof r.testPass=="object")for(const $ of Object.keys(r.testPass))w.add($)}}const p=[...S],D=[...w];for(const v of new Set([...p,...D])){const r=Number(v);Number.isInteger(r)&&r>=0&&r<=ae||s.testNumbers++}for(const v of h){if(v===null||typeof v!="object")continue;const r=v;r.hbin!=null&&typeof r.hbin!="number"&&u("hbin",r.hbin),r.sbin!=null&&typeof r.sbin!="number"&&u("sbin",r.sbin),a(r.hbin),a(r.sbin),g(r.x),g(r.y),typeof r.siteNum=="number"&&!(Number.isInteger(r.siteNum)&&r.siteNum>=0&&r.siteNum<=255)&&s.sites++;const $=r.testValues;if($&&typeof $=="object")for(let N=0;N<p.length;N++){const O=$[p[N]];O!=null&&(typeof O!="number"?u("testValues",O):Number.isFinite(O)||s.values++)}const I=r.testPass;if(I&&typeof I=="object")for(let N=0;N<D.length;N++){const O=I[D[N]];O!=null&&typeof O!="boolean"&&u("testPass",O)}}}const y=n.waferConfig?.orientation;typeof y=="number"&&![0,90,180,270].includes((y%360+360)%360)&&s.orientation++;const l=[],b=(h,S,w=`${S}s`)=>`${h} ${h===1?S:w}`,x=[];if(i.hbin&&x.push(b(i.hbin,"hard bin")),i.sbin&&x.push(b(i.sbin,"soft bin")),i.testValues&&x.push(b(i.testValues,"test value")),i.testPass&&x.push(b(i.testPass,"pass/fail verdict")),x.length){const h=`buildWaferMap received ${x.join(", ")} of the wrong type (for example ${o}). Bins and test values must be numbers and verdicts true/false \u2014 a bin of "1" is not pass bin 1, so those dies count as fails and yield is wrong, and a test value given as text is not plotted or analysed correctly. Convert them with Number() (and verdicts to booleans) and rebuild.`;console.warn(`[wafermap] ${h}`),l.push({code:"input-values-not-numbers",message:h,severity:"error"})}const M=[];if(s.bins&&M.push(`${b(s.bins,"bin")} (legal: whole numbers 0\u2013${oe})`),s.positions&&M.push(`${b(s.positions,"coordinate")} (legal: whole numbers \u2212${H} to ${H})`),s.testNumbers&&M.push(`${b(s.testNumbers,"test number")} (legal: whole numbers 0\u2013${ae})`),s.values&&M.push(`${b(s.values,"test value")} that ${s.values===1?"is":"are"} not finite`),s.sites&&M.push(`${b(s.sites,"site number")} (legal: 0\u2013255)`),s.orientation&&M.push("a waferConfig.orientation other than 0, 90, 180 or 270"),M.length||m){const h=[];M.length&&h.push(`buildWaferMap received values outside the STDF V4 ranges: ${M.join("; ")}. They are used as given for now; a future release treats them as missing.`),m&&h.push(`${b(m,"bin was","bins were")} NaN and ${m===1?"is":"are"} treated as no bin.`);const S=h.join(" ");console.warn(`[wafermap] ${S}`),l.push({code:"input-values-outside-stdf",message:S,severity:"warning"})}return l}const oe=32767,H=32767,ae=4294967295;function de(t){const n=(c,a)=>c!==null&&typeof c=="object"&&c[a]!==void 0,e=Array.isArray(t)?{}:t,f=[],i=(c,a,g)=>{c&&f.push(`\`${a}\` (now \`${g}\`)`)};i(n(e,"data"),"data","results"),i(n(e,"die"),"die","dieConfig"),i(n(e,"stack"),"stack","lotStack"),i(n(e.dieConfig,"origin"),"dieConfig.origin","dieConfig.coordinateOrigin"),i(n(e.waferConfig,"flat"),"waferConfig.flat","waferConfig.notch"),i(n(e.reticleConfig,"anchor"),"reticleConfig.anchor","reticleConfig.anchorDie"),i(n(e.lotStack,"aggr"),"lotStack.aggr","lotStack.method"),i(Array.isArray(e.testDefs)&&e.testDefs.some(c=>n(c,"index")&&!n(c,"testNumber")),"TestDef.index","TestDef.testNumber");const o=n(e.lotStack,"results")?e.lotStack.results:[],u=(Array.isArray(t)?[t]:[e.results,...Array.isArray(o)?o:[]]).filter(c=>Array.isArray(c));if(i(u.some(c=>c.slice(0,100).some(a=>n(a,"values"))),"values","testValues"),!f.length)return;const s=f.length>1,m=`buildWaferMap ignored ${f.join(", ")}: ${s?"these names were":"that name was"} removed in an earlier release, so what ${s?"they":"it"} described is not on this map. Rename and rebuild.`;return console.warn(`[wafermap] ${m}`),{code:"input-field-removed",message:m,severity:"error"}}function Te(t){if(t.results!==void 0||t.lotStack!==void 0)throw new Error("buildWaferMap: pass `layout`, `results` or `lotStack` \u2014 only one.");const n=t.waferConfig?.diameter,e=t.dieConfig?.width,f=t.dieConfig?.height;if(!(n>0&&e>0&&f>0))throw new Error("buildWaferMap: `layout` needs waferConfig.diameter and dieConfig.width and height, all positive.");const i=z({diameter:n,notch:t.waferConfig?.notch,orientation:0}),o=Me(ve(i,{width:e,height:f}),i,{width:e,height:f}).filter(a=>!a.partial),{flipX:u,flipY:s}=fe(t.dieConfig,{type:"center"}),{layout:m,...c}=t;return{...c,results:o.map(a=>({x:u?-a.x:a.x,y:s?-a.y:a.y})),waferConfig:{...t.waferConfig,center:{x:0,y:0}},dieConfig:{...t.dieConfig,coordinateOrigin:{type:"center"}}}}function Fe(t){if(!Array.isArray(t)&&"results"in t&&t.results!==void 0&&"lotStack"in t&&t.lotStack!==void 0)throw new Error("buildWaferMap: pass either `results` or `lotStack`, not both.");return Array.isArray(t)?{results:t,waferOpts:void 0,dieOpts:void 0,explicitDies:void 0,reticleOpts:void 0,lotStackOpts:void 0,passBins:[1],ringCount:4,ringCountWarning:void 0,removedFieldWarning:de(t),inputValueWarnings:re(t),standardDiameters:void 0,testDefs:void 0,hbinDefs:void 0,sbinDefs:void 0,metadataFields:void 0,derivedTests:void 0,retestPolicy:"last",edgeDieYieldMode:"exclude"}:{results:t.results??[],waferOpts:t.waferConfig,dieOpts:t.dieConfig,explicitDies:t.dies,reticleOpts:t.reticleConfig,lotStackOpts:t.lotStack,passBins:t.passBins??[1],...Ie(t.ringCount),removedFieldWarning:de(t),inputValueWarnings:re(t),standardDiameters:t.standardDiameters,testDefs:t.testDefs?.length?t.testDefs:void 0,hbinDefs:t.hbinDefs,sbinDefs:t.sbinDefs,metadataFields:t.metadataFields,derivedTests:t.derivedTests?.length?t.derivedTests:void 0,retestPolicy:t.retestPolicy??"last",edgeDieYieldMode:t.edgeDieYieldMode??"exclude"}}function _e(t,n){return n?.coordinateOrigin?n.coordinateOrigin:{type:"center"}}function Pe(t,n,e){if(n.type==="custom"&&n.offset)return{offsetX:n.offset.x,offsetY:n.offset.y};if(n.type!=="center"){let f=1/0,i=-1/0,o=1/0,u=-1/0;for(const s of t)s.x<f&&(f=s.x),s.x>i&&(i=s.x),s.y<o&&(o=s.y),s.y>u&&(u=s.y);return{offsetX:Math.round((i+f)/2),offsetY:Math.round((u+o)/2)}}return{offsetX:e.offsetX,offsetY:e.offsetY}}function Ve(t,n,e,f,i,o){if(o)return{colMidX:(Math.round(o.x)-n)*f,colMidY:(Math.round(o.y)-e)*i,anchored:!0};if(t.length===0)return{colMidX:0,colMidY:0,anchored:!1};let u=1/0,s=-1/0,m=1/0,c=-1/0;for(const a of t){const g=Math.round(a.x)-n,y=Math.round(a.y)-e;g<u&&(u=g),g>s&&(s=g),y<m&&(m=y),y>c&&(c=y)}return{colMidX:(u+s)/2*f,colMidY:(m+c)/2*i,anchored:!1}}function Be(t,n,e){let f=1/0,i=-1/0,o=1/0,u=-1/0,s=0,m=0;for(const g of t){const y=Math.round(g.x)-n,l=Math.round(g.y)-e;s+=y,m+=l,y<f&&(f=y),y>i&&(i=y),l<o&&(o=l),l>u&&(u=l)}const c=t.length,a=(g,y,l)=>{const b=(l-y)/2;return b<=0?0:Math.abs(g/c-(y+l)/2)/b};return a(s,f,i)>.11||a(m,o,u)>.11}function fe(t,n){let e=t?.xAxisDirection==="left",f=t?.yAxisDirection==="down";return(n.type==="UL"||n.type==="UR")&&(f=!0),(n.type==="LR"||n.type==="UR")&&(e=!0),{flipX:e,flipY:f}}function Ye(t,n){const{method:e,targetBin:f}=t,i=t.results.map(o=>o.filter(E));if(e==="mean"||e==="median"||e==="stddev"||e==="min"||e==="max"||e==="count"){const o=new Set((n??[]).filter(a=>!isParametricTest(a)).map(a=>a.testNumber)),u=new Set;for(const a of i)for(const g of a)if(g.testValues)for(const y of Object.keys(g.testValues)){const l=Number(y);o.has(l)||u.add(l)}if(u.size===0)return ie(i,e);const s=[...u],m=s.map(a=>ie(i,e,a)),c=new Map;for(let a=0;a<s.length;a++){const g=s[a];for(const y of m[a]){const l=B(y);c.has(l)||c.set(l,{template:y,testValues:{}});const b=c.get(l),x=y.testValues?.[0];x!==void 0&&(b.testValues[g]=x)}}return[...c.values()].map(({template:a,testValues:g})=>({...a,testValues:Object.keys(g).length>0?g:void 0}))}if(e==="countBin"||e==="percent"){if(f===void 0)return[];const o=Ee(i,f,"hard");if(e==="countBin")return o;const u=i.length;return o.map(s=>({...s,testValues:{0:u>0?(s.testValues?.[0]??0)/u*100:0}}))}if(e==="mode"){const o=new Map;for(const s of i)for(const m of s){const c=B(m);o.has(c)||o.set(c,[]),o.get(c).push(m)}const u=[];for(const[s,m]of o){const c=s.split(","),a=Number(c[0]),g=Number(c[1]),y=m.map(b=>b.hbin).filter(b=>b!==void 0),l=We(y);l!==null&&u.push({x:a,y:g,hbin:l})}return u}return[]}function ce(t){const n=t.filter(E),e=n.length,f=n.filter(o=>o.edgeExcluded).length,i=n.filter(o=>dieHasTestData(o)||o.hbin!==void 0||o.sbin!==void 0).length;return{filledDies:i,totalDies:e,edgeExcludedDies:f,unpositionedDies:t.length-n.length,ratio:e>0?i/e:0}}function le(t,n,e="exclude"){const f=new Set(n),o=t.filter(l=>!l.partial).filter(l=>l.edgeExcluded).length,u=t.filter(l=>l.partial).length;let s=0,m=0,c=!1;for(const l of t){if(!xe(l))continue;const b=l.hbin??l.sbin;b!==void 0&&(c=!0,f.has(b)?s++:m++)}const a=s+m,g=c&&a>0?s/a*100:null;let y=null;if(e==="denominator-only"){const l=a+o;y=c&&l>0?s/l*100:null}return{passDies:s,failDies:m,edgeExcludedDies:o,partialDies:u,totalDies:a,yieldPercent:g,yieldPercentGross:y}}function ue(t,n,e,f,i,o=0,u=0,s=0,m=0,c=0,a=!1,g=!1){if(!t)return[];const y=t.anchorDie??{x:0,y:0},l=Re(n,{width:t.width,height:t.height,diePitchX:f,diePitchY:i,anchorDie:{x:y.x-o,y:y.y-u},gridOrigin:{x:-s,y:-m}}),b=Se(Oe(a,g,n.center.x,n.center.y),Ce(c,n.center.x,n.center.y));return l.filter(x=>{const M=x.width/2,h=x.height/2,S=[[x.x-M,x.y-h],[x.x+M,x.y-h],[x.x+M,x.y+h],[x.x-M,x.y+h]].map(([r,$])=>$e(b,r,$)),w=se(S.map(r=>r.x)),p=G(S.map(r=>r.x)),D=se(S.map(r=>r.y)),v=G(S.map(r=>r.y));return e.some(r=>r.physX>=w&&r.physX<p&&r.physY>=D&&r.physY<v)})}function Le(t,n,e){const i=Math.max(0,n.radius-e)**2;return t.map(o=>{const u=o.physX-n.center.x,s=o.physY-n.center.y;return u*u+s*s>i?{...o,edgeExcluded:!0}:o})}const Xe=.2;function je(t,n,e){const f=new Set(e);function i(w,p){if(n==="last")return!0;if(n==="first")return!1;const D=L(w.hbin),v=L(p.hbin);if(D===void 0||v===void 0)return!1;const r=f.has(D),$=f.has(v);return n==="best"?$!==r?$:v<D:$!==r?r:v>D}const o=(w,p)=>p.supersedes!==void 0||i(w,p),u=w=>{const p=w.partId===void 0?"":String(w.partId).trim();return p===""?void 0:p},s=t.filter(w=>!E(w)),m=new Map;for(const w of s){const p=u(w);p!==void 0&&m.set(p,(m.get(p)??0)+1)}const a=(s.length===0?0:Math.max(0,...m.values())/s.length)<=Xe,g=new Map,y=[],l=new Map;let b=0,x=0;for(const w of t){const p=u(w);let D;if(E(w)?D=`p:${B(w)}`:a&&p!==void 0?D=`i:${p}`:D=`u:${x++}`,w.supersedes==="partId"&&p!==void 0){const r=l.get(p);r!==void 0&&r!==D&&g.has(r)&&g.delete(r)}const v=g.get(D);v===void 0?(g.set(D,{winner:w,count:1}),y.push(D)):(v.count++,D.startsWith("i:")&&b++,o(v.winner,w)&&(v.winner=w)),p!==void 0&&l.set(p,D)}const M=[],h=[...y.filter(w=>w.startsWith("p:")),...y.filter(w=>!w.startsWith("p:"))];for(const w of h){const p=g.get(w);p!==void 0&&M.push(p.count>1?{...p.winner,retestCount:p.count}:p.winner)}const S=b>0?{code:"retests-by-part-id",severity:"info",message:`${b} record${b===1?"":"s"} with no position shared a part ID with an earlier record and ${b===1?"was":"were"} treated as a retest of it.`}:void 0;return{results:M,warning:S}}export function getDieTestValue(t,n){return t.testValues?.[n]}export function getTestPassStatus(t,n,e){const f=t.testPass?.[n];if(f!==void 0)return f;if(e!==void 0&&!isParametricTest(e)){const i=t.testValues?.[n];if(i===0||i===1)return i===1}}export function dieHasTestData(t){return t.testValues!==void 0&&Object.keys(t.testValues).length>0||t.testPass!==void 0&&Object.keys(t.testPass).length>0}function L(t){return t===void 0||Number.isNaN(t)?void 0:t}function X(t,n){const e={},f=L(n.hbin),i=L(n.sbin);return f!==void 0&&(e.hbin=f),i!==void 0&&(e.sbin=i),n.retestCount!==void 0&&(e.retestCount=n.retestCount),n.siteNum!==void 0&&(e.siteNum=n.siteNum),n.partId!==void 0&&(e.partId=n.partId),n.metadata!==void 0&&(e.metadata=n.metadata),n.testPass!==void 0&&(e.testPass=n.testPass),n.testValues!==void 0&&(e.testValues=n.testValues),{...t,...e}}function he(t,n){return n.plotMode?n.plotMode:t.some(dieHasTestData)?"value":"hardBin"}function pe(t,n){return t.map(({code:e,message:f})=>({code:e,message:f,severity:e==="non-standard-diameter"||e==="diameter-exceeds-die-extent"?"warning":"error",confidence:n.wafer.confidence}))}const Ke="do not fit inside the supplied",Ue="Inferred wafer diameter",ze="The probed dies reach only",Ge=.75,He=20;export const STANDARD_WAFER_DIAMETERS_MM=[100,125,150,200,300];const qe=.02;function me(t,n){return n.reduce((e,f)=>Math.abs(f-t)<Math.abs(e-t)?f:e)}function Je(t,n){if(!n.length)return!0;const e=me(t,n);return Math.abs(e-t)/e<=qe}function Qe(t){return t===void 0?STANDARD_WAFER_DIAMETERS_MM:t.filter(n=>Number.isFinite(n)&&n>0)}export function buildWaferMap(t,n){const e=Fe(!Array.isArray(t)&&t.layout?Te(t):t),{debug:f,...i}=n??{},o=[];if(e.derivedTests){const d=new Set(e.passBins);if(e.lotStackOpts){const C=e.lotStackOpts.results.map(k=>te(k,e.testDefs,e.derivedTests,d));e.lotStackOpts={...e.lotStackOpts,results:C.map(k=>k.results)},e.testDefs=C[0]?.testDefs??e.testDefs,o.push(...C[0]?.warnings??[])}else{const C=te(e.results,e.testDefs,e.derivedTests,d);e.results=C.results,e.testDefs=C.testDefs,o.push(...C.warnings)}}const u=e.lotStackOpts?Ye(e.lotStackOpts,e.testDefs):e.results;for(let d=0;d<Math.min(5,u.length);d++){const C=u[d];if(typeof C.x=="string"||typeof C.y=="string")throw new TypeError("buildWaferMap: x and y must be numbers, received strings. Did you forget to cast CSV values? e.g. { x: +row.x, y: +row.y }")}for(const d of u)if(d.x==null!=(d.y==null))throw new TypeError(`buildWaferMap: die has only one of x/y set (x=${String(d.x)}, y=${String(d.y)}). A die must be either fully positioned (both x and y) or fully unpositioned (neither) \u2014 omit both to mark a die as having no reported position.`);const{results:s,warning:m}=je(u,e.retestPolicy,e.passBins),c={wafer:{confidence:1,method:"provided"},diePitch:{confidence:1,units:"mm"},grid:{confidence:1}},a=[];if(e.explicitDies){let d=e.explicitDies;const C=s.filter(E),k=s.filter(V=>!E(V));if(C.length>0){const V=new Map(C.map(W=>[B(W),W]));d=d.map(W=>{const Z=V.get(B(W));return Z?X(W,Z):W})}const P=e.waferOpts?.diameter??300,F=z({diameter:P,notch:e.waferOpts?.notch,orientation:e.waferOpts?.orientation??0,metadata:e.waferOpts?.metadata});F.orientation!==0&&(d=ee(d,F));const Q=ue(e.reticleOpts,F,d,1,1,0,0,0,0,F.orientation),be=i.showReticle??e.reticleOpts!==void 0,K=ne(F,d,{...i,reticles:Q,showReticle:be,plotMode:he(s,i),testDefs:e.testDefs,passBins:e.passBins,ringCount:e.ringCount,isLotStack:!1},{hbinDefs:e.hbinDefs,sbinDefs:e.sbinDefs,metadataFields:e.metadataFields}),we=k.map((V,W)=>X({id:`unpositioned_${W}`,width:d[0]?.width??1,height:d[0]?.height??1},V)),U=[...d,...we];return{wafer:F,dies:U,view:K,reticleConfig:e.reticleOpts,units:"mm",inference:c,warnings:[...e.removedFieldWarning?[e.removedFieldWarning]:[],...e.inputValueWarnings,...m?[m]:[],...pe(a,c),...o,...e.ringCountWarning?[e.ringCountWarning]:[]],plotMode:K.plotMode,metadata:K.metadata,isLotStack:!1,dataCoverage:ce(U),passBins:e.passBins,ringCount:e.ringCount,yield:le(U,e.passBins,e.edgeDieYieldMode),reticles:Q,hbinDefs:e.hbinDefs,sbinDefs:e.sbinDefs,testDefs:e.testDefs,metadataFields:e.metadataFields}}const g=s.filter(E),y=s.filter(d=>!E(d)),l=g.map(d=>({x:d.x,y:d.y})),b=Ae(l,e.dieOpts,e.waferOpts?.diameter);c.diePitch={confidence:b.confidence,units:b.units};const{pitchX:x,pitchY:M}=b,h=b.units,S=_e(g,e.dieOpts),w=Ne(l);c.grid={confidence:w.confidence};const{offsetX:p,offsetY:D}=Pe(l,S,w),{flipX:v,flipY:r}=fe(e.dieOpts,S),{colMidX:$,colMidY:I,anchored:N}=Ve(l,p,D,x,M,e.waferOpts?.center);let O=e.waferOpts?.diameter;const Y=l.map(d=>({x:(Math.round(d.x)-p)*x-$,y:(Math.round(d.y)-D)*M-I})),T=G(Y.map(({x:d,y:C})=>Math.hypot(Math.abs(d)+x/2,Math.abs(C)+M/2)));if(O===void 0)if(l.length>0)if(h==="mm"){const d=ke(Y,{minRadius:T});O=d.diameter,c.wafer={confidence:d.confidence,method:d.method};const C=Qe(e.standardDiameters);if(!Je(O,C)){const k=me(O,C);a.push({code:"non-standard-diameter",message:`${Ue} ${O.toFixed(1)} mm is not a standard wafer size (nearest is ${k} mm). The diameter was derived from the die extent, which assumes the probed grid reaches the wafer edge \u2014 a non-standard result usually means it does not, because the outer dies were never probed. Every die is then placed against a wafer that is too small, which moves dies between rings and changes ring and edge findings. Supply waferConfig.diameter.`})}}else O=T*2,c.wafer={confidence:b.confidence*.8,method:"extent"};else O=h==="mm"?300:30,c.wafer={confidence:0,method:"default"};if(!N&&l.length>0&&Be(l,p,D)&&(c.wafer.method="inferred-partial",a.push({code:"partial-coverage",message:"Wafer geometry was inferred from die positions alone. The data does not span a full symmetric wafer, so the inferred diameter and centre may be wrong and dies may be mis-positioned relative to the true wafer boundary. Supply waferConfig.diameter and waferConfig.center (the prober coordinate of the wafer centre) to position partial data correctly."})),e.waferOpts?.diameter!==void 0&&e.waferOpts.center===void 0&&T>0&&l.length>=He){const d=T/(O/2);d<Ge&&a.push({code:"diameter-exceeds-die-extent",message:`${ze} ${(d*100).toFixed(0)}% of the ${O} mm wafer's radius. Either the diameter is larger than the real one, or this is a partial map covering only part of the wafer. Ring bands are equal-radius, so the outer rings are mostly or entirely empty and ring, quadrant and edge findings describe the assumed wafer rather than the probed area. Check waferConfig.diameter, or drop it and let the diameter be inferred from the die extent.`})}if(e.waferOpts?.diameter!==void 0){const d=e.dieOpts?.width!==void 0&&e.dieOpts?.height!==void 0;if(d&&T>O/2+1e-9){const C=Y.filter(({x:k,y:P})=>Math.hypot(Math.abs(k)+x/2,Math.abs(P)+M/2)>O/2+1e-9).length;a.push({code:"geometry-conflict",message:`${C} of ${Y.length} probed die positions ${Ke} ${O} mm wafer at the supplied die pitch of ${x} \xD7 ${M} mm. A die with test results is a real prober position and is always fully on the wafer, so one of the two supplied values must be wrong: containing these dies at this pitch would need a diameter of at least ${(T*2).toFixed(1)} mm. Check waferConfig.diameter and dieConfig.width/height against the real device.`})}}const R=z({diameter:O,notch:e.waferOpts?.notch,orientation:e.waferOpts?.orientation??0,metadata:e.waferOpts?.metadata});let A=g.map(d=>{const C=Math.round(d.x)-p,k=Math.round(d.y)-D,P={id:`${C}_${k}`,x:C,y:k,physX:C*x-$,physY:k*M-I,width:x,height:M,insideWafer:!0,partial:!1};return X(P,d)});(p!==0||D!==0)&&(A=A.map(d=>({...d,x:d.x+p,y:d.y+D,id:`${d.x+p}_${d.y+D}`}))),A=ee(A,R),(v||r)&&(A=De(A,{flipX:v,flipY:r},R.center));const q=[];e.waferOpts?.edgeExclusion&&e.waferOpts.edgeExclusion>0&&(A=Le(A,R,e.waferOpts.edgeExclusion),e.waferOpts.edgeExclusion>R.radius&&q.push({code:"edge-exclusion-exceeds-radius",message:`edgeExclusion (${e.waferOpts.edgeExclusion}mm) exceeds the resolved wafer radius (${R.radius}mm) \u2014 the entire wafer is excluded.`,severity:"warning"}));const J=ue(e.reticleOpts,R,A,x,M,p,D,$,I,R.orientation,v,r),ge=i.showReticle??e.reticleOpts!==void 0,_=ne(R,A,{...i,reticles:J,showReticle:ge,plotMode:he(s,i),testDefs:e.testDefs,passBins:e.passBins,ringCount:e.ringCount,dataAxisFlip:{x:v,y:r},isLotStack:e.lotStackOpts!==void 0,aggregationMethod:e.lotStackOpts?.method,lotSize:e.lotStackOpts?.results.length},{hbinDefs:e.hbinDefs,sbinDefs:e.sbinDefs,metadataFields:e.metadataFields}),ye=y.map((d,C)=>X({id:`unpositioned_${C}`,width:x,height:M},d)),j=[...A,...ye];return{wafer:R,dies:j,view:_,reticleConfig:e.reticleOpts,units:h,inference:c,warnings:[...e.removedFieldWarning?[e.removedFieldWarning]:[],...e.inputValueWarnings,...m?[m]:[],...pe(a,c),...q,...o,...e.ringCountWarning?[e.ringCountWarning]:[]],plotMode:_.plotMode,metadata:_.metadata,isLotStack:e.lotStackOpts!==void 0,dataCoverage:ce(j),passBins:e.passBins,ringCount:e.ringCount,yield:le(j,e.passBins,e.edgeDieYieldMode),reticles:J,hbinDefs:e.hbinDefs,sbinDefs:e.sbinDefs,testDefs:e.testDefs,metadataFields:e.metadataFields,aggrMethod:_.aggrMethod,lotSize:_.lotSize}}
|
|
1
|
+
import{createWafer as G}from"../core/wafer.js";import{isYieldEligibleDie as ye,getDieKey as X,hasPosition as F}from"../core/dies.js";import{applyOrientation as ee,transformDies as be,clipDiesToWafer as we}from"../core/transforms.js";import{generateDies as xe}from"../core/dies.js";import{dieHasAnyTestData as De,dieLink as H,finaliseDie as j,linkDie as ve,recordedVerdict as Me,recordedVerdictsReader as Ce,tableFromRows as Se,testValue as q,testsPresent as Ae,VERDICT_FAIL as ke,VERDICT_NONE as Oe,VERDICT_PASS as Ne}from"../core/dieTable.js";import{isDieColumns as $e,linkColumns as Re,linkRows as te,relink as Te}from"./columnarInput.js";import{affineRotation as Ee,affineMirror as We,affineCompose as Fe,affinePoint as Ie}from"../core/transforms.js";import{inferWaferFromXY as _e}from"../core/inference/wafer.js";import{resolveGridPitch as Pe}from"../core/inference/pitch.js";import{assignGridIndices as Ve}from"../core/inference/grid.js";import{generateReticleGrid as Be}from"../core/reticle.js";import{applyDerivedTests as Ye}from"./derivedTests/apply.js";import{isStdfBin as Le,isStdfCoord as ne,isStdfTestNumber as ie,isStdfSite as Xe,STDF_BIN_MAX as je,STDF_COORD_MAX as se,STDF_TEST_NUM_MAX as Ke,STDF_SITE_MAX as Ue}from"../core/stdf.js";import{hasAnyKey as ze,maxOf as J,minOf as re,modeOf as Ge}from"../core/utils.js";import{aggregateValues as oe,aggregateBinCounts as He}from"../core/aggregates.js";export function isParametricTest(t){return t?.testType!=="F"}export function isBuiltMap(t){return"wafer"in t&&"dies"in t&&"dataAxisFlip"in t}function qe(t){const e=(i,a)=>({ringCount:i,ringCountWarning:{code:"analysis-option-corrected",severity:"warning",message:`ringCount=${String(t)} ${a} (using ${i}).`}});if(t===void 0)return{ringCount:4,ringCountWarning:void 0};if(typeof t!="number"||!Number.isFinite(t))return e(4,"is not a finite number");const n=Math.max(1,Math.round(t));return n===t?{ringCount:t,ringCountWarning:void 0}:e(n,"is not a whole number of at least 1")}function Je(t,e){const n=Array.isArray(t)?{}:t,i={hbin:0,sbin:0,siteNum:0,testValues:0,testPass:0};let a;const o=(l,b)=>{i[l]++,a??=`${l} ${JSON.stringify(b)}`},s={bins:0,positions:0,values:0,sites:0,orientation:0},r=new Set;let m=0;if(e){i.testValues+=e.wrongValues,i.testPass+=e.wrongVerdicts,e.example!==void 0&&(a??=e.example),s.values+=e.nonFinite;for(const l of e.badTestNumbers)r.add(l)}const h=l=>l==null?!0:typeof l!="number"?!1:Number.isNaN(l)?(m++,!1):Le(l)?!0:(s.bins++,!1);function d(l){const b=new Set,w=new Set;for(let O=0;O<l.length;O+=O<50?1:100){const A=l[O];if(!(A===null||typeof A!="object")){if(A.testValues&&typeof A.testValues=="object")for(const M of Object.keys(A.testValues))b.add(M);if(A.testPass&&typeof A.testPass=="object")for(const M of Object.keys(A.testPass))w.add(M)}}const g=[...b],y=[...w],k=O=>ie(Number(O));for(const O of new Set([...g,...y]))k(O)||r.add(O);const R=g.map(k),v=y.map(k),N=g.map(Number),B=y.map(Number);let I;for(let O=0;O<l.length;O++){const A=l[O];if(A===null||typeof A!="object")continue;const M=A;let L;const _=()=>L??={...M};M.hbin!=null&&typeof M.hbin!="number"&&o("hbin",M.hbin),M.sbin!=null&&typeof M.sbin!="number"&&o("sbin",M.sbin),h(M.hbin)||(_().hbin=void 0),h(M.sbin)||(_().sbin=void 0);const Y=typeof M.x=="number"&&!ne(M.x),f=typeof M.y=="number"&&!ne(M.y);if(Y||f){s.positions+=(Y?1:0)+(f?1:0);const C=_();C.x=void 0,C.y=void 0}M.siteNum!=null&&typeof M.siteNum!="number"?(o("siteNum",M.siteNum),_().siteNum=void 0):typeof M.siteNum=="number"&&!Xe(M.siteNum)&&(s.sites++,_().siteNum=void 0);const S=M.testValues;if(S&&typeof S=="object"){let C;for(let $=0;$<g.length;$++){const E=g[$],W=S[N[$]];if(W!=null){if(!R[$]){C??={...S},delete C[E];continue}typeof W!="number"?(o("testValues",W),C??={...S},delete C[E]):Number.isFinite(W)||(s.values++,C??={...S},delete C[E])}}C&&(_().testValues=C)}const T=M.testPass;if(T&&typeof T=="object"){let C;for(let $=0;$<y.length;$++){const E=y[$],W=T[B[$]];if(W!=null){if(!v[$]){C??={...T},delete C[E];continue}typeof W!="boolean"&&(o("testPass",W),C??={...T},delete C[E])}}C&&(_().testPass=C)}L&&((I??=l.slice())[O]=L)}return I}const c={warnings:[]};if(Array.isArray(t))c.results=d(t);else{Array.isArray(n.results)&&(c.results=d(n.results)),Array.isArray(n.dies)&&(c.dies=d(n.dies));const l=n.lotStack?.results;if(Array.isArray(l)){const b=l.map(w=>Array.isArray(w)?d(w):void 0);b.some(Boolean)&&(c.stackResults=b.map((w,g)=>w??l[g]))}}if(Array.isArray(n.testDefs)){const l=n.testDefs,b=l.filter(w=>w==null||typeof w.testNumber!="number"||ie(w.testNumber));for(const w of l)b.includes(w)||r.add(String(w.testNumber));b.length!==l.length&&(c.testDefs=b)}const p=n.waferConfig?.orientation;if(typeof p=="number"){const l=Number.isFinite(p)?(p%360+360)%360:NaN;l===0||l===90||l===180||l===270?c.orientation=l:(s.orientation++,c.orientation=0)}const u=(l,b,w=`${b}s`)=>`${l} ${l===1?b:w}`,x=[];if(i.hbin&&x.push(u(i.hbin,"hard bin")),i.sbin&&x.push(u(i.sbin,"soft bin")),i.siteNum&&x.push(u(i.siteNum,"site number")),i.testValues&&x.push(u(i.testValues,"test value")),i.testPass&&x.push(u(i.testPass,"pass/fail verdict")),x.length){const l=`buildWaferMap received ${x.join(", ")} of the wrong type (for example ${a}), treated as missing. Bins, sites and test values must be numbers and verdicts true/false \u2014 a bin of "1" is not pass bin 1 \u2014 so those dies have no bin, no site, no value or no verdict, and yield and every chart leave them out. Convert them with Number() (and verdicts to booleans) and rebuild.`;console.warn(`[wafermap] ${l}`),c.warnings.push({code:"input-values-not-numbers",message:l,severity:"error"})}const D=[];if(s.bins&&D.push(`${u(s.bins,"bin")} (legal: whole numbers 0\u2013${je}) \u2014 those dies have no bin`),s.positions&&D.push(`${u(s.positions,"coordinate")} (legal: whole numbers \u2212${se} to ${se}) \u2014 those dies have no position`),r.size&&D.push(`${u(r.size,"test number")} (legal: whole numbers 0\u2013${Ke}) \u2014 those tests are left out`),s.values&&D.push(`${u(s.values,"test value")} that ${s.values===1?"is":"are"} not finite \u2014 left out`),s.sites&&D.push(`${u(s.sites,"site number")} (legal: 0\u2013${Ue}) \u2014 those dies have no site`),s.orientation&&D.push("a waferConfig.orientation other than 0, 90, 180 or 270 \u2014 the map is built at 0"),D.length||m){const l=[];D.length&&l.push(`buildWaferMap received values outside the STDF V4 ranges, treated as missing: ${D.join("; ")}.`),m&&l.push(`${u(m,"bin was","bins were")} NaN and ${m===1?"is":"are"} treated as no bin.`);const b=l.join(" ");console.warn(`[wafermap] ${b}`),c.warnings.push({code:"input-values-outside-stdf",message:b,severity:"warning"})}return c}function ae(t){const e=(h,d)=>h!==null&&typeof h=="object"&&h[d]!==void 0,n=Array.isArray(t)?{}:t,i=[],a=(h,d,c)=>{h&&i.push(`\`${d}\` (now \`${c}\`)`)};a(e(n,"data"),"data","results"),a(e(n,"die"),"die","dieConfig"),a(e(n,"stack"),"stack","lotStack"),a(e(n.dieConfig,"origin"),"dieConfig.origin","dieConfig.coordinateOrigin"),a(e(n.waferConfig,"flat"),"waferConfig.flat","waferConfig.notch"),a(e(n.reticleConfig,"anchor"),"reticleConfig.anchor","reticleConfig.anchorDie"),a(e(n.lotStack,"aggr"),"lotStack.aggr","lotStack.method"),a(Array.isArray(n.testDefs)&&n.testDefs.some(h=>e(h,"index")&&!e(h,"testNumber")),"TestDef.index","TestDef.testNumber");const o=e(n.lotStack,"results")?n.lotStack.results:[],s=(Array.isArray(t)?[t]:[n.results,...Array.isArray(o)?o:[]]).filter(h=>Array.isArray(h));if(a(s.some(h=>h.slice(0,100).some(d=>e(d,"values"))),"values","testValues"),!i.length)return;const r=i.length>1,m=`buildWaferMap ignored ${i.join(", ")}: ${r?"these names were":"that name was"} removed in an earlier release, so what ${r?"they":"it"} described is not on this map. Rename and rebuild.`;return console.warn(`[wafermap] ${m}`),{code:"input-field-removed",message:m,severity:"error"}}function Qe(t){if(t.results!==void 0||t.lotStack!==void 0)throw new Error("buildWaferMap: pass `layout`, `results` or `lotStack` \u2014 only one.");const e=t.waferConfig?.diameter,n=t.dieConfig?.width,i=t.dieConfig?.height;if(!(e>0&&n>0&&i>0))throw new Error("buildWaferMap: `layout` needs waferConfig.diameter and dieConfig.width and height, all positive.");const a=G({diameter:e,notch:t.waferConfig?.notch,orientation:0}),o=we(xe(a,{width:n,height:i}),a,{width:n,height:i}).filter(d=>!d.partial),{flipX:s,flipY:r}=de(t.dieConfig,{type:"center"}),{layout:m,...h}=t;return{...h,results:o.map(d=>({x:s?-d.x:d.x,y:r?-d.y:d.y})),waferConfig:{...t.waferConfig,center:{x:0,y:0}},dieConfig:{...t.dieConfig,coordinateOrigin:{type:"center"}}}}function Ze(t){if(!Array.isArray(t)&&"results"in t&&t.results!==void 0&&"lotStack"in t&&t.lotStack!==void 0)throw new Error("buildWaferMap: pass either `results` or `lotStack`, not both.");const e={wrongVerdicts:0,wrongValues:0,nonFinite:0,badTestNumbers:[]},n=!Array.isArray(t)&&$e(t.results)?{...t,results:Re(t.results,e).records}:t,i=Je(n,e);return Array.isArray(n)?{results:Q(i.results??n),waferOpts:void 0,dieOpts:void 0,explicitDies:void 0,reticleOpts:void 0,lotStackOpts:void 0,passBins:[1],ringCount:4,ringCountWarning:void 0,removedFieldWarning:ae(n),inputValueWarnings:i.warnings,standardDiameters:void 0,testDefs:void 0,hbinDefs:void 0,sbinDefs:void 0,metadataFields:void 0,derivedTests:void 0,retestPolicy:"last",edgeDieYieldMode:"exclude"}:{results:Q(i.results??n.results??[]),waferOpts:i.orientation===void 0||n.waferConfig===void 0?n.waferConfig:{...n.waferConfig,orientation:i.orientation},dieOpts:n.dieConfig,explicitDies:i.dies??n.dies,reticleOpts:n.reticleConfig,lotStackOpts:n.lotStack?{...n.lotStack,results:(i.stackResults??n.lotStack.results).map(Q)}:void 0,passBins:n.passBins??[1],...qe(n.ringCount),removedFieldWarning:ae(n),inputValueWarnings:i.warnings,standardDiameters:n.standardDiameters,testDefs:(i.testDefs??n.testDefs)?.length?i.testDefs??n.testDefs:void 0,hbinDefs:n.hbinDefs,sbinDefs:n.sbinDefs,metadataFields:n.metadataFields,derivedTests:n.derivedTests?.length?n.derivedTests:void 0,retestPolicy:n.retestPolicy??"last",edgeDieYieldMode:n.edgeDieYieldMode??"exclude"}}function Q(t){return t.length>0&&H(t[0])!==void 0?t:te(t).records}function et(t,e){return e?.coordinateOrigin?e.coordinateOrigin:{type:"center"}}function tt(t,e,n){if(e.type==="custom"&&e.offset)return{offsetX:e.offset.x,offsetY:e.offset.y};if(e.type!=="center"){let i=1/0,a=-1/0,o=1/0,s=-1/0;for(const r of t)r.x<i&&(i=r.x),r.x>a&&(a=r.x),r.y<o&&(o=r.y),r.y>s&&(s=r.y);return{offsetX:Math.round((a+i)/2),offsetY:Math.round((s+o)/2)}}return{offsetX:n.offsetX,offsetY:n.offsetY}}function nt(t,e,n,i,a,o){if(o)return{colMidX:(Math.round(o.x)-e)*i,colMidY:(Math.round(o.y)-n)*a,anchored:!0};if(t.length===0)return{colMidX:0,colMidY:0,anchored:!1};let s=1/0,r=-1/0,m=1/0,h=-1/0;for(const d of t){const c=Math.round(d.x)-e,p=Math.round(d.y)-n;c<s&&(s=c),c>r&&(r=c),p<m&&(m=p),p>h&&(h=p)}return{colMidX:(s+r)/2*i,colMidY:(m+h)/2*a,anchored:!1}}function it(t,e,n){let i=1/0,a=-1/0,o=1/0,s=-1/0,r=0,m=0;for(const c of t){const p=Math.round(c.x)-e,u=Math.round(c.y)-n;r+=p,m+=u,p<i&&(i=p),p>a&&(a=p),u<o&&(o=u),u>s&&(s=u)}const h=t.length,d=(c,p,u)=>{const x=(u-p)/2;return x<=0?0:Math.abs(c/h-(p+u)/2)/x};return d(r,i,a)>.11||d(m,o,s)>.11}function de(t,e){let n=t?.xAxisDirection==="left",i=t?.yAxisDirection==="down";return(e.type==="UL"||e.type==="UR")&&(i=!0),(e.type==="LR"||e.type==="UR")&&(n=!0),{flipX:n,flipY:i}}function st(t,e){const{method:n,targetBin:i}=t,a=t.results.map(o=>o.filter(F));if(n==="mean"||n==="median"||n==="stddev"||n==="min"||n==="max"||n==="count"){const o=new Set((e??[]).filter(d=>!isParametricTest(d)).map(d=>d.testNumber)),s=new Set;for(const d of a)for(const c of Ae(d,"values"))o.has(c)||s.add(c);if(s.size===0)return oe(a,n);const r=[...s],m=r.map(d=>oe(a,n,d)),h=new Map;for(let d=0;d<r.length;d++){const c=r[d];for(const p of m[d]){const u=X(p);h.has(u)||h.set(u,{template:p,testValues:{}});const x=h.get(u),D=p.testValues?.[0];D!==void 0&&(x.testValues[c]=D)}}return[...h.values()].map(({template:d,testValues:c})=>({...d,testValues:ze(c)?c:void 0}))}if(n==="countBin"||n==="percent"){if(i===void 0)return[];const o=He(a,i,"hard");if(n==="countBin")return o;const s=a.length;return o.map(r=>({...r,testValues:{0:s>0?(r.testValues?.[0]??0)/s*100:0}}))}if(n==="mode"){const o=new Map;for(const r of a)for(const m of r){const h=X(m);o.has(h)||o.set(h,[]),o.get(h).push(m)}const s=[];for(const[r,m]of o){const h=r.split(","),d=Number(h[0]),c=Number(h[1]),p=m.map(x=>x.hbin).filter(x=>x!==void 0),u=Ge(p);u!==null&&s.push({x:d,y:c,hbin:u})}return s}return[]}function fe(t){const e=t.filter(F),n=e.length,i=e.filter(o=>o.edgeExcluded).length,a=e.filter(o=>o.hbin!==void 0||o.sbin!==void 0||dieHasTestData(o)).length;return{filledDies:a,totalDies:n,edgeExcludedDies:i,unpositionedDies:t.length-e.length,ratio:n>0?a/n:0}}function ue(t,e,n="exclude"){const i=new Set(e),o=t.filter(u=>!u.partial).filter(u=>u.edgeExcluded).length,s=t.filter(u=>u.partial).length;let r=0,m=0,h=!1;for(const u of t){if(!ye(u))continue;const x=u.hbin??u.sbin;x!==void 0&&(h=!0,i.has(x)?r++:m++)}const d=r+m,c=h&&d>0?r/d*100:null;let p=null;if(n==="denominator-only"){const u=d+o;p=h&&u>0?r/u*100:null}return{passDies:r,failDies:m,edgeExcludedDies:o,partialDies:s,totalDies:d,yieldPercent:c,yieldPercentGross:p}}function le(t,e,n,i,a,o=0,s=0,r=0,m=0,h=0,d=!1,c=!1){if(!t)return[];const p=t.anchorDie??{x:0,y:0},u=Be(e,{width:t.width,height:t.height,diePitchX:i,diePitchY:a,anchorDie:{x:p.x-o,y:p.y-s},gridOrigin:{x:-r,y:-m}}),x=Fe(We(d,c,e.center.x,e.center.y),Ee(h,e.center.x,e.center.y));return u.filter(D=>{const l=D.width/2,b=D.height/2,w=[[D.x-l,D.y-b],[D.x+l,D.y-b],[D.x+l,D.y+b],[D.x-l,D.y+b]].map(([v,N])=>Ie(x,v,N)),g=re(w.map(v=>v.x)),y=J(w.map(v=>v.x)),k=re(w.map(v=>v.y)),R=J(w.map(v=>v.y));return n.some(v=>v.physX>=g&&v.physX<y&&v.physY>=k&&v.physY<R)})}function rt(t,e,n){const a=Math.max(0,e.radius-n)**2;return t.map(o=>{const s=o.physX-e.center.x,r=o.physY-e.center.y;return s*s+r*r>a?{...o,edgeExcluded:!0}:o})}const ot=.2;function at(t,e,n){const i=new Set(n);function a(g,y){if(e==="last")return!0;if(e==="first")return!1;const k=K(g.hbin),R=K(y.hbin);if(k===void 0||R===void 0)return!1;const v=i.has(k),N=i.has(R);return e==="best"?N!==v?N:R<k:N!==v?v:R>k}const o=(g,y)=>y.supersedes!==void 0||a(g,y),s=g=>{const y=g.partId===void 0?"":String(g.partId).trim();return y===""?void 0:y},r=t.filter(g=>!F(g)),m=new Map;for(const g of r){const y=s(g);y!==void 0&&m.set(y,(m.get(y)??0)+1)}const d=(r.length===0?0:Math.max(0,...m.values())/r.length)<=ot,c=new Map,p=[],u=new Map;let x=0,D=0;for(const g of t){const y=s(g);let k;if(F(g)?k=`p:${X(g)}`:d&&y!==void 0?k=`i:${y}`:k=`u:${D++}`,g.supersedes==="partId"&&y!==void 0){const v=u.get(y);v!==void 0&&v!==k&&c.has(v)&&c.delete(v)}const R=c.get(k);R===void 0?(c.set(k,{winner:g,count:1}),p.push(k)):(R.count++,k.startsWith("i:")&&x++,o(R.winner,g)&&(R.winner=g)),y!==void 0&&u.set(y,k)}const l=[],b=[...p.filter(g=>g.startsWith("p:")),...p.filter(g=>!g.startsWith("p:"))];for(const g of b){const y=c.get(g);y!==void 0&&l.push(y.count>1?{...y.winner,retestCount:y.count}:y.winner)}const w=x>0?{code:"retests-by-part-id",severity:"info",message:`${x} record${x===1?"":"s"} with no position shared a part ID with an earlier record and ${x===1?"was":"were"} treated as a retest of it.`}:void 0;return{results:l,warning:w}}export function getDieTestValue(t,e){return q(t,e)}export function getTestPassStatus(t,e,n){const i=Me(t,e);return i!==void 0?i:n!==void 0&&!isParametricTest(n)?ce(q(t,e)):void 0}function ce(t){return t===0||t===1?t===1:void 0}export function testPassStatusReader(t){const e=t.map(a=>a.testNumber),n=Ce(e),i=t.map(a=>!isParametricTest(a));return function(o,s){n(o,s);for(let r=0;r<s.length;r++){if(s[r]!==Oe||!i[r])continue;const m=ce(q(o,e[r]));m!==void 0&&(s[r]=m?Ne:ke)}return s}}export function dieHasTestData(t){return De(t)}function K(t){return t===void 0||Number.isNaN(t)?void 0:t}function U(t,e){const n={},i=K(e.hbin),a=K(e.sbin);i!==void 0&&(n.hbin=i),a!==void 0&&(n.sbin=a),e.retestCount!==void 0&&(n.retestCount=e.retestCount),e.siteNum!==void 0&&(n.siteNum=e.siteNum),e.partId!==void 0&&(n.partId=e.partId),e.metadata!==void 0&&(n.metadata=e.metadata);const o=H(e);return ve({...t,...n},o.table,o.row)}function he(t){return t.some(dieHasTestData)?"value":"hardBin"}function pe(t,e){return t.map(({code:n,message:i})=>({code:n,message:i,severity:n==="non-standard-diameter"||n==="diameter-exceeds-die-extent"?"warning":"error",confidence:e.wafer.confidence}))}const dt="do not fit inside the supplied",ft="Inferred wafer diameter",ut="The probed dies reach only",lt=.75,ct=20;export const STANDARD_WAFER_DIAMETERS_MM=[100,125,150,200,300];const ht=.02;function me(t,e){return e.reduce((n,i)=>Math.abs(i-t)<Math.abs(n-t)?i:n)}function pt(t,e){if(!e.length)return!0;const n=me(t,e);return Math.abs(n-t)/n<=ht}function mt(t){return t===void 0?STANDARD_WAFER_DIAMETERS_MM:t.filter(e=>Number.isFinite(e)&&e>0)}export function buildWaferMap(t){const e=Ze(!Array.isArray(t)&&t.layout?Qe(t):t),n=[];if(e.derivedTests){const f=new Set(e.passBins),S=$=>{const E=$.length>0?H($[0]):void 0,W=Ye($,E?.table??Se([]),e.testDefs,e.derivedTests,f);return E&&Te($,W.table),W},C=(e.lotStackOpts?e.lotStackOpts.results:[e.results]).map(S);e.testDefs=C[0]?.testDefs??e.testDefs,n.push(...C[0]?.warnings??[])}const i=e.lotStackOpts?te(st(e.lotStackOpts,e.testDefs)).records:e.results;for(let f=0;f<Math.min(5,i.length);f++){const S=i[f];if(typeof S.x=="string"||typeof S.y=="string")throw new TypeError("buildWaferMap: x and y must be numbers, received strings. Did you forget to cast CSV values? e.g. { x: +row.x, y: +row.y }")}for(const f of i)if(f.x==null!=(f.y==null))throw new TypeError(`buildWaferMap: die has only one of x/y set (x=${String(f.x)}, y=${String(f.y)}). A die must be either fully positioned (both x and y) or fully unpositioned (neither) \u2014 omit both to mark a die as having no reported position.`);const{results:a,warning:o}=at(i,e.retestPolicy,e.passBins),s={wafer:{confidence:1,method:"provided"},diePitch:{confidence:1,units:"mm"},grid:{confidence:1}},r=[];if(e.explicitDies){let f=e.explicitDies.filter(F);const S=e.explicitDies.filter(V=>!F(V)),T=a.filter(F),C=a.filter(V=>!F(V));if(T.length>0){const V=new Map(T.map(P=>[X(P),P]));f=f.map(P=>{const Z=V.get(X(P));return Z?U(P,Z):P})}const $=e.waferOpts?.diameter??300,E=G({diameter:$,notch:e.waferOpts?.notch,orientation:e.waferOpts?.orientation??0,metadata:e.waferOpts?.metadata});E.orientation!==0&&(f=ee(f,E)),f=f.map(j);const W=le(e.reticleOpts,E,f,1,1,0,0,0,0,E.orientation),ge=C.map((V,P)=>j(U({id:`unpositioned_${P}`,width:f[0]?.width??1,height:f[0]?.height??1},V))),z=[...f,...S,...ge];return{wafer:E,dies:z,dataAxisFlip:{x:!1,y:!1},reticleConfig:e.reticleOpts,units:"mm",inference:s,warnings:[...e.removedFieldWarning?[e.removedFieldWarning]:[],...e.inputValueWarnings,...o?[o]:[],...pe(r,s),...n,...e.ringCountWarning?[e.ringCountWarning]:[]],plotMode:he(a),metadata:E.metadata??null,isLotStack:!1,dataCoverage:fe(z),passBins:e.passBins,ringCount:e.ringCount,yield:ue(z,e.passBins,e.edgeDieYieldMode),reticles:W,hbinDefs:e.hbinDefs,sbinDefs:e.sbinDefs,testDefs:e.testDefs,metadataFields:e.metadataFields}}const m=a.filter(F),h=a.filter(f=>!F(f)),d=m.map(f=>({x:f.x,y:f.y})),c=Pe(d,e.dieOpts,e.waferOpts?.diameter);s.diePitch={confidence:c.confidence,units:c.units};const{pitchX:p,pitchY:u}=c,x=c.units,D=et(m,e.dieOpts),l=Ve(d);s.grid={confidence:l.confidence};const{offsetX:b,offsetY:w}=tt(d,D,l),{flipX:g,flipY:y}=de(e.dieOpts,D),{colMidX:k,colMidY:R,anchored:v}=nt(d,b,w,p,u,e.waferOpts?.center);let N=e.waferOpts?.diameter;const B=d.map(f=>({x:(Math.round(f.x)-b)*p-k,y:(Math.round(f.y)-w)*u-R})),I=J(B.map(({x:f,y:S})=>Math.hypot(Math.abs(f)+p/2,Math.abs(S)+u/2)));if(N===void 0)if(d.length>0)if(x==="mm"){const f=_e(B,{minRadius:I});N=f.diameter,s.wafer={confidence:f.confidence,method:f.method};const S=mt(e.standardDiameters);if(!pt(N,S)){const T=me(N,S);r.push({code:"non-standard-diameter",message:`${ft} ${N.toFixed(1)} mm is not a standard wafer size (nearest is ${T} mm). The diameter was derived from the die extent, which assumes the probed grid reaches the wafer edge \u2014 a non-standard result usually means it does not, because the outer dies were never probed. Every die is then placed against a wafer that is too small, which moves dies between rings and changes ring and edge findings. Supply waferConfig.diameter.`})}}else N=I*2,s.wafer={confidence:c.confidence*.8,method:"extent"};else N=x==="mm"?300:30,s.wafer={confidence:0,method:"default"};if(!v&&d.length>0&&it(d,b,w)&&(s.wafer.method="inferred-partial",r.push({code:"partial-coverage",message:"Wafer geometry was inferred from die positions alone. The data does not span a full symmetric wafer, so the inferred diameter and centre may be wrong and dies may be mis-positioned relative to the true wafer boundary. Supply waferConfig.diameter and waferConfig.center (the prober coordinate of the wafer centre) to position partial data correctly."})),e.waferOpts?.diameter!==void 0&&e.waferOpts.center===void 0&&I>0&&d.length>=ct){const f=I/(N/2);f<lt&&r.push({code:"diameter-exceeds-die-extent",message:`${ut} ${(f*100).toFixed(0)}% of the ${N} mm wafer's radius. Either the diameter is larger than the real one, or this is a partial map covering only part of the wafer. Ring bands are equal-radius, so the outer rings are mostly or entirely empty and ring, quadrant and edge findings describe the assumed wafer rather than the probed area. Check waferConfig.diameter, or drop it and let the diameter be inferred from the die extent.`})}if(e.waferOpts?.diameter!==void 0){const f=e.dieOpts?.width!==void 0&&e.dieOpts?.height!==void 0;if(f&&I>N/2+1e-9){const S=B.filter(({x:T,y:C})=>Math.hypot(Math.abs(T)+p/2,Math.abs(C)+u/2)>N/2+1e-9).length;r.push({code:"geometry-conflict",message:`${S} of ${B.length} probed die positions ${dt} ${N} mm wafer at the supplied die pitch of ${p} \xD7 ${u} 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 ${(I*2).toFixed(1)} mm. Check waferConfig.diameter and dieConfig.width/height against the real device.`})}}const O=G({diameter:N,notch:e.waferOpts?.notch,orientation:e.waferOpts?.orientation??0,metadata:e.waferOpts?.metadata});let A=m.map(f=>{const S=Math.round(f.x)-b,T=Math.round(f.y)-w,C={id:`${S}_${T}`,x:S,y:T,physX:S*p-k,physY:T*u-R,width:p,height:u,insideWafer:!0,partial:!1};return U(C,f)});(b!==0||w!==0)&&(A=A.map(f=>({...f,x:f.x+b,y:f.y+w,id:`${f.x+b}_${f.y+w}`}))),A=ee(A,O),(g||y)&&(A=be(A,{flipX:g,flipY:y},O.center));const M=[];e.waferOpts?.edgeExclusion&&e.waferOpts.edgeExclusion>0&&(A=rt(A,O,e.waferOpts.edgeExclusion),e.waferOpts.edgeExclusion>O.radius&&M.push({code:"edge-exclusion-exceeds-radius",message:`edgeExclusion (${e.waferOpts.edgeExclusion}mm) exceeds the resolved wafer radius (${O.radius}mm) \u2014 the entire wafer is excluded.`,severity:"warning"})),A=A.map(j);const L=le(e.reticleOpts,O,A,p,u,b,w,k,R,O.orientation,g,y),_=h.map((f,S)=>j(U({id:`unpositioned_${S}`,width:p,height:u},f))),Y=[...A,..._];return{wafer:O,dies:Y,dataAxisFlip:{x:g,y},reticleConfig:e.reticleOpts,units:x,inference:s,warnings:[...e.removedFieldWarning?[e.removedFieldWarning]:[],...e.inputValueWarnings,...o?[o]:[],...pe(r,s),...M,...n,...e.ringCountWarning?[e.ringCountWarning]:[]],plotMode:he(a),metadata:O.metadata??null,isLotStack:e.lotStackOpts!==void 0,dataCoverage:fe(Y),passBins:e.passBins,ringCount:e.ringCount,yield:ue(Y,e.passBins,e.edgeDieYieldMode),reticles:L,hbinDefs:e.hbinDefs,sbinDefs:e.sbinDefs,testDefs:e.testDefs,metadataFields:e.metadataFields,aggrMethod:e.lotStackOpts?.method,lotSize:e.lotStackOpts?.results.length}}
|
|
@@ -28,8 +28,6 @@ export declare function wangHash(n: number): number;
|
|
|
28
28
|
/** Linear interpolation across RGB keypoints for t ∈ [0, 1]. */
|
|
29
29
|
export declare function lerpKp(kp: readonly [number, number, number][], t: number): string;
|
|
30
30
|
export declare const VIRIDIS: readonly [number, number, number][];
|
|
31
|
-
/** Map t ∈ [0, 1] to a Viridis RGB colour string. */
|
|
32
|
-
export declare function valueToViridis(t: number): string;
|
|
33
31
|
/**
|
|
34
32
|
* Categorical colour for the `index`-th distinct value of an active
|
|
35
33
|
* `'metadata'` field, where `index` comes from sorting the field's distinct
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{clamp01 as s}from"../core/utils.js";export const SPEC_PASS_FILL="#2ecc71",SPEC_FAIL_LOW="#3498db",SPEC_FAIL_HIGH="#e74c3c",NO_DATA_FILL="#d6d9dd",BIN_PALETTE=["#95a5a6","#1eb84b","#932cdd","#b8a51e","#2cbfdd","#b81e71","#58dd2c","#251eb8","#dd672c","#1eb87f","#ce2cdd","#98b81e","#2c84dd","#b81e3e","#2cdd3b","#581eb8","#dda22c","#1eb8b2","#dd2cb0","#64b81e","#2c49dd","#b8321e","#2cdd76","#8c1eb8","#dcdd2c","#1e8bb8","#dd2c75","#31b81e","#4b2cdd","#b8651e","#2cddb1","#b81eb1","#a1dd2c","#1e57b8","#dd2c3a","#1eb83f","#852cdd","#b8991e","#2ccddd","#b81e7e","#66dd2c","#1e24b8","#dd5a2c","#1eb872","#c02cdd","#a4b81e","#2c92dd","#b81e4a","#2cdd2e","#4c1eb8","#dd942c","#1eb8a6","#dd2cbe","#70b81e","#2c57dd","#b8261e","#2cdd69","#801eb8","#ddcf2c","#1e97b8","#dd2c83","#3db81e","#3d2cdd","#b8591e"];export function wangHash(e){let c=e|0;return c=Math.imul(c^c>>>16,73244475),c=Math.imul(c^c>>>16,73244475),(c^c>>>16)>>>0}const u=BIN_PALETTE.length-1;export function lerpKp(e,c){const n=s(c)*(e.length-1),d=Math.floor(n),t=Math.min(d+1,e.length-1),a=n-d,b=Math.round(e[d][0]+a*(e[t][0]-e[d][0])),o=Math.round(e[d][1]+a*(e[t][1]-e[d][1])),l=Math.round(e[d][2]+a*(e[t][2]-e[d][2]));return`rgb(${b},${o},${l})`}export const VIRIDIS=[[68,1,84],[59,82,139],[33,145,140],[94,201,98],[253,231,37]];
|
|
1
|
+
import{clamp01 as s}from"../core/utils.js";export const SPEC_PASS_FILL="#2ecc71",SPEC_FAIL_LOW="#3498db",SPEC_FAIL_HIGH="#e74c3c",NO_DATA_FILL="#d6d9dd",BIN_PALETTE=["#95a5a6","#1eb84b","#932cdd","#b8a51e","#2cbfdd","#b81e71","#58dd2c","#251eb8","#dd672c","#1eb87f","#ce2cdd","#98b81e","#2c84dd","#b81e3e","#2cdd3b","#581eb8","#dda22c","#1eb8b2","#dd2cb0","#64b81e","#2c49dd","#b8321e","#2cdd76","#8c1eb8","#dcdd2c","#1e8bb8","#dd2c75","#31b81e","#4b2cdd","#b8651e","#2cddb1","#b81eb1","#a1dd2c","#1e57b8","#dd2c3a","#1eb83f","#852cdd","#b8991e","#2ccddd","#b81e7e","#66dd2c","#1e24b8","#dd5a2c","#1eb872","#c02cdd","#a4b81e","#2c92dd","#b81e4a","#2cdd2e","#4c1eb8","#dd942c","#1eb8a6","#dd2cbe","#70b81e","#2c57dd","#b8261e","#2cdd69","#801eb8","#ddcf2c","#1e97b8","#dd2c83","#3db81e","#3d2cdd","#b8591e"];export function wangHash(e){let c=e|0;return c=Math.imul(c^c>>>16,73244475),c=Math.imul(c^c>>>16,73244475),(c^c>>>16)>>>0}const u=BIN_PALETTE.length-1;export function lerpKp(e,c){const n=s(c)*(e.length-1),d=Math.floor(n),t=Math.min(d+1,e.length-1),a=n-d,b=Math.round(e[d][0]+a*(e[t][0]-e[d][0])),o=Math.round(e[d][1]+a*(e[t][1]-e[d][1])),l=Math.round(e[d][2]+a*(e[t][2]-e[d][2]));return`rgb(${b},${o},${l})`}export const VIRIDIS=[[68,1,84],[59,82,139],[33,145,140],[94,201,98],[253,231,37]];const f=["#4e79a7","#f28e2b","#e15759","#76b7b2","#59a14f","#edc948","#b07aa1","#ff9da7","#9c755f","#bab0ac"],h=668265263;export function metadataValueColor(e){return e<f.length?f[e]:BIN_PALETTE[wangHash(e^h)%u+1]}export function valueToGreyscale(e){const c=Math.round(s(e)*200+30);return`rgb(${c},${c},${c})`}export function contrastTextColor(e){let c=0,r=0,n=0;const d=e.match(/rgb\((\d+),\s*(\d+),\s*(\d+)\)/);if(d)c=+d[1],r=+d[2],n=+d[3];else{const b=e.replace("#","");c=parseInt(b.slice(0,2),16),r=parseInt(b.slice(2,4),16),n=parseInt(b.slice(4,6),16)}const t=b=>{const o=b/255;return o<=.03928?o/12.92:((o+.055)/1.055)**2.4};return .2126*t(c)+.7152*t(r)+.0722*t(n)>.179?"#000000":"#ffffff"}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The front door: both forms of `results` (rows or columns) become the same
|
|
3
|
+
* internal form here, record shells linked to a value table, before anything
|
|
4
|
+
* else in `buildWaferMap` runs.
|
|
5
|
+
*
|
|
6
|
+
* A shell is a `DieResult` without `testValues`/`testPass`: position, bins,
|
|
7
|
+
* site, part ID, metadata, `supersedes`. It is linked to its row of the table
|
|
8
|
+
* (`linkDie`), so retest resolution, derived tests and the lot stack read its
|
|
9
|
+
* values through `core/dieTable.ts` like any die, and the dies built from the
|
|
10
|
+
* shells are linked to the same rows.
|
|
11
|
+
*/
|
|
12
|
+
import type { DieMetadata } from '../core/metadata.js';
|
|
13
|
+
import type { DieTable } from '../core/dieTable.js';
|
|
14
|
+
import type { DieResult } from './buildWaferMap.js';
|
|
15
|
+
/**
|
|
16
|
+
* One wafer's results as columns: one entry per **record**, retests included
|
|
17
|
+
* (wmap resolves retests exactly as for rows). An alternative to `DieResult[]`
|
|
18
|
+
* for a host that already holds columns (a parser, Arrow or Parquet): it builds
|
|
19
|
+
* the same map without an object per die. If you have rows, pass rows.
|
|
20
|
+
*
|
|
21
|
+
* Per-record columns must all have `count` entries. A missing entry is `NaN`,
|
|
22
|
+
* or STDF V4's own missing value in an integer column: −32768 for `x`/`y`,
|
|
23
|
+
* 65535 for `hbin`, `sbin` and `siteNum`. Any other value outside STDF's ranges
|
|
24
|
+
* is treated as missing and reported, as for rows. A die is either fully
|
|
25
|
+
* positioned or fully unpositioned: `x` and `y` must be missing together.
|
|
26
|
+
*
|
|
27
|
+
* Test values and verdicts are **sparse**: per test number, the record indices
|
|
28
|
+
* that have one, and their values. A record a test did not run on is simply
|
|
29
|
+
* not listed, so a missing value can never be mistaken for a reading, and
|
|
30
|
+
* nothing can shift. Indices must be whole numbers in `[0, count)`, each at
|
|
31
|
+
* most once per test, and `indices` and `values` must be the same length:
|
|
32
|
+
* `buildWaferMap` throws otherwise, because a misaligned column would draw a
|
|
33
|
+
* plausible, wrong map. Values are stored as supplied (`Float32Array` stays
|
|
34
|
+
* 32-bit); a non-finite value is treated as missing and reported. Verdicts are
|
|
35
|
+
* `true`/`1` (pass) or `false`/`0` (fail).
|
|
36
|
+
*/
|
|
37
|
+
export interface DieColumns {
|
|
38
|
+
/** Number of records. */
|
|
39
|
+
count: number;
|
|
40
|
+
x?: ArrayLike<number>;
|
|
41
|
+
y?: ArrayLike<number>;
|
|
42
|
+
hbin?: ArrayLike<number>;
|
|
43
|
+
sbin?: ArrayLike<number>;
|
|
44
|
+
siteNum?: ArrayLike<number>;
|
|
45
|
+
partId?: ArrayLike<number | string | undefined>;
|
|
46
|
+
supersedes?: ArrayLike<'partId' | 'position' | undefined>;
|
|
47
|
+
metadata?: ArrayLike<DieMetadata | undefined>;
|
|
48
|
+
/** Per test number: the records that have a value, and the values. */
|
|
49
|
+
testValues?: Record<number, {
|
|
50
|
+
indices: ArrayLike<number>;
|
|
51
|
+
values: ArrayLike<number>;
|
|
52
|
+
}>;
|
|
53
|
+
/** Per test number: the records that have a recorded verdict, and the verdicts. */
|
|
54
|
+
testPass?: Record<number, {
|
|
55
|
+
indices: ArrayLike<number>;
|
|
56
|
+
values: ArrayLike<boolean | number>;
|
|
57
|
+
}>;
|
|
58
|
+
}
|
|
59
|
+
/** Findings from the columns' test data, reported with the rest of the input checks. */
|
|
60
|
+
export interface ColumnFindings {
|
|
61
|
+
/** Verdicts that were neither true/false nor 1/0. */
|
|
62
|
+
wrongVerdicts: number;
|
|
63
|
+
/** Values that were not numbers. */
|
|
64
|
+
wrongValues: number;
|
|
65
|
+
example?: string;
|
|
66
|
+
/** Non-finite values. */
|
|
67
|
+
nonFinite: number;
|
|
68
|
+
/** Test numbers STDF cannot store, as given. */
|
|
69
|
+
badTestNumbers: string[];
|
|
70
|
+
}
|
|
71
|
+
/** A wafer as record shells, each linked to its row of `table`. */
|
|
72
|
+
export interface LinkedRecords {
|
|
73
|
+
records: DieResult[];
|
|
74
|
+
table: DieTable;
|
|
75
|
+
}
|
|
76
|
+
export declare function isDieColumns(v: unknown): v is DieColumns;
|
|
77
|
+
/** Rows as shells linked to a table built from their values. The rows are not changed. */
|
|
78
|
+
export declare function linkRows(rows: readonly DieResult[]): LinkedRecords;
|
|
79
|
+
/** Points every record at `table` (same rows): after derived tests extend it. */
|
|
80
|
+
export declare function relink(records: readonly DieResult[], table: DieTable): void;
|
|
81
|
+
/**
|
|
82
|
+
* Columns as shells linked to a table. Structural faults (lengths, indices)
|
|
83
|
+
* throw; value faults are left for the input checks (positions, bins, sites:
|
|
84
|
+
* copied onto the shells as given) or counted in `findings` (test data).
|
|
85
|
+
*/
|
|
86
|
+
export declare function linkColumns(cols: DieColumns, findings: ColumnFindings): LinkedRecords;
|
|
87
|
+
//# sourceMappingURL=columnarInput.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{dieLink as E,linkDie as w,tableFromRows as T,HAS_VALUES as M,HAS_VERDICTS as k,VERDICT_FAIL as V,VERDICT_NONE as x,VERDICT_PASS as _}from"../core/dieTable.js";import{isStdfTestNumber as v}from"../core/stdf.js";export function isDieColumns(t){return t!==null&&typeof t=="object"&&!Array.isArray(t)&&typeof t.count=="number"}export function linkRows(t){const f=T(t);return{records:t.map((m,c)=>{const{testValues:b,testPass:y,...l}=m;return w(l,f,c)}),table:f}}export function relink(t,f){for(const s of t)w(s,f,E(s).row)}const A=-32768,h=65535;export function linkColumns(t,f){const s=t.count;if(!Number.isInteger(s)||s<0)throw new TypeError(`buildWaferMap: columns \`count\` must be a whole number of records, got ${String(s)}.`);const m=["x","y","hbin","sbin","siteNum","partId","supersedes","metadata"];for(const e of m){const n=t[e];if(n!==void 0&&n.length!==s)throw new TypeError(`buildWaferMap: column \`${e}\` has ${n.length} entries for ${s} records. Every per-record column needs one entry per record.`)}const c=new Uint8Array(s),b=new Map,y=new Map,l=new Uint8Array(s),N=(e,n)=>{const a=t[e];if(a!==void 0)for(const r of Object.keys(a)){const{indices:o,values:u}=a[r]??{};if(o===void 0||u===void 0||o.length!==u.length)throw new TypeError(`buildWaferMap: ${e}[${r}] needs \`indices\` and \`values\` of the same length (got ${o?.length??"none"} and ${u?.length??"none"}).`);l.fill(0);for(let i=0;i<o.length;i++){const d=o[i];if(!Number.isInteger(d)||d<0||d>=s)throw new TypeError(`buildWaferMap: ${e}[${r}].indices[${i}] is ${String(d)}, not a record index in [0, ${s}).`);if(l[d])throw new TypeError(`buildWaferMap: ${e}[${r}] lists record ${d} twice.`);l[d]=1}if(!v(Number(r))){f.badTestNumbers.push(r);continue}n(Number(r),o,u)}};N("testValues",(e,n,a)=>{const r=a instanceof Float32Array?new Float32Array(s).fill(NaN):new Float64Array(s).fill(NaN);for(let o=0;o<n.length;o++){const u=n[o],i=a[o];if(c[u]|=M,typeof i!="number"){f.wrongValues++,f.example??=`testValues ${JSON.stringify(i)}`;continue}if(!Number.isFinite(i)){f.nonFinite++;continue}r[u]=i}b.set(e,r)}),N("testPass",(e,n,a)=>{const r=new Int8Array(s).fill(x);for(let o=0;o<n.length;o++){const u=n[o],i=a[o];c[u]|=k,i===!0||i===1?r[u]=_:i===!1||i===0?r[u]=V:(f.wrongVerdicts++,f.example??=`testPass ${JSON.stringify(i)}`)}y.set(e,r)});const $={rows:s,values:new Map([...b].sort((e,n)=>e[0]-n[0])),verdicts:new Map([...y].sort((e,n)=>e[0]-n[0])),fields:c},p=(e,n,a)=>{if(e===void 0)return;const r=e[n];return r===a||Number.isNaN(r)?void 0:r},I=new Array(s);for(let e=0;e<s;e++){const n={},a=p(t.x,e,A),r=p(t.y,e,A);a!==void 0&&(n.x=a),r!==void 0&&(n.y=r);const o=p(t.hbin,e,h),u=p(t.sbin,e,h);o!==void 0&&(n.hbin=o),u!==void 0&&(n.sbin=u);const i=p(t.siteNum,e,h);i!==void 0&&(n.siteNum=i);const d=t.partId?.[e];d!==void 0&&(n.partId=d);const g=t.supersedes?.[e];g!==void 0&&(n.supersedes=g);const S=t.metadata?.[e];S!==void 0&&(n.metadata=S),I[e]=w(n,$,e)}return{records:I,table:$}}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* and held to by tests/deprecations.test.mjs, which fails once the changelog or
|
|
5
5
|
* package.json reaches an export's removal release with it still here.
|
|
6
6
|
*/
|
|
7
|
-
export declare const DEPRECATED_REMOVAL_VERSION = "0.
|
|
7
|
+
export declare const DEPRECATED_REMOVAL_VERSION = "0.32.0";
|
|
8
8
|
/**
|
|
9
9
|
* @internal The release that removes each deprecated name. A name deprecated
|
|
10
10
|
* after the default removal release was scheduled gets a later one — a removal
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export const DEPRECATED_REMOVAL_VERSION="0.
|
|
1
|
+
export const DEPRECATED_REMOVAL_VERSION="0.32.0",DEPRECATED_REMOVALS=new Map,DEPRECATED_EXPORTS=new Map;const r=new Set;export function noticeOnce(t,e){r.has(t)||(r.add(t),console.warn(`[wafermap] ${e}`))}export function deprecated(t,e,n,o=DEPRECATED_REMOVAL_VERSION){DEPRECATED_EXPORTS.set(e,"function"),DEPRECATED_REMOVALS.set(e,o);const c=t;return((...E)=>(noticeOnce(e,`${e} is deprecated and will be removed in ${o}. ${n}`),c(...E)))}export function deprecatedValue(t,e,n=DEPRECATED_REMOVAL_VERSION){return DEPRECATED_EXPORTS.set(e,"value"),DEPRECATED_REMOVALS.set(e,n),t}
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
* with no further change.
|
|
22
22
|
*/
|
|
23
23
|
import type { DieResult, TestDef, WaferWarning } from '../buildWaferMap.js';
|
|
24
|
+
import type { DieTable } from '../../core/dieTable.js';
|
|
24
25
|
/**
|
|
25
26
|
* A test whose values are computed from other tests on the same die, rather than
|
|
26
27
|
* measured by the tester.
|
|
@@ -71,15 +72,16 @@ export interface DerivedTestDef extends TestDef {
|
|
|
71
72
|
readonly derived?: true;
|
|
72
73
|
}
|
|
73
74
|
export interface ApplyResult {
|
|
74
|
-
|
|
75
|
+
/** The wafer's table with the derived columns added (the input table when nothing was derived). */
|
|
76
|
+
table: DieTable;
|
|
75
77
|
testDefs: TestDef[] | undefined;
|
|
76
78
|
warnings: WaferWarning[];
|
|
77
79
|
}
|
|
78
80
|
/**
|
|
79
|
-
* Apply `derivedTests` to
|
|
80
|
-
*
|
|
81
|
-
*
|
|
82
|
-
*
|
|
81
|
+
* Apply `derivedTests` to a wafer's records: `records[i]` is row `i` of
|
|
82
|
+
* `table` (its bins give `diePass()`). Returns a copy of the table with a
|
|
83
|
+
* column per derived test, and `testDefs` extended with the derived defs.
|
|
84
|
+
* Neither the records nor the table passed in are changed.
|
|
83
85
|
*/
|
|
84
|
-
export declare function applyDerivedTests(
|
|
86
|
+
export declare function applyDerivedTests(records: readonly DieResult[], table: DieTable, testDefs: TestDef[] | undefined, derivedTests: DerivedTestDef[] | undefined, passBins: ReadonlySet<number>): ApplyResult;
|
|
85
87
|
//# sourceMappingURL=apply.d.ts.map
|