/ concept-collection / seqlab
Sign in
concept-collection / seqlab
seqlab / scripts / engine-test.mjs
118 lines · 4.0 KBCodeBlameHistory
78d04f1seqlab: write and view pulseq MRI sequences in the browserJeremy Magland 1// Headless check of the run pipeline in Node against the local numbl build:
2// stages the vendored +mr tree + the FID example exactly the way the
3// numbl/browser worker does for an idle boot (cwd /project, relative
4// workspace-file names, execute-by-name with mainScriptPath 'repl' and no
5// search paths), runs it, collects *.seq via the same dir() snippet the
6// runner uses, and byte-compares the result against the MATLAB golden.
7import fs from 'node:fs'
8import path from 'node:path'
9import { fileURLToPath } from 'node:url'
10import {
11 executeCode,
12 VirtualFileSystem,
13 BrowserFileIOAdapter,
14 BrowserSystemAdapter,
15} from 'numbl'
17const here = path.dirname(fileURLToPath(import.meta.url))
18const repoRoot = path.join(here, '..')
19const mrRoot = path.join(repoRoot, 'src', 'engine', 'pulseq')
21const MD5_OVERRIDE = `function digest = md5(message, noBuiltIn)
22 digest = hash('MD5', char(message));
23end
26const COLLECT_SNIPPET = `seqlab_d = dir('*.seq');
27for seqlab_i = 1:numel(seqlab_d)
28 fprintf('SEQLAB_FILE:%s\\n', seqlab_d(seqlab_i).name);
29end
32// Boot files, as mrFiles.ts builds them (paths relative to /project).
33function bootFiles() {
34 const files = []
35 for (const entry of fs.readdirSync(path.join(mrRoot, '+mr'), { recursive: true })) {
36 const rel = String(entry)
37 const abs = path.join(mrRoot, '+mr', rel)
38 if (!fs.statSync(abs).isFile() || !rel.endsWith('.m')) continue
39 const vfsPath = `+mr/${rel}`
40 files.push({
41 path: vfsPath,
42 content: vfsPath === '+mr/+aux/md5.m' ? MD5_OVERRIDE : fs.readFileSync(abs, 'utf8'),
43 })
44 }
45 return files
48const script = fs.readFileSync(path.join(repoRoot, 'src', 'examples', 'fid.m'), 'utf8')
49const files = [...bootFiles(), { path: 'main.m', content: script }]
51// Mirror of worker.ts boot (idle) + execute('main').
52const enc = new TextEncoder()
53const vfs = new VirtualFileSystem()
54for (const f of files) vfs.writeFile('/project/' + f.path, enc.encode(f.content))
55vfs.setCwd('/project')
56const workspaceFiles = files.map((f) => ({ name: f.path, source: f.content }))
58const workerOptions = {
59 onOutput: (text) => process.stdout.write(`[numbl] ${text}`),
60 displayResults: true,
61 maxIterations: 1e9,
62 optimization: '1',
63 fileIO: new BrowserFileIOAdapter(vfs),
64 system: new BrowserSystemAdapter(vfs),
67const t0 = Date.now()
68let result
69try {
70 result = executeCode('main;', workerOptions, workspaceFiles, 'repl', [])
71} catch (err) {
72 console.error('run failed:', err.message ?? err)
73 if (err.file) console.error(` at ${err.file}:${err.line}`)
74 process.exit(1)
76console.log(`run finished in ${((Date.now() - t0) / 1000).toFixed(1)} s`)
78// Collect .seq files through the same follow-up execute the runner performs.
79const collected = []
80const collectResult = executeCode(
81 COLLECT_SNIPPET,
82 {
83 ...workerOptions,
84 onOutput: (text) => collected.push(text),
85 initialVariableValues: result.variableValues,
86 initialHoldState: result.holdState,
87 implicitCwdPath: result.implicitCwdPath,
88 },
89 workspaceFiles,
90 'repl',
91 result.searchPaths ?? [],
93if (!collectResult) process.exit(1)
94const names = [...collected.join('').matchAll(/^SEQLAB_FILE:(.+)$/gm)].map((m) => m[1])
95console.log('collected .seq files:', names)
96if (names.length !== 1 || names[0] !== 'fid.seq') {
97 console.error('FAIL: expected exactly [fid.seq]')
98 process.exit(1)
101const produced = Buffer.from(vfs.readFile('/project/fid.seq'))
102const golden = fs.readFileSync(path.join(repoRoot, 'test-data', 'golden', 'fid.seq'))
103if (produced.equals(golden)) {
104 console.log(`PASS: fid.seq is byte-identical to the MATLAB golden (${golden.length} bytes)`)
105} else {
106 console.error(`FAIL: fid.seq differs from golden (${produced.length} vs ${golden.length} bytes)`)
107 const a = produced.toString('utf8').split('\n')
108 const b = golden.toString('utf8').split('\n')
109 for (let i = 0; i < Math.max(a.length, b.length); i++) {
110 if (a[i] !== b[i]) {
111 console.error(` first diff at line ${i + 1}:`)
112 console.error(` produced: ${a[i]}`)
113 console.error(` golden: ${b[i]}`)
114 break
115 }
116 }
117 process.exit(1)
moveopenescclose