seqeyes-python 0.3.7__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.
Files changed (20) hide show
  1. {seqeyes_python-0.3.7 → seqeyes_python-0.3.8}/PKG-INFO +1 -1
  2. {seqeyes_python-0.3.7 → seqeyes_python-0.3.8}/pyproject.toml +1 -1
  3. {seqeyes_python-0.3.7 → seqeyes_python-0.3.8}/src/seqeyes/__init__.py +1 -1
  4. {seqeyes_python-0.3.7 → seqeyes_python-0.3.8}/src/seqeyes/resources/pulseq-bundle.js +1 -1
  5. {seqeyes_python-0.3.7 → seqeyes_python-0.3.8}/src/seqeyes/resources/webview-bundle.js +77 -12
  6. {seqeyes_python-0.3.7 → seqeyes_python-0.3.8}/.gitignore +0 -0
  7. {seqeyes_python-0.3.7 → seqeyes_python-0.3.8}/README.md +0 -0
  8. {seqeyes_python-0.3.7 → seqeyes_python-0.3.8}/examples/demo.ipynb +0 -0
  9. {seqeyes_python-0.3.7 → seqeyes_python-0.3.8}/examples/demo.py +0 -0
  10. {seqeyes_python-0.3.7 → seqeyes_python-0.3.8}/src/seqeyes/_plot.py +0 -0
  11. {seqeyes_python-0.3.7 → seqeyes_python-0.3.8}/src/seqeyes/_renderer.py +0 -0
  12. {seqeyes_python-0.3.7 → seqeyes_python-0.3.8}/src/seqeyes/resources/viewer.html +0 -0
  13. {seqeyes_python-0.3.7 → seqeyes_python-0.3.8}/tests/conftest.py +0 -0
  14. {seqeyes_python-0.3.7 → seqeyes_python-0.3.8}/tests/debug_full.html +0 -0
  15. {seqeyes_python-0.3.7 → seqeyes_python-0.3.8}/tests/debug_minimal.html +0 -0
  16. {seqeyes_python-0.3.7 → seqeyes_python-0.3.8}/tests/debug_viewer.html +0 -0
  17. {seqeyes_python-0.3.7 → seqeyes_python-0.3.8}/tests/debug_viewer.py +0 -0
  18. {seqeyes_python-0.3.7 → seqeyes_python-0.3.8}/tests/integration_test.py +0 -0
  19. {seqeyes_python-0.3.7 → seqeyes_python-0.3.8}/tests/test_plot.py +0 -0
  20. {seqeyes_python-0.3.7 → 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.7
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"
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"}
@@ -29,4 +29,4 @@ from seqeyes._plot import set, reset
29
29
  from seqeyes._renderer import SeqEyesViewer
30
30
 
31
31
  __all__ = ["set", "reset", "SeqEyesViewer"]
32
- __version__ = "0.3.7"
32
+ __version__ = "0.3.8"
@@ -87,7 +87,7 @@ var Pulseq = (() => {
87
87
  });
88
88
 
89
89
  // package.json
90
- var version = "0.3.7";
90
+ var version = "0.3.8";
91
91
 
92
92
  // src/pulseq/decompressor.ts
93
93
  function decompressShape(compressed, numSamples) {
@@ -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
- var n=payload.nAdc||0;
645
- if(n>0&&payload.axb){
646
- kAdc=[decodeB64F32(payload.axb,n),decodeB64F32(payload.ayb,n),decodeB64F32(payload.azb,n)];
647
- kAdcTime=decodeB64F32(payload.tab,n);uploadKSpaceGPU();
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
- applySerializedKspace(m.kspace);finishDangerousKspaceCalculation(null);drawKs();
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){console.warn("[SeqEyes] WebGL unavailable");return false;}
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
- if(!gl&&!initWebGL())return;
2552
+ kGpuFailure=null;
2553
+ if(!gl&&!initWebGL()){glN=0;return;}
2519
2554
  var n=kAdc[0].length;
2520
- var data=new Float32Array(n*4);
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