@wafertools/wafermap 0.23.1 → 0.24.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +99 -0
- package/README.md +1 -1
- package/dist/packages/canvas-adapter/dieList.d.ts +83 -17
- package/dist/packages/canvas-adapter/dieList.js +13 -2
- package/dist/packages/canvas-adapter/index.d.ts +1 -1
- package/dist/packages/canvas-adapter/renderWaferGallery.d.ts +10 -0
- package/dist/packages/canvas-adapter/renderWaferGallery.js +2 -2
- package/dist/packages/canvas-adapter/renderWaferMap.d.ts +13 -0
- package/dist/packages/canvas-adapter/renderWaferMap.js +1 -1
- package/dist/packages/canvas-adapter/summaryPanel.d.ts +36 -3
- package/dist/packages/canvas-adapter/summaryPanel.js +3 -3
- package/dist/packages/canvas-adapter/userGuideHtml.d.ts +1 -1
- package/dist/packages/canvas-adapter/userGuideHtml.js +1 -1
- package/dist/packages/canvas-adapter/version.d.ts +2 -2
- package/dist/packages/canvas-adapter/version.js +1 -1
- package/dist/packages/core/metadata.d.ts +29 -0
- package/dist/packages/core/metadata.js +1 -1
- package/dist/packages/renderer/buildView.d.ts +8 -0
- package/dist/packages/renderer/buildView.js +1 -1
- package/dist/packages/stats/facets.js +1 -1
- package/dist/packages/stats/index.d.ts +2 -0
- package/dist/packages/stats/index.js +1 -1
- package/dist/packages/stats/metadataColumns.d.ts +70 -0
- package/dist/packages/stats/metadataColumns.js +1 -0
- package/dist/packages/stats/renderFindingsReport.js +16 -16
- package/package.json +8 -3
|
@@ -1 +1 @@
|
|
|
1
|
-
import{hasPosition as Qt}from"../core/dies.js";import{getReticleCell as Ut}from"../core/reticle.js";import{affineRotation as ft,affineMirror as Vt,affineCompose as at,affinePoint as X,affineVector as dt,affineSwapsAxes as Jt}from"../core/transforms.js";import{contrastTextColor as Y,SPEC_PASS_FILL as ht,SPEC_FAIL_LOW as Zt,SPEC_FAIL_HIGH as pt}from"./colorMap.js";import{getColorScheme as te}from"./colorSchemes.js";import{getDieTestValue as R,getTestPassStatus as G,isParametricTest as mt}from"./buildWaferMap.js";import{fmt as yt,fmtColorbarAxis as gt,fmtAggregationMethod as Rt}from"./fmt.js";import{metadataValueColor as ee}from"./colorMap.js";import{compareNatural as ne,clamp01 as Ct}from"../core/utils.js";import{prettyKey as ae}from"../core/utils.js";export function resolveTestNumber(t,e){return e?.length?{testNumber:(findTestDef(e,t)??e[0]).testNumber}:{testNumber:t}}export function findTestDef(t,e){return t?.find(n=>n.testNumber===e)}export function getUniqueTestNumbers(t){return[...new Set(t.flatMap(e=>e.testValues?Object.keys(e.testValues).map(Number):[]))].sort((e,n)=>e-n)}const ie="#d3d6db",Nt="#e8e9ea",se="#eceef0",w="#d6d9dd";function it(t,e){const n=e?t.metadata?.[e]:void 0;return n!=null&&(typeof n=="string"||typeof n=="number"||typeof n=="boolean")?String(n):void 0}export function classifySpec(t,e){return t===void 0?null:e?.limitLow!==void 0&&t<e.limitLow?"failLow":e?.limitHigh!==void 0&&t>e.limitHigh?"failHigh":"pass"}function oe(t,e,n){const{x:i,y:s}=t.center,a=ft(t.orientation,i,s),o=at(Vt(e?.x??!1,e?.y??!1,i,s),ft(t.orientation,i,s)),l=at(Vt(n?.flipX??!1,n?.flipY??!1,i,s),ft(n?.rotation??0,i,s));return{bakedToScreen:l,physicalToScreen:at(l,a),gridToScreen:at(l,o)}}function _(t,e=!1){return{points:[t],closed:e}}function re(t,e){const{center:n,radius:i,notch:s}=t;let a=n.x+i*Math.cos(e),o=n.y+i*Math.sin(e);if(!s)return{x:a,y:o};const l=Math.sqrt(i**2-(s.length/2)**2),f=s.length/2,y=a-n.x,m=o-n.y;return s.type==="bottom"&&m<-l?(o=n.y-l,a=n.x+Math.max(-f,Math.min(f,y))):s.type==="top"&&m>l?(o=n.y+l,a=n.x+Math.max(-f,Math.min(f,y))):s.type==="left"&&y<-l?(a=n.x-l,o=n.y+Math.max(-f,Math.min(f,m))):s.type==="right"&&y>l&&(a=n.x+l,o=n.y+Math.max(-f,Math.min(f,m))),{x:a,y:o}}function Ht(t,e){return t.map(e).join(" / ")}function le(t,e=256){const n=Array.from({length:e},(i,s)=>t(s/(e-1)));return i=>n[Math.min(e-1,Math.max(0,Math.round(i*(e-1))))]}function ce(t,e){const n=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(n*.55,i*1.8))))}function ue(t,e,n){if(e?.length){const i=e.flatMap(s=>{const a=s.testNumber;if(!mt(s)){const f=G(t,a,s);return f===void 0?[]:[{key:a,label:s.name,value:f?"Pass":"Fail"}]}const o=R(t,a);if(o===void 0)return[];const l=t.testPass?.[a]===!1?" <i>(recorded fail)</i>":"";return[{key:a,label:s.name,value:`${yt(o,s.unit,n)}${l}`}]});if(i.length)return i}return t.testValues&&Object.keys(t.testValues).length>0?Object.entries(t.testValues).map(([i,s])=>({key:Number(i),label:`Test ${i}`,value:yt(s,void 0,n)})):t.testPass&&Object.keys(t.testPass).length>0?Object.entries(t.testPass).map(([i,s])=>({key:Number(i),label:`Test ${i}`,value:s?"Pass":"Fail"})):[]}export function buildHoverText(t,e,n={}){const{testDefs:i,hbinDefs:s,sbinDefs:a,fallbackFormat:o,aggrMethod:l,lotSize:f,waferMeta:y,activeTest:m,reticleConfig:$}=n,P=s?new Map(s.map(r=>[r.bin,r])):null,v=a?new Map(a.map(r=>[r.bin,r])):null,x=[`Die (${t.x}, ${t.y})`];if($&&Qt(t)){const r=Ut(t,$);x.push(`Reticle (${r.column}, ${r.row})`)}if(e==="stackedValues"){const r=R(t,0);if(r!==void 0){const c=i?.[0],p=c?.testNumber,h=c?.name??(p!=null?`Test ${p}`:"Value"),M=l?` (${l})`:"";x.push(`${h}${M}: ${yt(r,c?.unit,o)}`)}}else if(e==="stackedBins"||e==="stackedSoftBins"){const r=R(t,0),c=e==="stackedSoftBins"?t.sbin:t.hbin;if(r!==void 0){const p=e==="stackedSoftBins"?v:P,h=c!==void 0?p?.get(c)?.name?`${c} \xB7 ${p.get(c).name}`:`Bin ${c}`:"Bin";let M;if(l==="percent")M=`${r.toFixed(0)}%`;else{const b=f?` (${(r/f*100).toFixed(0)}% of lot)`:"";M=`${r}${b}`}const T=l?` [${Rt(l)}]`:"";x.push(`${h}: ${M}${T}`)}}else{const r=ue(t,i,o);if(e==="value"&&r.length){const{testNumber:c}=resolveTestNumber(m??0,i),p=findTestDef(i,c),h=R(t,c);let M=r.findIndex(A=>A.key===c);M<0&&(M=0);const T=r[M];let b=`<b>${T.label}: ${T.value}</b>`;if(T.key===c&&p){const A=classifySpec(h,p);(A==="failLow"||A==="failHigh")&&(b+=" <i>(out of spec)</i>")}x.push(b);const O=r.length-1;O>0&&x.push(`<i>+${O} more test${O===1?"":"s"}</i>`)}else if(r.length){const c=r.length;x.push(`<i>${c} test result${c===1?"":"s"} recorded</i>`)}if(t.hbin!==void 0||t.sbin!==void 0){const c=[];if(t.hbin!==void 0){const p=P?.get(t.hbin)?.name,h=p?`${t.hbin} \xB7 ${p}`:String(t.hbin);c.push(`HBin: ${h}`)}if(t.sbin!==void 0){const p=v?.get(t.sbin)?.name,h=p?`${t.sbin} \xB7 ${p}`:String(t.sbin);c.push(`SBin: ${h}`)}x.push(c.join(" \xB7 "))}}t.retestCount!==void 0&&x.push(`Retests: ${t.retestCount}`),t.siteNum!==void 0&&x.push(`Site: ${t.siteNum}`),t.partId!==void 0&&x.push(`Part ID: ${t.partId}`),t.partial&&x.push("<i>partial die</i>"),t.probeIndex!==void 0&&x.push(`Probe: #${t.probeIndex}`);const k={...y??{},...t.metadata??{}};for(const[r,c]of Object.entries(k))c!=null&&x.push(`${r}: ${String(c)}`);return x.join("<br>")}export function buildMapTitle(t,e="engineering",n){const i=t.isLotStack&&t.lotSize!==void 0?`stacked (${t.lotSize} ${t.lotSize===1?"wafer":"wafers"})`:"stacked",s=a=>{const o=n?.length===1?n[0]:void 0;return o===void 0?`${a} Bin`:o.name?`${a} Bin ${o.bin} \xB7 ${o.name}`:`${a} 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 a=t.metadataFields?.find(l=>l.key===t.activeMetadataKey),o=t.activeMetadataKey?ae(t.activeMetadataKey):"Metadata";return{primary:a?.label??o,secondary:""}}case"stackedValues":{const a=t.testDefs?.[0],o=t.valueRange[1]||t.valueRange[0]||0,{axisLabel:l}=gt(o,a?.name,a?.unit,e),f=l||"Value",y=t.aggrMethod?` \xB7 ${Rt(t.aggrMethod)}`:"";return{primary:`${f}${y}`,secondary:i}}default:{const a=findTestDef(t.testDefs,t.activeTest),o=t.valueRange[1]||t.valueRange[0]||0,{axisLabel:l}=gt(o,a?.name,a?.unit,e),f=l||a?.name;if(t.passFailDisplay!=="off"){const y=a?.testNumber??t.activeTest,m=f?`${f} \xB7 #${y}`:`Test ${y}`,$=t.passFailDisplay==="spec"?"Spec pass/fail":a!==void 0&&!mt(a)?"Functional pass/fail":"Tester pass/fail";return{primary:m,secondary:$}}return{primary:f??`Test ${t.activeTest}`,secondary:""}}}}export function generateTextOverlay(t,e,n){const{plotMode:i,colorFns:s,normalize:a,activeTest:o,valueRange:l,testDefs:f,fallbackFormat:y,passFailDisplay:m="off",activeMetadataKey:$,metadataColorMap:P}=n,v=findTestDef(f,o),{tickFmt:x}=gt(l[1],v?.name,v?.unit,y),{testNumber:k}=resolveTestNumber(o,f);return t.flatMap((r,c)=>{let p="",h="#111111";if(i==="value"&&m==="test"){const b=G(r,k,v);if(b===void 0)return[];p=b?"P":"F",h=Y(b?ht:pt)}else if(i==="value"){const b=R(r,k);if(b===void 0)return[];p=Ht([b],x),h=Y(s.forValue(a(b)))}else if(i==="hardBin"||i==="softBin"){const b=i==="softBin"?r.sbin:r.hbin;if(b===void 0)return[];p=String(b),h=Y(s.forBin(b))}else if(i==="metadata"){const b=it(r,$);if(b===void 0)return[];p=b,h=Y(P?.get(p)??w)}else{const b=R(r,0);if(b===void 0)return[];p=Ht([b],x),h=Y(s.forValue(a(b)))}const M=e?e[c*2]:r.physX,T=e?e[c*2+1]:r.physY;return[{x:M,y:T,text:p,fontSize:ce(r,p),color:h,align:"center"}]})}function Ot(t){return t==="top"?Math.PI/2:t==="bottom"?-Math.PI/2:t==="left"?Math.PI:0}function fe(t,e,n=720){const{center:i,radius:s,notch:a}=t,o=k=>X(e,k.x,k.y);if(!(a!==void 0&&t.diameter>150)){const k=[];for(let r=0;r<=n;r++){const c=2*Math.PI*r/n;k.push(o(re(t,c)))}return[{kind:"wafer-boundary",..._(k,!0),lineColor:"#888888",lineWidth:1}]}const f=1.25,y=a.length,m=Ot(a.type),$=Math.atan2(y,s),P=s-f,v=[];let x=!1;for(let k=0;k<=n;k++){const r=2*Math.PI*k/n;let c=r-m;for(;c>Math.PI;)c-=2*Math.PI;for(;c<-Math.PI;)c+=2*Math.PI;if(Math.abs(c)<=$){if(!x){x=!0;const p={x:i.x+s*Math.cos(m-$),y:i.y+s*Math.sin(m-$)},h={x:i.x+P*Math.cos(m),y:i.y+P*Math.sin(m)},M={x:i.x+s*Math.cos(m+$),y:i.y+s*Math.sin(m+$)};v.push(o(p),o(h),o(M))}continue}v.push(o({x:i.x+s*Math.cos(r),y:i.y+s*Math.sin(r)}))}return[{kind:"wafer-boundary",..._(v,!0),lineColor:"#888888",lineWidth:1}]}function de(t,e,n,i=360){const s=[],a=Math.max(1,Math.floor(n));for(let o=1;o<a;o++){const l=t.radius*o/a,f=[];for(let y=0;y<=i;y++){const m=2*Math.PI*y/i,$={x:t.center.x+l*Math.cos(m),y:t.center.y+l*Math.sin(m)};f.push(X(e,$.x,$.y))}s.push({kind:"ring-boundary",..._(f,!0),lineColor:"rgba(255,255,255,0.7)",lineWidth:1.5})}return s}function he(t,e,n,i){const{x:s,y:a}=t.center,o=t.radius,l=(r,c)=>X(e,r,c),f=n-s,y=Math.sqrt(Math.max(0,o*o-f*f)),m=l(n,a-y),$=l(n,a+y),P=i-a,v=Math.sqrt(Math.max(0,o*o-P*P)),x=l(s-v,i),k=l(s+v,i);return[{kind:"quadrant-boundary",..._([m,$]),lineColor:"rgba(255,255,255,0.7)",lineWidth:1.5},{kind:"quadrant-boundary",..._([x,k]),lineColor:"rgba(255,255,255,0.7)",lineWidth:1.5}]}function pe(t,e){return t.map(n=>{const i=n.width/2,s=n.height/2;return{kind:"reticle",points:[[[n.x-i,n.y-s],[n.x+i,n.y-s],[n.x+i,n.y+s],[n.x-i,n.y+s]].map(([o,l])=>X(e,o,l))],closed:!0,lineColor:"rgba(255,255,255,0.7)",lineWidth:1.5,fill:"rgba(0,0,0,0)"}})}function me(t){const e=t.filter(n=>n.probeIndex!==void 0).sort((n,i)=>(n.probeIndex??0)-(i.probeIndex??0));return e.length?[{kind:"probe-path",..._(e.map(n=>({x:n.physX,y:n.physY}))),lineColor:"rgba(220,80,0,0.55)",lineWidth:1}]:[]}function ye(t,e,n,i,s,a,o,l,f,y,m,$,P,v="off",x,k,r){const c=e.width-o,p=e.height-o,h=a?p:c,M=a?c:p,T=B=>s==="softBin"?B.sbin:B.hbin,b=B=>it(B,x);if(e.partial){t.push({x:n,y:i,width:h,height:M,fill:ie,type:"stacked",metadata:e.metadata});return}if(e.edgeExcluded){t.push({x:n,y:i,width:h,height:M,fill:se,type:"stacked",metadata:e.metadata});return}if(f!==void 0&&(s==="hardBin"||s==="softBin")&&T(e)!==f){t.push({x:n,y:i,width:h,height:M,fill:Nt,type:"hardBin",metadata:e.metadata});return}if(r!==void 0&&s==="metadata"&&b(e)!==r){t.push({x:n,y:i,width:h,height:M,fill:Nt,type:"metadata",metadata:e.metadata});return}if(s==="value"){if(v==="test"){const U=G(e,m,P),st=U===void 0?w:U?ht:pt;t.push({x:n,y:i,width:h,height:M,fill:st,type:"value",metadata:e.metadata});return}const B=R(e,m),F=classifySpec(B,P);let W,Q;B===void 0?W=w:v==="spec"?W=F==="failLow"?Zt:F==="failHigh"?pt:ht:(W=l.forValue(y(B)),(F==="failLow"||F==="failHigh")&&(Q=F)),t.push({x:n,y:i,width:h,height:M,fill:W,type:"value",specMark:Q,metadata:e.metadata});return}if(s==="hardBin"||s==="softBin"){const B=T(e),F=B!=null?l.forBin(B):w;t.push({x:n,y:i,width:h,height:M,fill:F,type:s,metadata:e.metadata});return}if(s==="metadata"){const B=b(e),F=B!==void 0?k?.get(B)??w:w;t.push({x:n,y:i,width:h,height:M,fill:F,type:"metadata",metadata:e.metadata});return}const O=R(e,0),A=O!==void 0?l.forValue(y(O)):w;t.push({x:n,y:i,width:h,height:M,fill:A,type:"value",metadata:e.metadata})}function ge(t,e,n){const i=t.radius*.15,s=dt(e,i,0),a=dt(e,0,i),o=s.x+a.x>=0?-1:1,l=s.y+a.y>=0?-1:1,f={x:t.center.x+o*t.radius*.9,y:t.center.y+l*t.radius*.9},y={x:f.x+s.x,y:f.y+s.y},m={x:f.x+a.x,y:f.y+a.y};return n.push({x:y.x+s.x*.35,y:y.y+s.y*.35,text:"+X",fontSize:10,color:"#cc3300",align:"center",role:"indicator"},{x:m.x+a.x*.35,y:m.y+a.y*.35,text:"+Y",fontSize:10,color:"#0044cc",align:"center",role:"indicator"}),[{kind:"xy-indicator",..._([f,y]),lineColor:"#cc3300",lineWidth:2},{kind:"xy-indicator",..._([f,m]),lineColor:"#0044cc",lineWidth:2}]}export function buildView(t,e,n={},i){const s=n.reticles??[],{plotMode:a="value",showDieLabels:o=!1,showReticle:l=!1,showProbePath:f=!1,ringCount:y=4,showRingBoundaries:m=!1,showQuadrantBoundaries:$=!1,showXYIndicator:P=!1,dieGap:v=1,colorScheme:x="default",highlightBin:k,highlightMetadataValue:r,interactiveTransform:c,valueRange:p,testDefs:h,activeTest:M=0,activeMetadataKey:T,fallbackFormat:b="engineering",aggregationMethod:O,lotSize:A,dataAxisFlip:B,isLotStack:F=!1,logScale:W,colorbarRangeMode:Q="spec",passFailDisplay:U,showPartialDies:st=!0}=n,xt=U??"off",bt=i?.hbinDefs,Mt=i?.sbinDefs,St=i?.metadataFields,At=St?.find(u=>u.key===T),Et={x:(B?.x??!1)!==(c?.flipX??!1),y:(B?.y??!1)!==(c?.flipY??!1)},_t=bt?new Map(bt.map(u=>[u.bin,u])):null,wt=Mt?new Map(Mt.map(u=>[u.bin,u])):null,ot=a==="softBin"?wt:_t,rt=te(x),$t={forValue:le(rt.forValue),forBin:x==="custom"&&ot?u=>ot.get(u)?.color??rt.forBin(u):rt.forBin};let{testNumber:H}=resolveTestNumber(M,h);if(!h?.length&&a==="value"&&!e.some(d=>d.testValues&&H in d.testValues)){const d=e.reduce((g,L)=>{if(!L.testValues)return g;const S=Object.keys(L.testValues).map(Number),K=S.length?Math.min(...S):void 0;return K!==void 0&&(g===void 0||K<g)?K:g},void 0);d!==void 0&&(H=d)}let J;if(Array.isArray(p))J=p;else if(p){const{testNumber:u}=resolveTestNumber(p.test,h);J=u===H?p.range:void 0}const I=findTestDef(h,H),kt=a==="value"&&I!==void 0&&!mt(I),Wt=I!==void 0&&(I.limitLow!==void 0||I.limitHigh!==void 0);let C="off";a==="value"&&(kt?C="test":xt==="spec"&&Wt?C="spec":xt==="test"&&e.some(u=>G(u,H,I)!==void 0)&&(C="test"));const Bt=C==="spec"?"spec":Q;let V,N,vt=!1;if(J)[V,N]=J;else{const u=a==="stackedValues"||a==="stackedBins"||a==="stackedSoftBins",d=!u&&a==="value"&&Bt==="spec"&&I&&(I.limitLow!==void 0||I.limitHigh!==void 0);let g=1/0,L=-1/0,S=!0;for(const K of e){const q=u?R(K,0):R(K,H);q!==void 0&&(q<g&&(g=q),q>L&&(L=q),Number.isInteger(q)||(S=!1))}isFinite(g)||(S=!1),vt=S,d?(V=I.limitLow!==void 0?I.limitLow:isFinite(g)?g:0,N=I.limitHigh!==void 0?I.limitHigh:isFinite(L)?L:1):(V=isFinite(g)?g:0,N=isFinite(L)?L:1)}V===N&&(N=V+1);const Tt=kt?!1:W??I?.logScale??!1,It=Tt&&V>0&&N>0,jt=It;let Z;if(It){const u=Math.log10(V),d=Math.log10(N)-u;Z=g=>g<=0?0:Ct((Math.log10(g)-u)/d)}else Z=u=>Ct((u-V)/(N-V));const D=oe(t,B,c);let Lt=null;if(t.notch){const u=Ot(t.notch.type),d=dt(D.physicalToScreen,Math.cos(u)*t.radius,Math.sin(u)*t.radius),g=Math.hypot(d.x,d.y);g>0&&(Lt={x:d.x/g,y:d.y/g})}let j=1/0;for(let u=0;u<e.length;u++){const d=e[u];d.width<j&&(j=d.width),d.height<j&&(j=d.height)}const zt=j!==1/0?Math.min(v,j*.12):v,Pt=[],lt=[],Kt=D.bakedToScreen.a===1&&D.bakedToScreen.b===0&&D.bakedToScreen.c===0&&D.bakedToScreen.d===1&&D.bakedToScreen.e===0&&D.bakedToScreen.f===0;let E=null;if(!Kt){E=new Float64Array(e.length*2);for(let u=0;u<e.length;u++){const d=X(D.bakedToScreen,e[u].physX,e[u].physY);E[u*2]=d.x,E[u*2+1]=d.y}}const qt=Jt(D.gridToScreen),Xt=a==="hardBin"||a==="softBin"||a==="stackedBins"||a==="stackedSoftBins",Yt=a==="softBin"||a==="stackedSoftBins";let tt=Xt?new Map:void 0;const ct=a==="value"&&C==="spec"?{pass:0,failHigh:0,failLow:0}:void 0,ut=a==="value"&&C==="test"?{pass:0,fail:0}:void 0,Dt=a==="metadata";let et=Dt?new Map:void 0,nt=null;if(Dt&&T){const u=new Set;for(const g of e){if(g.partial||g.edgeExcluded)continue;const L=it(g,T);L!==void 0&&u.add(L)}const d=[...u].sort(ne);nt=new Map(d.map((g,L)=>[g,At?.values?.find(S=>S.value===g)?.color??ee(L)]))}for(let u=0;u<e.length;u++){const d=e[u],g=E?E[u*2]:d.physX,L=E?E[u*2+1]:d.physY;if(lt.push({x:g,y:L}),tt&&!d.partial&&!d.edgeExcluded){const S=Yt?d.sbin:d.hbin;S!=null&&tt.set(S,(tt.get(S)??0)+1)}if(ct&&!d.partial&&!d.edgeExcluded){const S=classifySpec(R(d,H),I);S&&ct[S]++}if(ut&&!d.partial&&!d.edgeExcluded){const S=G(d,H,I);S!==void 0&&ut[S?"pass":"fail"]++}if(et&&!d.partial&&!d.edgeExcluded){const S=it(d,T);S!==void 0&&et.set(S,(et.get(S)??0)+1)}d.partial&&!st||ye(Pt,d,g,L,a,qt,zt,$t,k,Z,H,ot,I,C,T,nt,r)}const Ft=o?generateTextOverlay(e,E,{plotMode:a,colorFns:$t,normalize:Z,activeTest:M,valueRange:[V,N],testDefs:h,fallbackFormat:b,passFailDisplay:C,activeMetadataKey:T,metadataColorMap:nt}):[],z=fe(t,D.physicalToScreen);m&&z.push(...de(t,D.physicalToScreen,y)),$&&z.push(...he(t,D.physicalToScreen,t.center.x,t.center.y)),l&&z.push(...pe(s,D.gridToScreen)),f&&z.push(...me(e)),P&&z.push(...ge(t,D.gridToScreen,Ft));const Gt=lt.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:Pt,hoverPoints:lt,texts:Ft,overlays:z,plotMode:a,colorScheme:x,metadata:t.metadata??null,dies:e,valueRange:[V,N],allIntegerValues:vt,testDefs:h,activeTest:M,logScale:jt,logScaleRequested:Tt,aggrMethod:O,lotSize:A,axisFlip:Et,dataAxisFlip:{x:B?.x??!1,y:B?.y??!1},gridToScreen:D.gridToScreen,rotation:((t.orientation+(c?.rotation??0))%360+360)%360,hasReticle:s.length>0,isLotStack:F,passFailDisplay:C,colorBySpec:C==="spec",colorbarRangeMode:Bt,waferCenter:t.center,waferRadius:t.radius,notchDir:Lt,binCounts:tt,metadataCounts:et,metadataColorMap:nt,activeMetadataKey:T,metadataFields:St,specCounts:ct,passFailCounts:ut,dieBounds:Gt}}export{getDieKey}from"../core/dies.js";
|
|
1
|
+
import{hasPosition as Ut}from"../core/dies.js";import{getReticleCell as Jt}from"../core/reticle.js";import{metadataCategoricalValue as Zt,metadataDisplayValue as te}from"../core/metadata.js";import{affineRotation as ft,affineMirror as Pt,affineCompose as at,affinePoint as X,affineVector as dt,affineSwapsAxes as ee}from"../core/transforms.js";import{contrastTextColor as Y,SPEC_PASS_FILL as ht,SPEC_FAIL_LOW as ne,SPEC_FAIL_HIGH as mt}from"./colorMap.js";import{getColorScheme as ae}from"./colorSchemes.js";import{getDieTestValue as R,getTestPassStatus as G,isParametricTest as pt}from"./buildWaferMap.js";import{fmt as yt,fmtColorbarAxis as gt,fmtAggregationMethod as Rt}from"./fmt.js";import{metadataValueColor as ie}from"./colorMap.js";import{compareNatural as se,clamp01 as Ct}from"../core/utils.js";import{prettyKey as Nt}from"../core/utils.js";export function resolveTestNumber(t,e){return e?.length?{testNumber:(findTestDef(e,t)??e[0]).testNumber}:{testNumber:t}}export function findTestDef(t,e){return t?.find(a=>a.testNumber===e)}export function getUniqueTestNumbers(t){return[...new Set(t.flatMap(e=>e.testValues?Object.keys(e.testValues).map(Number):[]))].sort((e,a)=>e-a)}const oe="#d3d6db",Ht="#e8e9ea",re="#eceef0",w="#d6d9dd";function it(t,e){return e?Zt(t.metadata?.[e]):void 0}export function classifySpec(t,e){return t===void 0?null:e?.limitLow!==void 0&&t<e.limitLow?"failLow":e?.limitHigh!==void 0&&t>e.limitHigh?"failHigh":"pass"}function le(t,e,a){const{x:i,y:s}=t.center,n=ft(t.orientation,i,s),o=at(Pt(e?.x??!1,e?.y??!1,i,s),ft(t.orientation,i,s)),r=at(Pt(a?.flipX??!1,a?.flipY??!1,i,s),ft(a?.rotation??0,i,s));return{bakedToScreen:r,physicalToScreen:at(r,n),gridToScreen:at(r,o)}}function E(t,e=!1){return{points:[t],closed:e}}function ce(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),u=s.length/2,y=n-a.x,p=o-a.y;return s.type==="bottom"&&p<-r?(o=a.y-r,n=a.x+Math.max(-u,Math.min(u,y))):s.type==="top"&&p>r?(o=a.y+r,n=a.x+Math.max(-u,Math.min(u,y))):s.type==="left"&&y<-r?(n=a.x-r,o=a.y+Math.max(-u,Math.min(u,p))):s.type==="right"&&y>r&&(n=a.x+r,o=a.y+Math.max(-u,Math.min(u,p))),{x:n,y:o}}function Ot(t,e){return t.map(e).join(" / ")}function ue(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 fe(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 de(t,e,a){if(e?.length){const i=e.flatMap(s=>{const n=s.testNumber;if(!pt(s)){const u=G(t,n,s);return u===void 0?[]:[{key:n,label:s.name,value:u?"Pass":"Fail"}]}const o=R(t,n);if(o===void 0)return[];const r=t.testPass?.[n]===!1?" <i>(recorded fail)</i>":"";return[{key:n,label:s.name,value:`${yt(o,s.unit,a)}${r}`}]});if(i.length)return i}return t.testValues&&Object.keys(t.testValues).length>0?Object.entries(t.testValues).map(([i,s])=>({key:Number(i),label:`Test ${i}`,value:yt(s,void 0,a)})):t.testPass&&Object.keys(t.testPass).length>0?Object.entries(t.testPass).map(([i,s])=>({key:Number(i),label:`Test ${i}`,value:s?"Pass":"Fail"})):[]}export function buildHoverText(t,e,a={}){const{testDefs:i,hbinDefs:s,sbinDefs:n,fallbackFormat:o,aggrMethod:r,lotSize:u,waferMeta:y,activeTest:p,reticleConfig:B,metadataFields:D}=a,v=s?new Map(s.map(l=>[l.bin,l])):null,L=n?new Map(n.map(l=>[l.bin,l])):null,m=[`Die (${t.x}, ${t.y})`];if(B&&Ut(t)){const l=Jt(t,B);m.push(`Reticle (${l.column}, ${l.row})`)}if(e==="stackedValues"){const l=R(t,0);if(l!==void 0){const f=i?.[0],d=f?.testNumber,g=f?.name??(d!=null?`Test ${d}`:"Value"),$=r?` (${r})`:"";m.push(`${g}${$}: ${yt(l,f?.unit,o)}`)}}else if(e==="stackedBins"||e==="stackedSoftBins"){const l=R(t,0),f=e==="stackedSoftBins"?t.sbin:t.hbin;if(l!==void 0){const d=e==="stackedSoftBins"?L:v,g=f!==void 0?d?.get(f)?.name?`${f} \xB7 ${d.get(f).name}`:`Bin ${f}`:"Bin";let $;if(r==="percent")$=`${l.toFixed(0)}%`;else{const H=u?` (${(l/u*100).toFixed(0)}% of lot)`:"";$=`${l}${H}`}const x=r?` [${Rt(r)}]`:"";m.push(`${g}: ${$}${x}`)}}else{const l=de(t,i,o);if(e==="value"&&l.length){const{testNumber:f}=resolveTestNumber(p??0,i),d=findTestDef(i,f),g=R(t,f);let $=l.findIndex(S=>S.key===f);$<0&&($=0);const x=l[$];let H=`<b>${x.label}: ${x.value}</b>`;if(x.key===f&&d){const S=classifySpec(g,d);(S==="failLow"||S==="failHigh")&&(H+=" <i>(out of spec)</i>")}m.push(H);const _=l.length-1;_>0&&m.push(`<i>+${_} more test${_===1?"":"s"}</i>`)}else if(l.length){const f=l.length;m.push(`<i>${f} test result${f===1?"":"s"} recorded</i>`)}if(t.hbin!==void 0||t.sbin!==void 0){const f=[];if(t.hbin!==void 0){const d=v?.get(t.hbin)?.name,g=d?`${t.hbin} \xB7 ${d}`:String(t.hbin);f.push(`HBin: ${g}`)}if(t.sbin!==void 0){const d=L?.get(t.sbin)?.name,g=d?`${t.sbin} \xB7 ${d}`:String(t.sbin);f.push(`SBin: ${g}`)}m.push(f.join(" \xB7 "))}}t.retestCount!==void 0&&m.push(`Retests: ${t.retestCount}`),t.siteNum!==void 0&&m.push(`Site: ${t.siteNum}`),t.partId!==void 0&&m.push(`Part ID: ${t.partId}`),t.partial&&m.push("<i>partial die</i>"),t.probeIndex!==void 0&&m.push(`Probe: #${t.probeIndex}`);const M={...y??{},...t.metadata??{}};for(const[l,f]of Object.entries(M)){const d=te(f);if(d===void 0)continue;const g=D?.find($=>$.key===l)?.label??Nt(l);m.push(`${g}: ${d}`)}return m.join("<br>")}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?Nt(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}=gt(o,n?.name,n?.unit,e),u=r||"Value",y=t.aggrMethod?` \xB7 ${Rt(t.aggrMethod)}`:"";return{primary:`${u}${y}`,secondary:i}}default:{const n=findTestDef(t.testDefs,t.activeTest),o=t.valueRange[1]||t.valueRange[0]||0,{axisLabel:r}=gt(o,n?.name,n?.unit,e),u=r||n?.name;if(t.passFailDisplay!=="off"){const y=n?.testNumber??t.activeTest,p=u?`${u} \xB7 #${y}`:`Test ${y}`,B=t.passFailDisplay==="spec"?"Spec pass/fail":n!==void 0&&!pt(n)?"Functional pass/fail":"Tester pass/fail";return{primary:p,secondary:B}}return{primary:u??`Test ${t.activeTest}`,secondary:""}}}}export function generateTextOverlay(t,e,a){const{plotMode:i,colorFns:s,normalize:n,activeTest:o,valueRange:r,testDefs:u,fallbackFormat:y,passFailDisplay:p="off",activeMetadataKey:B,metadataColorMap:D}=a,v=findTestDef(u,o),{tickFmt:L}=gt(r[1],v?.name,v?.unit,y),{testNumber:m}=resolveTestNumber(o,u);return t.flatMap((M,l)=>{let f="",d="#111111";if(i==="value"&&p==="test"){const x=G(M,m,v);if(x===void 0)return[];f=x?"P":"F",d=Y(x?ht:mt)}else if(i==="value"){const x=R(M,m);if(x===void 0)return[];f=Ot([x],L),d=Y(s.forValue(n(x)))}else if(i==="hardBin"||i==="softBin"){const x=i==="softBin"?M.sbin:M.hbin;if(x===void 0)return[];f=String(x),d=Y(s.forBin(x))}else if(i==="metadata"){const x=it(M,B);if(x===void 0)return[];f=x,d=Y(D?.get(f)??w)}else{const x=R(M,0);if(x===void 0)return[];f=Ot([x],L),d=Y(s.forValue(n(x)))}const g=e?e[l*2]:M.physX,$=e?e[l*2+1]:M.physY;return[{x:g,y:$,text:f,fontSize:fe(M,f),color:d,align:"center"}]})}function At(t){return t==="top"?Math.PI/2:t==="bottom"?-Math.PI/2:t==="left"?Math.PI:0}function he(t,e,a=720){const{center:i,radius:s,notch:n}=t,o=m=>X(e,m.x,m.y);if(!(n!==void 0&&t.diameter>150)){const m=[];for(let M=0;M<=a;M++){const l=2*Math.PI*M/a;m.push(o(ce(t,l)))}return[{kind:"wafer-boundary",...E(m,!0),lineColor:"#888888",lineWidth:1}]}const u=1.25,y=n.length,p=At(n.type),B=Math.atan2(y,s),D=s-u,v=[];let L=!1;for(let m=0;m<=a;m++){const M=2*Math.PI*m/a;let l=M-p;for(;l>Math.PI;)l-=2*Math.PI;for(;l<-Math.PI;)l+=2*Math.PI;if(Math.abs(l)<=B){if(!L){L=!0;const f={x:i.x+s*Math.cos(p-B),y:i.y+s*Math.sin(p-B)},d={x:i.x+D*Math.cos(p),y:i.y+D*Math.sin(p)},g={x:i.x+s*Math.cos(p+B),y:i.y+s*Math.sin(p+B)};v.push(o(f),o(d),o(g))}continue}v.push(o({x:i.x+s*Math.cos(M),y:i.y+s*Math.sin(M)}))}return[{kind:"wafer-boundary",...E(v,!0),lineColor:"#888888",lineWidth:1}]}function me(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,u=[];for(let y=0;y<=i;y++){const p=2*Math.PI*y/i,B={x:t.center.x+r*Math.cos(p),y:t.center.y+r*Math.sin(p)};u.push(X(e,B.x,B.y))}s.push({kind:"ring-boundary",...E(u,!0),lineColor:"rgba(255,255,255,0.7)",lineWidth:1.5})}return s}function pe(t,e,a,i){const{x:s,y:n}=t.center,o=t.radius,r=(M,l)=>X(e,M,l),u=a-s,y=Math.sqrt(Math.max(0,o*o-u*u)),p=r(a,n-y),B=r(a,n+y),D=i-n,v=Math.sqrt(Math.max(0,o*o-D*D)),L=r(s-v,i),m=r(s+v,i);return[{kind:"quadrant-boundary",...E([p,B]),lineColor:"rgba(255,255,255,0.7)",lineWidth:1.5},{kind:"quadrant-boundary",...E([L,m]),lineColor:"rgba(255,255,255,0.7)",lineWidth:1.5}]}function ye(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])=>X(e,o,r))],closed:!0,lineColor:"rgba(255,255,255,0.7)",lineWidth:1.5,fill:"rgba(0,0,0,0)"}})}function ge(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",...E(e.map(a=>({x:a.physX,y:a.physY}))),lineColor:"rgba(220,80,0,0.55)",lineWidth:1}]:[]}function xe(t,e,a,i,s,n,o,r,u,y,p,B,D,v="off",L,m,M){const l=e.width-o,f=e.height-o,d=n?f:l,g=n?l:f,$=S=>s==="softBin"?S.sbin:S.hbin,x=S=>it(S,L);if(e.partial){t.push({x:a,y:i,width:d,height:g,fill:oe,type:"stacked",metadata:e.metadata});return}if(e.edgeExcluded){t.push({x:a,y:i,width:d,height:g,fill:re,type:"stacked",metadata:e.metadata});return}if(u!==void 0&&(s==="hardBin"||s==="softBin")&&$(e)!==u){t.push({x:a,y:i,width:d,height:g,fill:Ht,type:"hardBin",metadata:e.metadata});return}if(M!==void 0&&s==="metadata"&&x(e)!==M){t.push({x:a,y:i,width:d,height:g,fill:Ht,type:"metadata",metadata:e.metadata});return}if(s==="value"){if(v==="test"){const U=G(e,p,D),st=U===void 0?w:U?ht:mt;t.push({x:a,y:i,width:d,height:g,fill:st,type:"value",metadata:e.metadata});return}const S=R(e,p),F=classifySpec(S,D);let W,Q;S===void 0?W=w:v==="spec"?W=F==="failLow"?ne:F==="failHigh"?mt:ht:(W=r.forValue(y(S)),(F==="failLow"||F==="failHigh")&&(Q=F)),t.push({x:a,y:i,width:d,height:g,fill:W,type:"value",specMark:Q,metadata:e.metadata});return}if(s==="hardBin"||s==="softBin"){const S=$(e),F=S!=null?r.forBin(S):w;t.push({x:a,y:i,width:d,height:g,fill:F,type:s,metadata:e.metadata});return}if(s==="metadata"){const S=x(e),F=S!==void 0?m?.get(S)??w:w;t.push({x:a,y:i,width:d,height:g,fill:F,type:"metadata",metadata:e.metadata});return}const H=R(e,0),_=H!==void 0?r.forValue(y(H)):w;t.push({x:a,y:i,width:d,height:g,fill:_,type:"value",metadata:e.metadata})}function be(t,e,a){const i=t.radius*.15,s=dt(e,i,0),n=dt(e,0,i),o=s.x+n.x>=0?-1:1,r=s.y+n.y>=0?-1:1,u={x:t.center.x+o*t.radius*.9,y:t.center.y+r*t.radius*.9},y={x:u.x+s.x,y:u.y+s.y},p={x:u.x+n.x,y:u.y+n.y};return a.push({x:y.x+s.x*.35,y:y.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",...E([u,y]),lineColor:"#cc3300",lineWidth:2},{kind:"xy-indicator",...E([u,p]),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:u=!1,ringCount:y=4,showRingBoundaries:p=!1,showQuadrantBoundaries:B=!1,showXYIndicator:D=!1,dieGap:v=1,colorScheme:L="default",highlightBin:m,highlightMetadataValue:M,interactiveTransform:l,valueRange:f,testDefs:d,activeTest:g=0,activeMetadataKey:$,fallbackFormat:x="engineering",aggregationMethod:H,lotSize:_,dataAxisFlip:S,isLotStack:F=!1,logScale:W,colorbarRangeMode:Q="spec",passFailDisplay:U,showPartialDies:st=!0}=a,xt=U??"off",bt=i?.hbinDefs,Mt=i?.sbinDefs,St=i?.metadataFields,Et=St?.find(c=>c.key===$),_t={x:(S?.x??!1)!==(l?.flipX??!1),y:(S?.y??!1)!==(l?.flipY??!1)},wt=bt?new Map(bt.map(c=>[c.bin,c])):null,Wt=Mt?new Map(Mt.map(c=>[c.bin,c])):null,ot=n==="softBin"?Wt:wt,rt=ae(L),$t={forValue:ue(rt.forValue),forBin:L==="custom"&&ot?c=>ot.get(c)?.color??rt.forBin(c):rt.forBin};let{testNumber:O}=resolveTestNumber(g,d);if(!d?.length&&n==="value"&&!e.some(h=>h.testValues&&O in h.testValues)){const h=e.reduce((b,I)=>{if(!I.testValues)return b;const k=Object.keys(I.testValues).map(Number),K=k.length?Math.min(...k):void 0;return K!==void 0&&(b===void 0||K<b)?K:b},void 0);h!==void 0&&(O=h)}let J;if(Array.isArray(f))J=f;else if(f){const{testNumber:c}=resolveTestNumber(f.test,d);J=c===O?f.range:void 0}const T=findTestDef(d,O),kt=n==="value"&&T!==void 0&&!pt(T),jt=T!==void 0&&(T.limitLow!==void 0||T.limitHigh!==void 0);let C="off";n==="value"&&(kt?C="test":xt==="spec"&&jt?C="spec":xt==="test"&&e.some(c=>G(c,O,T)!==void 0)&&(C="test"));const Bt=C==="spec"?"spec":Q;let P,N,vt=!1;if(J)[P,N]=J;else{const c=n==="stackedValues"||n==="stackedBins"||n==="stackedSoftBins",h=!c&&n==="value"&&Bt==="spec"&&T&&(T.limitLow!==void 0||T.limitHigh!==void 0);let b=1/0,I=-1/0,k=!0;for(const K of e){const q=c?R(K,0):R(K,O);q!==void 0&&(q<b&&(b=q),q>I&&(I=q),Number.isInteger(q)||(k=!1))}isFinite(b)||(k=!1),vt=k,h?(P=T.limitLow!==void 0?T.limitLow:isFinite(b)?b:0,N=T.limitHigh!==void 0?T.limitHigh:isFinite(I)?I:1):(P=isFinite(b)?b:0,N=isFinite(I)?I:1)}P===N&&(N=P+1);const Tt=kt?!1:W??T?.logScale??!1,It=Tt&&P>0&&N>0,zt=It;let Z;if(It){const c=Math.log10(P),h=Math.log10(N)-c;Z=b=>b<=0?0:Ct((Math.log10(b)-c)/h)}else Z=c=>Ct((c-P)/(N-P));const V=le(t,S,l);let Lt=null;if(t.notch){const c=At(t.notch.type),h=dt(V.physicalToScreen,Math.cos(c)*t.radius,Math.sin(c)*t.radius),b=Math.hypot(h.x,h.y);b>0&&(Lt={x:h.x/b,y:h.y/b})}let j=1/0;for(let c=0;c<e.length;c++){const h=e[c];h.width<j&&(j=h.width),h.height<j&&(j=h.height)}const Kt=j!==1/0?Math.min(v,j*.12):v,Vt=[],lt=[],qt=V.bakedToScreen.a===1&&V.bakedToScreen.b===0&&V.bakedToScreen.c===0&&V.bakedToScreen.d===1&&V.bakedToScreen.e===0&&V.bakedToScreen.f===0;let A=null;if(!qt){A=new Float64Array(e.length*2);for(let c=0;c<e.length;c++){const h=X(V.bakedToScreen,e[c].physX,e[c].physY);A[c*2]=h.x,A[c*2+1]=h.y}}const Xt=ee(V.gridToScreen),Yt=n==="hardBin"||n==="softBin"||n==="stackedBins"||n==="stackedSoftBins",Gt=n==="softBin"||n==="stackedSoftBins";let tt=Yt?new Map:void 0;const ct=n==="value"&&C==="spec"?{pass:0,failHigh:0,failLow:0}:void 0,ut=n==="value"&&C==="test"?{pass:0,fail:0}:void 0,Dt=n==="metadata";let et=Dt?new Map:void 0,nt=null;if(Dt&&$){const c=new Set;for(const b of e){if(b.partial||b.edgeExcluded)continue;const I=it(b,$);I!==void 0&&c.add(I)}const h=[...c].sort(se);nt=new Map(h.map((b,I)=>[b,Et?.values?.find(k=>k.value===b)?.color??ie(I)]))}for(let c=0;c<e.length;c++){const h=e[c],b=A?A[c*2]:h.physX,I=A?A[c*2+1]:h.physY;if(lt.push({x:b,y:I}),tt&&!h.partial&&!h.edgeExcluded){const k=Gt?h.sbin:h.hbin;k!=null&&tt.set(k,(tt.get(k)??0)+1)}if(ct&&!h.partial&&!h.edgeExcluded){const k=classifySpec(R(h,O),T);k&&ct[k]++}if(ut&&!h.partial&&!h.edgeExcluded){const k=G(h,O,T);k!==void 0&&ut[k?"pass":"fail"]++}if(et&&!h.partial&&!h.edgeExcluded){const k=it(h,$);k!==void 0&&et.set(k,(et.get(k)??0)+1)}h.partial&&!st||xe(Vt,h,b,I,n,Xt,Kt,$t,m,Z,O,ot,T,C,$,nt,M)}const Ft=o?generateTextOverlay(e,A,{plotMode:n,colorFns:$t,normalize:Z,activeTest:g,valueRange:[P,N],testDefs:d,fallbackFormat:x,passFailDisplay:C,activeMetadataKey:$,metadataColorMap:nt}):[],z=he(t,V.physicalToScreen);p&&z.push(...me(t,V.physicalToScreen,y)),B&&z.push(...pe(t,V.physicalToScreen,t.center.x,t.center.y)),r&&z.push(...ye(s,V.gridToScreen)),u&&z.push(...ge(e)),D&&z.push(...be(t,V.gridToScreen,Ft));const Qt=lt.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:Vt,hoverPoints:lt,texts:Ft,overlays:z,plotMode:n,colorScheme:L,metadata:t.metadata??null,dies:e,valueRange:[P,N],allIntegerValues:vt,testDefs:d,activeTest:g,logScale:zt,logScaleRequested:Tt,aggrMethod:H,lotSize:_,axisFlip:_t,dataAxisFlip:{x:S?.x??!1,y:S?.y??!1},gridToScreen:V.gridToScreen,rotation:((t.orientation+(l?.rotation??0))%360+360)%360,hasReticle:s.length>0,isLotStack:F,passFailDisplay:C,colorBySpec:C==="spec",colorbarRangeMode:Bt,waferCenter:t.center,waferRadius:t.radius,notchDir:Lt,binCounts:tt,metadataCounts:et,metadataColorMap:nt,activeMetadataKey:$,metadataFields:St,specCounts:ct,passFailCounts:ut,dieBounds:Qt}}export{getDieKey}from"../core/dies.js";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{compareNatural as
|
|
1
|
+
import{metadataDisplayValue as b}from"../core/metadata.js";import{compareNatural as C}from"../core/utils.js";export const FACET_NONE_VALUE="(none)";export{prettyKey}from"../core/utils.js";export const DEFAULT_FACET_CURATION={split:{label:"Split"},lot:{label:"Lot"},product:{label:"Product"},testProgram:{label:"Program"},temperature:{label:"Temperature"},testDate:{label:"Test date",date:!0},operator:{label:"Operator"},waferId:{label:"Wafer",facet:!1}};function y(e){const t=e.indexOf("T");return t>0?e.slice(0,t):e}export function facetValueOf(e,t,o=DEFAULT_FACET_CURATION){const l=b(e?.[t]);if(l!==void 0)return o[t]?.date?y(l):l}export function commonMetadata(e){if(!e.length)return{};const t=e[0].metadata??{},o={};for(const[l,r]of Object.entries(t))r==null||r===""||e.every(d=>(d.metadata??{})[l]===r)&&(o[l]=r);return o}export function buildFacetTable(e,t={}){const o={...DEFAULT_FACET_CURATION,...t.curation},l=t.facetableOnly??!0,r=new Set;for(const n of e)for(const c of Object.keys(n.metadata??{}))r.add(c);const m=[...Object.keys(o).filter(n=>r.has(n)),...[...r].filter(n=>!(n in o))],p=[];for(const n of m){const c=o[n];if(l&&c&&c.facet===!1)continue;const u=new Map;for(const f of e){const a=facetValueOf(f.metadata,n,o)??FACET_NONE_VALUE,i=u.get(a)??{waferCount:0,dieCount:0};i.waferCount+=1,i.dieCount+=f.dieCount??0,u.set(a,i)}if(u.size===0)continue;const s=Array.from(u,([f,a])=>({value:f,waferCount:a.waferCount,dieCount:a.dieCount}));s.sort((f,a)=>(f.value===FACET_NONE_VALUE?1:0)-(a.value===FACET_NONE_VALUE?1:0)||a.waferCount-f.waferCount||C(f.value,a.value)),p.push({key:n,label:c?.label??n,values:s,splittable:s.length>1})}return p}
|
|
@@ -10,6 +10,8 @@ export type { SummaryReportParams, LotSummaryReportParams } from './renderSummar
|
|
|
10
10
|
export { renderSummaryReportHtml, renderLotSummaryReportHtml } from './renderSummaryReport.js';
|
|
11
11
|
export { buildFacetTable, facetValueOf, FACET_NONE_VALUE, DEFAULT_FACET_CURATION } from './facets.js';
|
|
12
12
|
export type { FacetField, FacetValue, FacetCuration, FacetItem, BuildFacetTableOptions } from './facets.js';
|
|
13
|
+
export { resolveMetadataColumns, discoverDieMetadataKeys } from './metadataColumns.js';
|
|
14
|
+
export type { MetadataColumn, MetadataColumnScope, MetadataColumnSet, MetadataKeySelection, ResolveMetadataColumnsOptions, } from './metadataColumns.js';
|
|
13
15
|
export { buildCapabilityData } from './capability.js';
|
|
14
16
|
export type { CapabilityDatum, CapabilityItem } from './capability.js';
|
|
15
17
|
export { buildCorrelationMatrix, filterCorrelationMatrix } from './correlation.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export*from"./types.js";export*from"./regions.js";export*from"./analyzeWaferMap.js";export*from"./analyzeWaferLot.js";export*from"./filterFindings.js";export{classifyPattern}from"./patternClassification.js";export{renderFindingsReportHtml,openHtmlReport,setReportOpener}from"./renderFindingsReport.js";export{renderSummaryReportHtml,renderLotSummaryReportHtml}from"./renderSummaryReport.js";export{buildFacetTable,facetValueOf,FACET_NONE_VALUE,DEFAULT_FACET_CURATION}from"./facets.js";export{buildCapabilityData}from"./capability.js";export{buildCorrelationMatrix,filterCorrelationMatrix}from"./correlation.js";export{buildTestBoxplotData}from"./boxplot.js";export{buildTestHistogramData,buildTestHistogramSeries}from"./histogram.js";export{buildScatterData,buildScatterDataGrouped}from"./scatter.js";export{buildYieldData,buildYieldDataCombined}from"./yield.js";export{buildBinParetoData,buildBinClusterData}from"./binPareto.js";
|
|
1
|
+
export*from"./types.js";export*from"./regions.js";export*from"./analyzeWaferMap.js";export*from"./analyzeWaferLot.js";export*from"./filterFindings.js";export{classifyPattern}from"./patternClassification.js";export{renderFindingsReportHtml,openHtmlReport,setReportOpener}from"./renderFindingsReport.js";export{renderSummaryReportHtml,renderLotSummaryReportHtml}from"./renderSummaryReport.js";export{buildFacetTable,facetValueOf,FACET_NONE_VALUE,DEFAULT_FACET_CURATION}from"./facets.js";export{resolveMetadataColumns,discoverDieMetadataKeys}from"./metadataColumns.js";export{buildCapabilityData}from"./capability.js";export{buildCorrelationMatrix,filterCorrelationMatrix}from"./correlation.js";export{buildTestBoxplotData}from"./boxplot.js";export{buildTestHistogramData,buildTestHistogramSeries}from"./histogram.js";export{buildScatterData,buildScatterDataGrouped}from"./scatter.js";export{buildYieldData,buildYieldDataCombined}from"./yield.js";export{buildBinParetoData,buildBinClusterData}from"./binPareto.js";
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import type { Die } from '../core/dies.js';
|
|
2
|
+
import type { WaferMetadata } from '../core/metadata.js';
|
|
3
|
+
import type { MetadataFieldDef } from '../renderer/buildWaferMap.js';
|
|
4
|
+
/** Where a column's value comes from. A `'wafer'` column is constant down
|
|
5
|
+
* every row; a `'die'` column genuinely varies die to die. */
|
|
6
|
+
export type MetadataColumnScope = 'die' | 'wafer';
|
|
7
|
+
/** How metadata keys are chosen. `'auto'` = every key present in the data. */
|
|
8
|
+
export type MetadataKeySelection = 'auto' | 'none' | string[];
|
|
9
|
+
export interface MetadataColumn {
|
|
10
|
+
/** The metadata key this column reads. */
|
|
11
|
+
key: string;
|
|
12
|
+
/** Column header, already de-duplicated against every other column. */
|
|
13
|
+
label: string;
|
|
14
|
+
scope: MetadataColumnScope;
|
|
15
|
+
/** Value for one die. Always a string — `''` for absent, never `'undefined'`. */
|
|
16
|
+
get: (die: Die) => string;
|
|
17
|
+
/** Wafer scope only: the die-independent value, for exports with no die rows. */
|
|
18
|
+
constant?: string;
|
|
19
|
+
/** True when the column belongs in the CSV but not the on-screen table. */
|
|
20
|
+
csvOnly: boolean;
|
|
21
|
+
}
|
|
22
|
+
export interface ResolveMetadataColumnsOptions {
|
|
23
|
+
/** Population to discover die keys from. Scanned in full — never sampled. */
|
|
24
|
+
dies?: Die[];
|
|
25
|
+
waferMetadata?: WaferMetadata;
|
|
26
|
+
/** Supplies label and ordering for keys the host has already declared. */
|
|
27
|
+
metadataFields?: MetadataFieldDef[];
|
|
28
|
+
/** Die-level key selection. Default `'auto'`. */
|
|
29
|
+
dieKeys?: MetadataKeySelection;
|
|
30
|
+
/** Wafer-level key selection. Default `'auto'`. */
|
|
31
|
+
waferKeys?: MetadataKeySelection;
|
|
32
|
+
/** Where wafer-level columns appear. Default `'csv'`. */
|
|
33
|
+
waferPlacement?: 'csv' | 'both' | 'none';
|
|
34
|
+
/** Headers already taken by built-in columns, for collision resolution. */
|
|
35
|
+
reservedLabels?: string[];
|
|
36
|
+
/**
|
|
37
|
+
* Cap on auto-discovered die keys. Default 64. Exceeding it is a data shape
|
|
38
|
+
* no table can usefully show; the excess is reported in `truncatedKeys` so
|
|
39
|
+
* the caller can state the omission rather than hide it.
|
|
40
|
+
*/
|
|
41
|
+
maxDieKeys?: number;
|
|
42
|
+
}
|
|
43
|
+
export interface MetadataColumnSet {
|
|
44
|
+
columns: MetadataColumn[];
|
|
45
|
+
/** Die keys dropped by `maxDieKeys`, in the order they would have appeared. */
|
|
46
|
+
truncatedKeys: string[];
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Union of `die.metadata` keys across the whole population, deterministically
|
|
50
|
+
* ordered: keys named in `metadataFields` first (declaration order), then the
|
|
51
|
+
* rest by `compareNatural`. Never object insertion order — two runs over the
|
|
52
|
+
* same data must produce byte-identical exports.
|
|
53
|
+
*
|
|
54
|
+
* The scan is deliberately exhaustive rather than sampled: a key appearing
|
|
55
|
+
* only on late dies would otherwise vanish from the export with no signal.
|
|
56
|
+
*/
|
|
57
|
+
export declare function discoverDieMetadataKeys(dies: Die[], metadataFields?: MetadataFieldDef[], limit?: number): {
|
|
58
|
+
keys: string[];
|
|
59
|
+
truncated: string[];
|
|
60
|
+
};
|
|
61
|
+
/**
|
|
62
|
+
* Build the metadata column set for a population.
|
|
63
|
+
*
|
|
64
|
+
* A key present on both a die and its wafer yields exactly ONE column, scope
|
|
65
|
+
* `'die'`, carrying the die value — the same shadowing rule the hover tooltip
|
|
66
|
+
* applies with `{ ...waferMeta, ...die.metadata }`. Two columns for one key
|
|
67
|
+
* could otherwise disagree in the same row.
|
|
68
|
+
*/
|
|
69
|
+
export declare function resolveMetadataColumns(o: ResolveMetadataColumnsOptions): MetadataColumnSet;
|
|
70
|
+
//# sourceMappingURL=metadataColumns.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{metadataDisplayValue as k}from"../core/metadata.js";import{compareNatural as w,prettyKey as h}from"../core/utils.js";import{DEFAULT_FACET_CURATION as v}from"./facets.js";const K=64;export function discoverDieMetadataKeys(e,n,c=K){const d=new Set;for(const a of e)if(a.metadata)for(const i of Object.keys(a.metadata))d.add(i);const t=[];for(const a of n??[])d.has(a.key)&&!t.includes(a.key)&&t.push(a.key);const y=[...d].filter(a=>!t.includes(a)).sort(w),r=[...t,...y];return{keys:r.slice(0,c),truncated:r.slice(c)}}function A(e){const n=Object.keys(e).filter(t=>k(e[t])!==void 0),c=Object.keys(v).filter(t=>n.includes(t)),d=n.filter(t=>!c.includes(t)).sort(w);return[...c,...d]}const b=(e,n)=>e==="none"?[]:Array.isArray(e)?e.filter(c=>n.includes(c)):n;export function resolveMetadataColumns(e){const n=e.waferMetadata??{},c=e.waferPlacement??"csv",d=discoverDieMetadataKeys(e.dies??[],e.metadataFields,e.maxDieKeys??K),t=b(e.dieKeys,d.keys),y=c==="none"?[]:b(e.waferKeys,A(n)).filter(s=>!t.includes(s)),r=new Set(e.reservedLabels??[]),a=(s,o)=>{const f=e.metadataFields?.find(u=>u.key===s)?.label??h(s);if(!r.has(f))return r.add(f),f;const m=o==="die"?`${f} (metadata)`:`${f} (wafer metadata)`;if(!r.has(m))return r.add(m),m;const l=`${f} (${s})`;if(!r.has(l))return r.add(l),l;for(let u=2;;u++){const p=`${l} (${u})`;if(!r.has(p))return r.add(p),p}},i=[];for(const s of t)i.push({key:s,label:a(s,"die"),scope:"die",get:o=>k(o.metadata?.[s])??"",csvOnly:!1});for(const s of y){const o=k(n[s])??"";i.push({key:s,label:a(s,"wafer"),scope:"wafer",get:()=>o,constant:o,csvOnly:c==="csv"})}return{columns:i,truncatedKeys:d.truncated}}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import{formatFindingDelta as b,formatFindingCoverage as
|
|
2
|
-
<td class="tight">${
|
|
3
|
-
<td class="tight">${
|
|
4
|
-
<td>${
|
|
5
|
-
<td class="numeric">${
|
|
6
|
-
<td class="numeric">${
|
|
1
|
+
import{formatFindingDelta as b,formatFindingCoverage as g,formatFindingTooltip as v,escHtml as a,buildMetadataRows as c,renderDefinitionList as p,renderSection as f,renderSeverityBadge as y,reportStyles as R}from"./reportHtml.js";import{buildFindingsNarrative as $}from"./findingsNarrative.js";import{plainBinTerms as u}from"../renderer/fmt.js";function S(e,n){if(e.level==="lot"){const o=e,l=o.perWafer.map(r=>r.summary.wafer?.wafer??r.summary.wafer?.waferId).filter(r=>r!=null).map(String).join(", ");return p([...c(o.perWafer.map(r=>({metadata:r.summary.wafer}))),...l?[{label:"Wafers",value:l}]:[],{label:"Wafer count",value:String(o.stats.waferCount)},{label:"Generated",value:n}])}const t=e,i=t.stats.yieldPercent!==null?`${t.stats.yieldPercent.toFixed(1)}%`:"N/A";return p([...c([{metadata:t.wafer}]),{label:"Total dies",value:String(t.stats.totalDies)},{label:"Analysed dies",value:String(t.stats.analyzedDies)},{label:"Yield",value:i},{label:"Generated",value:n}])}function F(e,n){return e.length?e.map(t=>`<tr title="${a(v(t))}">
|
|
2
|
+
<td class="tight">${y(t.severity)}</td>
|
|
3
|
+
<td class="tight">${a(t.comparison.left)}</td>
|
|
4
|
+
<td>${a(u(t.variable.label))}</td>
|
|
5
|
+
<td class="numeric">${a(b(t))}</td>
|
|
6
|
+
<td class="numeric">${a(g(t,n))}</td>
|
|
7
7
|
</tr>`).join(`
|
|
8
|
-
`):'<tr><td colspan="5" class="no-data">No significant findings</td></tr>'}function
|
|
8
|
+
`):'<tr><td colspan="5" class="no-data">No significant findings</td></tr>'}function T(e,n){return`<table class="report-table findings-table compact">
|
|
9
9
|
<thead>
|
|
10
10
|
<tr>
|
|
11
11
|
<th>Severity</th>
|
|
@@ -16,26 +16,26 @@ import{formatFindingDelta as b,formatFindingCoverage as w,formatFindingTooltip a
|
|
|
16
16
|
</tr>
|
|
17
17
|
</thead>
|
|
18
18
|
<tbody>
|
|
19
|
-
${F(
|
|
19
|
+
${F(e,n)}
|
|
20
20
|
</tbody>
|
|
21
|
-
</table>`}export function renderFindingsReportHtml(
|
|
22
|
-
`:"",
|
|
21
|
+
</table>`}export function renderFindingsReportHtml(e,n={}){const t=e.level==="lot",i=n.title??(t?"Lot Findings Report":"Wafer Findings Report"),o=new Date().toLocaleString(),l=new Set(e.findings.flatMap(s=>s.absorbedIds??[])),r=e.findings.filter(s=>!l.has(s.id)),m=e.level==="lot"?e.stats.waferCount:void 0,d=u($(r)??""),h=d?`<p class="findings-narrative">${a(d)}</p>
|
|
22
|
+
`:"",w=[f("Summary",S(e,o)),f("Findings",h+T(r,m))].join(`
|
|
23
23
|
`);return`<!DOCTYPE html>
|
|
24
24
|
<html lang="en">
|
|
25
25
|
<head>
|
|
26
26
|
<meta charset="UTF-8">
|
|
27
|
-
<title>${
|
|
27
|
+
<title>${a(i)}</title>
|
|
28
28
|
<style>
|
|
29
|
-
${
|
|
29
|
+
${R()}
|
|
30
30
|
</style>
|
|
31
31
|
</head>
|
|
32
32
|
<body>
|
|
33
33
|
<main class="report">
|
|
34
34
|
<header class="report-header">
|
|
35
|
-
<h1>${
|
|
36
|
-
<p class="report-subtitle">Generated ${
|
|
35
|
+
<h1>${a(i)}</h1>
|
|
36
|
+
<p class="report-subtitle">Generated ${a(o)}</p>
|
|
37
37
|
</header>
|
|
38
|
-
${
|
|
38
|
+
${w}
|
|
39
39
|
</main>
|
|
40
40
|
</body>
|
|
41
|
-
</html>`}export function openHtmlReport(
|
|
41
|
+
</html>`}export function openHtmlReport(e){if(typeof window.__openHtmlReport=="function"){window.__openHtmlReport(e);return}const n=URL.createObjectURL(new Blob([e],{type:"text/html"})),t=window.open(n,"_blank");setTimeout(()=>URL.revokeObjectURL(n),3e4),t||console.warn("wmap: window.open() returned null, so the report could not be shown. If this host cannot use window.open (e.g. Tauri, or popups are blocked), call setReportOpener() to supply your own opener.")}export function setReportOpener(e){window.__openHtmlReport=e}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wafertools/wafermap",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.24.1",
|
|
4
4
|
"description": "Interactive wafer map visualization and yield analysis for semiconductor test data. Hard bins, soft bins, test values, retests, edge exclusion, reticle overlays, spatial statistics, failure clustering, and lot-level trend analysis — pure ES modules, no runtime dependencies, no server required.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -71,19 +71,24 @@
|
|
|
71
71
|
"node": ">=18"
|
|
72
72
|
},
|
|
73
73
|
"scripts": {
|
|
74
|
+
"help": "node scripts/help.mjs",
|
|
74
75
|
"build": "node scripts/build-user-guide.mjs && node scripts/sync-icons.mjs && node scripts/sync-version.mjs && rm -rf dist && tsc -p tsconfig.build.json",
|
|
75
76
|
"build:guide": "node scripts/build-user-guide.mjs",
|
|
76
77
|
"clean": "rm -rf dist",
|
|
77
|
-
"check": "node scripts/sync-version.mjs && tsc --noEmit -p tsconfig.json && node scripts/check-examples-manifest.mjs && node scripts/build-agents-page.mjs --check && node scripts/check-agents-guide.mjs && node scripts/check-toolbar-docs.mjs",
|
|
78
|
+
"check": "node scripts/build-user-guide.mjs && node scripts/sync-version.mjs && tsc --noEmit -p tsconfig.json && node scripts/check-examples-manifest.mjs && node scripts/build-agents-page.mjs --check && node scripts/check-agents-guide.mjs && node scripts/check-toolbar-docs.mjs && node scripts/check-changelog.mjs && node scripts/help.mjs --check",
|
|
78
79
|
"build:examples-index": "node scripts/build-examples-index.mjs",
|
|
79
80
|
"check:drift": "node scripts/check-drift.mjs",
|
|
80
81
|
"dev": "rm -rf docs/dist && cp -r dist docs/dist && ./.venv/bin/zensical serve --dev-addr localhost:8001",
|
|
81
82
|
"test": "npm run build && node --test tests/*.test.mjs",
|
|
82
|
-
"build:site": "rm -rf docs/dist && cp -r dist docs/ && ${ZENSICAL:-.venv/bin/zensical} build --clean && cp -r docs/data site/data && rm -rf docs/dist && node scripts/bundle-docs.mjs && node scripts/build-examples-archive.mjs",
|
|
83
|
+
"build:site": "rm -rf docs/dist && cp -r dist docs/ && ${ZENSICAL:-.venv/bin/zensical} build --clean && cp -r docs/data site/data && cp llms.txt site/llms.txt && rm -rf docs/dist && node scripts/bundle-docs.mjs && node scripts/build-examples-archive.mjs",
|
|
83
84
|
"preview:site": "npm run build:site && echo \"\\nServing bundled site at http://localhost:8002/examples/ — Ctrl-C to stop\\n\" && cd site && python3 -m http.server 8002",
|
|
84
85
|
"prepack": "npm run build && node scripts/minify-dist.mjs",
|
|
85
86
|
"pack:check": "npm pack --dry-run",
|
|
86
87
|
"prepublishOnly": "npm test",
|
|
88
|
+
"verify": "npm run check && npm test",
|
|
89
|
+
"preversion": "npm run verify",
|
|
90
|
+
"version": "node scripts/check-changelog.mjs",
|
|
91
|
+
"postversion": "echo \"\\nTagged v$npm_package_version. Next:\\n git push && git push origin v$npm_package_version\\n npm publish\\n\"",
|
|
87
92
|
"postpublish": "echo \"\\ndist/ was minified for publish — run 'npm run build' to restore the readable dev build.\\n\"",
|
|
88
93
|
"screenshots": "node scripts/capture-screenshots.mjs",
|
|
89
94
|
"screenshots:list": "node scripts/capture-screenshots.mjs --list",
|