seqeyes-python 0.3.6__tar.gz → 0.3.8__tar.gz
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.
- {seqeyes_python-0.3.6 → seqeyes_python-0.3.8}/PKG-INFO +1 -1
- {seqeyes_python-0.3.6 → seqeyes_python-0.3.8}/pyproject.toml +1 -1
- {seqeyes_python-0.3.6 → seqeyes_python-0.3.8}/src/seqeyes/__init__.py +1 -1
- {seqeyes_python-0.3.6 → seqeyes_python-0.3.8}/src/seqeyes/resources/pulseq-bundle.js +47 -22
- {seqeyes_python-0.3.6 → seqeyes_python-0.3.8}/src/seqeyes/resources/webview-bundle.js +77 -12
- {seqeyes_python-0.3.6 → seqeyes_python-0.3.8}/.gitignore +0 -0
- {seqeyes_python-0.3.6 → seqeyes_python-0.3.8}/README.md +0 -0
- {seqeyes_python-0.3.6 → seqeyes_python-0.3.8}/examples/demo.ipynb +0 -0
- {seqeyes_python-0.3.6 → seqeyes_python-0.3.8}/examples/demo.py +0 -0
- {seqeyes_python-0.3.6 → seqeyes_python-0.3.8}/src/seqeyes/_plot.py +0 -0
- {seqeyes_python-0.3.6 → seqeyes_python-0.3.8}/src/seqeyes/_renderer.py +0 -0
- {seqeyes_python-0.3.6 → seqeyes_python-0.3.8}/src/seqeyes/resources/viewer.html +0 -0
- {seqeyes_python-0.3.6 → seqeyes_python-0.3.8}/tests/conftest.py +0 -0
- {seqeyes_python-0.3.6 → seqeyes_python-0.3.8}/tests/debug_full.html +0 -0
- {seqeyes_python-0.3.6 → seqeyes_python-0.3.8}/tests/debug_minimal.html +0 -0
- {seqeyes_python-0.3.6 → seqeyes_python-0.3.8}/tests/debug_viewer.html +0 -0
- {seqeyes_python-0.3.6 → seqeyes_python-0.3.8}/tests/debug_viewer.py +0 -0
- {seqeyes_python-0.3.6 → seqeyes_python-0.3.8}/tests/integration_test.py +0 -0
- {seqeyes_python-0.3.6 → seqeyes_python-0.3.8}/tests/test_plot.py +0 -0
- {seqeyes_python-0.3.6 → seqeyes_python-0.3.8}/tests/test_renderer.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: seqeyes-python
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.8
|
|
4
4
|
Summary: Interactive Pulseq MRI sequence viewer for Jupyter — lightweight, beautiful, replaces seq.plot() in pypulseq.
|
|
5
5
|
Project-URL: Homepage, https://github.com/bughht/seqeyes_plugin
|
|
6
6
|
Project-URL: Repository, https://github.com/bughht/seqeyes_plugin
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "seqeyes-python"
|
|
7
|
-
version = "0.3.
|
|
7
|
+
version = "0.3.8"
|
|
8
8
|
description = "Interactive Pulseq MRI sequence viewer for Jupyter — lightweight, beautiful, replaces seq.plot() in pypulseq."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = {text = "MIT"}
|
|
@@ -23,31 +23,24 @@ var Pulseq = (() => {
|
|
|
23
23
|
// web/pulseq-browser.ts
|
|
24
24
|
var pulseq_browser_exports = {};
|
|
25
25
|
__export(pulseq_browser_exports, {
|
|
26
|
-
BAND_DETAIL_SAMPLES: () => BAND_DETAIL_SAMPLES,
|
|
27
|
-
EXACT_DETAIL_SAMPLES: () => EXACT_DETAIL_SAMPLES,
|
|
28
26
|
INTERACTIVE_COMPUTE_LIMITS: () => INTERACTIVE_COMPUTE_LIMITS,
|
|
29
|
-
MAX_DETAIL_PTS: () => MAX_DETAIL_PTS,
|
|
30
|
-
MAX_DISPLAY_PTS: () => MAX_DISPLAY_PTS,
|
|
31
|
-
MAX_ENVELOPE_COLUMNS: () => MAX_ENVELOPE_COLUMNS,
|
|
32
27
|
MAX_RF_RESPONSE_BANDS: () => MAX_RF_RESPONSE_BANDS,
|
|
33
28
|
MAX_RF_RESPONSE_FFT_POINTS: () => MAX_RF_RESPONSE_FFT_POINTS,
|
|
34
29
|
MAX_RF_RESPONSE_SAMPLES: () => MAX_RF_RESPONSE_SAMPLES,
|
|
35
30
|
PACKAGE_VERSION: () => PACKAGE_VERSION,
|
|
36
31
|
analyzeRfResponse: () => analyzeRfResponse,
|
|
37
32
|
audioBudgetRefusal: () => audioBudgetRefusal,
|
|
33
|
+
buildWaveformDetailReply: () => buildWaveformDetailReply,
|
|
38
34
|
calculateKspace: () => calculateKspace,
|
|
39
35
|
calculateM1: () => calculateM1,
|
|
40
36
|
calculateM1Coarse: () => calculateM1Coarse,
|
|
41
37
|
calculatePns: () => calculatePns,
|
|
42
38
|
calculatePnsCoarse: () => calculatePnsCoarse,
|
|
43
39
|
computeGradSpectrumParity: () => computeGradSpectrumParity,
|
|
44
|
-
computeGradientEnvelope: () => computeGradientEnvelope,
|
|
45
40
|
computeGradientSpectrogram: () => computeGradientSpectrogram,
|
|
46
41
|
computeGradientSpectrumAverage: () => computeGradientSpectrumAverage,
|
|
47
42
|
computeGradientSpectrumSlice: () => computeGradientSpectrumSlice,
|
|
48
43
|
countBandsOutsideRange: () => countBandsOutsideRange,
|
|
49
|
-
countExactDetailSamples: () => countExactDetailSamples,
|
|
50
|
-
countGradientSamples: () => countGradientSamples,
|
|
51
44
|
createSequenceDecodeContext: () => createSequenceDecodeContext,
|
|
52
45
|
decodeAllBlocks: () => decodeAllBlocks,
|
|
53
46
|
decodeBlockRange: () => decodeBlockRange,
|
|
@@ -72,8 +65,6 @@ var Pulseq = (() => {
|
|
|
72
65
|
hasPulseqBinaryMagic: () => hasPulseqBinaryMagic,
|
|
73
66
|
isEmptyAscProfile: () => isEmptyAscProfile,
|
|
74
67
|
kspaceExceedsInteractiveBudget: () => kspaceExceedsInteractiveBudget,
|
|
75
|
-
packGradientEnvelope: () => packGradientEnvelope,
|
|
76
|
-
packSequenceBlockRange: () => packSequenceBlockRange,
|
|
77
68
|
packSequenceBlocks: () => packSequenceBlocks,
|
|
78
69
|
parseAcousticResonancesAsc: () => parseAcousticResonancesAsc,
|
|
79
70
|
parseAscProfile: () => parseAscProfile,
|
|
@@ -84,7 +75,6 @@ var Pulseq = (() => {
|
|
|
84
75
|
parseSequenceText: () => parseSequenceText,
|
|
85
76
|
physicalGradientValueAt: () => physicalGradientValueAt,
|
|
86
77
|
resamplePhysicalGradients: () => resamplePhysicalGradients,
|
|
87
|
-
resolveDetailBlockRange: () => resolveDetailBlockRange,
|
|
88
78
|
resolveSpectrogramParams: () => resolveSpectrogramParams,
|
|
89
79
|
rotateGradient: () => rotateGradient,
|
|
90
80
|
safePnsModel: () => safePnsModel,
|
|
@@ -97,7 +87,7 @@ var Pulseq = (() => {
|
|
|
97
87
|
});
|
|
98
88
|
|
|
99
89
|
// package.json
|
|
100
|
-
var version = "0.3.
|
|
90
|
+
var version = "0.3.8";
|
|
101
91
|
|
|
102
92
|
// src/pulseq/decompressor.ts
|
|
103
93
|
function decompressShape(compressed, numSamples) {
|
|
@@ -2862,16 +2852,6 @@ var Pulseq = (() => {
|
|
|
2862
2852
|
}
|
|
2863
2853
|
return { startSec, endSec, columns: width, channels };
|
|
2864
2854
|
}
|
|
2865
|
-
function countGradientSamples(blocks) {
|
|
2866
|
-
let total = 0;
|
|
2867
|
-
for (const block of blocks) {
|
|
2868
|
-
for (const key of GRADIENT_CHANNELS) {
|
|
2869
|
-
const grad = block[key];
|
|
2870
|
-
if (grad && grad.type !== "none") total += grad.timePoints.length;
|
|
2871
|
-
}
|
|
2872
|
-
}
|
|
2873
|
-
return total;
|
|
2874
|
-
}
|
|
2875
2855
|
function packGradientEnvelope(envelope) {
|
|
2876
2856
|
const { columns } = envelope;
|
|
2877
2857
|
const out = new Float32Array(columns * 6);
|
|
@@ -2886,6 +2866,51 @@ var Pulseq = (() => {
|
|
|
2886
2866
|
return { startSec: envelope.startSec, endSec: envelope.endSec, columns, values: out.buffer };
|
|
2887
2867
|
}
|
|
2888
2868
|
|
|
2869
|
+
// src/editor/waveformDetailReply.ts
|
|
2870
|
+
var WINDOW_DETAIL_BLOCK_LIMIT = 2e4;
|
|
2871
|
+
function buildWaveformDetailReply(seq, context, request, generation = 0, lookup, blockLimit = WINDOW_DETAIL_BLOCK_LIMIT) {
|
|
2872
|
+
const startSec = Number(request.startSec);
|
|
2873
|
+
const endSec = Number(request.endSec);
|
|
2874
|
+
const { start, end } = resolveDetailBlockRange(
|
|
2875
|
+
context.blockStartTimes,
|
|
2876
|
+
seq.blocks.length,
|
|
2877
|
+
startSec,
|
|
2878
|
+
endSec
|
|
2879
|
+
);
|
|
2880
|
+
if (!(endSec > startSec)) {
|
|
2881
|
+
return { kind: "error", message: "This waveform detail window is empty." };
|
|
2882
|
+
}
|
|
2883
|
+
const columns = Math.max(1, Math.min(
|
|
2884
|
+
MAX_ENVELOPE_COLUMNS,
|
|
2885
|
+
Math.floor(Number(request.columns) || 0) || 1
|
|
2886
|
+
));
|
|
2887
|
+
const cacheKey = `${generation}:${start}:${end}:${startSec}:${endSec}`;
|
|
2888
|
+
const cached = lookup?.(cacheKey);
|
|
2889
|
+
if (cached && end - start <= blockLimit) {
|
|
2890
|
+
return { kind: "samples", startBlock: start, endBlock: end, startSec, endSec, packed: cached, cacheKey };
|
|
2891
|
+
}
|
|
2892
|
+
const decoded = decodeBlockRange(seq, start, end, context);
|
|
2893
|
+
const windowSamples = countExactDetailSamples(decoded, { startSec, endSec });
|
|
2894
|
+
if (windowSamples > EXACT_DETAIL_SAMPLES) {
|
|
2895
|
+
if (windowSamples > BAND_DETAIL_SAMPLES) return { kind: "unavailable", startSec, endSec };
|
|
2896
|
+
const band = packGradientEnvelope(computeGradientEnvelope(decoded, startSec, endSec, columns));
|
|
2897
|
+
return { kind: "band", startSec: band.startSec, endSec: band.endSec, columns: band.columns, values: band.values };
|
|
2898
|
+
}
|
|
2899
|
+
if (end - start > blockLimit) {
|
|
2900
|
+
return { kind: "error", message: "This waveform detail window is too large. Zoom in further." };
|
|
2901
|
+
}
|
|
2902
|
+
const packed = packSequenceBlockRange(
|
|
2903
|
+
seq,
|
|
2904
|
+
start,
|
|
2905
|
+
end,
|
|
2906
|
+
context,
|
|
2907
|
+
MAX_DETAIL_PTS,
|
|
2908
|
+
void 0,
|
|
2909
|
+
{ startSec, endSec }
|
|
2910
|
+
);
|
|
2911
|
+
return { kind: "samples", startBlock: start, endBlock: end, startSec, endSec, packed, cacheKey };
|
|
2912
|
+
}
|
|
2913
|
+
|
|
2889
2914
|
// src/pulseq/physicalGradients.ts
|
|
2890
2915
|
var GRADIENT_ENDPOINT_TOLERANCE_SEC = 1e-12;
|
|
2891
2916
|
function rotateGradient(block, gx, gy, gz) {
|
|
@@ -637,15 +637,27 @@ buildLegend();
|
|
|
637
637
|
tuSel.onchange=function(){timeUnit=tuSel.value;draw();};
|
|
638
638
|
guSel.onchange=function(){gradUnit=guSel.value;draw();};
|
|
639
639
|
function setExportButtonEnabled(enabled){if(exportBtn)exportBtn.disabled=!enabled;}
|
|
640
|
+
/* Throws rather than drawing an empty panel when a trajectory arrives without
|
|
641
|
+
its ADC samples. A reply that lost its arrays in transit used to clear the
|
|
642
|
+
safety notice and leave a blank k-space view, which reads as "this sequence
|
|
643
|
+
has no k-space" instead of "the trajectory never got here". */
|
|
640
644
|
function applySerializedKspace(payload){
|
|
641
645
|
kTraj=null;kAdc=null;kTime=null;kAdcTime=null;
|
|
642
646
|
if(!payload)return;
|
|
643
647
|
kTraj=[payload.kx,payload.ky,payload.kz];kTime=payload.tk;
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
648
|
+
if(typeof payload.nAdc!=='number')
|
|
649
|
+
throw new Error('the trajectory arrived without its ADC sample count');
|
|
650
|
+
var n=payload.nAdc;
|
|
651
|
+
if(n<=0)return;
|
|
652
|
+
var ax=asTypedView(payload.adcX,Float32Array),ay=asTypedView(payload.adcY,Float32Array),
|
|
653
|
+
az=asTypedView(payload.adcZ,Float32Array),at=asTypedView(payload.adcTime,Float32Array);
|
|
654
|
+
if(!ax||!ay||!az||!at)
|
|
655
|
+
throw new Error('the K‑space ADC samples did not arrive as binary data');
|
|
656
|
+
if(ax.length<n||ay.length<n||az.length<n||at.length<n)
|
|
657
|
+
throw new Error('the K‑space ADC samples arrived truncated ('+
|
|
658
|
+
Math.min(ax.length,ay.length,az.length,at.length)+' of '+n+' samples)');
|
|
659
|
+
kAdc=[ax.subarray(0,n),ay.subarray(0,n),az.subarray(0,n)];
|
|
660
|
+
kAdcTime=at.subarray(0,n);uploadKSpaceGPU();
|
|
649
661
|
}
|
|
650
662
|
|
|
651
663
|
/* Report a load that did not arrive. Clearing the previous sequence matters as
|
|
@@ -739,7 +751,13 @@ window.addEventListener('message',function(e){
|
|
|
739
751
|
}else if(m.type==='loadError'){
|
|
740
752
|
showSequenceLoadFailure(m.message||'The sequence could not be loaded.');
|
|
741
753
|
}else if(m.type==='kspaceData'){
|
|
742
|
-
|
|
754
|
+
try{
|
|
755
|
+
if(!m.kspace)throw new Error('the trajectory did not arrive from the extension host');
|
|
756
|
+
applySerializedKspace(m.kspace);
|
|
757
|
+
}catch(err){
|
|
758
|
+
finishDangerousKspaceCalculation(err&&err.message||String(err));drawKs();return;
|
|
759
|
+
}
|
|
760
|
+
finishDangerousKspaceCalculation(null);drawKs();
|
|
743
761
|
SeqEyesPanel.showKspaceIfClosed();
|
|
744
762
|
SeqEyesPanel.refreshKspace();
|
|
745
763
|
}else if(m.type==='kspaceError'){
|
|
@@ -2387,6 +2405,11 @@ if(systemThemeQuery){
|
|
|
2387
2405
|
WebGL state
|
|
2388
2406
|
═══════════════════════════════════════════════════════════════════════ */
|
|
2389
2407
|
var gl=null, glProgram=null, glBuf=null, glN=0;
|
|
2408
|
+
/* Why the GPU layer has nothing to draw, or null when it is healthy. A
|
|
2409
|
+
failure here used to be a console warning at most, so the panel opened
|
|
2410
|
+
with its axes and no points — indistinguishable from a sequence whose
|
|
2411
|
+
trajectory never arrived. */
|
|
2412
|
+
var kGpuFailure=null;
|
|
2390
2413
|
var glAttribPos=-1, glAttribTime=-1;
|
|
2391
2414
|
var glU_cy=-1,glU_sy=-1,glU_cx=-1,glU_sx=-1,glU_pan=-1,glU_scale=-1;
|
|
2392
2415
|
var glU_halfRes=-1,glU_tMin=-1,glU_tMax=-1,glU_dot=-1,glU_color=-1;
|
|
@@ -2399,7 +2422,18 @@ function initWebGL(){
|
|
|
2399
2422
|
var c=document.getElementById("kg");
|
|
2400
2423
|
gl=c.getContext("webgl2",{antialias:true,alpha:true,premultipliedAlpha:false})
|
|
2401
2424
|
||c.getContext("webgl",{antialias:true,alpha:true,premultipliedAlpha:false});
|
|
2402
|
-
if(!gl){
|
|
2425
|
+
if(!gl){
|
|
2426
|
+
console.warn("[SeqEyes] WebGL unavailable");
|
|
2427
|
+
kGpuFailure="this viewer has no WebGL context, so K\u2011space points cannot be drawn";
|
|
2428
|
+
return false;
|
|
2429
|
+
}
|
|
2430
|
+
// A lost context blanks every later frame in silence; say so instead.
|
|
2431
|
+
c.addEventListener("webglcontextlost",function(ev){
|
|
2432
|
+
ev.preventDefault();
|
|
2433
|
+
gl=null;glProgram=null;glBuf=null;glN=0;
|
|
2434
|
+
kGpuFailure="the graphics context was lost while drawing K\u2011space";
|
|
2435
|
+
if(typeof drawKs==="function")drawKs();
|
|
2436
|
+
},false);
|
|
2403
2437
|
gl.enable(gl.BLEND);gl.blendFunc(gl.SRC_ALPHA,gl.ONE_MINUS_SRC_ALPHA);
|
|
2404
2438
|
|
|
2405
2439
|
var vs=gl.createShader(gl.VERTEX_SHADER), fs=gl.createShader(gl.FRAGMENT_SHADER);
|
|
@@ -2429,12 +2463,12 @@ function initWebGL(){
|
|
|
2429
2463
|
float a=1.0-smoothstep(0.40,0.50,d);\
|
|
2430
2464
|
gl_FragColor=vec4(uColor.rgb,uColor.a*a);\
|
|
2431
2465
|
}');
|
|
2432
|
-
gl.compileShader(vs);if(!gl.getShaderParameter(vs,gl.COMPILE_STATUS)){console.warn("[SeqEyes] VS:",gl.getShaderInfoLog(vs));return false;}
|
|
2433
|
-
gl.compileShader(fs);if(!gl.getShaderParameter(fs,gl.COMPILE_STATUS)){console.warn("[SeqEyes] FS:",gl.getShaderInfoLog(fs));return false;}
|
|
2466
|
+
gl.compileShader(vs);if(!gl.getShaderParameter(vs,gl.COMPILE_STATUS)){kGpuFailure="the K\u2011space vertex shader did not compile";console.warn("[SeqEyes] VS:",gl.getShaderInfoLog(vs));return false;}
|
|
2467
|
+
gl.compileShader(fs);if(!gl.getShaderParameter(fs,gl.COMPILE_STATUS)){kGpuFailure="the K\u2011space fragment shader did not compile";console.warn("[SeqEyes] FS:",gl.getShaderInfoLog(fs));return false;}
|
|
2434
2468
|
glProgram=gl.createProgram();
|
|
2435
2469
|
gl.attachShader(glProgram,vs);gl.attachShader(glProgram,fs);
|
|
2436
2470
|
gl.linkProgram(glProgram);
|
|
2437
|
-
if(!gl.getProgramParameter(glProgram,gl.LINK_STATUS)){console.warn("[SeqEyes] Link:",gl.getProgramInfoLog(glProgram));return false;}
|
|
2471
|
+
if(!gl.getProgramParameter(glProgram,gl.LINK_STATUS)){kGpuFailure="the K\u2011space shader program did not link";console.warn("[SeqEyes] Link:",gl.getProgramInfoLog(glProgram));return false;}
|
|
2438
2472
|
|
|
2439
2473
|
glAttribPos=gl.getAttribLocation(glProgram,"aPos");
|
|
2440
2474
|
glAttribTime=gl.getAttribLocation(glProgram,"aTime");
|
|
@@ -2515,15 +2549,33 @@ function kSpaceWindowRange(times,total,vs,ve){
|
|
|
2515
2549
|
/* ── Upload ADC k‑space data to GPU (called after base64 decode) ────── */
|
|
2516
2550
|
function uploadKSpaceGPU(){
|
|
2517
2551
|
if(!kAdc||!kAdc[0]||kAdc[0].length===0){glN=0;return;}
|
|
2518
|
-
|
|
2552
|
+
kGpuFailure=null;
|
|
2553
|
+
if(!gl&&!initWebGL()){glN=0;return;}
|
|
2519
2554
|
var n=kAdc[0].length;
|
|
2520
|
-
var data
|
|
2555
|
+
var data;
|
|
2556
|
+
try{
|
|
2557
|
+
data=new Float32Array(n*4);
|
|
2558
|
+
}catch(err){
|
|
2559
|
+
glN=0;
|
|
2560
|
+
kGpuFailure="there was not enough memory to stage "+n.toLocaleString()+" K\u2011space points for the GPU";
|
|
2561
|
+
return;
|
|
2562
|
+
}
|
|
2521
2563
|
var ax=kAdc[0],ay=kAdc[1],az=kAdc[2],at=kAdcTime;
|
|
2522
2564
|
for(var i=0;i<n;i++){var j=i*4;data[j]=ax[i];data[j+1]=ay[i];data[j+2]=az[i];data[j+3]=at[i];}
|
|
2523
2565
|
if(glBuf)gl.deleteBuffer(glBuf);
|
|
2524
2566
|
glBuf=gl.createBuffer();
|
|
2525
2567
|
gl.bindBuffer(gl.ARRAY_BUFFER,glBuf);
|
|
2568
|
+
while(gl.getError()!==gl.NO_ERROR){/* drain errors from earlier frames */}
|
|
2526
2569
|
gl.bufferData(gl.ARRAY_BUFFER,data,gl.STATIC_DRAW);
|
|
2570
|
+
// An upload the driver refused reports here and nowhere else: every later
|
|
2571
|
+
// draw call simply renders nothing.
|
|
2572
|
+
var uploadError=gl.getError();
|
|
2573
|
+
if(uploadError!==gl.NO_ERROR){
|
|
2574
|
+
glN=0;
|
|
2575
|
+
kGpuFailure=(uploadError===gl.OUT_OF_MEMORY?"the graphics driver ran out of memory uploading ":
|
|
2576
|
+
"the graphics driver rejected ")+n.toLocaleString()+" K\u2011space points";
|
|
2577
|
+
return;
|
|
2578
|
+
}
|
|
2527
2579
|
glN=n;
|
|
2528
2580
|
// Compute bounds once
|
|
2529
2581
|
var xmin=Infinity,xmax=-Infinity,ymin=Infinity,ymax=-Infinity,zmin=Infinity,zmax=-Infinity;
|
|
@@ -2921,6 +2973,19 @@ function drawKs_core(W,H,dpr){
|
|
|
2921
2973
|
if(!kAdc||!kAdcTime||!kAdc[0]||kAdc[0].length===0){
|
|
2922
2974
|
ctx.fillStyle="#f00";ctx.font="11px monospace";ctx.fillText("NO ADC data",10,20);return;
|
|
2923
2975
|
}
|
|
2976
|
+
/* ADC samples are loaded but the GPU layer cannot draw them. Without this
|
|
2977
|
+
the panel shows its axes over an empty field, which looks like a
|
|
2978
|
+
sequence that simply has no trajectory. */
|
|
2979
|
+
// The panel may have been closed when the samples arrived, leaving the
|
|
2980
|
+
// canvas unsized; retry once before calling it a failure.
|
|
2981
|
+
if(!kGpuFailure&&(!gl||!glBuf||glN===0))uploadKSpaceGPU();
|
|
2982
|
+
if(kGpuFailure||!gl||!glBuf||glN===0){
|
|
2983
|
+
ctx.fillStyle="#f00";ctx.font="11px monospace";
|
|
2984
|
+
ctx.fillText("K\u2011space cannot be drawn: "+
|
|
2985
|
+
(kGpuFailure||"the GPU layer holds no points"),10,20);
|
|
2986
|
+
ctx.fillText(kAdc[0].length.toLocaleString()+" ADC samples were received.",10,36);
|
|
2987
|
+
return;
|
|
2988
|
+
}
|
|
2924
2989
|
var adcX=kAdc[0],adcY=kAdc[1],adcZ=kAdc[2], nAdc=adcX.length;
|
|
2925
2990
|
|
|
2926
2991
|
// ── Use cached bounds (computed once in uploadKSpaceGPU) ──────────
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|