seqeyes-python 0.2.10__tar.gz → 0.2.12__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.2.10 → seqeyes_python-0.2.12}/PKG-INFO +1 -1
- {seqeyes_python-0.2.10 → seqeyes_python-0.2.12}/pyproject.toml +1 -1
- {seqeyes_python-0.2.10 → seqeyes_python-0.2.12}/src/seqeyes/__init__.py +1 -1
- {seqeyes_python-0.2.10 → seqeyes_python-0.2.12}/src/seqeyes/resources/pulseq-bundle.js +294 -158
- {seqeyes_python-0.2.10 → seqeyes_python-0.2.12}/src/seqeyes/resources/webview-bundle.js +12 -9
- {seqeyes_python-0.2.10 → seqeyes_python-0.2.12}/.gitignore +0 -0
- {seqeyes_python-0.2.10 → seqeyes_python-0.2.12}/README.md +0 -0
- {seqeyes_python-0.2.10 → seqeyes_python-0.2.12}/examples/demo.ipynb +0 -0
- {seqeyes_python-0.2.10 → seqeyes_python-0.2.12}/examples/demo.py +0 -0
- {seqeyes_python-0.2.10 → seqeyes_python-0.2.12}/src/seqeyes/_plot.py +0 -0
- {seqeyes_python-0.2.10 → seqeyes_python-0.2.12}/src/seqeyes/_renderer.py +0 -0
- {seqeyes_python-0.2.10 → seqeyes_python-0.2.12}/src/seqeyes/resources/viewer.html +0 -0
- {seqeyes_python-0.2.10 → seqeyes_python-0.2.12}/tests/conftest.py +0 -0
- {seqeyes_python-0.2.10 → seqeyes_python-0.2.12}/tests/debug_full.html +0 -0
- {seqeyes_python-0.2.10 → seqeyes_python-0.2.12}/tests/debug_minimal.html +0 -0
- {seqeyes_python-0.2.10 → seqeyes_python-0.2.12}/tests/debug_viewer.html +0 -0
- {seqeyes_python-0.2.10 → seqeyes_python-0.2.12}/tests/debug_viewer.py +0 -0
- {seqeyes_python-0.2.10 → seqeyes_python-0.2.12}/tests/integration_test.py +0 -0
- {seqeyes_python-0.2.10 → seqeyes_python-0.2.12}/tests/test_plot.py +0 -0
- {seqeyes_python-0.2.10 → seqeyes_python-0.2.12}/tests/test_renderer.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: seqeyes-python
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.12
|
|
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.2.
|
|
7
|
+
version = "0.2.12"
|
|
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"}
|
|
@@ -31,6 +31,7 @@ var Pulseq = (() => {
|
|
|
31
31
|
calculatePns: () => calculatePns,
|
|
32
32
|
calculatePnsCoarse: () => calculatePnsCoarse,
|
|
33
33
|
decodeAllBlocks: () => decodeAllBlocks,
|
|
34
|
+
derivedDetailViewLimitSec: () => derivedDetailViewLimitSec,
|
|
34
35
|
detectSequenceTiming: () => detectSequenceTiming,
|
|
35
36
|
estimateDerivedCost: () => estimateDerivedCost,
|
|
36
37
|
estimateKspaceCost: () => estimateKspaceCost,
|
|
@@ -53,7 +54,7 @@ var Pulseq = (() => {
|
|
|
53
54
|
});
|
|
54
55
|
|
|
55
56
|
// package.json
|
|
56
|
-
var version = "0.2.
|
|
57
|
+
var version = "0.2.12";
|
|
57
58
|
|
|
58
59
|
// src/pulseq/decompressor.ts
|
|
59
60
|
function decompressShape(compressed, numSamples) {
|
|
@@ -1403,21 +1404,112 @@ var Pulseq = (() => {
|
|
|
1403
1404
|
return parseSequenceText(text);
|
|
1404
1405
|
}
|
|
1405
1406
|
|
|
1406
|
-
// src/pulseq/
|
|
1407
|
+
// src/pulseq/rfClassification.ts
|
|
1407
1408
|
var GAMMA_HZ_T = 42576e3;
|
|
1408
1409
|
var DEFAULT_B0_T = 3;
|
|
1410
|
+
var sequenceUseCache = /* @__PURE__ */ new WeakMap();
|
|
1411
|
+
function classifyRfUse(rf, seq) {
|
|
1412
|
+
if (seq.versionCombined >= VER_V15 && rf.use && rf.use.toLowerCase() !== "u") {
|
|
1413
|
+
return rf.use.toLowerCase();
|
|
1414
|
+
}
|
|
1415
|
+
const flipAngleDeg = estimateRfFlipAngleDeg(rf, seq);
|
|
1416
|
+
if (isLegacyFatSaturation(rf, seq)) return "s";
|
|
1417
|
+
return flipAngleDeg >= 120 ? "r" : "e";
|
|
1418
|
+
}
|
|
1419
|
+
function classifyRfUses(seq) {
|
|
1420
|
+
const cachedUses = sequenceUseCache.get(seq);
|
|
1421
|
+
if (cachedUses) return cachedUses;
|
|
1422
|
+
const libraryUses = /* @__PURE__ */ new Map();
|
|
1423
|
+
const uses = seq.blocks.map((block) => {
|
|
1424
|
+
if (block.rfId <= 0) return "";
|
|
1425
|
+
const cached = libraryUses.get(block.rfId);
|
|
1426
|
+
if (cached !== void 0) return cached;
|
|
1427
|
+
const rf = seq.rfs.get(block.rfId);
|
|
1428
|
+
const use = rf ? classifyRfUse(rf, seq) : "";
|
|
1429
|
+
libraryUses.set(block.rfId, use);
|
|
1430
|
+
return use;
|
|
1431
|
+
});
|
|
1432
|
+
if (seq.versionCombined >= VER_V15 || uses.includes("e")) {
|
|
1433
|
+
sequenceUseCache.set(seq, uses);
|
|
1434
|
+
return uses;
|
|
1435
|
+
}
|
|
1436
|
+
const saturationBlocks = uses.map((use, index) => use === "s" ? index : -1).filter((index) => index >= 0);
|
|
1437
|
+
if (saturationBlocks.length < 2) {
|
|
1438
|
+
sequenceUseCache.set(seq, uses);
|
|
1439
|
+
return uses;
|
|
1440
|
+
}
|
|
1441
|
+
for (let anchor = 0; anchor < saturationBlocks.length; anchor++) {
|
|
1442
|
+
const start = saturationBlocks[anchor] + 1;
|
|
1443
|
+
const end = saturationBlocks[anchor + 1] ?? uses.length;
|
|
1444
|
+
for (let index = start; index < end; index++) {
|
|
1445
|
+
if (!uses[index] || uses[index] === "s") continue;
|
|
1446
|
+
uses[index] = "e";
|
|
1447
|
+
break;
|
|
1448
|
+
}
|
|
1449
|
+
}
|
|
1450
|
+
sequenceUseCache.set(seq, uses);
|
|
1451
|
+
return uses;
|
|
1452
|
+
}
|
|
1453
|
+
function estimateRfFlipAngleDeg(rf, seq) {
|
|
1454
|
+
const magShape = seq.shapes.get(rf.magShapeId);
|
|
1455
|
+
if (magShape && magShape.numSamples > 0) {
|
|
1456
|
+
const raster = seq.rasterTimes.rfRaster;
|
|
1457
|
+
const timeShape = rf.timeShapeId > 0 ? seq.shapes.get(rf.timeShapeId)?.samples : void 0;
|
|
1458
|
+
let area = 0;
|
|
1459
|
+
let previousTime = timeShape ? timeShape[0] * raster : 0.5 * raster;
|
|
1460
|
+
let previousAmplitude = Math.abs(rf.amplitude * magShape.samples[0]);
|
|
1461
|
+
for (let index = 1; index < magShape.numSamples; index++) {
|
|
1462
|
+
const time = timeShape ? timeShape[index] * raster : (index + 0.5) * raster;
|
|
1463
|
+
const amplitude = Math.abs(rf.amplitude * magShape.samples[index]);
|
|
1464
|
+
const duration = time - previousTime;
|
|
1465
|
+
if (duration > 0) area += 0.5 * (previousAmplitude + amplitude) * duration;
|
|
1466
|
+
previousTime = time;
|
|
1467
|
+
previousAmplitude = amplitude;
|
|
1468
|
+
}
|
|
1469
|
+
return 360 * area;
|
|
1470
|
+
}
|
|
1471
|
+
const absoluteAmplitude = Math.abs(rf.amplitude);
|
|
1472
|
+
if (absoluteAmplitude > 3e3) return 180;
|
|
1473
|
+
if (absoluteAmplitude > 1500) return 120;
|
|
1474
|
+
return 90;
|
|
1475
|
+
}
|
|
1476
|
+
function isLegacyFatSaturation(rf, seq) {
|
|
1477
|
+
const b0Tesla = getB0(seq);
|
|
1478
|
+
const frequencyPpm = rf.freqPPM !== 0 ? rf.freqPPM : b0Tesla > 0 ? 1e6 * rf.freqOffset / (GAMMA_HZ_T * b0Tesla) : 0;
|
|
1479
|
+
const durationSec = estimateRfDuration(rf, seq);
|
|
1480
|
+
return durationSec > 6e-3 && frequencyPpm >= -4.5 && frequencyPpm <= -3;
|
|
1481
|
+
}
|
|
1482
|
+
function estimateRfDuration(rf, seq) {
|
|
1483
|
+
const magShape = seq.shapes.get(rf.magShapeId);
|
|
1484
|
+
if (!magShape || magShape.numSamples <= 0) return 0;
|
|
1485
|
+
const raster = seq.rasterTimes.rfRaster;
|
|
1486
|
+
const timeShape = rf.timeShapeId > 0 ? seq.shapes.get(rf.timeShapeId)?.samples : void 0;
|
|
1487
|
+
if (timeShape && timeShape.length > 0) {
|
|
1488
|
+
return timeShape[timeShape.length - 1] * raster + raster;
|
|
1489
|
+
}
|
|
1490
|
+
return magShape.numSamples * raster;
|
|
1491
|
+
}
|
|
1409
1492
|
function getB0(seq) {
|
|
1493
|
+
const raw = seq.definitions.get("B0") ?? seq.definitions.get("b0") ?? seq.definitions.get("b_0");
|
|
1494
|
+
if (raw && raw.length > 0) return +raw[0];
|
|
1495
|
+
return DEFAULT_B0_T;
|
|
1496
|
+
}
|
|
1497
|
+
|
|
1498
|
+
// src/pulseq/decoder.ts
|
|
1499
|
+
var GAMMA_HZ_T2 = 42576e3;
|
|
1500
|
+
var DEFAULT_B0_T2 = 3;
|
|
1501
|
+
function getB02(seq) {
|
|
1410
1502
|
const raw = seq.definitions.get("B0");
|
|
1411
1503
|
if (raw && Array.isArray(raw) && raw.length > 0) return +raw[0];
|
|
1412
1504
|
const raw2 = seq.definitions.get("b0") ?? seq.definitions.get("b_0");
|
|
1413
1505
|
if (raw2 && Array.isArray(raw2) && raw2.length > 0) return +raw2[0];
|
|
1414
|
-
return
|
|
1506
|
+
return DEFAULT_B0_T2;
|
|
1415
1507
|
}
|
|
1416
1508
|
function effFreqOff(freqOffset, freqPPM, b0) {
|
|
1417
|
-
return freqOffset + freqPPM * 1e-6 *
|
|
1509
|
+
return freqOffset + freqPPM * 1e-6 * GAMMA_HZ_T2 * b0;
|
|
1418
1510
|
}
|
|
1419
1511
|
function effPhaseOff(phaseOffset, phasePPM, b0) {
|
|
1420
|
-
return phaseOffset + phasePPM * 1e-6 *
|
|
1512
|
+
return phaseOffset + phasePPM * 1e-6 * GAMMA_HZ_T2 * b0;
|
|
1421
1513
|
}
|
|
1422
1514
|
function decodeAllBlocks(seq) {
|
|
1423
1515
|
return decodeBlockRange(seq, 0, seq.blocks.length);
|
|
@@ -1434,13 +1526,14 @@ var Pulseq = (() => {
|
|
|
1434
1526
|
cumulative += blockDurationSeconds(seq, seq.blocks[i]);
|
|
1435
1527
|
}
|
|
1436
1528
|
const decoded = [];
|
|
1529
|
+
const classifiedRfUses = classifyRfUses(seq);
|
|
1437
1530
|
for (let i = s; i < e; i++) {
|
|
1438
1531
|
const block = seq.blocks[i];
|
|
1439
1532
|
const dur = blockDurationSeconds(seq, block);
|
|
1440
1533
|
const db = { index: block.num, duration: dur, startTime: cumulative };
|
|
1441
1534
|
if (block.rfId > 0) {
|
|
1442
1535
|
const rf = seq.rfs.get(block.rfId);
|
|
1443
|
-
if (rf) db.rf = decodeRF(seq, rf, cumulative, dur);
|
|
1536
|
+
if (rf) db.rf = decodeRF(seq, rf, cumulative, dur, classifiedRfUses[i]);
|
|
1444
1537
|
}
|
|
1445
1538
|
db.gx = decodeGradient(seq, block.gxId, cumulative, dur, "gx");
|
|
1446
1539
|
db.gy = decodeGradient(seq, block.gyId, cumulative, dur, "gy");
|
|
@@ -1469,11 +1562,11 @@ var Pulseq = (() => {
|
|
|
1469
1562
|
if (seq.versionCombined < VER_PRE_14) return block.dur * 1e-6;
|
|
1470
1563
|
return block.dur * seq.rasterTimes.blockDurationRaster;
|
|
1471
1564
|
}
|
|
1472
|
-
function decodeRF(seq, rf, blockStart, _blockDur) {
|
|
1565
|
+
function decodeRF(seq, rf, blockStart, _blockDur, classifiedUse) {
|
|
1473
1566
|
const raster = seq.rasterTimes.rfRaster;
|
|
1474
1567
|
const rfDelay = rf.delay * 1e-6;
|
|
1475
1568
|
const rfStart = blockStart + rfDelay;
|
|
1476
|
-
const b0 =
|
|
1569
|
+
const b0 = getB02(seq);
|
|
1477
1570
|
const freqFull = effFreqOff(rf.freqOffset, rf.freqPPM, b0);
|
|
1478
1571
|
const phaseFull = effPhaseOff(rf.phaseOffset, rf.phasePPM, b0);
|
|
1479
1572
|
const magShape = seq.shapes.get(rf.magShapeId);
|
|
@@ -1494,15 +1587,7 @@ var Pulseq = (() => {
|
|
|
1494
1587
|
}
|
|
1495
1588
|
const duration = n > 0 ? t[n - 1] - rfStart + raster : 0;
|
|
1496
1589
|
const centerTime = rf.center >= 0 ? blockStart + rfDelay + rf.center * 1e-6 : estimateRfPeakTime(t, amp, rfStart, duration);
|
|
1497
|
-
|
|
1498
|
-
if (!use || use === "u") {
|
|
1499
|
-
let faDeg = 0;
|
|
1500
|
-
for (let i = 1; i < n; i++) {
|
|
1501
|
-
const dt = t[i] - t[i - 1];
|
|
1502
|
-
faDeg += 360 * (amp[i] + amp[i - 1]) * 0.5 * dt;
|
|
1503
|
-
}
|
|
1504
|
-
use = faDeg >= 120 ? "r" : "e";
|
|
1505
|
-
}
|
|
1590
|
+
const use = classifiedUse || "u";
|
|
1506
1591
|
return {
|
|
1507
1592
|
blockIndex: rf.id,
|
|
1508
1593
|
startTime: rfStart,
|
|
@@ -1658,7 +1743,7 @@ var Pulseq = (() => {
|
|
|
1658
1743
|
return value * amplitude;
|
|
1659
1744
|
}
|
|
1660
1745
|
function decodeADC(adc, blockStart, seq) {
|
|
1661
|
-
const b0 =
|
|
1746
|
+
const b0 = getB02(seq);
|
|
1662
1747
|
const freqFull = effFreqOff(adc.freqOffset, adc.freqPPM, b0);
|
|
1663
1748
|
const phaseFull = effPhaseOff(adc.phaseOffset, adc.phasePPM, b0);
|
|
1664
1749
|
return {
|
|
@@ -1784,9 +1869,7 @@ var Pulseq = (() => {
|
|
|
1784
1869
|
// src/pulseq/kspace.ts
|
|
1785
1870
|
var TRAJECTORY_TIME_ACCURACY_SEC = 1e-10;
|
|
1786
1871
|
var GRADIENT_ENDPOINT_TOLERANCE_SEC = 1e-12;
|
|
1787
|
-
|
|
1788
|
-
return TRAJECTORY_TIME_ACCURACY_SEC * Math.round(timeSec / TRAJECTORY_TIME_ACCURACY_SEC);
|
|
1789
|
-
}
|
|
1872
|
+
var POLYNOMIAL_SUPPORT_EPSILON_SEC = 1e-12;
|
|
1790
1873
|
function calculateKspace(blocks, gradientRaster, totalDuration, trajectoryDelay = 0, _options) {
|
|
1791
1874
|
if (!blocks.length || !gradientRaster || gradientRaster <= 0) return null;
|
|
1792
1875
|
const GR = gradientRaster;
|
|
@@ -1794,7 +1877,6 @@ var Pulseq = (() => {
|
|
|
1794
1877
|
const tacc = TRAJECTORY_TIME_ACCURACY_SEC;
|
|
1795
1878
|
const gradientSupport = _options?.gradientSupport ?? "endpoints";
|
|
1796
1879
|
const excT = [], refT = [];
|
|
1797
|
-
const gradTimes = [];
|
|
1798
1880
|
let totalAdcSamples = 0;
|
|
1799
1881
|
for (const b of blocks) {
|
|
1800
1882
|
if (b.adc) totalAdcSamples += b.adc.numSamples;
|
|
@@ -1807,9 +1889,6 @@ var Pulseq = (() => {
|
|
|
1807
1889
|
const adcT = new Float64Array(totalAdcSamples);
|
|
1808
1890
|
let adcIdx = 0;
|
|
1809
1891
|
for (const b of blocks) {
|
|
1810
|
-
collectGradientSupport(b.gx, gradTimes, gradientSupport);
|
|
1811
|
-
collectGradientSupport(b.gy, gradTimes, gradientSupport);
|
|
1812
|
-
collectGradientSupport(b.gz, gradTimes, gradientSupport);
|
|
1813
1892
|
if (b.rf) {
|
|
1814
1893
|
const iso = Number.isFinite(b.rf.centerTime) ? b.rf.centerTime : b.rf.startTime + b.rf.duration * 0.5;
|
|
1815
1894
|
const u = b.rf.use || "";
|
|
@@ -1824,11 +1903,12 @@ var Pulseq = (() => {
|
|
|
1824
1903
|
adcT[adcIdx++] = t0 + (s + 0.5) * dwell + trajectoryDelay;
|
|
1825
1904
|
}
|
|
1826
1905
|
}
|
|
1906
|
+
const gradientSeries = buildGlobalGradientSeries(blocks, GR, totalDuration);
|
|
1827
1907
|
const cand = [];
|
|
1828
1908
|
const pushC = (t) => {
|
|
1829
1909
|
if (isFinite(t) && t >= -tacc) cand.push(Math.max(0, tacc * Math.round(t / tacc)));
|
|
1830
1910
|
};
|
|
1831
|
-
for (const
|
|
1911
|
+
for (const series of gradientSeries) collectSeriesSupport(series, gradientSupport, pushC);
|
|
1832
1912
|
for (const t of excT) {
|
|
1833
1913
|
pushC(t);
|
|
1834
1914
|
pushC(t - RF);
|
|
@@ -1855,39 +1935,12 @@ var Pulseq = (() => {
|
|
|
1855
1935
|
if (N < 2) return null;
|
|
1856
1936
|
if (_options?.maxGridPoints && N > _options.maxGridPoints) return null;
|
|
1857
1937
|
const gx = new Float64Array(N), gy = new Float64Array(N), gz = new Float64Array(N);
|
|
1858
|
-
const
|
|
1859
|
-
let cum = 0;
|
|
1860
|
-
for (const b of blocks) {
|
|
1861
|
-
cum += b.duration;
|
|
1862
|
-
edges.push(canonicalTrajectoryTime(cum));
|
|
1863
|
-
}
|
|
1938
|
+
const cursors = [0, 0, 0];
|
|
1864
1939
|
for (let i = 0; i < N; i++) {
|
|
1865
1940
|
const t = grid[i];
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
const localX = gradVal(block.gx, t);
|
|
1870
|
-
const localY = gradVal(block.gy, t);
|
|
1871
|
-
const localZ = gradVal(block.gz, t);
|
|
1872
|
-
const rotated = rotateGradient(block, localX, localY, localZ);
|
|
1873
|
-
gx[i] = rotated[0];
|
|
1874
|
-
gy[i] = rotated[1];
|
|
1875
|
-
gz[i] = rotated[2];
|
|
1876
|
-
}
|
|
1877
|
-
}
|
|
1878
|
-
const kx = new Float64Array(N), ky = new Float64Array(N), kz = new Float64Array(N);
|
|
1879
|
-
for (let i = 1; i < N; i++) {
|
|
1880
|
-
const dt = grid[i] - grid[i - 1];
|
|
1881
|
-
if (dt <= 0) {
|
|
1882
|
-
kx[i] = kx[i - 1];
|
|
1883
|
-
ky[i] = ky[i - 1];
|
|
1884
|
-
kz[i] = kz[i - 1];
|
|
1885
|
-
continue;
|
|
1886
|
-
}
|
|
1887
|
-
const gxm = 0.5 * (gx[i - 1] + gx[i]), gym = 0.5 * (gy[i - 1] + gy[i]), gzm = 0.5 * (gz[i - 1] + gz[i]);
|
|
1888
|
-
kx[i] = kx[i - 1] + gxm * dt;
|
|
1889
|
-
ky[i] = ky[i - 1] + gym * dt;
|
|
1890
|
-
kz[i] = kz[i - 1] + gzm * dt;
|
|
1941
|
+
gx[i] = sampleSeries(gradientSeries[0], t, cursors, 0);
|
|
1942
|
+
gy[i] = sampleSeries(gradientSeries[1], t, cursors, 1);
|
|
1943
|
+
gz[i] = sampleSeries(gradientSeries[2], t, cursors, 2);
|
|
1891
1944
|
}
|
|
1892
1945
|
const eIdx = [], rIdx = [];
|
|
1893
1946
|
for (const t of excT) {
|
|
@@ -1900,37 +1953,52 @@ var Pulseq = (() => {
|
|
|
1900
1953
|
}
|
|
1901
1954
|
eIdx.sort((a, b) => a - b);
|
|
1902
1955
|
rIdx.sort((a, b) => a - b);
|
|
1903
|
-
const
|
|
1904
|
-
|
|
1905
|
-
for (const i of
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1956
|
+
const excitationAt = new Uint8Array(N);
|
|
1957
|
+
const refocusingAt = new Uint8Array(N);
|
|
1958
|
+
for (const i of eIdx) excitationAt[i] = 1;
|
|
1959
|
+
for (const i of rIdx) refocusingAt[i] = 1;
|
|
1960
|
+
const kx = new Float64Array(N), ky = new Float64Array(N), kz = new Float64Array(N);
|
|
1961
|
+
let cx = 0, cy = 0, cz = 0;
|
|
1962
|
+
if (refocusingAt[0] && !excitationAt[0]) {
|
|
1963
|
+
kx[0] = -kx[0];
|
|
1964
|
+
ky[0] = -ky[0];
|
|
1965
|
+
kz[0] = -kz[0];
|
|
1966
|
+
}
|
|
1967
|
+
for (let i = 1; i < N; i++) {
|
|
1968
|
+
const dt = grid[i] - grid[i - 1];
|
|
1969
|
+
if (dt <= 0) {
|
|
1970
|
+
kx[i] = kx[i - 1];
|
|
1971
|
+
ky[i] = ky[i - 1];
|
|
1972
|
+
kz[i] = kz[i - 1];
|
|
1973
|
+
continue;
|
|
1974
|
+
}
|
|
1975
|
+
const dx = 0.5 * (gx[i - 1] + gx[i]) * dt;
|
|
1976
|
+
const dy = 0.5 * (gy[i - 1] + gy[i]) * dt;
|
|
1977
|
+
const dz = 0.5 * (gz[i - 1] + gz[i]) * dt;
|
|
1978
|
+
const yx = dx - cx, yy = dy - cy, yz = dz - cz;
|
|
1979
|
+
const nx = kx[i - 1] + yx, ny = ky[i - 1] + yy, nz = kz[i - 1] + yz;
|
|
1980
|
+
cx = nx - kx[i - 1] - yx;
|
|
1981
|
+
cy = ny - ky[i - 1] - yy;
|
|
1982
|
+
cz = nz - kz[i - 1] - yz;
|
|
1983
|
+
kx[i] = nx;
|
|
1984
|
+
ky[i] = ny;
|
|
1985
|
+
kz[i] = nz;
|
|
1986
|
+
if (excitationAt[i]) {
|
|
1987
|
+
kx[i] = 0;
|
|
1988
|
+
ky[i] = 0;
|
|
1989
|
+
kz[i] = 0;
|
|
1990
|
+
cx = 0;
|
|
1991
|
+
cy = 0;
|
|
1992
|
+
cz = 0;
|
|
1993
|
+
} else if (refocusingAt[i]) {
|
|
1994
|
+
kx[i] = -kx[i];
|
|
1995
|
+
ky[i] = -ky[i];
|
|
1996
|
+
kz[i] = -kz[i];
|
|
1997
|
+
cx = -cx;
|
|
1998
|
+
cy = -cy;
|
|
1999
|
+
cz = -cz;
|
|
2000
|
+
}
|
|
2001
|
+
}
|
|
1934
2002
|
const kxP = new Float64Array(kx), kyP = new Float64Array(ky), kzP = new Float64Array(kz);
|
|
1935
2003
|
for (const i of eIdx) {
|
|
1936
2004
|
if (i > 0) {
|
|
@@ -1948,13 +2016,135 @@ var Pulseq = (() => {
|
|
|
1948
2016
|
}
|
|
1949
2017
|
return { ktraj: [kxP, kyP, kzP], t_ktraj: new Float64Array(grid), ktraj_adc: [kxA, kyA, kzA], t_adc: new Float64Array(adcT) };
|
|
1950
2018
|
}
|
|
1951
|
-
function
|
|
1952
|
-
if (
|
|
2019
|
+
function collectSeriesSupport(series, mode, push) {
|
|
2020
|
+
if (series.times.length < 2) return;
|
|
1953
2021
|
if (mode === "all") {
|
|
1954
|
-
for (
|
|
2022
|
+
for (const time of series.times) push(time);
|
|
1955
2023
|
return;
|
|
1956
2024
|
}
|
|
1957
|
-
|
|
2025
|
+
for (const time of series.requiredSupport) push(time);
|
|
2026
|
+
}
|
|
2027
|
+
function buildGlobalGradientSeries(blocks, gradientRaster, totalDuration) {
|
|
2028
|
+
const output = [
|
|
2029
|
+
{ times: [], values: [], requiredSupport: [] },
|
|
2030
|
+
{ times: [], values: [], requiredSupport: [] },
|
|
2031
|
+
{ times: [], values: [], requiredSupport: [] }
|
|
2032
|
+
];
|
|
2033
|
+
for (const block of blocks) {
|
|
2034
|
+
for (let axis = 0; axis < 3; axis++) {
|
|
2035
|
+
const piece = physicalGradientPiece(block, axis);
|
|
2036
|
+
if (piece.times.length) appendGradientPiece(output[axis], piece, gradientRaster);
|
|
2037
|
+
}
|
|
2038
|
+
}
|
|
2039
|
+
for (const series of output) {
|
|
2040
|
+
if (!series.times.length) continue;
|
|
2041
|
+
const first = series.times[0];
|
|
2042
|
+
const last = series.times[series.times.length - 1];
|
|
2043
|
+
if (first > 0) {
|
|
2044
|
+
series.times.unshift(-POLYNOMIAL_SUPPORT_EPSILON_SEC, first - POLYNOMIAL_SUPPORT_EPSILON_SEC);
|
|
2045
|
+
series.values.unshift(0, 0);
|
|
2046
|
+
series.requiredSupport.push(-POLYNOMIAL_SUPPORT_EPSILON_SEC, first - POLYNOMIAL_SUPPORT_EPSILON_SEC);
|
|
2047
|
+
}
|
|
2048
|
+
if (last < totalDuration) {
|
|
2049
|
+
series.times.push(last + POLYNOMIAL_SUPPORT_EPSILON_SEC, totalDuration + POLYNOMIAL_SUPPORT_EPSILON_SEC);
|
|
2050
|
+
series.values.push(0, 0);
|
|
2051
|
+
series.requiredSupport.push(last + POLYNOMIAL_SUPPORT_EPSILON_SEC, totalDuration + POLYNOMIAL_SUPPORT_EPSILON_SEC);
|
|
2052
|
+
}
|
|
2053
|
+
}
|
|
2054
|
+
return output;
|
|
2055
|
+
}
|
|
2056
|
+
function physicalGradientPiece(block, axis) {
|
|
2057
|
+
const gradients = [block.gx, block.gy, block.gz];
|
|
2058
|
+
const hasGradient = gradients.some((g) => g && g.type !== "none" && g.timePoints.length >= 2);
|
|
2059
|
+
if (!hasGradient) return { times: [], values: [], requiredSupport: [] };
|
|
2060
|
+
if (!block.rotation?.values) {
|
|
2061
|
+
const gradient = gradients[axis];
|
|
2062
|
+
if (!gradient || gradient.type === "none" || gradient.timePoints.length < 2) {
|
|
2063
|
+
return { times: [], values: [], requiredSupport: [] };
|
|
2064
|
+
}
|
|
2065
|
+
return {
|
|
2066
|
+
times: Array.from(gradient.timePoints),
|
|
2067
|
+
values: Array.from(gradient.waveform),
|
|
2068
|
+
requiredSupport: []
|
|
2069
|
+
};
|
|
2070
|
+
}
|
|
2071
|
+
const times = [];
|
|
2072
|
+
for (const gradient of gradients) {
|
|
2073
|
+
if (!gradient || gradient.type === "none") continue;
|
|
2074
|
+
for (const time of gradient.timePoints) times.push(time);
|
|
2075
|
+
}
|
|
2076
|
+
times.sort((a, b) => a - b);
|
|
2077
|
+
const uniqueTimes = [];
|
|
2078
|
+
for (const time of times) {
|
|
2079
|
+
if (!uniqueTimes.length || time - uniqueTimes[uniqueTimes.length - 1] > GRADIENT_ENDPOINT_TOLERANCE_SEC) {
|
|
2080
|
+
uniqueTimes.push(time);
|
|
2081
|
+
}
|
|
2082
|
+
}
|
|
2083
|
+
return {
|
|
2084
|
+
times: uniqueTimes,
|
|
2085
|
+
values: uniqueTimes.map((time) => {
|
|
2086
|
+
const rotated = rotateGradient(
|
|
2087
|
+
block,
|
|
2088
|
+
gradVal(block.gx, time),
|
|
2089
|
+
gradVal(block.gy, time),
|
|
2090
|
+
gradVal(block.gz, time)
|
|
2091
|
+
);
|
|
2092
|
+
return rotated[axis];
|
|
2093
|
+
}),
|
|
2094
|
+
requiredSupport: []
|
|
2095
|
+
};
|
|
2096
|
+
}
|
|
2097
|
+
function appendGradientPiece(target, piece, gradientRaster) {
|
|
2098
|
+
if (!piece.times.length) return;
|
|
2099
|
+
target.requiredSupport.push(piece.times[0], piece.times[piece.times.length - 1]);
|
|
2100
|
+
if (!target.times.length) {
|
|
2101
|
+
target.times.push(...piece.times);
|
|
2102
|
+
target.values.push(...piece.values);
|
|
2103
|
+
return;
|
|
2104
|
+
}
|
|
2105
|
+
const lastIndex = target.times.length - 1;
|
|
2106
|
+
const previousTime = target.times[lastIndex];
|
|
2107
|
+
const firstTime = piece.times[0];
|
|
2108
|
+
if (previousTime + gradientRaster < firstTime) {
|
|
2109
|
+
if (target.values[lastIndex] !== 0) {
|
|
2110
|
+
if (Math.abs(target.values[lastIndex]) > 1e-6) {
|
|
2111
|
+
target.times.push(previousTime + gradientRaster * 0.5);
|
|
2112
|
+
target.values.push(0);
|
|
2113
|
+
target.requiredSupport.push(previousTime + gradientRaster * 0.5);
|
|
2114
|
+
} else {
|
|
2115
|
+
target.values[lastIndex] = 0;
|
|
2116
|
+
}
|
|
2117
|
+
}
|
|
2118
|
+
if (piece.values[0] !== 0) {
|
|
2119
|
+
if (Math.abs(piece.values[0]) > 1e-6) {
|
|
2120
|
+
target.times.push(firstTime - gradientRaster * 0.5);
|
|
2121
|
+
target.values.push(0);
|
|
2122
|
+
target.requiredSupport.push(firstTime - gradientRaster * 0.5);
|
|
2123
|
+
} else {
|
|
2124
|
+
piece.values[0] = 0;
|
|
2125
|
+
}
|
|
2126
|
+
}
|
|
2127
|
+
}
|
|
2128
|
+
let start = 0;
|
|
2129
|
+
const currentLast = target.times[target.times.length - 1];
|
|
2130
|
+
while (start < piece.times.length && piece.times[start] <= currentLast) start++;
|
|
2131
|
+
for (let i = start; i < piece.times.length; i++) {
|
|
2132
|
+
target.times.push(piece.times[i]);
|
|
2133
|
+
target.values.push(piece.values[i]);
|
|
2134
|
+
}
|
|
2135
|
+
}
|
|
2136
|
+
function sampleSeries(series, time, cursors, axis) {
|
|
2137
|
+
const n = series.times.length;
|
|
2138
|
+
if (!n || time < series.times[0] || time > series.times[n - 1]) return 0;
|
|
2139
|
+
let cursor = Math.min(cursors[axis], n - 2);
|
|
2140
|
+
while (cursor + 1 < n && series.times[cursor + 1] < time) cursor++;
|
|
2141
|
+
cursors[axis] = cursor;
|
|
2142
|
+
if (cursor + 1 >= n) return series.values[n - 1];
|
|
2143
|
+
const t0 = series.times[cursor], t1 = series.times[cursor + 1];
|
|
2144
|
+
const v0 = series.values[cursor], v1 = series.values[cursor + 1];
|
|
2145
|
+
if (time <= t0 || t1 <= t0) return v0;
|
|
2146
|
+
if (time >= t1) return v1;
|
|
2147
|
+
return v0 + (v1 - v0) * (time - t0) / (t1 - t0);
|
|
1958
2148
|
}
|
|
1959
2149
|
function gradVal(g, t) {
|
|
1960
2150
|
if (!g || g.type === "none") return 0;
|
|
@@ -1974,15 +2164,6 @@ var Pulseq = (() => {
|
|
|
1974
2164
|
if (s <= 0) return wf[lo];
|
|
1975
2165
|
return wf[lo] + (wf[hi] - wf[lo]) * (t - tp[lo]) / s;
|
|
1976
2166
|
}
|
|
1977
|
-
function blockIdx(t, edges) {
|
|
1978
|
-
let lo = 0, hi = edges.length - 1;
|
|
1979
|
-
while (lo < hi) {
|
|
1980
|
-
const m = lo + hi >> 1;
|
|
1981
|
-
if (edges[m] <= t + 1e-12) lo = m + 1;
|
|
1982
|
-
else hi = m;
|
|
1983
|
-
}
|
|
1984
|
-
return Math.max(0, lo - 1);
|
|
1985
|
-
}
|
|
1986
2167
|
function timeIdx(t, g) {
|
|
1987
2168
|
let lo = 0, hi = g.length;
|
|
1988
2169
|
while (lo < hi) {
|
|
@@ -2414,7 +2595,7 @@ var Pulseq = (() => {
|
|
|
2414
2595
|
}
|
|
2415
2596
|
}
|
|
2416
2597
|
warnings.push(
|
|
2417
|
-
`Showing a bounded full-sequence M1 envelope (at most ${maxBuckets.toLocaleString()} buckets per axis). Zoom
|
|
2598
|
+
`Showing a bounded full-sequence M1 envelope (at most ${maxBuckets.toLocaleString()} buckets per axis). Zoom in for an automatic detailed calculation; viewport sample limits also apply.`
|
|
2418
2599
|
);
|
|
2419
2600
|
return {
|
|
2420
2601
|
valid: true,
|
|
@@ -2515,7 +2696,7 @@ var Pulseq = (() => {
|
|
|
2515
2696
|
const events = [];
|
|
2516
2697
|
for (const block of blocks) {
|
|
2517
2698
|
if (!block.rf) continue;
|
|
2518
|
-
const use =
|
|
2699
|
+
const use = classifyRfUse2(block.rf.use);
|
|
2519
2700
|
if (!use) continue;
|
|
2520
2701
|
const rec = { tSec: block.rf.centerTime, use };
|
|
2521
2702
|
events.push(rec);
|
|
@@ -2530,7 +2711,7 @@ var Pulseq = (() => {
|
|
|
2530
2711
|
events.sort((a, b) => a.tSec - b.tSec);
|
|
2531
2712
|
return events;
|
|
2532
2713
|
}
|
|
2533
|
-
function
|
|
2714
|
+
function classifyRfUse2(raw) {
|
|
2534
2715
|
const c = (raw || "u").toLowerCase();
|
|
2535
2716
|
if (c === "e" || c === "r" || c === "s" || c === "i" || c === "p") return c;
|
|
2536
2717
|
return "u";
|
|
@@ -2890,7 +3071,7 @@ var Pulseq = (() => {
|
|
|
2890
3071
|
}
|
|
2891
3072
|
}
|
|
2892
3073
|
const warnings = [
|
|
2893
|
-
`Showing a bounded full-sequence PNS envelope (at most ${maxBuckets.toLocaleString()} buckets per curve). Zoom
|
|
3074
|
+
`Showing a bounded full-sequence PNS envelope (at most ${maxBuckets.toLocaleString()} buckets per curve). Zoom in for an automatic detailed calculation; viewport sample limits also apply.`
|
|
2894
3075
|
];
|
|
2895
3076
|
return {
|
|
2896
3077
|
valid: true,
|
|
@@ -3163,6 +3344,12 @@ var Pulseq = (() => {
|
|
|
3163
3344
|
kspaceGridCandidates: 18e6,
|
|
3164
3345
|
derivedRasterSamples: 2e6
|
|
3165
3346
|
});
|
|
3347
|
+
function derivedDetailViewLimitSec(gradientRaster, trTimeSec, maxRasterSamples = INTERACTIVE_COMPUTE_LIMITS.derivedRasterSamples) {
|
|
3348
|
+
if (!(gradientRaster > 0) || !(maxRasterSamples > 0)) return 0;
|
|
3349
|
+
const sampleLimitedDuration = maxRasterSamples * gradientRaster / 2;
|
|
3350
|
+
if (!(trTimeSec > 0)) return sampleLimitedDuration;
|
|
3351
|
+
return Math.min(sampleLimitedDuration, trTimeSec * 100.5);
|
|
3352
|
+
}
|
|
3166
3353
|
function estimateKspaceCost(blocks, gradientRaster, totalDuration) {
|
|
3167
3354
|
let adcSamples = 0;
|
|
3168
3355
|
let gradientSupportPoints = 0;
|
|
@@ -3232,11 +3419,9 @@ var Pulseq = (() => {
|
|
|
3232
3419
|
}
|
|
3233
3420
|
|
|
3234
3421
|
// src/pulseq/trdetect.ts
|
|
3235
|
-
var GAMMA_HZ_T2 = 42576e3;
|
|
3236
|
-
var DEFAULT_B0_T2 = 3;
|
|
3237
3422
|
function detectSequenceTiming(seq) {
|
|
3238
|
-
const b0 = getB02(seq);
|
|
3239
3423
|
const supportsRfUse = seq.versionCombined >= 1005e3;
|
|
3424
|
+
const classifiedRfUses = classifyRfUses(seq);
|
|
3240
3425
|
let teTimeSec = 0;
|
|
3241
3426
|
let hasExplicitTE = false;
|
|
3242
3427
|
const teDef = seq.definitions.get("EchoTime") ?? seq.definitions.get("TE");
|
|
@@ -3266,7 +3451,7 @@ var Pulseq = (() => {
|
|
|
3266
3451
|
rfUsePerBlock.push(0);
|
|
3267
3452
|
continue;
|
|
3268
3453
|
}
|
|
3269
|
-
const useChar =
|
|
3454
|
+
const useChar = classifiedRfUses[i] || "u";
|
|
3270
3455
|
const useCode = useChar.charCodeAt(0);
|
|
3271
3456
|
rfUsePerBlock.push(useCode);
|
|
3272
3457
|
if (useChar === "e") {
|
|
@@ -3319,11 +3504,6 @@ var Pulseq = (() => {
|
|
|
3319
3504
|
rfUsePerBlock
|
|
3320
3505
|
};
|
|
3321
3506
|
}
|
|
3322
|
-
function getB02(seq) {
|
|
3323
|
-
const raw = seq.definitions.get("B0") ?? seq.definitions.get("b0") ?? seq.definitions.get("b_0");
|
|
3324
|
-
if (raw && Array.isArray(raw) && raw.length > 0) return +raw[0];
|
|
3325
|
-
return DEFAULT_B0_T2;
|
|
3326
|
-
}
|
|
3327
3507
|
function computeCumulativeTimes(seq) {
|
|
3328
3508
|
const times = [];
|
|
3329
3509
|
let cum = 0;
|
|
@@ -3337,40 +3517,6 @@ var Pulseq = (() => {
|
|
|
3337
3517
|
if (seq.versionCombined < VER_PRE_14) return block.dur * 1e-6;
|
|
3338
3518
|
return block.dur * seq.rasterTimes.blockDurationRaster;
|
|
3339
3519
|
}
|
|
3340
|
-
function classifyRfUse2(rf, seq, supportsMetadata, b0Tesla) {
|
|
3341
|
-
if (supportsMetadata && rf.use && rf.use !== "u" && rf.use !== "U") {
|
|
3342
|
-
return rf.use.toLowerCase();
|
|
3343
|
-
}
|
|
3344
|
-
const faDeg = estimateFlipAngleDeg(rf, seq);
|
|
3345
|
-
if (faDeg < 90.01) return "e";
|
|
3346
|
-
const freqPPM = rf.freqPPM !== 0 ? rf.freqPPM : b0Tesla > 0 ? 1e6 * rf.freqOffset / (GAMMA_HZ_T2 * b0Tesla) : 0;
|
|
3347
|
-
const durEst = estimateRfDuration(rf, seq);
|
|
3348
|
-
if (durEst > 6e-3 && freqPPM >= -4.5 && freqPPM <= -3) return "s";
|
|
3349
|
-
return "r";
|
|
3350
|
-
}
|
|
3351
|
-
function estimateFlipAngleDeg(rf, seq) {
|
|
3352
|
-
const magShape = seq.shapes.get(rf.magShapeId);
|
|
3353
|
-
if (magShape && magShape.numSamples > 0) {
|
|
3354
|
-
const raster = seq.rasterTimes.rfRaster;
|
|
3355
|
-
const timeShape = rf.timeShapeId > 0 ? seq.shapes.get(rf.timeShapeId)?.samples : void 0;
|
|
3356
|
-
let area = 0;
|
|
3357
|
-
let prevT = timeShape ? timeShape[0] * raster : 0.5 * raster;
|
|
3358
|
-
let prevAmp = Math.abs(rf.amplitude * magShape.samples[0]);
|
|
3359
|
-
for (let i = 1; i < magShape.numSamples; i++) {
|
|
3360
|
-
const t = timeShape ? timeShape[i] * raster : (i + 0.5) * raster;
|
|
3361
|
-
const amp = Math.abs(rf.amplitude * magShape.samples[i]);
|
|
3362
|
-
const dt = t - prevT;
|
|
3363
|
-
if (dt > 0) area += 0.5 * (prevAmp + amp) * dt;
|
|
3364
|
-
prevT = t;
|
|
3365
|
-
prevAmp = amp;
|
|
3366
|
-
}
|
|
3367
|
-
return 360 * area;
|
|
3368
|
-
}
|
|
3369
|
-
const absAmp = Math.abs(rf.amplitude);
|
|
3370
|
-
if (absAmp > 3e3) return 180;
|
|
3371
|
-
if (absAmp > 1500) return 120;
|
|
3372
|
-
return 90;
|
|
3373
|
-
}
|
|
3374
3520
|
function estimateRfCenter(rf, _seq) {
|
|
3375
3521
|
const magShape = _seq.shapes.get(rf.magShapeId);
|
|
3376
3522
|
if (!magShape || magShape.numSamples <= 0) return 0;
|
|
@@ -3387,16 +3533,6 @@ var Pulseq = (() => {
|
|
|
3387
3533
|
const timeShape = rf.timeShapeId > 0 ? _seq.shapes.get(rf.timeShapeId)?.samples : void 0;
|
|
3388
3534
|
return timeShape ? (timeShape[peakIdx] ?? 0) * raster : (peakIdx + 0.5) * raster;
|
|
3389
3535
|
}
|
|
3390
|
-
function estimateRfDuration(rf, seq) {
|
|
3391
|
-
const magShape = seq.shapes.get(rf.magShapeId);
|
|
3392
|
-
if (!magShape || magShape.numSamples <= 0) return 0;
|
|
3393
|
-
const raster = seq.rasterTimes.rfRaster;
|
|
3394
|
-
const timeShape = rf.timeShapeId > 0 ? seq.shapes.get(rf.timeShapeId)?.samples : void 0;
|
|
3395
|
-
if (timeShape && timeShape.length > 0) {
|
|
3396
|
-
return timeShape[timeShape.length - 1] * raster + raster;
|
|
3397
|
-
}
|
|
3398
|
-
return magShape.numSamples * raster;
|
|
3399
|
-
}
|
|
3400
3536
|
function estimateTRFromExcitations(excTimesSec) {
|
|
3401
3537
|
if (excTimesSec.length < 2) return 0;
|
|
3402
3538
|
const intervals = [];
|
|
@@ -97,7 +97,7 @@ function scheduleViewerDraw(includeMinimap){
|
|
|
97
97
|
}
|
|
98
98
|
|
|
99
99
|
/* Timing metadata (TR/TE info for minimap tooltip) */
|
|
100
|
-
var seqTiming=null; // {trTimeSec,trCount,hasExplicitTR,teTimeSec,hasExplicitTE,rfUseGuessed}
|
|
100
|
+
var seqTiming=null; // {trTimeSec,trCount,hasExplicitTR,teTimeSec,hasExplicitTE,rfUseGuessed,derivedDetailMaxViewSec}
|
|
101
101
|
|
|
102
102
|
/* Block positions for minimap: [{i,s,d}, ...] */
|
|
103
103
|
var blockPos=[];
|
|
@@ -304,7 +304,7 @@ window.addEventListener('message',function(e){
|
|
|
304
304
|
}else chVis[m1RequestedChannel]=true;
|
|
305
305
|
computeGlobalMax();buildLegend();draw();
|
|
306
306
|
if(m.m1.warnings&&m.m1.warnings.length)console.warn('[SeqEyes M1]',m.m1.warnings.join('\n'));
|
|
307
|
-
setViewerNotice('m1Coarse',m.m1.coarse?'Large sequence: showing bounded full-sequence M1. Zoom
|
|
307
|
+
setViewerNotice('m1Coarse',m.m1.coarse?'Large sequence: showing bounded full-sequence M1. Zoom in for automatic detail.':null);
|
|
308
308
|
}else{
|
|
309
309
|
setViewerNotice('m1','M1 calculation failed: '+((m.m1&&m.m1.error)||'unknown error')+' Zoom in to inspect waveform detail.');
|
|
310
310
|
}
|
|
@@ -324,7 +324,7 @@ window.addEventListener('message',function(e){
|
|
|
324
324
|
pnsWindowData=null;pnsWindowPending=null;chVis[7]=true;
|
|
325
325
|
computeGlobalMax();buildLegend();draw();
|
|
326
326
|
setViewerNotice('pnsThreshold',m.pns.ok?null:'PNS warning: predicted level reaches or exceeds 100%.');
|
|
327
|
-
setViewerNotice('pnsCoarse',m.pns.coarse?'Large sequence: showing bounded full-sequence PNS. Zoom
|
|
327
|
+
setViewerNotice('pnsCoarse',m.pns.coarse?'Large sequence: showing bounded full-sequence PNS. Zoom in for automatic detail.':null);
|
|
328
328
|
}else{
|
|
329
329
|
setViewerNotice('pns','PNS calculation failed: '+((m.pns&&m.pns.error)||'unknown error')+' Zoom in to inspect waveform detail.');
|
|
330
330
|
}
|
|
@@ -396,10 +396,14 @@ function createM1SeriesPayload(m1){
|
|
|
396
396
|
};
|
|
397
397
|
}
|
|
398
398
|
|
|
399
|
+
function shouldRequestFineDerivedWindow(vs,ve){
|
|
400
|
+
var dur=ve-vs,maxView=seqTiming&&Number(seqTiming.derivedDetailMaxViewSec);
|
|
401
|
+
return !!(dur>0&&isFinite(maxView)&&maxView>0&&dur<=maxView+1e-12);
|
|
402
|
+
}
|
|
403
|
+
|
|
399
404
|
function shouldRequestFineM1Window(vs,ve){
|
|
400
405
|
if(!vscApi||!m1Data||!m1Data.coarse||!(chVis[8]||chVis[9]||chVis[10]))return false;
|
|
401
|
-
|
|
402
|
-
return !!(seqTiming&&seqTiming.trTimeSec>0&&dur<=seqTiming.trTimeSec*100.5);
|
|
406
|
+
return shouldRequestFineDerivedWindow(vs,ve);
|
|
403
407
|
}
|
|
404
408
|
|
|
405
409
|
function derivedWindowCovers(data,vs,ve){
|
|
@@ -421,14 +425,13 @@ function m1SeriesForView(vs,ve){
|
|
|
421
425
|
requestM1Window(vs,ve);
|
|
422
426
|
if(derivedWindowCovers(m1WindowData,vs,ve)){setViewerNotice('m1Detail',null);setViewerNotice('m1Coarse','Large sequence: full sequence uses bounded M1; the current view is detailed.');return m1WindowData;}
|
|
423
427
|
}
|
|
424
|
-
if(m1Data.coarse)setViewerNotice('m1Coarse','Large sequence: showing bounded full-sequence M1. Zoom
|
|
428
|
+
if(m1Data.coarse)setViewerNotice('m1Coarse','Large sequence: showing bounded full-sequence M1. Zoom in for automatic detail.');
|
|
425
429
|
return m1Data;
|
|
426
430
|
}
|
|
427
431
|
|
|
428
432
|
function shouldRequestFinePnsWindow(vs,ve){
|
|
429
433
|
if(!vscApi||!pnsData||!pnsData.coarse||!chVis[7])return false;
|
|
430
|
-
|
|
431
|
-
return !!(seqTiming&&seqTiming.trTimeSec>0&&dur<=seqTiming.trTimeSec*100.5);
|
|
434
|
+
return shouldRequestFineDerivedWindow(vs,ve);
|
|
432
435
|
}
|
|
433
436
|
|
|
434
437
|
function pnsWindowCovers(data,vs,ve){
|
|
@@ -450,7 +453,7 @@ function pnsSeriesForView(vs,ve){
|
|
|
450
453
|
requestPnsWindow(vs,ve);
|
|
451
454
|
if(pnsWindowCovers(pnsWindowData,vs,ve)){setViewerNotice('pnsCoarse','Large sequence: full sequence uses bounded PNS; the current view is detailed.');return pnsWindowData;}
|
|
452
455
|
}
|
|
453
|
-
if(pnsData.coarse)setViewerNotice('pnsCoarse','Large sequence: showing bounded full-sequence PNS. Zoom
|
|
456
|
+
if(pnsData.coarse)setViewerNotice('pnsCoarse','Large sequence: showing bounded full-sequence PNS. Zoom in for automatic detail.');
|
|
454
457
|
return pnsData;
|
|
455
458
|
}
|
|
456
459
|
|
|
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
|