seqeyes-python 0.2.9__tar.gz → 0.2.11__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.2.9 → seqeyes_python-0.2.11}/PKG-INFO +1 -1
  2. {seqeyes_python-0.2.9 → seqeyes_python-0.2.11}/pyproject.toml +1 -1
  3. {seqeyes_python-0.2.9 → seqeyes_python-0.2.11}/src/seqeyes/__init__.py +1 -1
  4. {seqeyes_python-0.2.9 → seqeyes_python-0.2.11}/src/seqeyes/resources/pulseq-bundle.js +180 -84
  5. seqeyes_python-0.2.11/src/seqeyes/resources/webview-bundle.js +2667 -0
  6. {seqeyes_python-0.2.9 → seqeyes_python-0.2.11}/.gitignore +0 -0
  7. {seqeyes_python-0.2.9 → seqeyes_python-0.2.11}/README.md +0 -0
  8. {seqeyes_python-0.2.9 → seqeyes_python-0.2.11}/examples/demo.ipynb +0 -0
  9. {seqeyes_python-0.2.9 → seqeyes_python-0.2.11}/examples/demo.py +0 -0
  10. {seqeyes_python-0.2.9 → seqeyes_python-0.2.11}/src/seqeyes/_plot.py +0 -0
  11. {seqeyes_python-0.2.9 → seqeyes_python-0.2.11}/src/seqeyes/_renderer.py +0 -0
  12. {seqeyes_python-0.2.9 → seqeyes_python-0.2.11}/src/seqeyes/resources/viewer.html +0 -0
  13. {seqeyes_python-0.2.9 → seqeyes_python-0.2.11}/tests/conftest.py +0 -0
  14. {seqeyes_python-0.2.9 → seqeyes_python-0.2.11}/tests/debug_full.html +0 -0
  15. {seqeyes_python-0.2.9 → seqeyes_python-0.2.11}/tests/debug_minimal.html +0 -0
  16. {seqeyes_python-0.2.9 → seqeyes_python-0.2.11}/tests/debug_viewer.html +0 -0
  17. {seqeyes_python-0.2.9 → seqeyes_python-0.2.11}/tests/debug_viewer.py +0 -0
  18. {seqeyes_python-0.2.9 → seqeyes_python-0.2.11}/tests/integration_test.py +0 -0
  19. {seqeyes_python-0.2.9 → seqeyes_python-0.2.11}/tests/test_plot.py +0 -0
  20. {seqeyes_python-0.2.9 → seqeyes_python-0.2.11}/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.9
3
+ Version: 0.2.11
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.9"
7
+ version = "0.2.11"
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.2.9"
32
+ __version__ = "0.2.11"
@@ -53,7 +53,7 @@ var Pulseq = (() => {
53
53
  });
54
54
 
55
55
  // package.json
56
- var version = "0.2.9";
56
+ var version = "0.2.11";
57
57
 
58
58
  // src/pulseq/decompressor.ts
59
59
  function decompressShape(compressed, numSamples) {
@@ -1784,9 +1784,7 @@ var Pulseq = (() => {
1784
1784
  // src/pulseq/kspace.ts
1785
1785
  var TRAJECTORY_TIME_ACCURACY_SEC = 1e-10;
1786
1786
  var GRADIENT_ENDPOINT_TOLERANCE_SEC = 1e-12;
1787
- function canonicalTrajectoryTime(timeSec) {
1788
- return TRAJECTORY_TIME_ACCURACY_SEC * Math.round(timeSec / TRAJECTORY_TIME_ACCURACY_SEC);
1789
- }
1787
+ var POLYNOMIAL_SUPPORT_EPSILON_SEC = 1e-12;
1790
1788
  function calculateKspace(blocks, gradientRaster, totalDuration, trajectoryDelay = 0, _options) {
1791
1789
  if (!blocks.length || !gradientRaster || gradientRaster <= 0) return null;
1792
1790
  const GR = gradientRaster;
@@ -1794,7 +1792,6 @@ var Pulseq = (() => {
1794
1792
  const tacc = TRAJECTORY_TIME_ACCURACY_SEC;
1795
1793
  const gradientSupport = _options?.gradientSupport ?? "endpoints";
1796
1794
  const excT = [], refT = [];
1797
- const gradTimes = [];
1798
1795
  let totalAdcSamples = 0;
1799
1796
  for (const b of blocks) {
1800
1797
  if (b.adc) totalAdcSamples += b.adc.numSamples;
@@ -1807,9 +1804,6 @@ var Pulseq = (() => {
1807
1804
  const adcT = new Float64Array(totalAdcSamples);
1808
1805
  let adcIdx = 0;
1809
1806
  for (const b of blocks) {
1810
- collectGradientSupport(b.gx, gradTimes, gradientSupport);
1811
- collectGradientSupport(b.gy, gradTimes, gradientSupport);
1812
- collectGradientSupport(b.gz, gradTimes, gradientSupport);
1813
1807
  if (b.rf) {
1814
1808
  const iso = Number.isFinite(b.rf.centerTime) ? b.rf.centerTime : b.rf.startTime + b.rf.duration * 0.5;
1815
1809
  const u = b.rf.use || "";
@@ -1824,11 +1818,12 @@ var Pulseq = (() => {
1824
1818
  adcT[adcIdx++] = t0 + (s + 0.5) * dwell + trajectoryDelay;
1825
1819
  }
1826
1820
  }
1821
+ const gradientSeries = buildGlobalGradientSeries(blocks, GR, totalDuration);
1827
1822
  const cand = [];
1828
1823
  const pushC = (t) => {
1829
1824
  if (isFinite(t) && t >= -tacc) cand.push(Math.max(0, tacc * Math.round(t / tacc)));
1830
1825
  };
1831
- for (const t of gradTimes) pushC(t);
1826
+ for (const series of gradientSeries) collectSeriesSupport(series, gradientSupport, pushC);
1832
1827
  for (const t of excT) {
1833
1828
  pushC(t);
1834
1829
  pushC(t - RF);
@@ -1855,39 +1850,12 @@ var Pulseq = (() => {
1855
1850
  if (N < 2) return null;
1856
1851
  if (_options?.maxGridPoints && N > _options.maxGridPoints) return null;
1857
1852
  const gx = new Float64Array(N), gy = new Float64Array(N), gz = new Float64Array(N);
1858
- const edges = [0];
1859
- let cum = 0;
1860
- for (const b of blocks) {
1861
- cum += b.duration;
1862
- edges.push(canonicalTrajectoryTime(cum));
1863
- }
1853
+ const cursors = [0, 0, 0];
1864
1854
  for (let i = 0; i < N; i++) {
1865
1855
  const t = grid[i];
1866
- const bi = blockIdx(t, edges);
1867
- if (bi >= 0 && bi < blocks.length) {
1868
- const block = blocks[bi];
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;
1856
+ gx[i] = sampleSeries(gradientSeries[0], t, cursors, 0);
1857
+ gy[i] = sampleSeries(gradientSeries[1], t, cursors, 1);
1858
+ gz[i] = sampleSeries(gradientSeries[2], t, cursors, 2);
1891
1859
  }
1892
1860
  const eIdx = [], rIdx = [];
1893
1861
  for (const t of excT) {
@@ -1900,37 +1868,52 @@ var Pulseq = (() => {
1900
1868
  }
1901
1869
  eIdx.sort((a, b) => a - b);
1902
1870
  rIdx.sort((a, b) => a - b);
1903
- const bounds = [0];
1904
- for (const i of eIdx) bounds.push(i);
1905
- for (const i of rIdx) bounds.push(i);
1906
- bounds.push(N - 1);
1907
- bounds.sort((a, b) => a - b);
1908
- const bUniq = [bounds[0]];
1909
- for (let i = 1; i < bounds.length; i++) if (bounds[i] !== bUniq[bUniq.length - 1]) bUniq.push(bounds[i]);
1910
- let dkX = -kx[0], dkY = -ky[0], dkZ = -kz[0];
1911
- let pE = 0, pR = 0;
1912
- for (let s = 0; s < bUniq.length - 1; s++) {
1913
- const st = bUniq[s], en = bUniq[s + 1];
1914
- if (pE < eIdx.length && eIdx[pE] === st) {
1915
- dkX = -kx[st];
1916
- dkY = -ky[st];
1917
- dkZ = -kz[st];
1918
- pE++;
1919
- } else if (pR < rIdx.length && rIdx[pR] === st) {
1920
- dkX = -2 * kx[st] - dkX;
1921
- dkY = -2 * ky[st] - dkY;
1922
- dkZ = -2 * kz[st] - dkZ;
1923
- pR++;
1924
- }
1925
- for (let j = st; j < en; j++) {
1926
- kx[j] += dkX;
1927
- ky[j] += dkY;
1928
- kz[j] += dkZ;
1929
- }
1930
- }
1931
- kx[N - 1] += dkX;
1932
- ky[N - 1] += dkY;
1933
- kz[N - 1] += dkZ;
1871
+ const excitationAt = new Uint8Array(N);
1872
+ const refocusingAt = new Uint8Array(N);
1873
+ for (const i of eIdx) excitationAt[i] = 1;
1874
+ for (const i of rIdx) refocusingAt[i] = 1;
1875
+ const kx = new Float64Array(N), ky = new Float64Array(N), kz = new Float64Array(N);
1876
+ let cx = 0, cy = 0, cz = 0;
1877
+ if (refocusingAt[0] && !excitationAt[0]) {
1878
+ kx[0] = -kx[0];
1879
+ ky[0] = -ky[0];
1880
+ kz[0] = -kz[0];
1881
+ }
1882
+ for (let i = 1; i < N; i++) {
1883
+ const dt = grid[i] - grid[i - 1];
1884
+ if (dt <= 0) {
1885
+ kx[i] = kx[i - 1];
1886
+ ky[i] = ky[i - 1];
1887
+ kz[i] = kz[i - 1];
1888
+ continue;
1889
+ }
1890
+ const dx = 0.5 * (gx[i - 1] + gx[i]) * dt;
1891
+ const dy = 0.5 * (gy[i - 1] + gy[i]) * dt;
1892
+ const dz = 0.5 * (gz[i - 1] + gz[i]) * dt;
1893
+ const yx = dx - cx, yy = dy - cy, yz = dz - cz;
1894
+ const nx = kx[i - 1] + yx, ny = ky[i - 1] + yy, nz = kz[i - 1] + yz;
1895
+ cx = nx - kx[i - 1] - yx;
1896
+ cy = ny - ky[i - 1] - yy;
1897
+ cz = nz - kz[i - 1] - yz;
1898
+ kx[i] = nx;
1899
+ ky[i] = ny;
1900
+ kz[i] = nz;
1901
+ if (excitationAt[i]) {
1902
+ kx[i] = 0;
1903
+ ky[i] = 0;
1904
+ kz[i] = 0;
1905
+ cx = 0;
1906
+ cy = 0;
1907
+ cz = 0;
1908
+ } else if (refocusingAt[i]) {
1909
+ kx[i] = -kx[i];
1910
+ ky[i] = -ky[i];
1911
+ kz[i] = -kz[i];
1912
+ cx = -cx;
1913
+ cy = -cy;
1914
+ cz = -cz;
1915
+ }
1916
+ }
1934
1917
  const kxP = new Float64Array(kx), kyP = new Float64Array(ky), kzP = new Float64Array(kz);
1935
1918
  for (const i of eIdx) {
1936
1919
  if (i > 0) {
@@ -1948,13 +1931,135 @@ var Pulseq = (() => {
1948
1931
  }
1949
1932
  return { ktraj: [kxP, kyP, kzP], t_ktraj: new Float64Array(grid), ktraj_adc: [kxA, kyA, kzA], t_adc: new Float64Array(adcT) };
1950
1933
  }
1951
- function collectGradientSupport(g, support, mode) {
1952
- if (!g || g.type === "none" || !g.timePoints || g.timePoints.length < 2) return;
1934
+ function collectSeriesSupport(series, mode, push) {
1935
+ if (series.times.length < 2) return;
1953
1936
  if (mode === "all") {
1954
- for (let i = 0; i < g.timePoints.length; i++) support.push(g.timePoints[i]);
1937
+ for (const time of series.times) push(time);
1938
+ return;
1939
+ }
1940
+ for (const time of series.requiredSupport) push(time);
1941
+ }
1942
+ function buildGlobalGradientSeries(blocks, gradientRaster, totalDuration) {
1943
+ const output = [
1944
+ { times: [], values: [], requiredSupport: [] },
1945
+ { times: [], values: [], requiredSupport: [] },
1946
+ { times: [], values: [], requiredSupport: [] }
1947
+ ];
1948
+ for (const block of blocks) {
1949
+ for (let axis = 0; axis < 3; axis++) {
1950
+ const piece = physicalGradientPiece(block, axis);
1951
+ if (piece.times.length) appendGradientPiece(output[axis], piece, gradientRaster);
1952
+ }
1953
+ }
1954
+ for (const series of output) {
1955
+ if (!series.times.length) continue;
1956
+ const first = series.times[0];
1957
+ const last = series.times[series.times.length - 1];
1958
+ if (first > 0) {
1959
+ series.times.unshift(-POLYNOMIAL_SUPPORT_EPSILON_SEC, first - POLYNOMIAL_SUPPORT_EPSILON_SEC);
1960
+ series.values.unshift(0, 0);
1961
+ series.requiredSupport.push(-POLYNOMIAL_SUPPORT_EPSILON_SEC, first - POLYNOMIAL_SUPPORT_EPSILON_SEC);
1962
+ }
1963
+ if (last < totalDuration) {
1964
+ series.times.push(last + POLYNOMIAL_SUPPORT_EPSILON_SEC, totalDuration + POLYNOMIAL_SUPPORT_EPSILON_SEC);
1965
+ series.values.push(0, 0);
1966
+ series.requiredSupport.push(last + POLYNOMIAL_SUPPORT_EPSILON_SEC, totalDuration + POLYNOMIAL_SUPPORT_EPSILON_SEC);
1967
+ }
1968
+ }
1969
+ return output;
1970
+ }
1971
+ function physicalGradientPiece(block, axis) {
1972
+ const gradients = [block.gx, block.gy, block.gz];
1973
+ const hasGradient = gradients.some((g) => g && g.type !== "none" && g.timePoints.length >= 2);
1974
+ if (!hasGradient) return { times: [], values: [], requiredSupport: [] };
1975
+ if (!block.rotation?.values) {
1976
+ const gradient = gradients[axis];
1977
+ if (!gradient || gradient.type === "none" || gradient.timePoints.length < 2) {
1978
+ return { times: [], values: [], requiredSupport: [] };
1979
+ }
1980
+ return {
1981
+ times: Array.from(gradient.timePoints),
1982
+ values: Array.from(gradient.waveform),
1983
+ requiredSupport: []
1984
+ };
1985
+ }
1986
+ const times = [];
1987
+ for (const gradient of gradients) {
1988
+ if (!gradient || gradient.type === "none") continue;
1989
+ for (const time of gradient.timePoints) times.push(time);
1990
+ }
1991
+ times.sort((a, b) => a - b);
1992
+ const uniqueTimes = [];
1993
+ for (const time of times) {
1994
+ if (!uniqueTimes.length || time - uniqueTimes[uniqueTimes.length - 1] > GRADIENT_ENDPOINT_TOLERANCE_SEC) {
1995
+ uniqueTimes.push(time);
1996
+ }
1997
+ }
1998
+ return {
1999
+ times: uniqueTimes,
2000
+ values: uniqueTimes.map((time) => {
2001
+ const rotated = rotateGradient(
2002
+ block,
2003
+ gradVal(block.gx, time),
2004
+ gradVal(block.gy, time),
2005
+ gradVal(block.gz, time)
2006
+ );
2007
+ return rotated[axis];
2008
+ }),
2009
+ requiredSupport: []
2010
+ };
2011
+ }
2012
+ function appendGradientPiece(target, piece, gradientRaster) {
2013
+ if (!piece.times.length) return;
2014
+ target.requiredSupport.push(piece.times[0], piece.times[piece.times.length - 1]);
2015
+ if (!target.times.length) {
2016
+ target.times.push(...piece.times);
2017
+ target.values.push(...piece.values);
1955
2018
  return;
1956
2019
  }
1957
- support.push(g.timePoints[0], g.timePoints[g.timePoints.length - 1]);
2020
+ const lastIndex = target.times.length - 1;
2021
+ const previousTime = target.times[lastIndex];
2022
+ const firstTime = piece.times[0];
2023
+ if (previousTime + gradientRaster < firstTime) {
2024
+ if (target.values[lastIndex] !== 0) {
2025
+ if (Math.abs(target.values[lastIndex]) > 1e-6) {
2026
+ target.times.push(previousTime + gradientRaster * 0.5);
2027
+ target.values.push(0);
2028
+ target.requiredSupport.push(previousTime + gradientRaster * 0.5);
2029
+ } else {
2030
+ target.values[lastIndex] = 0;
2031
+ }
2032
+ }
2033
+ if (piece.values[0] !== 0) {
2034
+ if (Math.abs(piece.values[0]) > 1e-6) {
2035
+ target.times.push(firstTime - gradientRaster * 0.5);
2036
+ target.values.push(0);
2037
+ target.requiredSupport.push(firstTime - gradientRaster * 0.5);
2038
+ } else {
2039
+ piece.values[0] = 0;
2040
+ }
2041
+ }
2042
+ }
2043
+ let start = 0;
2044
+ const currentLast = target.times[target.times.length - 1];
2045
+ while (start < piece.times.length && piece.times[start] <= currentLast) start++;
2046
+ for (let i = start; i < piece.times.length; i++) {
2047
+ target.times.push(piece.times[i]);
2048
+ target.values.push(piece.values[i]);
2049
+ }
2050
+ }
2051
+ function sampleSeries(series, time, cursors, axis) {
2052
+ const n = series.times.length;
2053
+ if (!n || time < series.times[0] || time > series.times[n - 1]) return 0;
2054
+ let cursor = Math.min(cursors[axis], n - 2);
2055
+ while (cursor + 1 < n && series.times[cursor + 1] < time) cursor++;
2056
+ cursors[axis] = cursor;
2057
+ if (cursor + 1 >= n) return series.values[n - 1];
2058
+ const t0 = series.times[cursor], t1 = series.times[cursor + 1];
2059
+ const v0 = series.values[cursor], v1 = series.values[cursor + 1];
2060
+ if (time <= t0 || t1 <= t0) return v0;
2061
+ if (time >= t1) return v1;
2062
+ return v0 + (v1 - v0) * (time - t0) / (t1 - t0);
1958
2063
  }
1959
2064
  function gradVal(g, t) {
1960
2065
  if (!g || g.type === "none") return 0;
@@ -1974,15 +2079,6 @@ var Pulseq = (() => {
1974
2079
  if (s <= 0) return wf[lo];
1975
2080
  return wf[lo] + (wf[hi] - wf[lo]) * (t - tp[lo]) / s;
1976
2081
  }
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
2082
  function timeIdx(t, g) {
1987
2083
  let lo = 0, hi = g.length;
1988
2084
  while (lo < hi) {