UNPKG

@nuralogix.ai/anura-web-core-sdk

Version:

Anura Web Core SDK

739 lines (676 loc) 96.1 kB
var wi="0.1.0-alpha.20",Se=(e=>(e.DELETE="DELETE",e.GET="GET",e.PATCH="PATCH",e.POST="POST",e.PUT="PUT",e.CONNECT="CONNECT",e))(Se||{}),xi=(e=>(e.OK="200",e))(xi||{}),Ri=(e=>(e.NotModified="304",e))(Ri||{}),ki=(e=>(e.BadRequest="400",e.Unauthorized="401",e.Forbidden="403",e.NotFound="404",e.NotAcceptable="406",e.RequestTimeout="408",e.TooManyRequests="429",e))(ki||{}),yi=(e=>(e.InternalServerError="500",e.BadGateway="502",e.ServiceUnavailable="503",e.GatewayTimeout="504",e))(yi||{}),vi=(e=>(e.UNSUPPORTED_RESPONSE_CONTENT_TYPE="UNSUPPORTED_RESPONSE_CONTENT_TYPE",e.UNEXPECTED_ERROR="UNEXPECTED_ERROR",e.SYNTAX_ERROR="SYNTAX_ERROR",e.UNKNOWN="UNKNOWN",e))(vi||{}),ae=(e=>(e.ON_BEFORE_REST_CALL_ERROR="ON_BEFORE_REST_CALL_ERROR",e))(ae||{});const kt=typeof DedicatedWorkerGlobalScope<"u",ot=async({baseUrl:e,urlFragment:t,method:i,headers:a,data:n},o)=>{const s=`${e}/${t}`;let h={Code:"",Message:""};try{const d=await fetch(s,{method:i,cache:"no-store",credentials:"include",headers:a,...n!=null&&{body:JSON.stringify(n)}}),m=d.headers.get("content-type"),E=d.status.toString();h={Code:"UNSUPPORTED_RESPONSE_CONTENT_TYPE",Message:"UNSUPPORTED_RESPONSE_CONTENT_TYPE"},E==="503"&&(h={Code:"SERVICE_UNAVAILABLE",Message:"Service unavailable"}),E==="429"&&(h={Code:"TOO_MANY_REQUESTS",Message:"Too many requests"}),m!=null&&(m.startsWith("application/json;")&&(h=await d.json()),m.startsWith("text/plain;")&&(h={data:await d.text()}));let R=d.headers;if(kt){const y={};d.headers.forEach((F,N)=>{y[N]=F}),R=y}try{o(E,E==="200"?void 0:h)}catch(y){console.error("Error running onAfterRESTCall callback",y)}return{status:E,headers:R,body:h}}catch(d){return d instanceof SyntaxError?h={Code:"SYNTAX_ERROR",Message:"SYNTAX_ERROR"}:h={Code:"UNEXPECTED_ERROR",Message:"UNEXPECTED_ERROR"},{status:"UNKNOWN",headers:kt?{}:new Headers,body:h}}},Pe=()=>new Headers({"Content-Type":"application/json"});var Ci=Object.defineProperty,Ai=(e,t,i)=>t in e?Ci(e,t,{enumerable:!0,configurable:!0,writable:!0,value:i}):e[t]=i,_i=(e,t,i)=>Ai(e,t+"",i);class ct{constructor(t){_i(this,"parent"),this.parent=t}async onBeforeRESTCall(){try{return this.parent.onBeforeRESTCall(),{Code:"SUCCESS",Message:"SUCCESS"}}catch{return{Code:ae.ON_BEFORE_REST_CALL_ERROR,Message:ae.ON_BEFORE_REST_CALL_ERROR}}}getDefaults(t){return{baseUrl:JSON.parse(this.parent.getUrl()).http.slice(0,-1),method:Se[t],headers:Pe()}}getHeaderWithDeviceToken(){const t=Pe();return t.set("Authorization",`Bearer ${this.parent.getSession().deviceToken}`),t}getHeaderWithUserToken(){const t=Pe();return t.set("Authorization",`Bearer ${this.parent.getSession().userToken}`),t}getHeaderWithSessionEnabled(){const t=Pe();return t.set("x-nura-session","true"),t}}class Oi extends ct{async create(t){const{Code:i,Message:a}=await this.onBeforeRESTCall();if(i===ae.ON_BEFORE_REST_CALL_ERROR)return{status:i,body:{Code:i,Message:a},headers:new Headers};const{status:n,body:o,headers:s}=await ot({...this.getDefaults(Se.POST),headers:this.getHeaderWithUserToken(),urlFragment:"measurements",data:t},this.parent.onAfterRESTCall);return n==="200"&&this.parent.setSession({lastMeasurementId:o.ID}),{status:n,body:o,headers:s}}}class Di extends ct{async retrieveSdkConfigData(t){const{Code:i,Message:a}=await this.onBeforeRESTCall();if(i===ae.ON_BEFORE_REST_CALL_ERROR)return{status:i,body:{Code:i,Message:a},headers:new Headers};const{status:n,body:o,headers:s}=await ot({...this.getDefaults(Se.POST),headers:this.getHeaderWithUserToken(),urlFragment:"studies/sdkconfig",data:t},this.parent.onAfterRESTCall);return n==="200"&&this.parent.setSession({studyCfgData:o.ConfigFile,studyCfgHash:o.MD5Hash}),{status:n,body:o,headers:s}}}class Ii extends ct{async renew(t,i){const{Code:a,Message:n}=await this.onBeforeRESTCall();if(a===ae.ON_BEFORE_REST_CALL_ERROR)return{status:a,body:{Code:a,Message:n},headers:new Headers};const{status:o,body:s,headers:h}=await ot({...this.getDefaults(Se.POST),headers:this.getHeaderWithDeviceToken(),urlFragment:"auths/renew",data:t},this.parent.onAfterRESTCall);if(o==="200"){const d=s.Token,m=s.RefreshToken,E={...i==="user"&&{userToken:d,userRefreshToken:m},...i==="device"&&{deviceToken:d,deviceRefreshToken:m}};this.parent.setSession(E)}return{status:o,body:s,headers:h}}}var Li=Object.defineProperty,yt=e=>{throw TypeError(e)},Ni=(e,t,i)=>t in e?Li(e,t,{enumerable:!0,configurable:!0,writable:!0,value:i}):e[t]=i,lt=(e,t,i)=>Ni(e,typeof t!="symbol"?t+"":t,i),vt=(e,t,i)=>t.has(e)||yt("Cannot "+i),ht=(e,t,i)=>(vt(e,t,"read from private field"),i?i.call(e):t.get(e)),Ct=(e,t,i)=>t.has(e)?yt("Cannot add the same private member more than once"):t instanceof WeakSet?t.add(e):t.set(e,i),$e=(e,t,i,a)=>(vt(e,t,"write to private field"),t.set(e,i),i),we,se;let Fi=class{constructor(t){Ct(this,we,{http:new URL("https://api.deepaffex.ai")}),Ct(this,se),lt(this,"onBeforeRESTCall"),lt(this,"onAfterRESTCall"),lt(this,"http"),$e(this,se,{deviceToken:"",deviceRefreshToken:"",userToken:"",userRefreshToken:"",deviceId:"",roleId:"",userId:"",selectedStudy:"",lastMeasurementId:"",studyCfgHash:"",studyCfgData:""}),this.http={measurements:new Oi(this),studies:new Di(this),auths:new Ii(this)},this.onBeforeRESTCall=t?.onBeforeRESTCall?t.onBeforeRESTCall:()=>{},this.onAfterRESTCall=t?.onAfterRESTCall?t.onAfterRESTCall:()=>{},t?.url!=null&&$e(this,we,t.url)}static new(t){return new this(t??void 0)}getUrl(){return JSON.stringify(ht(this,we))}setUrl(t){$e(this,we,{http:new URL(t)})}getSession(){return ht(this,se)}setSession(t){const i=["deviceToken","deviceRefreshToken","userToken","userRefreshToken","deviceId","userId","roleId","selectedStudy","lastMeasurementId","studyCfgHash","studyCfgData"];Object.keys(t).every(a=>i.includes(a)&&typeof t[a]=="string")&&$e(this,se,{...ht(this,se),...t})}};we=new WeakMap,se=new WeakMap;const Mi=e=>Fi.new(e);var Wi=Object.defineProperty,At=e=>{throw TypeError(e)},Pi=(e,t,i)=>t in e?Wi(e,t,{enumerable:!0,configurable:!0,writable:!0,value:i}):e[t]=i,$i=(e,t,i)=>Pi(e,t+"",i),Ui=(e,t,i)=>t.has(e)||At("Cannot "+i),Bi=(e,t,i)=>t.has(e)?At("Cannot add the same private member more than once"):t instanceof WeakSet?t.add(e):t.set(e,i),dt=(e,t,i)=>(Ui(e,t,"access private method"),i),xe,mt,_t,Gi=(e=>(e.BYTES_DOWNLOADED="bytesDownloaded",e.DOWNLOAD_ERROR="downloadedError",e))(Gi||{});let Ot=e=>new Int8Array(e),Hi=class extends EventTarget{constructor(){super(...arguments),Bi(this,xe),$i(this,"canDecompress",!1)}static init(){return new this}dispatch(t,i){this.dispatchEvent(new CustomEvent(t,{detail:i}))}getBytesDownloadedEvent(t,i,a,n){return new CustomEvent("bytesDownloaded",{detail:{bytes:t,uncompressedSize:i,url:a,done:n}})}getBytesDownloadErrorEvent(t,i){return new CustomEvent("downloadedError",{detail:{url:t,error:i}})}decompressBrotli(t){const i=atob(t),a=new Uint8Array(i.length);for(let o=0;o<i.length;o++)a[o]=i.charCodeAt(o);const n=new Int8Array(a.length);for(let o=0;o<a.length;o++)n[o]=a[o]>=128?a[o]-256:a[o];return Ot(n).buffer}async fetchAsset(t,i,a,n,o){const s=i+a,h=t.find(m=>m.file===a).uncompressedSize,d=(m,E,R,y)=>dt(this,xe,mt).call(this,this.getBytesDownloadedEvent(m,E,R,y));try{const m=(await fetch(s)).body?.getReader();if(!m)throw new Error("Failed to get reader from response body.");let E=0;const R=new ReadableStream({async start(F){await N();async function N(){const{done:M,value:be}=await m.read();if(M){F.close(),d(E,h,s,!0);return}be&&(F.enqueue(be),E+=be.length,d(E,h,s,!1)),await N()}}}),y=new Response(R);if(n){const F=await y.json(),{base64EncodedValue:N}=F;if(o==="gzip"){await dt(this,xe,_t).call(this,N);return}return this.decompressBrotli(N)}else return await y.arrayBuffer()}catch(m){dt(this,xe,mt).call(this,this.getBytesDownloadErrorEvent(s,m))}}};xe=new WeakSet,mt=function(e){this.dispatchEvent(e)},_t=async function(e){const t=Uint8Array.from(atob(e),h=>h.charCodeAt(0)),i=new Blob([t]).stream().pipeThrough(new DecompressionStream("gzip")),a=await new Response(i).arrayBuffer(),n=new TextDecoder().decode(new Uint8Array(a)),o=new Blob([n],{type:"application/javascript"}),s=URL.createObjectURL(o);Ot=(await import(s)).BrotliDecode,this.canDecompress=!0,URL.revokeObjectURL(s)};var Dt=e=>{throw TypeError(e)},It=(e,t,i)=>t.has(e)||Dt("Cannot "+i),v=(e,t,i)=>(It(e,t,"read from private field"),i?i.call(e):t.get(e)),Ue=(e,t,i)=>t.has(e)?Dt("Cannot add the same private member more than once"):t instanceof WeakSet?t.add(e):t.set(e,i),Ki=(e,t,i,a)=>(It(e,t,"write to private field"),t.set(e,i),i),j,q,V,B;const Be=class A{constructor(t){Ue(this,B),Ki(this,B,new Int32Array(t))}lock(){for(;;){if(Atomics.compareExchange(v(this,B),v(A,V),v(A,q),v(A,j))==v(A,q))return;Atomics.wait(v(this,B),v(A,V),v(A,j))}}unlock(){if(Atomics.compareExchange(v(this,B),v(A,V),v(A,j),v(A,q))!=v(A,j))throw new Error("Tried to unlock while not holding the mutex");Atomics.notify(v(this,B),v(A,V))}AsyncLock(t){const i=this;async function a(){for(;;){if(Atomics.compareExchange(v(i,B),v(A,V),v(A,q),v(A,j))==v(A,q)){t(),i.unlock();return}await Atomics.waitAsync(v(i,B),v(A,V),v(A,j)).value}}a()}};j=new WeakMap,q=new WeakMap,V=new WeakMap,B=new WeakMap,Ue(Be,j,1),Ue(Be,q,0),Ue(Be,V,0);let Lt=Be;var ji=Object.defineProperty,Nt=e=>{throw TypeError(e)},Vi=(e,t,i)=>t in e?ji(e,t,{enumerable:!0,configurable:!0,writable:!0,value:i}):e[t]=i,Ge=(e,t,i)=>Vi(e,typeof t!="symbol"?t+"":t,i),Yi=(e,t,i)=>t.has(e)||Nt("Cannot "+i),G=(e,t,i)=>(Yi(e,t,"read from private field"),i?i.call(e):t.get(e)),ut=(e,t,i)=>t.has(e)?Nt("Cannot add the same private member more than once"):t instanceof WeakSet?t.add(e):t.set(e,i),ne,Re,He;const Xi=1024*1024*4;let zi=class{constructor(){Ge(this,"sharedBuffer",new SharedArrayBuffer(Xi)),ut(this,ne,new Uint8Array(this.sharedBuffer,Int32Array.BYTES_PER_ELEMENT)),ut(this,Re,new Int32Array(this.sharedBuffer,0,1)),Ge(this,"sharedMutexBuffer",new SharedArrayBuffer(Int32Array.BYTES_PER_ELEMENT)),ut(this,He,new Lt(this.sharedMutexBuffer)),Ge(this,"version",{webSDK:"",extractionLib:{version:"",sdkId:""},faceTracker:""}),Ge(this,"debugLogs",[])}appendLog(t){const i=new TextEncoder().encode(JSON.stringify(t)),a=i.length;G(this,He).AsyncLock(()=>{const n=G(this,Re)[0];if(n+a+1>G(this,ne).length)throw new Error("Buffer overflow: not enough space for log");G(this,ne)[n]=a,G(this,ne).set(i,n),G(this,Re)[0]=n+a+1})}transferLogs(){G(this,He).AsyncLock(()=>{const t=G(this,Re)[0],i=G(this,ne).subarray(0,t),a=new Uint8Array(i),n=new TextDecoder().decode(new Uint8Array(a)).split("\0");this.debugLogs=n.slice(0,n.length-1).map(o=>JSON.parse(o))})}generateHTMLTable(){return` <html lang="en"> <head> <meta charset="UTF-8"> <title>Anura Web Core SDK - Logs</title> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <style> body { font-family: Arial, sans-serif; margin: 20px; display: flex; flex-direction: column; } .header { display: flex; justify-content: space-between;} .box { padding: 20px; margin-bottom: 20px; border-radius: 5px; border: 1px solid #ccc; } svg { display: block; width: 100%; height: auto; margin: 20px 0; } .tooltip { position: absolute; background-color: rgba(0, 0, 0, 0.7); color: white; padding: 5px; border-radius: 5px; display: none; } .axis-label { font-size: 14px; text-anchor: middle; } label { font-size: 16px; margin-bottom: 10px; } select { font-size: 16px; padding: 8px 12px; border: 1px solid #ccc; border-radius: 4px; background-color: #f9f9f9; outline: none; transition: all 0.3s ease; width: 320px; } select:hover { border-color: #888; } select:focus { border-color: #555; box-shadow: 0 0 5px rgba(0, 0, 0, 0.2); } #download {width: 200px;} table {width: 100%; max-width: 100%; table-layout: fixed; border-collapse: collapse;} th, td {border: 1px solid #ccc; padding: 8px; text-align: left; word-wrap: break-word;} td pre { margin: 0; white-space: pre-wrap; /* Preserve whitespace and line breaks */ } .json-key { color: blue; } th {background-color: #f2f2f2;} .odd-row { background-color: #f9f9f9; } /* Light grey for odd rows */ .even-row { background-color: #ffffff; } /* White for even rows */ .col-60 { width: 60px } .col-200 { width: 200px } .col-230 { width: 230px } .col-350 { width: 350px } .chart-container { width: 100%; margin-top: 40px;} </style> </head> <body> <div class="header"> <div> <h1>Anura Web Core SDK</h1> </div> <div> <button type="button" id="download">Download</button> </div> </div> <br><br> <div id="info-table"></div> <br><br> <h4>Network Delays</h4> <div id="network-table"></div> <br><br> <div class="chart-container"> <div class="box"> <h2>Instantaneous video playback FPS between consecutive frames</h2> FPS = 1 / time difference between consecutive frames. <br><br> The time difference is the gap between the <a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLVideoElement/requestVideoFrameCallback#capturetime" target="_blank" rel="noopener noreferrer">capturetime</a> values of two consecutive frames. This method will provide a better representation of the FPS at any given point in time, as it accounts for variations in frame rate over time (e.g., if the video slows down or experiences drops). It is generally more useful for tracking real-time performance. <svg id="fps-chart" viewBox="0 0 800 400"></svg> </div> <div class="box"> <h2>Tracking Time per Frame by Face Tracker Worker</h2> To track every frame in a video with a playback speed of 30 frames per second (FPS), the face tracking time per frame should be less than or equal to the time allocated for each frame, which is: <br><br> Frame time= 1 / FPS = 1 / 30 \u2248 0.0333 seconds or 33.3 milliseconds. <br><br> To ensure smooth and consistent face tracking, the face tracking process should ideally complete in less than 33.3 milliseconds per frame. <svg id="ft-tracking-time-chart" viewBox="0 0 800 400"></svg> </div> <div class="box"> <h2>Face tracking Throughput Over Time</h2> This chart will help you monitor the system's face tracking capacity over time. It displays the number of frames tracked per unit time (1 second). <svg id="ft-throughput-chart" viewBox="0 0 800 400"></svg> </div> <div class="box"> <h2>Frame Latency Per Frame</h2> This chart shows total processing delay per frame, including face tracking time and DFX processing time. <br> <br> <div id="total-latency"></div> <svg id="frame-latency-chart" viewBox="0 0 800 400"></svg> </div> <div class="tooltip" id="tooltip"></div> </div> <label for="thread-filter">Filter by Thread:</label> <select id="thread-filter"> <option value="none">None</option> ${[...new Set(this.debugLogs.map(t=>t.thread))].map(t=>`<option value="${t}">${t}</option>`).join("")} </select> <br> <br> <div id="logs-table"></div> <script> const logs = ${JSON.stringify(this.debugLogs,null,2)}; const logsTable = document.getElementById('logs-table'); const infoTable = document.getElementById('info-table'); const networkTable = document.getElementById('network-table'); const filter = document.getElementById('thread-filter'); const fpsChart = document.getElementById('fps-chart'); const ftTrackingTimeChart = document.getElementById('ft-tracking-time-chart'); const ftThroughputChart = document.getElementById('ft-throughput-chart'); const frameLatencyChart = document.getElementById('frame-latency-chart'); const totalLatency = document.getElementById('total-latency'); const tooltip = document.getElementById('tooltip'); const frameNumCaptureTimeArr = logs.filter( item => item.category === 'SDK' && "captureTime" in item.meta && "presentedFrames" in item.meta ).map((item) => item.meta); const ftResolution = logs.find( item => item.category === 'SDK' && "ftWidth" in item.meta && "ftHeight" in item.meta ).meta; const frameMetrics = logs.find( item => item.category === 'SDK' && "numOfFramesPresented" in item.meta && "warmupFrameNumber" in item.meta && "numOftrackedVideoFrames" in item.meta ).meta; const { numOfFramesPresented, warmupFrameNumber, numOftrackedVideoFrames } = frameMetrics; const numOfFramesPresentedSinceWarmUp = numOfFramesPresented - warmupFrameNumber; const numOfDroppedFrames = Math.max( numOfFramesPresented - numOftrackedVideoFrames - warmupFrameNumber, 0 ); const percentageOfDroppedFrames = numOfDroppedFrames === 0 || numOfFramesPresented === warmupFrameNumber ? 0 : (numOfDroppedFrames * 100) / (numOfFramesPresented - warmupFrameNumber); const wsCallDelayArr = logs.filter( item => item.category === 'WebSocket' && "delay" in item.meta && "actionId" in item.meta ).map(item => ({ protocol: "WebSocket", timestamp: item.timestamp.slice(1, -1), ...item.meta })); const restCallDelayArr = logs.filter( item => item.category === 'DFX API client' && "delay" in item.meta && "actionId" in item.meta ).map(item => ({ protocol: "HTTP", timestamp: item.timestamp.slice(1, -1), ...item.meta })); const measurement = restCallDelayArr.find(item => "measurementId" in item); const measurementId = measurement ? measurement.measurementId : 'N/A'; const ftTrackingTimeArr = logs.filter( item => item.category === 'MediaPipe' && item.meta && "frameNumber" in item.meta && "frameTimestamp" in item.meta && "frameTrackingTime" in item.meta ).map(item => ({thread: item.thread, ...item.meta}) ).sort((a, b) => a.frameNumber - b.frameNumber); const ftChartData = ftTrackingTimeArr.slice(4); // remove the first 4 elements (warm-up frames) from the array const frameThroughput = calculateThroughput(ftChartData, 1000); const frameLatencyArr = logs.filter( item => item.category === 'DFX Worker' && item.meta && "frameNumber" in item.meta && "frameTrackingTime" in item.meta && "dfxProcessingTime" in item.meta) .map(item => ({overall: item.meta.frameTrackingTime + item.meta.dfxProcessingTime, ...item.meta})) .sort((a, b) => a.frameNumber - b.frameNumber); const latency = frameLatencyArr.reduce((sum, frame) => sum + frame.overall, 0); totalLatency.innerHTML = \`Total Frames Processed: <strong>\${frameLatencyArr.length}</strong><br><br>Total Latency: <strong>\${(latency / 1000).toFixed(2)} seconds</strong>\`; const getCategoryColor = (category) => { switch (category) { case 'Before REST call event': return 'red'; case 'After REST call event': return 'orange'; case 'MediaPipe': return 'green'; case 'DFX Extraction lib WASM': return 'violet'; case 'DFX API client': return '#d34110'; case 'WebSocket': return '#d34110'; case 'SDK': return '#d34110'; case 'DFX Worker': return '#ad4393'; default: return 'black'; } }; // Function to escape HTML special characters function escapeHTML(html) { const div = document.createElement('div'); div.textContent = html; return div.innerHTML; } function formatJsonWithKeyStyling(jsonString) { return jsonString.replace(/"(.*?)":/g, (match, p1) => \`<span class="json-key">"\${p1}"</span>:\`); } // Function to find and extract the JSON string function extractJson(str) { let jsonStart = str.indexOf('{'); if (jsonStart === -1) return null; // No JSON found let stack = []; let jsonEnd = jsonStart; for (let i = jsonStart; i < str.length; i++) { if (str[i] === '{') stack.push('{'); if (str[i] === '}') stack.pop(); if (stack.length === 0) { jsonEnd = i + 1; break; } } return str.slice(jsonStart, jsonEnd); } // Function to format and display the string function formatString(str) { const jsonPart = extractJson(str); let formattedString = escapeHTML(str); if (jsonPart) { try { const jsonObject = JSON.parse(jsonPart); const formattedJson = JSON.stringify(jsonObject, null, 4); const styledJson = formatJsonWithKeyStyling(formattedJson); // Replace the JSON part with formatted JSON formattedString = str.replace(jsonPart, \`<pre>\${styledJson}</pre>\`); } catch (e) { console.error('Invalid JSON:', e); } } return formattedString; } function renderInfoTable(data) { const tableHtml = \` <table> <thead> <tr> <th class="col-350">Title</th> <th>Description</th> </tr> </thead> <tbody> \${data.map((item, index) => { const rowClass = index % 2 === 0 ? 'even-row' : 'odd-row'; const [title, description] = item; return \` <tr class="\${rowClass}"> <td class="col-350">\${title}</td> <td>\${description}</td> </tr> \`; }).join("")} </tbody> </table> \`; infoTable.innerHTML = tableHtml; } function renderNetworkTable(data) { const tableHtml = \` <table> <thead> <tr> <th class="col-230">Timestamp</th> <th class="col-200">Protocol</th> <th class="col-200">Delay (ms)</th> <th>Info</th> </tr> </thead> <tbody> \${data.map((item, index) => { const rowClass = index % 2 === 0 ? 'even-row' : 'odd-row'; const { timestamp, protocol, delay, actionId, description } = item; return \` <tr class="\${rowClass}"> <td class="col-230">\${timestamp}</td> <td class="col-200">\${protocol}</td> <td class="col-200">\${delay.toFixed(2)}</td> <td>\${description} - Action ID: \${actionId}</td> </tr> \`; }).join("")} </tbody> </table> \`; networkTable.innerHTML = tableHtml; } function renderLogsTable(data) { const tableHtml = \` <table> <thead> <tr> <th class="col-60">Index</th> <th class="col-230">Timestamp</th> <th class="col-230">Thread</th> <th class="col-200">Category</th> <th>Message</th> </tr> </thead> <tbody> \${data.map((item, index) => { const rowClass = index % 2 === 0 ? 'even-row' : 'odd-row'; const categoryColor = getCategoryColor(item.category); return \` <tr class="\${rowClass}"> <td class="col-60">\${index + 1}</td> <td class="col-230">\${item.timestamp.slice(1, -1)}</td> <td class="col-230">\${item.thread}</td> <td class="col-200" style="color: \${categoryColor};">\${item.category}</td> <td>\${formatString(item.message)}</td> </tr> \`; }).join("")} </tbody> </table> \`; logsTable.innerHTML = tableHtml; } // Instantaneous FPS (between consecutive frames) const instantaneousFrameRates = frameNumCaptureTimeArr.map((point, index) => { if (index === 0) { return { captureTime: point.captureTime, frameRate: 0 }; // No FPS for the first frame } else { const prevPoint = frameNumCaptureTimeArr[index - 1]; const timeDifference = point.captureTime - prevPoint.captureTime; // Time difference in seconds const fps = timeDifference > 0 ? 1000 / timeDifference : 0; // Convert to FPS by multiplying by 1000 return { captureTime: point.captureTime, frameRate: fps }; } }); function calculateThroughput(frameData, intervalMs = 1000) { // Sort frame data by frameTimestamp frameData.sort((a, b) => a.frameTimestamp - b.frameTimestamp); // Find the time range const startTime = frameData[0].frameTimestamp; const endTime = frameData[frameData.length - 1].frameTimestamp; // Group frames into time intervals const throughputData = []; for (let t = startTime; t <= endTime; t += intervalMs) { const nextInterval = t + intervalMs; const framesInInterval = frameData.filter( frame => frame.frameTimestamp >= t && frame.frameTimestamp < nextInterval ).length; throughputData.push({ time: t - startTime, // Time offset in milliseconds throughput: framesInInterval, }); } return throughputData; }; // Function to render the SVG chart function renderChart(data, svg, xKey, yKey, xAxisLabel, yAxisLabel, lineColor, getCircleColor, xValuesCallback, tooltipCallback) { const width = svg.getAttribute("viewBox").split(" ")[2]; const height = svg.getAttribute("viewBox").split(" ")[3]; const padding = 70; // Increase padding to provide more space for y-axis title const titlePadding = 30; // Adjust this for more space between title and axis const maxTime = Math.max(...data.map(d => d[xKey])); const minTime = Math.min(...data.map(d => d[xKey])); const maxFrameRate = Math.max(...data.map(d => d[yKey])); const minFrameRate = Math.min(...data.map(d => d[yKey])); const xScale = (value) => padding + (value / maxTime) * (width - 2 * padding); const yScale = (value) => height - padding - (value / maxFrameRate) * (height - 2 * padding); // Clear existing SVG content svg.innerHTML = ""; // Draw grid lines function drawGridLines() { const gridLines = document.createElementNS("http://www.w3.org/2000/svg", "g"); for (let i = 1; i <= 5; i++) { const x = xScale(i * (maxTime / 5)); const y1 = padding; const y2 = height - padding; const gridLine = document.createElementNS("http://www.w3.org/2000/svg", "line"); gridLine.setAttribute("x1", x); gridLine.setAttribute("y1", y1); gridLine.setAttribute("x2", x); gridLine.setAttribute("y2", y2); gridLine.setAttribute("stroke", "#ccc"); gridLine.setAttribute("stroke-width", 1); gridLine.setAttribute("stroke-dasharray", "5,5"); gridLines.appendChild(gridLine); } for (let i = 1; i <= 5; i++) { const y = yScale(i * (maxFrameRate / 5)); const x1 = padding; const x2 = width - padding; const gridLine = document.createElementNS("http://www.w3.org/2000/svg", "line"); gridLine.setAttribute("x1", x1); gridLine.setAttribute("y1", y); gridLine.setAttribute("x2", x2); gridLine.setAttribute("y2", y); gridLine.setAttribute("stroke", "#ccc"); gridLine.setAttribute("stroke-width", 1); gridLine.setAttribute("stroke-dasharray", "5,5"); gridLines.appendChild(gridLine); } svg.appendChild(gridLines); } drawGridLines(); // Draw axes const xAxis = document.createElementNS("http://www.w3.org/2000/svg", "line"); xAxis.setAttribute("x1", padding); xAxis.setAttribute("y1", height - padding); xAxis.setAttribute("x2", width - padding); xAxis.setAttribute("y2", height - padding); xAxis.setAttribute("stroke", "black"); svg.appendChild(xAxis); const yAxis = document.createElementNS("http://www.w3.org/2000/svg", "line"); yAxis.setAttribute("x1", padding); yAxis.setAttribute("y1", padding); yAxis.setAttribute("x2", padding); yAxis.setAttribute("y2", height - padding); yAxis.setAttribute("stroke", "black"); svg.appendChild(yAxis); // Add axis labels const xLabel = document.createElementNS("http://www.w3.org/2000/svg", "text"); xLabel.setAttribute("x", width / 2); xLabel.setAttribute("y", height - padding / 3); xLabel.setAttribute("class", "axis-label"); xLabel.textContent = xAxisLabel; svg.appendChild(xLabel); // Add axis label for Y (Frame Rate) const yLabel = document.createElementNS("http://www.w3.org/2000/svg", "text"); yLabel.setAttribute("x", -height / 2); // Rotate around the center of the y-axis yLabel.setAttribute("y", padding / 2 - 24); // Adjust vertical position to add space yLabel.setAttribute("class", "axis-label"); yLabel.setAttribute("transform", "rotate(-90)"); yLabel.setAttribute("text-anchor", "middle"); yLabel.textContent = yAxisLabel; svg.appendChild(yLabel); // Add axis ticks and labels for X (time) const numXTicks = 5; for (let i = 0; i <= numXTicks; i++) { const xValue = minTime + (i * (maxTime - minTime) / numXTicks); const x = xScale(xValue); const tick = document.createElementNS("http://www.w3.org/2000/svg", "line"); tick.setAttribute("x1", x); tick.setAttribute("y1", height - padding); tick.setAttribute("x2", x); tick.setAttribute("y2", height - padding + 6); tick.setAttribute("stroke", "black"); svg.appendChild(tick); const tickLabel = document.createElementNS("http://www.w3.org/2000/svg", "text"); tickLabel.setAttribute("x", x); tickLabel.setAttribute("y", height - padding + 20); tickLabel.setAttribute("class", "axis-label"); tickLabel.textContent = xValuesCallback(xValue); svg.appendChild(tickLabel); } // Add axis ticks and labels for Y (frame rate) const numYTicks = 5; for (let i = 0; i <= numYTicks; i++) { const yValue = minFrameRate + (i * (maxFrameRate - minFrameRate) / numYTicks); const y = yScale(yValue); const tick = document.createElementNS("http://www.w3.org/2000/svg", "line"); tick.setAttribute("x1", padding - 6); // Move ticks to the left a bit tick.setAttribute("y1", y); tick.setAttribute("x2", padding); // Position ticks a bit further from the axis line tick.setAttribute("y2", y); tick.setAttribute("stroke", "black"); svg.appendChild(tick); const tickLabel = document.createElementNS("http://www.w3.org/2000/svg", "text"); tickLabel.setAttribute("x", padding - 10); // Adjust the label placement to shift it to the right tickLabel.setAttribute("y", y + 5); // Center the label vertically on the tick tickLabel.setAttribute("text-anchor", "end"); tickLabel.textContent = yValue.toFixed(1); svg.appendChild(tickLabel); } // Draw the line chart const line = document.createElementNS("http://www.w3.org/2000/svg", "path"); const lineData = data.map((point, index) => { const x = xScale(point[xKey]); const y = yScale(point[yKey]); return index === 0 ? 'M' + x + ',' + y : 'L' + x + ',' + y; }).join(" "); line.setAttribute("d", lineData); line.setAttribute("fill", "none"); line.setAttribute("stroke", lineColor); line.setAttribute("stroke-width", 2); svg.appendChild(line); // Add tooltip on hover data.forEach((point) => { const circle = document.createElementNS("http://www.w3.org/2000/svg", "circle"); circle.setAttribute("cx", xScale(point[xKey])); circle.setAttribute("cy", yScale(point[yKey])); circle.setAttribute("r", 4); circle.setAttribute("fill", getCircleColor(point)); circle.addEventListener("mouseover", (event) => { tooltip.style.display = "block"; tooltip.style.left = event.pageX + 10 + 'px'; tooltip.style.top = event.pageY + 10 + 'px'; tooltip.innerHTML = tooltipCallback(point); }); circle.addEventListener("mouseout", () => { tooltip.style.display = "none"; }); svg.appendChild(circle); }); } const renderFpsTooltip = (point) => { return \`Time: \${(point.captureTime/1000).toFixed(4)}s, Frame Rate: \${point.frameRate.toFixed(2)} FPS\`; }; const renderTrackingTimeTooltip = (point) => { return \`Frame: \${point.frameNumber}, Tracking Time: \${point.frameTrackingTime.toFixed(2)} ms, Thread: \${point.thread}\`; }; const renderThroughputTooltip = (point) => { return \`Time: \${point.time / 1000}s, Throughput: \${point.throughput} FPS\`; }; const renderFrameLatencyTooltip = (point) => { return \`Frame: \${point.frameNumber}, Overall: \${point.overall.toFixed(2)} ms, Face Tracking Time: \${point.frameTrackingTime.toFixed(2)} ms, DFX processing: \${point.dfxProcessingTime.toFixed(2)} ms\`; }; const getFTCircleColor = (point) => { const colors = ['#c7e9c0' ,'#74c476', '#31a354', '#006d2c']; const index = parseInt(point.thread.slice(-1)); return colors[index]; }; const getUTCDate = () => { const now = new Date(); const year = now.getUTCFullYear(); const month = String(now.getUTCMonth() + 1).padStart(2, '0'); // Months are 0-indexed const date = String(now.getUTCDate()).padStart(2, '0'); const hours = String(now.getUTCHours()).padStart(2, '0'); const minutes = String(now.getUTCMinutes()).padStart(2, '0'); const seconds = String(now.getUTCSeconds()).padStart(2, '0'); return year + '-' + month + '-' + date + ' ' + hours + ':' + minutes + ':' + seconds + ' UTC'; }; const info = [ ['Report date', getUTCDate()], ['SDK Version', '${this.version.webSDK}'], ['Face Tracker Version', '${this.version.faceTracker}'], ['Extraction Lib Version', '${this.version.extractionLib.version}'], ['Extraction Lib SDK ID', '${this.version.extractionLib.sdkId}'], ['Video Resolution (w x h)', ftResolution.frameWidth + ' x ' + ftResolution.frameHeight + ' pixels'], ['Face Tracker Resolution (w x h)', ftResolution.ftWidth + ' x ' + ftResolution.ftHeight + ' pixels'], ['Measurement ID', measurementId], ['Number of Frames Presented', numOfFramesPresented], ['Face Trackers Warmup Frame Number', warmupFrameNumber], ['Number of Tracked Video Frames', numOftrackedVideoFrames], ['Number of Frames Presented Since Warmup', numOfFramesPresentedSinceWarmUp], ['Number of dropped Frames', numOfDroppedFrames], ['Percentage of dropped frames', percentageOfDroppedFrames.toFixed(2) + '%'], ]; info.push([ 'Touch screen', 'maxTouchPoints' in navigator && navigator.maxTouchPoints > 0 ? 'Available' : 'Not available', ]); info.push([ 'Window size (w x h)', '${window.innerWidth} x ${window.innerHeight} pixels', ]); info.push([ 'Screen orientation', window.innerHeight > window.innerWidth ? 'Portrait' : 'Landscape', ]); info.push([ 'User agent', window.navigator.userAgent, ]); info.push([ 'Time Zone', Intl.DateTimeFormat().resolvedOptions().timeZone, ]); info.push([ 'Locale', Intl.DateTimeFormat().resolvedOptions().locale, ]); renderInfoTable(info); renderNetworkTable([...restCallDelayArr, ...wsCallDelayArr]); renderLogsTable(logs); renderChart( instantaneousFrameRates, fpsChart, 'captureTime', 'frameRate', 'Time (s)', 'Frame Rate (FPS)', 'blue', (point) => 'blue', (xValue) => (xValue / 1000).toFixed(1), renderFpsTooltip ); renderChart( ftChartData, ftTrackingTimeChart, 'frameNumber', 'frameTrackingTime', 'Frame Number', 'Frame Tracking Time (ms)', 'green', getFTCircleColor, (xValue) => Math.trunc(xValue), renderTrackingTimeTooltip ); renderChart( frameThroughput, ftThroughputChart, 'time', 'throughput', 'Time (s)', 'Face tracking throughput (FPS)', 'orange', (point) => 'orange', (xValue) => xValue / 1000, renderThroughputTooltip ); renderChart( frameLatencyArr, frameLatencyChart, 'frameNumber', 'overall', 'Frame Number', 'Frame Latency (ms)', '#f93e3e', (point) => '#f93e3e', (xValue) => Math.trunc(xValue), renderFrameLatencyTooltip ); filter.addEventListener('change', () => { const selectedThread = filter.value; if (selectedThread === 'none') { renderLogsTable(logs); } else { const filteredData = logs.filter(item => item.thread === selectedThread); renderLogsTable(filteredData); } }); function downloadFile() { const now = new Date(); const formattedDate = now.toISOString().replace(/[-:T]/g, '_').split('.')[0]; const blob = new Blob([document.documentElement.outerHTML], { type: 'text/html' }); const link = document.createElement('a'); link.href = URL.createObjectURL(blob); link.download = \`log_\${formattedDate}.html\`; document.body.appendChild(link); link.click(); URL.revokeObjectURL(link.href); document.body.removeChild(link); } const download = document.getElementById('download'); download.onclick = downloadFile; <\/script> </body> </html> `}openLogInNewWindow(){const t=this.generateHTMLTable(),i=new Blob([t],{type:"text/html"}),a=URL.createObjectURL(i);window.open(a,"_blank")}};ne=new WeakMap,Re=new WeakMap,He=new WeakMap;var Ji=Object.defineProperty,qi=(e,t,i)=>t in e?Ji(e,t,{enumerable:!0,configurable:!0,writable:!0,value:i}):e[t]=i,Ft=(e,t,i)=>qi(e,typeof t!="symbol"?t+"":t,i);class nt{constructor(t,i){Ft(this,"workerUrl"),Ft(this,"worker"),this.worker=t,this.workerUrl=i}static init(t,i,a){const n=new Blob([t],{type:"application/javascript"}),o=URL.createObjectURL(n),s=a||`WORKER${String(new Date().getTime())}`,h=new Worker(new URL(o,import.meta.url),{name:s,type:i});return new nt(h,o)}destroy(){this.worker&&(this.worker.terminate(),URL.revokeObjectURL(this.worker))}}const Qi=(e,t,i,a)=>{const n=e/t,o=i/a;let s=0,h=0;n>o?(s=e,h=e/o):(h=t,s=t*o);const d=(e-s)/2,m=(t-h)/2;return{width:s||0,height:h||0,offsetX:d||0,offsetY:m||0}},Zi=(e,t,i,a)=>{const n=(e-i)/2,o=(t-a)/2;return{width:i||0,height:a||0,offsetX:n||0,offsetY:o||0}},er=(e,t,i,a)=>{const n=e/t,o=i/a;let s=0,h=0;n>o?(h=t,s=t*o):(s=e,h=e/o);const d=(e-s)/2,m=(t-h)/2;return{width:s||0,height:h||0,offsetX:d||0,offsetY:m||0}},tr=(e,t)=>{switch(e){case"none":return Zi(...t);case"cover":return Qi(...t);case"contain":default:return er(...t)}},Mt=e=>/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/.test(e),ir=e=>e===void 0?!0:!(typeof e.mediaPipe<"u"&&typeof e.mediaPipe!="boolean"||typeof e.beforeRESTCall<"u"&&typeof e.beforeRESTCall!="boolean"||typeof e.afterRESTCall<"u"&&typeof e.afterRESTCall!="boolean"||typeof e.extractionLibWasm<"u"&&typeof e.extractionLibWasm!="boolean"||typeof e.apiClient<"u"&&typeof e.apiClient!="boolean"||typeof e.extractionWorker<"u"&&typeof e.extractionWorker!="boolean"||typeof e.faceTrackerWorkers<"u"&&typeof e.faceTrackerWorkers!="boolean"||typeof e.sdk<"u"&&typeof e.sdk!="boolean"),rr=(e,t)=>{const i={...e};if(t.logger)if(ir(t.logger))i.logger={...i.logger,...t.logger};else return{results:"invalid"};if(t.apiUrl!==void 0)if(typeof t.apiUrl=="string")i.apiUrl=t.apiUrl;else return{results:"invalid"};if(t.metrics!==void 0)if(typeof t.metrics=="boolean")i.metrics=t.metrics;else return{results:"invalid"};if(t.mirrorVideo!==void 0)if(typeof t.mirrorVideo=="boolean")i.mirrorVideo=t.mirrorVideo;else return{results:"invalid"};if(t.displayMediaStream!==void 0)if(typeof t.displayMediaStream=="boolean")i.displayMediaStream=t.displayMediaStream;else return{results:"invalid"};if(t.assetFolder!==void 0)if(typeof t.assetFolder=="string")i.assetFolder=t.assetFolder;else return{results:"invalid"};if(t.mediaElement!==void 0)if(t.mediaElement instanceof HTMLDivElement)i.mediaElement=t.mediaElement;else return{results:"invalid"};if(t.constraintOverrides!==void 0){let a=function(o,s){if(!(o in n))return!1;const h=n[o];return typeof s===h};const n=Object.fromEntries(Object.entries({backLightMaxPixels_pct:30,backLightSearchMult:1.5,backLightThresh:240,boxCenterX_pct:50,boxCenterY_pct:50,boxHeight_pct:99,boxWidth_pct:50,cameraRot_chunkThresh:.7853981633974483,cameraRot_windowThresh:.2617993877991494,checkBackLight:!1,checkCameraMovement:!1,checkCentered:!1,checkDistance:!1,checkEyebrowMovement:!1,checkFaceDirection:!1,checkLighting:!1,checkMaxDistance:!1,checkMinFps:!1,checkMovement:!1,chunkMovementThresh_pct:60,enableDebugLog:!1,enableFigures:!1,faceRotLR_thresh:.33,faceRotUD_lowerthresh:.25,faceRotUD_upperThresh:5,hy_faceRotLR_thresh:.05,hy_maxFaceRotLR_deg:2,hy_maxFaceRotUD_deg:2,hy_minInterPupilDist_px:5,hy_minimumFps:1,maxEyebrowMovement_mm:3,maxFaceRotLR_deg:12.5,maxFaceRotUD_deg:25,maxMovement_mm:10,minInterPupilDist_px:100,minimumFps:26,movementWindow_ms:2e3,threshBright:225,threshDark:48}).map(([o,s])=>[o,typeof s]));for(const o in t.constraintOverrides){const s=t.constraintOverrides[o];if(!a(o,s))return{results:"invalid"}}i.constraintOverrides=t.constraintOverrides}return{results:"valid",settings:i}},ar=e=>typeof e.age!="number"||e.age<13||e.age>120?(console.error("Invalid age. It should be a number between 13 and 120."),!1):typeof e.height!="number"||e.height<120||e.height>220?(console.error("Invalid height. It should be a number between 120 and 220."),!1):typeof e.weight!="number"||e.weight<30||e.weight>300?(console.error("Invalid weight. It should be a number between 30 and 300."),!1):typeof e.sex!="number"||e.sex<1||e.sex>3?(console.error("Invalid sex. It should be a number between 1 and 3."),!1):typeof e.diabetes!="number"||e.diabetes<4||e.diabetes>6?(console.error("Invalid diabetes. It should be a number between 4 and 6."),!1):typeof e.bloodPressureMedication!="number"||e.bloodPressureMedication!==0&&e.bloodPressureMedication!==1?(console.error("Invalid bloodPressureMedication. It should be either 0 or 1."),!1):typeof e.smoking!="number"||e.smoking!==0&&e.smoking!==1?(console.error("Invalid smoking. It should be either 0 or 1."),!1):e.unit!=="Metric"&&e.unit!=="Imperial"?(console.error("Invalid unit. It should be either 'Metric' or 'Imperial'."),!1):!0;var u=(e=>(e.beforeRESTCall="Before REST call event",e.afterRESTCall="After REST call event",e.mediaPipe="MediaPipe",e.extractionLibWasm="DFX Extraction lib WASM",e.apiClient="DFX API client",e.webSocket="WebSocket",e.sdk="SDK",e.dfxWorker="DFX Worker",e))(u||{});const sr=e=>{switch(e){case"Before REST call event":return"red";case"After REST call event":return"orange";case"MediaPipe":return"green";case"DFX Extraction lib WASM":return"violet";case"DFX API client":return"#d34110";case"WebSocket":return"#d34110";case"SDK":return"#d34110";case"DFX Worker":return"#ad4393";default:return"black"}};var nr=Object.defineProperty,or=(e,t,i)=>t in e?nr(e,t,{enumerable:!0,configurable:!0,writable:!0,value:i}):e[t]=i,Wt=(e,t,i)=>or(e,typeof t!="symbol"?t+"":t,i);const cr=()=>{const e=new Date,t=(e.getMonth()+1).toSt