seqeyes-python 0.3.11__tar.gz → 0.3.13__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.11 → seqeyes_python-0.3.13}/PKG-INFO +1 -1
- {seqeyes_python-0.3.11 → seqeyes_python-0.3.13}/pyproject.toml +1 -1
- {seqeyes_python-0.3.11 → seqeyes_python-0.3.13}/src/seqeyes/__init__.py +1 -1
- {seqeyes_python-0.3.11 → seqeyes_python-0.3.13}/src/seqeyes/resources/pulseq-bundle.js +1 -1
- {seqeyes_python-0.3.11 → seqeyes_python-0.3.13}/src/seqeyes/resources/webview-bundle.js +90 -8
- {seqeyes_python-0.3.11 → seqeyes_python-0.3.13}/.gitignore +0 -0
- {seqeyes_python-0.3.11 → seqeyes_python-0.3.13}/README.md +0 -0
- {seqeyes_python-0.3.11 → seqeyes_python-0.3.13}/examples/demo.ipynb +0 -0
- {seqeyes_python-0.3.11 → seqeyes_python-0.3.13}/examples/demo.py +0 -0
- {seqeyes_python-0.3.11 → seqeyes_python-0.3.13}/src/seqeyes/_plot.py +0 -0
- {seqeyes_python-0.3.11 → seqeyes_python-0.3.13}/src/seqeyes/_renderer.py +0 -0
- {seqeyes_python-0.3.11 → seqeyes_python-0.3.13}/src/seqeyes/resources/seqeyes-labels.js +0 -0
- {seqeyes_python-0.3.11 → seqeyes_python-0.3.13}/src/seqeyes/resources/viewer.html +0 -0
- {seqeyes_python-0.3.11 → seqeyes_python-0.3.13}/tests/conftest.py +0 -0
- {seqeyes_python-0.3.11 → seqeyes_python-0.3.13}/tests/debug_full.html +0 -0
- {seqeyes_python-0.3.11 → seqeyes_python-0.3.13}/tests/debug_minimal.html +0 -0
- {seqeyes_python-0.3.11 → seqeyes_python-0.3.13}/tests/debug_viewer.html +0 -0
- {seqeyes_python-0.3.11 → seqeyes_python-0.3.13}/tests/debug_viewer.py +0 -0
- {seqeyes_python-0.3.11 → seqeyes_python-0.3.13}/tests/integration_test.py +0 -0
- {seqeyes_python-0.3.11 → seqeyes_python-0.3.13}/tests/test_plot.py +0 -0
- {seqeyes_python-0.3.11 → seqeyes_python-0.3.13}/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.13
|
|
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.13"
|
|
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"}
|
|
@@ -1804,7 +1804,11 @@ function drawMinimap(){
|
|
|
1804
1804
|
|
|
1805
1805
|
// ── Blit cached block bands ──
|
|
1806
1806
|
if(mmCache){
|
|
1807
|
-
|
|
1807
|
+
// The cache is built at device resolution while this context carries a
|
|
1808
|
+
// scale(dpr) transform, so it must be given the CSS-pixel destination
|
|
1809
|
+
// size. Without it the cache draws dpr times too wide and only the
|
|
1810
|
+
// leading 1/dpr of the sequence stays on the strip.
|
|
1811
|
+
mmCtx.drawImage(mmCache,0,0,W,H);
|
|
1808
1812
|
}else{
|
|
1809
1813
|
mmCtx.fillStyle=s.getPropertyValue('--trbg').trim();mmCtx.fillRect(0,0,W,H);
|
|
1810
1814
|
}
|
|
@@ -3149,6 +3153,16 @@ if(systemThemeQuery){
|
|
|
3149
3153
|
WebGL state
|
|
3150
3154
|
═══════════════════════════════════════════════════════════════════════ */
|
|
3151
3155
|
var gl=null, glProgram=null, glBuf=null, glN=0;
|
|
3156
|
+
/* Decimation index buffer. WebGL caps a vertex attribute stride at 255 bytes
|
|
3157
|
+
and this buffer is 16 bytes a point, so `16*stride` is rejected outright for
|
|
3158
|
+
any stride above 15 — silently, since a failed vertexAttribPointer only sets
|
|
3159
|
+
an error nobody reads. The attribute then keeps its previous unstrided
|
|
3160
|
+
setup while the draw range is still scaled down, so what reaches the screen
|
|
3161
|
+
is one contiguous run of the acquisition rather than a thinned cloud: a slab
|
|
3162
|
+
at one end of the encode. Selecting the same points through an index buffer
|
|
3163
|
+
has no such limit. */
|
|
3164
|
+
var glIdxBuf=null, glUint32Indices=false;
|
|
3165
|
+
var _idxFirst=-1, _idxCount=-1, _idxStride=-1, _idxN=0;
|
|
3152
3166
|
/* Why the GPU layer has nothing to draw, or null when it is healthy. A
|
|
3153
3167
|
failure here used to be a console warning at most, so the panel opened
|
|
3154
3168
|
with its axes and no points — indistinguishable from a sequence whose
|
|
@@ -3178,6 +3192,10 @@ function initWebGL(){
|
|
|
3178
3192
|
kGpuFailure="the graphics context was lost while drawing K\u2011space";
|
|
3179
3193
|
if(typeof drawKs==="function")drawKs();
|
|
3180
3194
|
},false);
|
|
3195
|
+
// WebGL2 always has 32-bit indices; WebGL1 needs the extension for a cloud
|
|
3196
|
+
// longer than 65535 points, which every sequence reaching the stride has.
|
|
3197
|
+
glUint32Indices=(typeof WebGL2RenderingContext!=='undefined'&&gl instanceof WebGL2RenderingContext)
|
|
3198
|
+
||!!gl.getExtension('OES_element_index_uint');
|
|
3181
3199
|
gl.enable(gl.BLEND);gl.blendFunc(gl.SRC_ALPHA,gl.ONE_MINUS_SRC_ALPHA);
|
|
3182
3200
|
|
|
3183
3201
|
var vs=gl.createShader(gl.VERTEX_SHADER), fs=gl.createShader(gl.FRAGMENT_SHADER);
|
|
@@ -3245,6 +3263,23 @@ var KSPACE_MOVING_POINT_TARGET = 600000;
|
|
|
3245
3263
|
*
|
|
3246
3264
|
* Returns 1 below the target, so narrow views and modest sequences are drawn
|
|
3247
3265
|
* exactly even in motion.
|
|
3266
|
+
*
|
|
3267
|
+
* The stride is rounded up to a prime, which costs a handful of points and
|
|
3268
|
+
* avoids aliasing with the readout. Sampling every k-th sample of a sequence
|
|
3269
|
+
* whose readouts are R samples long only ever lands on R/gcd(k,R) of the R
|
|
3270
|
+
* positions within a readout — and on the same ones in every readout. When an
|
|
3271
|
+
* axis is swept during the readout, as in a wave or CAIPI sequence, those
|
|
3272
|
+
* positions are k-space planes, so a shared factor drops whole planes rather
|
|
3273
|
+
* than thinning the cloud evenly. gre_3d_wave_FC.seq sweeps kz across its
|
|
3274
|
+
* full range inside each 1000-sample readout; at stride 56, gcd 8 with 1000
|
|
3275
|
+
* left 125 of the 1000 positions and 12% of the planes. An odd prime shares
|
|
3276
|
+
* no factor with a readout length it does not divide, which restores 96% —
|
|
3277
|
+
* indistinguishable from sampling at random.
|
|
3278
|
+
*
|
|
3279
|
+
* Odd, not merely prime: readout lengths are usually even, so a stride of 2
|
|
3280
|
+
* would reach only every second position and lose half the planes. The point
|
|
3281
|
+
* target is a ceiling rather than a goal, so rounding 2 up to 3 draws somewhat
|
|
3282
|
+
* fewer points and stays well inside budget.
|
|
3248
3283
|
*/
|
|
3249
3284
|
function kSpaceMovingStride(count){
|
|
3250
3285
|
// The target is overridable so the settle-exactness guarantee can be tested
|
|
@@ -3252,7 +3287,40 @@ function kSpaceMovingStride(count){
|
|
|
3252
3287
|
var target=(typeof window!=='undefined'&&window.__kMovingPointTarget>0)
|
|
3253
3288
|
?window.__kMovingPointTarget:KSPACE_MOVING_POINT_TARGET;
|
|
3254
3289
|
if(!(count>target))return 1;
|
|
3255
|
-
return Math.ceil(count/target);
|
|
3290
|
+
return nextOddPrimeAtLeast(Math.ceil(count/target));
|
|
3291
|
+
}
|
|
3292
|
+
|
|
3293
|
+
/** Smallest odd prime >= n, for n small enough that trial division is free. */
|
|
3294
|
+
function nextOddPrimeAtLeast(n){
|
|
3295
|
+
var candidate=(n>3)?n:3;
|
|
3296
|
+
if(candidate%2===0)candidate++;
|
|
3297
|
+
while(!isSmallPrime(candidate))candidate+=2;
|
|
3298
|
+
return candidate;
|
|
3299
|
+
}
|
|
3300
|
+
|
|
3301
|
+
function isSmallPrime(n){
|
|
3302
|
+
if(n<2)return false;
|
|
3303
|
+
if(n%2===0)return n===2;
|
|
3304
|
+
for(var d=3;d*d<=n;d+=2)if(n%d===0)return false;
|
|
3305
|
+
return true;
|
|
3306
|
+
}
|
|
3307
|
+
|
|
3308
|
+
/**
|
|
3309
|
+
* Upload indices selecting every `stride`-th point of [first, first+count),
|
|
3310
|
+
* returning how many there are. Cached on the range, so a drag uploads once
|
|
3311
|
+
* rather than per frame.
|
|
3312
|
+
*/
|
|
3313
|
+
function kSpaceStrideIndices(first,count,stride){
|
|
3314
|
+
if(_idxFirst===first&&_idxCount===count&&_idxStride===stride)return _idxN;
|
|
3315
|
+
var n=Math.floor(count/stride);
|
|
3316
|
+
_idxFirst=first;_idxCount=count;_idxStride=stride;_idxN=n;
|
|
3317
|
+
if(n<=0)return 0;
|
|
3318
|
+
var idx=new Uint32Array(n);
|
|
3319
|
+
for(var i=0;i<n;i++)idx[i]=first+i*stride;
|
|
3320
|
+
if(!glIdxBuf)glIdxBuf=gl.createBuffer();
|
|
3321
|
+
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER,glIdxBuf);
|
|
3322
|
+
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER,idx,gl.DYNAMIC_DRAW);
|
|
3323
|
+
return n;
|
|
3256
3324
|
}
|
|
3257
3325
|
|
|
3258
3326
|
/** True while a drag, touch gesture, or camera easing is in progress. */
|
|
@@ -3321,6 +3389,7 @@ function uploadKSpaceGPU(){
|
|
|
3321
3389
|
return;
|
|
3322
3390
|
}
|
|
3323
3391
|
glN=n;
|
|
3392
|
+
_idxStride=-1; // the indices point into the buffer just replaced
|
|
3324
3393
|
// Compute bounds once
|
|
3325
3394
|
var xmin=Infinity,xmax=-Infinity,ymin=Infinity,ymax=-Infinity,zmin=Infinity,zmax=-Infinity;
|
|
3326
3395
|
for(var a=0;a<n;a++){var xi=ax[a],yi=ay[a],zi=az[a];if(isFinite(xi)){if(xi<xmin)xmin=xi;if(xi>xmax)xmax=xi;}if(isFinite(yi)){if(yi<ymin)ymin=yi;if(yi>ymax)ymax=yi;}if(isFinite(zi)){if(zi<zmin)zmin=zi;if(zi>zmax)zmax=zi;}}
|
|
@@ -3779,17 +3848,24 @@ function drawKs_core(W,H,dpr){
|
|
|
3779
3848
|
|
|
3780
3849
|
var win=kSpaceWindowRange(kAdcTime,glN,vs,ve);
|
|
3781
3850
|
var stride=kSpaceCameraMoving()?kSpaceMovingStride(win.count):1;
|
|
3851
|
+
if(stride>1&&!glUint32Indices)stride=1; // draw it all rather than wrongly
|
|
3782
3852
|
|
|
3783
3853
|
gl.bindBuffer(gl.ARRAY_BUFFER,glBuf);
|
|
3784
3854
|
gl.enableVertexAttribArray(glAttribPos);
|
|
3785
|
-
gl.vertexAttribPointer(glAttribPos,3,gl.FLOAT,false,16
|
|
3855
|
+
gl.vertexAttribPointer(glAttribPos,3,gl.FLOAT,false,16,0);
|
|
3786
3856
|
gl.enableVertexAttribArray(glAttribTime);
|
|
3787
|
-
gl.vertexAttribPointer(glAttribTime,1,gl.FLOAT,false,16
|
|
3857
|
+
gl.vertexAttribPointer(glAttribTime,1,gl.FLOAT,false,16,12);
|
|
3788
3858
|
|
|
3789
3859
|
kSpaceTrajectoryDrawCount++;
|
|
3790
|
-
|
|
3791
|
-
|
|
3792
|
-
|
|
3860
|
+
if(stride>1){
|
|
3861
|
+
var drawn=kSpaceStrideIndices(win.first,win.count,stride);
|
|
3862
|
+
if(drawn>0){
|
|
3863
|
+
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER,glIdxBuf);
|
|
3864
|
+
gl.drawElements(gl.POINTS,drawn,gl.UNSIGNED_INT,0);
|
|
3865
|
+
}
|
|
3866
|
+
}else if(win.count>0){
|
|
3867
|
+
gl.drawArrays(gl.POINTS,win.first,win.count);
|
|
3868
|
+
}
|
|
3793
3869
|
}
|
|
3794
3870
|
|
|
3795
3871
|
drawKsOverlay(W,H,dpr,cs);
|
|
@@ -7269,7 +7345,13 @@ function placeTooltip(cx,cy){
|
|
|
7269
7345
|
var r=tt.getBoundingClientRect(),vw=window.innerWidth,vh=window.innerHeight;
|
|
7270
7346
|
var left=Math.min(Math.max(pad,cx+15),Math.max(pad,vw-r.width-pad));
|
|
7271
7347
|
var top=cy+12;if(top+r.height+pad>vh)top=cy-r.height-12;if(top<pad)top=pad;
|
|
7272
|
-
|
|
7348
|
+
/* `left`/`top` are client coordinates, but #tt is absolutely positioned
|
|
7349
|
+
inside #cc, so its style offsets are measured from #cc's box. Writing
|
|
7350
|
+
client coordinates straight out put the tooltip #cc's own offset too low
|
|
7351
|
+
and made the bottom flip fire that far past the real bottom of the
|
|
7352
|
+
window. */
|
|
7353
|
+
var host=tt.offsetParent?tt.offsetParent.getBoundingClientRect():{left:0,top:0};
|
|
7354
|
+
tt.style.left=(left-host.left)+'px';tt.style.top=(top-host.top)+'px';
|
|
7273
7355
|
}
|
|
7274
7356
|
function fmtFlipAngle(value){
|
|
7275
7357
|
if(!isFinite(value))return null;
|
|
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
|
|
File without changes
|