seqeyes-python 0.3.4__tar.gz → 0.3.5__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.4 → seqeyes_python-0.3.5}/PKG-INFO +1 -1
  2. {seqeyes_python-0.3.4 → seqeyes_python-0.3.5}/pyproject.toml +1 -1
  3. {seqeyes_python-0.3.4 → seqeyes_python-0.3.5}/src/seqeyes/__init__.py +1 -1
  4. {seqeyes_python-0.3.4 → seqeyes_python-0.3.5}/src/seqeyes/resources/pulseq-bundle.js +1 -1
  5. {seqeyes_python-0.3.4 → seqeyes_python-0.3.5}/src/seqeyes/resources/webview-bundle.js +96 -6
  6. {seqeyes_python-0.3.4 → seqeyes_python-0.3.5}/.gitignore +0 -0
  7. {seqeyes_python-0.3.4 → seqeyes_python-0.3.5}/README.md +0 -0
  8. {seqeyes_python-0.3.4 → seqeyes_python-0.3.5}/examples/demo.ipynb +0 -0
  9. {seqeyes_python-0.3.4 → seqeyes_python-0.3.5}/examples/demo.py +0 -0
  10. {seqeyes_python-0.3.4 → seqeyes_python-0.3.5}/src/seqeyes/_plot.py +0 -0
  11. {seqeyes_python-0.3.4 → seqeyes_python-0.3.5}/src/seqeyes/_renderer.py +0 -0
  12. {seqeyes_python-0.3.4 → seqeyes_python-0.3.5}/src/seqeyes/resources/viewer.html +0 -0
  13. {seqeyes_python-0.3.4 → seqeyes_python-0.3.5}/tests/conftest.py +0 -0
  14. {seqeyes_python-0.3.4 → seqeyes_python-0.3.5}/tests/debug_full.html +0 -0
  15. {seqeyes_python-0.3.4 → seqeyes_python-0.3.5}/tests/debug_minimal.html +0 -0
  16. {seqeyes_python-0.3.4 → seqeyes_python-0.3.5}/tests/debug_viewer.html +0 -0
  17. {seqeyes_python-0.3.4 → seqeyes_python-0.3.5}/tests/debug_viewer.py +0 -0
  18. {seqeyes_python-0.3.4 → seqeyes_python-0.3.5}/tests/integration_test.py +0 -0
  19. {seqeyes_python-0.3.4 → seqeyes_python-0.3.5}/tests/test_plot.py +0 -0
  20. {seqeyes_python-0.3.4 → seqeyes_python-0.3.5}/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.4
3
+ Version: 0.3.5
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.4"
7
+ version = "0.3.5"
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.4"
32
+ __version__ = "0.3.5"
@@ -97,7 +97,7 @@ var Pulseq = (() => {
97
97
  });
98
98
 
99
99
  // package.json
100
- var version = "0.3.4";
100
+ var version = "0.3.5";
101
101
 
102
102
  // src/pulseq/decompressor.ts
103
103
  function decompressShape(compressed, numSamples) {
@@ -2281,6 +2281,13 @@ var kCx=0, kCy=0, kCz=0, kScl=1; // view center & zoom
2281
2281
  var kAutoFit=true;
2282
2282
  var kRotX=-0.5, kRotY=0.7; // default 3D perspective
2283
2283
  var kDragging=false, kDragPrev=null, kDragBtn=0;
2284
+ /**
2285
+ * Pending coalesced drag redraw. A drag updates the rotation on every
2286
+ * mousemove but only needs one draw per displayed frame; the frames in between
2287
+ * are overwritten before anyone sees them. The inertial path is already
2288
+ * requestAnimationFrame-driven, so this only brings the drag into line with it.
2289
+ */
2290
+ var _kDragRaf=0;
2284
2291
  var kCanvas=document.getElementById("kc"), kCtx=kCanvas.getContext("2d");
2285
2292
  var kDotSize=2, kUnit="cyc"; // cyc=1/m, rad=rad/m
2286
2293
 
@@ -2307,6 +2314,7 @@ function startKSpaceAnim() {
2307
2314
  // Snap to exact targets
2308
2315
  kRotX=_tRotX; kRotY=_tRotY; kScl=_tScl; kCx=_tCx; kCy=_tCy; kCz=_tCz;
2309
2316
  _kAnimId = null;
2317
+ drawKsFast(); // no longer moving: redraw the complete cloud
2310
2318
  }
2311
2319
  }
2312
2320
  _kAnimId = requestAnimationFrame(tick);
@@ -2427,7 +2435,72 @@ function initWebGL(){
2427
2435
  glU_halfRes=gl.getUniformLocation(glProgram,"uHalfRes");glU_dot=gl.getUniformLocation(glProgram,"uDot");
2428
2436
  glU_tMin=gl.getUniformLocation(glProgram,"uTMin");glU_tMax=gl.getUniformLocation(glProgram,"uTMax");
2429
2437
  glU_color=gl.getUniformLocation(glProgram,"uColor");
2430
- return true;
2438
+ return !!(kDragging||_kTouchActive||_kAnimId);
2439
+ }
2440
+
2441
+ /** Points to keep on screen while the camera is moving. */
2442
+ var KSPACE_MOVING_POINT_TARGET = 600000;
2443
+
2444
+ /**
2445
+ * Stride to draw the cloud with while the camera is moving.
2446
+ *
2447
+ * Window culling cannot help when the whole sequence is in view: every point
2448
+ * really is visible, and 6.4 million of them onto a few hundred thousand
2449
+ * pixels costs about a second a frame. While the camera moves, every k-th
2450
+ * point is drawn instead.
2451
+ *
2452
+ * This is a true subset — each drawn point is a real ADC k-space location, and
2453
+ * nothing is interpolated or invented. The moment the drag or the easing
2454
+ * settles the full set is redrawn, so the image actually being read is always
2455
+ * exact; only the motion is cheaper. A subset also needs no extra memory: the
2456
+ * buffer is interleaved x,y,z,t at 16 bytes, so widening the attribute stride
2457
+ * to 16k selects every k-th vertex with no CPU work and no second upload.
2458
+ *
2459
+ * Returns 1 below the target, so narrow views and modest sequences are drawn
2460
+ * exactly even in motion.
2461
+ */
2462
+ function kSpaceMovingStride(count){
2463
+ // The target is overridable so the settle-exactness guarantee can be tested
2464
+ // against a fixture small enough to ship; no repository fixture exceeds it.
2465
+ var target=(typeof window!=='undefined'&&window.__kMovingPointTarget>0)
2466
+ ?window.__kMovingPointTarget:KSPACE_MOVING_POINT_TARGET;
2467
+ if(!(count>target))return 1;
2468
+ return Math.ceil(count/target);
2469
+ }
2470
+
2471
+ /** True while a drag, touch gesture, or camera easing is in progress. */
2472
+ function kSpaceCameraMoving(){
2473
+ return !!(kDragging||_kTouchActive||_kAnimId);
2474
+ }
2475
+
2476
+ /**
2477
+ * First index and count of the ADC points whose time lies in `[vs, ve]`.
2478
+ *
2479
+ * `kAdcTime` ascends by construction — src/pulseq/kspace.ts fills it block by
2480
+ * block in sequence order — and `uploadKSpaceGPU` writes the GPU buffer in that
2481
+ * same order, so the visible window is one contiguous run of vertices.
2482
+ *
2483
+ * Drawing only that run is not a reduction. The vertex shader already rejects
2484
+ * everything outside the window with `gl_PointSize = 0` and a `discard`, so the
2485
+ * pixels are identical; the GPU simply stops transforming millions of points in
2486
+ * order to throw them away. A 1.1 ms view of a 16 s sequence was submitting
2487
+ * 3.38 million vertices to draw about 218 of them.
2488
+ *
2489
+ * The range is padded by one point on each side and the shader keeps its own
2490
+ * time test, so an off-by-one here cannot change what is drawn.
2491
+ *
2492
+ * Pure and parameterised on purpose: the standalone app runs its renderer in an
2493
+ * IIFE with its own state and calls this global. Do not copy it into
2494
+ * web/index.html — a copy there would shadow this one and diverge.
2495
+ */
2496
+ function kSpaceWindowRange(times,total,vs,ve){
2497
+ if(!times||!(total>0)||!(ve>=vs))return{first:0,count:0};
2498
+ var n=Math.min(total,times.length),lo=0,hi=n,mid;
2499
+ while(lo<hi){mid=(lo+hi)>>1;if(times[mid]<vs)lo=mid+1;else hi=mid;}
2500
+ var first=Math.max(0,lo-1);
2501
+ lo=first;hi=n;
2502
+ while(lo<hi){mid=(lo+hi)>>1;if(times[mid]<=ve)lo=mid+1;else hi=mid;}
2503
+ return{first:first,count:Math.max(0,Math.min(n,lo+1)-first)};
2431
2504
  }
2432
2505
 
2433
2506
  /* ── Upload ADC k‑space data to GPU (called after base64 decode) ────── */
@@ -2621,9 +2694,19 @@ window.addEventListener("mousemove",function(e){
2621
2694
  _tCy=kCy; _tCx=kCx; _tCz=kCz; // sync targets
2622
2695
  kAutoFit=false;
2623
2696
  }
2624
- drawKsFast();
2697
+ scheduleKsDragDraw();
2698
+ });
2699
+ window.addEventListener("mouseup",function(){
2700
+ if(!kDragging)return;
2701
+ kDragging=false;kDragPrev=null;
2702
+ drawKsFast(); // settled: replace the moving subset with every point
2625
2703
  });
2626
- window.addEventListener("mouseup",function(){kDragging=false;kDragPrev=null;});
2704
+
2705
+ /** Draw at most once per frame, always from the latest rotation. */
2706
+ function scheduleKsDragDraw(){
2707
+ if(_kDragRaf)return;
2708
+ _kDragRaf=requestAnimationFrame(function(){_kDragRaf=0;drawKsFast();});
2709
+ }
2627
2710
 
2628
2711
  /* ── Touch: 3D k-space viewer ─────────────────────────────────────── */
2629
2712
  var _kTouchActive=false,_kTouchPrev=null,_kTouchPinch0=0,_kTouchMid=null,_kTouchBtn=0;
@@ -2675,6 +2758,7 @@ kCanvas.addEventListener("touchmove",function(e){
2675
2758
  },{passive:false});
2676
2759
  kCanvas.addEventListener("touchend",function(e){
2677
2760
  _kTouchActive=false;_kTouchPrev=null;_kTouchPinch0=0;_kTouchMid=null;
2761
+ if(!e.touches.length)drawKsFast();
2678
2762
  if(e.touches.length===1){
2679
2763
  // Transition from 2‑finger to 1‑finger
2680
2764
  _kTouchActive=true;_kTouchBtn=0;
@@ -2683,6 +2767,7 @@ kCanvas.addEventListener("touchend",function(e){
2683
2767
  });
2684
2768
  kCanvas.addEventListener("touchcancel",function(){
2685
2769
  _kTouchActive=false;_kTouchPrev=null;_kTouchPinch0=0;_kTouchMid=null;
2770
+ drawKsFast();
2686
2771
  });
2687
2772
 
2688
2773
  /* ── Resize handle ───────────────────────────────────────────────────── */
@@ -2869,14 +2954,19 @@ function drawKs_core(W,H,dpr){
2869
2954
  var rgb=parseCSSColor(ac);
2870
2955
  gl.uniform4f(glU_color,rgb[0],rgb[1],rgb[2],0.85);
2871
2956
 
2957
+ var win=kSpaceWindowRange(kAdcTime,glN,vs,ve);
2958
+ var stride=kSpaceCameraMoving()?kSpaceMovingStride(win.count):1;
2959
+
2872
2960
  gl.bindBuffer(gl.ARRAY_BUFFER,glBuf);
2873
2961
  gl.enableVertexAttribArray(glAttribPos);
2874
- gl.vertexAttribPointer(glAttribPos,3,gl.FLOAT,false,16,0);
2962
+ gl.vertexAttribPointer(glAttribPos,3,gl.FLOAT,false,16*stride,0);
2875
2963
  gl.enableVertexAttribArray(glAttribTime);
2876
- gl.vertexAttribPointer(glAttribTime,1,gl.FLOAT,false,16,12);
2964
+ gl.vertexAttribPointer(glAttribTime,1,gl.FLOAT,false,16*stride,12);
2877
2965
 
2878
2966
  kSpaceTrajectoryDrawCount++;
2879
- gl.drawArrays(gl.POINTS,0,glN);
2967
+ // Stride reindexes the buffer, so the window offset and count scale with it.
2968
+ var drawFirst=Math.floor(win.first/stride),drawCount=Math.floor(win.count/stride);
2969
+ if(drawCount>0)gl.drawArrays(gl.POINTS,drawFirst,drawCount);
2880
2970
  }
2881
2971
 
2882
2972
  drawKsOverlay(W,H,dpr,cs);
File without changes