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.
- {seqeyes_python-0.3.4 → seqeyes_python-0.3.5}/PKG-INFO +1 -1
- {seqeyes_python-0.3.4 → seqeyes_python-0.3.5}/pyproject.toml +1 -1
- {seqeyes_python-0.3.4 → seqeyes_python-0.3.5}/src/seqeyes/__init__.py +1 -1
- {seqeyes_python-0.3.4 → seqeyes_python-0.3.5}/src/seqeyes/resources/pulseq-bundle.js +1 -1
- {seqeyes_python-0.3.4 → seqeyes_python-0.3.5}/src/seqeyes/resources/webview-bundle.js +96 -6
- {seqeyes_python-0.3.4 → seqeyes_python-0.3.5}/.gitignore +0 -0
- {seqeyes_python-0.3.4 → seqeyes_python-0.3.5}/README.md +0 -0
- {seqeyes_python-0.3.4 → seqeyes_python-0.3.5}/examples/demo.ipynb +0 -0
- {seqeyes_python-0.3.4 → seqeyes_python-0.3.5}/examples/demo.py +0 -0
- {seqeyes_python-0.3.4 → seqeyes_python-0.3.5}/src/seqeyes/_plot.py +0 -0
- {seqeyes_python-0.3.4 → seqeyes_python-0.3.5}/src/seqeyes/_renderer.py +0 -0
- {seqeyes_python-0.3.4 → seqeyes_python-0.3.5}/src/seqeyes/resources/viewer.html +0 -0
- {seqeyes_python-0.3.4 → seqeyes_python-0.3.5}/tests/conftest.py +0 -0
- {seqeyes_python-0.3.4 → seqeyes_python-0.3.5}/tests/debug_full.html +0 -0
- {seqeyes_python-0.3.4 → seqeyes_python-0.3.5}/tests/debug_minimal.html +0 -0
- {seqeyes_python-0.3.4 → seqeyes_python-0.3.5}/tests/debug_viewer.html +0 -0
- {seqeyes_python-0.3.4 → seqeyes_python-0.3.5}/tests/debug_viewer.py +0 -0
- {seqeyes_python-0.3.4 → seqeyes_python-0.3.5}/tests/integration_test.py +0 -0
- {seqeyes_python-0.3.4 → seqeyes_python-0.3.5}/tests/test_plot.py +0 -0
- {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.
|
|
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.
|
|
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"}
|
|
@@ -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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|