seqeyes-python 0.3.12__tar.gz → 0.3.14__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.12 → seqeyes_python-0.3.14}/PKG-INFO +1 -1
- {seqeyes_python-0.3.12 → seqeyes_python-0.3.14}/pyproject.toml +1 -1
- {seqeyes_python-0.3.12 → seqeyes_python-0.3.14}/src/seqeyes/__init__.py +1 -1
- {seqeyes_python-0.3.12 → seqeyes_python-0.3.14}/src/seqeyes/resources/pulseq-bundle.js +143 -88
- {seqeyes_python-0.3.12 → seqeyes_python-0.3.14}/src/seqeyes/resources/webview-bundle.js +85 -7
- {seqeyes_python-0.3.12 → seqeyes_python-0.3.14}/.gitignore +0 -0
- {seqeyes_python-0.3.12 → seqeyes_python-0.3.14}/README.md +0 -0
- {seqeyes_python-0.3.12 → seqeyes_python-0.3.14}/examples/demo.ipynb +0 -0
- {seqeyes_python-0.3.12 → seqeyes_python-0.3.14}/examples/demo.py +0 -0
- {seqeyes_python-0.3.12 → seqeyes_python-0.3.14}/src/seqeyes/_plot.py +0 -0
- {seqeyes_python-0.3.12 → seqeyes_python-0.3.14}/src/seqeyes/_renderer.py +0 -0
- {seqeyes_python-0.3.12 → seqeyes_python-0.3.14}/src/seqeyes/resources/seqeyes-labels.js +0 -0
- {seqeyes_python-0.3.12 → seqeyes_python-0.3.14}/src/seqeyes/resources/viewer.html +0 -0
- {seqeyes_python-0.3.12 → seqeyes_python-0.3.14}/tests/conftest.py +0 -0
- {seqeyes_python-0.3.12 → seqeyes_python-0.3.14}/tests/debug_full.html +0 -0
- {seqeyes_python-0.3.12 → seqeyes_python-0.3.14}/tests/debug_minimal.html +0 -0
- {seqeyes_python-0.3.12 → seqeyes_python-0.3.14}/tests/debug_viewer.html +0 -0
- {seqeyes_python-0.3.12 → seqeyes_python-0.3.14}/tests/debug_viewer.py +0 -0
- {seqeyes_python-0.3.12 → seqeyes_python-0.3.14}/tests/integration_test.py +0 -0
- {seqeyes_python-0.3.12 → seqeyes_python-0.3.14}/tests/test_plot.py +0 -0
- {seqeyes_python-0.3.12 → seqeyes_python-0.3.14}/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.14
|
|
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.14"
|
|
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"}
|
|
@@ -24,6 +24,7 @@ var Pulseq = (() => {
|
|
|
24
24
|
var pulseq_browser_exports = {};
|
|
25
25
|
__export(pulseq_browser_exports, {
|
|
26
26
|
INTERACTIVE_COMPUTE_LIMITS: () => INTERACTIVE_COMPUTE_LIMITS,
|
|
27
|
+
MAX_KSPACE_OVERVIEW_POINTS: () => MAX_KSPACE_OVERVIEW_POINTS,
|
|
27
28
|
MAX_RF_RESPONSE_BANDS: () => MAX_RF_RESPONSE_BANDS,
|
|
28
29
|
MAX_RF_RESPONSE_FFT_POINTS: () => MAX_RF_RESPONSE_FFT_POINTS,
|
|
29
30
|
MAX_RF_RESPONSE_SAMPLES: () => MAX_RF_RESPONSE_SAMPLES,
|
|
@@ -89,7 +90,7 @@ var Pulseq = (() => {
|
|
|
89
90
|
});
|
|
90
91
|
|
|
91
92
|
// package.json
|
|
92
|
-
var version = "0.3.
|
|
93
|
+
var version = "0.3.14";
|
|
93
94
|
|
|
94
95
|
// src/pulseq/decompressor.ts
|
|
95
96
|
function decompressShape(compressed, numSamples) {
|
|
@@ -2326,8 +2327,8 @@ var Pulseq = (() => {
|
|
|
2326
2327
|
};
|
|
2327
2328
|
}
|
|
2328
2329
|
function estimateKspacePeakMemoryBytes(estimate) {
|
|
2329
|
-
const gridBytes = Math.max(0, estimate.gridCandidatePoints) *
|
|
2330
|
-
const adcAndTransferBytes = Math.max(0, estimate.adcSamples) *
|
|
2330
|
+
const gridBytes = Math.max(0, estimate.gridCandidatePoints) * 11;
|
|
2331
|
+
const adcAndTransferBytes = Math.max(0, estimate.adcSamples) * 43;
|
|
2331
2332
|
return Math.ceil(Math.min(Number.MAX_SAFE_INTEGER, (gridBytes + adcAndTransferBytes) * 1.25));
|
|
2332
2333
|
}
|
|
2333
2334
|
function kspaceExceedsInteractiveBudget(estimate) {
|
|
@@ -3102,9 +3103,20 @@ var Pulseq = (() => {
|
|
|
3102
3103
|
}
|
|
3103
3104
|
}
|
|
3104
3105
|
const gradientSeries = buildGlobalGradientSeries(blocks, GR, totalDuration);
|
|
3105
|
-
|
|
3106
|
+
let candBound = 2;
|
|
3107
|
+
for (const series of gradientSeries) candBound += countSeriesSupport(series, gradientSupport);
|
|
3108
|
+
candBound += excT.length * 3 + refT.length * 2 + adcT.length;
|
|
3109
|
+
if (totalDuration > 0) candBound += Math.max(1, Math.round(totalDuration / GR)) + 1;
|
|
3110
|
+
const cand = new Float64Array(candBound);
|
|
3111
|
+
let candCount = 0;
|
|
3112
|
+
let candOverflow = false;
|
|
3106
3113
|
const pushC = (t) => {
|
|
3107
|
-
if (isFinite(t) && t >= -tacc)
|
|
3114
|
+
if (!(isFinite(t) && t >= -tacc)) return;
|
|
3115
|
+
if (candCount >= cand.length) {
|
|
3116
|
+
candOverflow = true;
|
|
3117
|
+
return;
|
|
3118
|
+
}
|
|
3119
|
+
cand[candCount++] = Math.max(0, tacc * Math.round(t / tacc));
|
|
3108
3120
|
};
|
|
3109
3121
|
for (const series of gradientSeries) collectSeriesSupport(series, gradientSupport, pushC);
|
|
3110
3122
|
for (const t of excT) {
|
|
@@ -3123,23 +3135,17 @@ var Pulseq = (() => {
|
|
|
3123
3135
|
const nS = Math.max(1, Math.round(totalDuration / GR));
|
|
3124
3136
|
for (let i = 0; i <= nS; i++) pushC(i * GR);
|
|
3125
3137
|
}
|
|
3126
|
-
if (
|
|
3127
|
-
|
|
3128
|
-
|
|
3129
|
-
|
|
3130
|
-
|
|
3138
|
+
if (candOverflow) return null;
|
|
3139
|
+
if (candCount === 0) return null;
|
|
3140
|
+
cand.subarray(0, candCount).sort();
|
|
3141
|
+
let kept = 0;
|
|
3142
|
+
for (let i = 0; i < candCount; i++) {
|
|
3143
|
+
if (i === 0 || cand[i] - cand[i - 1] > tacc * 0.5) cand[kept++] = cand[i];
|
|
3131
3144
|
}
|
|
3132
|
-
const N =
|
|
3145
|
+
const N = kept;
|
|
3133
3146
|
if (N < 2) return null;
|
|
3134
3147
|
if (_options?.maxGridPoints && N > _options.maxGridPoints) return null;
|
|
3135
|
-
const
|
|
3136
|
-
const cursors = [0, 0, 0];
|
|
3137
|
-
for (let i = 0; i < N; i++) {
|
|
3138
|
-
const t = grid[i];
|
|
3139
|
-
gx[i] = sampleSeries(gradientSeries[0], t, cursors, 0);
|
|
3140
|
-
gy[i] = sampleSeries(gradientSeries[1], t, cursors, 1);
|
|
3141
|
-
gz[i] = sampleSeries(gradientSeries[2], t, cursors, 2);
|
|
3142
|
-
}
|
|
3148
|
+
const grid = cand.subarray(0, N);
|
|
3143
3149
|
const eIdx = [], rIdx = [];
|
|
3144
3150
|
for (const t of excT) {
|
|
3145
3151
|
const i = timeIdx(t, grid);
|
|
@@ -3149,70 +3155,132 @@ var Pulseq = (() => {
|
|
|
3149
3155
|
const i = timeIdx(t, grid);
|
|
3150
3156
|
if (i >= 0) rIdx.push(i);
|
|
3151
3157
|
}
|
|
3152
|
-
eIdx.sort((
|
|
3153
|
-
rIdx.sort((
|
|
3158
|
+
eIdx.sort((a2, b) => a2 - b);
|
|
3159
|
+
rIdx.sort((a2, b) => a2 - b);
|
|
3154
3160
|
const excitationAt = new Uint8Array(N);
|
|
3155
3161
|
const refocusingAt = new Uint8Array(N);
|
|
3156
3162
|
for (const i of eIdx) excitationAt[i] = 1;
|
|
3157
3163
|
for (const i of rIdx) refocusingAt[i] = 1;
|
|
3158
|
-
const
|
|
3164
|
+
const outCount = _options?.maxTrajectoryPoints && _options.maxTrajectoryPoints > 0 ? Math.min(_options.maxTrajectoryPoints, N) : N;
|
|
3165
|
+
const outStep = N / outCount;
|
|
3166
|
+
const outX = new Float64Array(outCount), outY = new Float64Array(outCount), outZ = new Float64Array(outCount);
|
|
3167
|
+
const outT = new Float64Array(outCount);
|
|
3168
|
+
const nA = adcT.length;
|
|
3169
|
+
const f32Adc = _options?.adcPrecision === "f32";
|
|
3170
|
+
const kxA = f32Adc ? new Float32Array(nA) : new Float64Array(nA);
|
|
3171
|
+
const kyA = f32Adc ? new Float32Array(nA) : new Float64Array(nA);
|
|
3172
|
+
const kzA = f32Adc ? new Float32Array(nA) : new Float64Array(nA);
|
|
3173
|
+
const cursors = [0, 0, 0];
|
|
3159
3174
|
let cx = 0, cy = 0, cz = 0;
|
|
3175
|
+
let lx = 0, ly = 0, lz = 0;
|
|
3176
|
+
let a = 0;
|
|
3177
|
+
let out = 0;
|
|
3178
|
+
let nextKeep = 0;
|
|
3179
|
+
let ec = 0;
|
|
3180
|
+
let gxPrev = sampleSeries(gradientSeries[0], grid[0], cursors, 0);
|
|
3181
|
+
let gyPrev = sampleSeries(gradientSeries[1], grid[0], cursors, 1);
|
|
3182
|
+
let gzPrev = sampleSeries(gradientSeries[2], grid[0], cursors, 2);
|
|
3160
3183
|
if (refocusingAt[0] && !excitationAt[0]) {
|
|
3161
|
-
|
|
3162
|
-
|
|
3163
|
-
|
|
3184
|
+
lx = -lx;
|
|
3185
|
+
ly = -ly;
|
|
3186
|
+
lz = -lz;
|
|
3187
|
+
}
|
|
3188
|
+
if (nextKeep === 0) {
|
|
3189
|
+
while (ec < eIdx.length && eIdx[ec] < 1) ec++;
|
|
3190
|
+
const brk = ec < eIdx.length && eIdx[ec] === 1;
|
|
3191
|
+
outT[0] = grid[0];
|
|
3192
|
+
outX[0] = brk ? NaN : lx;
|
|
3193
|
+
outY[0] = brk ? NaN : ly;
|
|
3194
|
+
outZ[0] = brk ? NaN : lz;
|
|
3195
|
+
out = 1;
|
|
3196
|
+
nextKeep = out < outCount ? Math.floor(out * outStep) : -1;
|
|
3197
|
+
}
|
|
3198
|
+
while (a < nA && adcT[a] <= grid[0]) {
|
|
3199
|
+
kxA[a] = lx;
|
|
3200
|
+
kyA[a] = ly;
|
|
3201
|
+
kzA[a] = lz;
|
|
3202
|
+
a++;
|
|
3164
3203
|
}
|
|
3165
3204
|
for (let i = 1; i < N; i++) {
|
|
3166
|
-
const
|
|
3167
|
-
|
|
3168
|
-
|
|
3169
|
-
|
|
3170
|
-
|
|
3171
|
-
|
|
3205
|
+
const gxi = sampleSeries(gradientSeries[0], grid[i], cursors, 0);
|
|
3206
|
+
const gyi = sampleSeries(gradientSeries[1], grid[i], cursors, 1);
|
|
3207
|
+
const gzi = sampleSeries(gradientSeries[2], grid[i], cursors, 2);
|
|
3208
|
+
const tPrev = grid[i - 1], tCur = grid[i];
|
|
3209
|
+
const dt = tCur - tPrev;
|
|
3210
|
+
const px = lx, py = ly, pz = lz;
|
|
3211
|
+
if (dt > 0) {
|
|
3212
|
+
const dx = 0.5 * (gxPrev + gxi) * dt;
|
|
3213
|
+
const dy = 0.5 * (gyPrev + gyi) * dt;
|
|
3214
|
+
const dz = 0.5 * (gzPrev + gzi) * dt;
|
|
3215
|
+
const yx = dx - cx, yy = dy - cy, yz = dz - cz;
|
|
3216
|
+
const nx = lx + yx, ny = ly + yy, nz = lz + yz;
|
|
3217
|
+
cx = nx - lx - yx;
|
|
3218
|
+
cy = ny - ly - yy;
|
|
3219
|
+
cz = nz - lz - yz;
|
|
3220
|
+
lx = nx;
|
|
3221
|
+
ly = ny;
|
|
3222
|
+
lz = nz;
|
|
3223
|
+
if (excitationAt[i]) {
|
|
3224
|
+
lx = 0;
|
|
3225
|
+
ly = 0;
|
|
3226
|
+
lz = 0;
|
|
3227
|
+
cx = 0;
|
|
3228
|
+
cy = 0;
|
|
3229
|
+
cz = 0;
|
|
3230
|
+
} else if (refocusingAt[i]) {
|
|
3231
|
+
lx = -lx;
|
|
3232
|
+
ly = -ly;
|
|
3233
|
+
lz = -lz;
|
|
3234
|
+
cx = -cx;
|
|
3235
|
+
cy = -cy;
|
|
3236
|
+
cz = -cz;
|
|
3237
|
+
}
|
|
3172
3238
|
}
|
|
3173
|
-
|
|
3174
|
-
|
|
3175
|
-
|
|
3176
|
-
|
|
3177
|
-
|
|
3178
|
-
|
|
3179
|
-
|
|
3180
|
-
|
|
3181
|
-
|
|
3182
|
-
|
|
3183
|
-
|
|
3184
|
-
|
|
3185
|
-
|
|
3186
|
-
|
|
3187
|
-
|
|
3188
|
-
|
|
3189
|
-
|
|
3190
|
-
|
|
3191
|
-
|
|
3192
|
-
|
|
3193
|
-
|
|
3194
|
-
|
|
3195
|
-
cx = -cx;
|
|
3196
|
-
cy = -cy;
|
|
3197
|
-
cz = -cz;
|
|
3198
|
-
}
|
|
3199
|
-
}
|
|
3200
|
-
const kxP = new Float64Array(kx), kyP = new Float64Array(ky), kzP = new Float64Array(kz);
|
|
3201
|
-
for (const i of eIdx) {
|
|
3202
|
-
if (i > 0) {
|
|
3203
|
-
kxP[i - 1] = NaN;
|
|
3204
|
-
kyP[i - 1] = NaN;
|
|
3205
|
-
kzP[i - 1] = NaN;
|
|
3239
|
+
if (i === nextKeep) {
|
|
3240
|
+
while (ec < eIdx.length && eIdx[ec] < i + 1) ec++;
|
|
3241
|
+
const brk = ec < eIdx.length && eIdx[ec] === i + 1;
|
|
3242
|
+
outT[out] = tCur;
|
|
3243
|
+
outX[out] = brk ? NaN : lx;
|
|
3244
|
+
outY[out] = brk ? NaN : ly;
|
|
3245
|
+
outZ[out] = brk ? NaN : lz;
|
|
3246
|
+
out++;
|
|
3247
|
+
nextKeep = out < outCount ? Math.floor(out * outStep) : -1;
|
|
3248
|
+
}
|
|
3249
|
+
while (a < nA && adcT[a] <= tCur) {
|
|
3250
|
+
const t = adcT[a];
|
|
3251
|
+
if (Math.abs(tCur - t) < 1e-12 || dt <= 0) {
|
|
3252
|
+
kxA[a] = lx;
|
|
3253
|
+
kyA[a] = ly;
|
|
3254
|
+
kzA[a] = lz;
|
|
3255
|
+
} else {
|
|
3256
|
+
kxA[a] = px + (lx - px) * (t - tPrev) / dt;
|
|
3257
|
+
kyA[a] = py + (ly - py) * (t - tPrev) / dt;
|
|
3258
|
+
kzA[a] = pz + (lz - pz) * (t - tPrev) / dt;
|
|
3259
|
+
}
|
|
3260
|
+
a++;
|
|
3206
3261
|
}
|
|
3262
|
+
gxPrev = gxi;
|
|
3263
|
+
gyPrev = gyi;
|
|
3264
|
+
gzPrev = gzi;
|
|
3207
3265
|
}
|
|
3208
|
-
|
|
3209
|
-
|
|
3210
|
-
|
|
3211
|
-
|
|
3212
|
-
|
|
3213
|
-
kzA[a] = interp(kz, grid, adcT[a]);
|
|
3266
|
+
while (a < nA) {
|
|
3267
|
+
kxA[a] = lx;
|
|
3268
|
+
kyA[a] = ly;
|
|
3269
|
+
kzA[a] = lz;
|
|
3270
|
+
a++;
|
|
3214
3271
|
}
|
|
3215
|
-
|
|
3272
|
+
const tAdc = adcIdx === adcT.length ? adcT : adcT.slice(0, adcIdx);
|
|
3273
|
+
return {
|
|
3274
|
+
ktraj: [outX, outY, outZ],
|
|
3275
|
+
t_ktraj: outT,
|
|
3276
|
+
ktraj_adc: [kxA, kyA, kzA],
|
|
3277
|
+
t_adc: tAdc,
|
|
3278
|
+
rasterSampleCount: N
|
|
3279
|
+
};
|
|
3280
|
+
}
|
|
3281
|
+
function countSeriesSupport(series, mode) {
|
|
3282
|
+
if (series.times.length < 2) return 0;
|
|
3283
|
+
return mode === "all" ? series.times.length : series.requiredSupport.length;
|
|
3216
3284
|
}
|
|
3217
3285
|
function collectSeriesSupport(series, mode, push) {
|
|
3218
3286
|
if (series.times.length < 2) return;
|
|
@@ -3312,22 +3380,9 @@ var Pulseq = (() => {
|
|
|
3312
3380
|
}
|
|
3313
3381
|
return lo < g.length ? lo : -1;
|
|
3314
3382
|
}
|
|
3315
|
-
|
|
3316
|
-
|
|
3317
|
-
|
|
3318
|
-
let lo = 0, hi = n;
|
|
3319
|
-
while (lo < hi) {
|
|
3320
|
-
const m = lo + hi >> 1;
|
|
3321
|
-
if (g[m] < t) lo = m + 1;
|
|
3322
|
-
else hi = m;
|
|
3323
|
-
}
|
|
3324
|
-
if (lo === 0) return d[0];
|
|
3325
|
-
if (lo >= n) return d[n - 1];
|
|
3326
|
-
if (Math.abs(g[lo] - t) < 1e-12) return d[lo];
|
|
3327
|
-
const i0 = lo - 1, i1 = lo, dt = g[i1] - g[i0];
|
|
3328
|
-
if (dt <= 0) return d[i1];
|
|
3329
|
-
return d[i0] + (d[i1] - d[i0]) * (t - g[i0]) / dt;
|
|
3330
|
-
}
|
|
3383
|
+
|
|
3384
|
+
// src/editor/kspaceTransport.ts
|
|
3385
|
+
var MAX_KSPACE_OVERVIEW_POINTS = 3e4;
|
|
3331
3386
|
|
|
3332
3387
|
// src/pulseq/labels.ts
|
|
3333
3388
|
var COUNTER_ORDER = ["SLC", "SEG", "REP", "AVG", "SET", "ECO", "PHS", "LIN", "PAR", "ACQ", "TRID", "ONCE"];
|
|
@@ -5670,7 +5725,7 @@ var Pulseq = (() => {
|
|
|
5670
5725
|
},
|
|
5671
5726
|
totalDurationSec,
|
|
5672
5727
|
adcSampleCount: kspace.t_adc.length,
|
|
5673
|
-
trajectorySampleCount: kspace.
|
|
5728
|
+
trajectorySampleCount: kspace.rasterSampleCount,
|
|
5674
5729
|
units: {
|
|
5675
5730
|
trajectory: "1/m",
|
|
5676
5731
|
time: "s",
|
|
@@ -3153,6 +3153,16 @@ if(systemThemeQuery){
|
|
|
3153
3153
|
WebGL state
|
|
3154
3154
|
═══════════════════════════════════════════════════════════════════════ */
|
|
3155
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;
|
|
3156
3166
|
/* Why the GPU layer has nothing to draw, or null when it is healthy. A
|
|
3157
3167
|
failure here used to be a console warning at most, so the panel opened
|
|
3158
3168
|
with its axes and no points — indistinguishable from a sequence whose
|
|
@@ -3182,6 +3192,10 @@ function initWebGL(){
|
|
|
3182
3192
|
kGpuFailure="the graphics context was lost while drawing K\u2011space";
|
|
3183
3193
|
if(typeof drawKs==="function")drawKs();
|
|
3184
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');
|
|
3185
3199
|
gl.enable(gl.BLEND);gl.blendFunc(gl.SRC_ALPHA,gl.ONE_MINUS_SRC_ALPHA);
|
|
3186
3200
|
|
|
3187
3201
|
var vs=gl.createShader(gl.VERTEX_SHADER), fs=gl.createShader(gl.FRAGMENT_SHADER);
|
|
@@ -3249,6 +3263,23 @@ var KSPACE_MOVING_POINT_TARGET = 600000;
|
|
|
3249
3263
|
*
|
|
3250
3264
|
* Returns 1 below the target, so narrow views and modest sequences are drawn
|
|
3251
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.
|
|
3252
3283
|
*/
|
|
3253
3284
|
function kSpaceMovingStride(count){
|
|
3254
3285
|
// The target is overridable so the settle-exactness guarantee can be tested
|
|
@@ -3256,7 +3287,40 @@ function kSpaceMovingStride(count){
|
|
|
3256
3287
|
var target=(typeof window!=='undefined'&&window.__kMovingPointTarget>0)
|
|
3257
3288
|
?window.__kMovingPointTarget:KSPACE_MOVING_POINT_TARGET;
|
|
3258
3289
|
if(!(count>target))return 1;
|
|
3259
|
-
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;
|
|
3260
3324
|
}
|
|
3261
3325
|
|
|
3262
3326
|
/** True while a drag, touch gesture, or camera easing is in progress. */
|
|
@@ -3325,6 +3389,7 @@ function uploadKSpaceGPU(){
|
|
|
3325
3389
|
return;
|
|
3326
3390
|
}
|
|
3327
3391
|
glN=n;
|
|
3392
|
+
_idxStride=-1; // the indices point into the buffer just replaced
|
|
3328
3393
|
// Compute bounds once
|
|
3329
3394
|
var xmin=Infinity,xmax=-Infinity,ymin=Infinity,ymax=-Infinity,zmin=Infinity,zmax=-Infinity;
|
|
3330
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;}}
|
|
@@ -3783,17 +3848,24 @@ function drawKs_core(W,H,dpr){
|
|
|
3783
3848
|
|
|
3784
3849
|
var win=kSpaceWindowRange(kAdcTime,glN,vs,ve);
|
|
3785
3850
|
var stride=kSpaceCameraMoving()?kSpaceMovingStride(win.count):1;
|
|
3851
|
+
if(stride>1&&!glUint32Indices)stride=1; // draw it all rather than wrongly
|
|
3786
3852
|
|
|
3787
3853
|
gl.bindBuffer(gl.ARRAY_BUFFER,glBuf);
|
|
3788
3854
|
gl.enableVertexAttribArray(glAttribPos);
|
|
3789
|
-
gl.vertexAttribPointer(glAttribPos,3,gl.FLOAT,false,16
|
|
3855
|
+
gl.vertexAttribPointer(glAttribPos,3,gl.FLOAT,false,16,0);
|
|
3790
3856
|
gl.enableVertexAttribArray(glAttribTime);
|
|
3791
|
-
gl.vertexAttribPointer(glAttribTime,1,gl.FLOAT,false,16
|
|
3857
|
+
gl.vertexAttribPointer(glAttribTime,1,gl.FLOAT,false,16,12);
|
|
3792
3858
|
|
|
3793
3859
|
kSpaceTrajectoryDrawCount++;
|
|
3794
|
-
|
|
3795
|
-
|
|
3796
|
-
|
|
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
|
+
}
|
|
3797
3869
|
}
|
|
3798
3870
|
|
|
3799
3871
|
drawKsOverlay(W,H,dpr,cs);
|
|
@@ -7273,7 +7345,13 @@ function placeTooltip(cx,cy){
|
|
|
7273
7345
|
var r=tt.getBoundingClientRect(),vw=window.innerWidth,vh=window.innerHeight;
|
|
7274
7346
|
var left=Math.min(Math.max(pad,cx+15),Math.max(pad,vw-r.width-pad));
|
|
7275
7347
|
var top=cy+12;if(top+r.height+pad>vh)top=cy-r.height-12;if(top<pad)top=pad;
|
|
7276
|
-
|
|
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';
|
|
7277
7355
|
}
|
|
7278
7356
|
function fmtFlipAngle(value){
|
|
7279
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
|