Skip to content

MaRGE_Bug_Report

José Miguel Algarín edited this page Jun 2, 2026 · 1 revision

MaRGE Bug Report & Patch Summary

Date: March 2026
System: MaRGE on Windows 10, PyPulseq 1.5.0, marcos_client, Red Pitaya (rp-122)
Sequences tested: FID, Larmor, Larmor Raw, Noise, RabiFlops, Shimming, RareDoubleImage
Hardware: SAMuRaI Solenoid coil, ~15.38 MHz (0.36 T)


Summary

During hands-on testing of the MaRGE NMR/MRI control software, several bugs were identified and patched across the sequence, reconstruction, and interpreter layers. The most significant issue was a full incompatibility between PyPulseq 1.5.0 (the currently pip-installable version) and the marga_pulseq interpreter, which was written for PyPulseq 1.4. This made the RareDoubleImage sequence completely non-functional. Additional bugs caused crashes in FID, Shimming, and Larmor sequences, and a silent calibration corruption in RabiFlops that affected all subsequent sequences.

All patches have been applied and tested. A new minimal SpinEchoImage sequence was also contributed as a simpler imaging reference.


Bug 1 — FID: argmin of an empty sequence

File: marge/seq/fid.py
Error: ERROR: An error occurred in sequenceAnalysis method: attempt to get argmin of an empty sequence

Root cause: When the acquired signal was too weak or noisy, the spectrum contained no identifiable peak. getFWHM called np.argmin and np.argmax directly on the result without checking for empty or flat arrays.

Fix: Added empty-array guards before every np.argmin / np.argmax call in getFWHM, returning np.nan on failure. Added a ValueError guard at the top of sequenceAnalysis to catch empty data before processing begins.

Great!


Bug 2 — Larmor: Frequency Corruption (larmorFreq = 431152977055367)

Files: marge/seq/mriBlankSeq.py, marge/seq/larmor.py
Error: After running Larmor, the stored Larmor frequency became a number in the hundreds of trillions (Hz instead of MHz), breaking all subsequent sequences.

Root cause: Double unit conversion. sequenceAtributes in mriBlankSeq multiplies larmorFreq by units.MHz = 1e6 (correctly converting the display value in MHz to Hz for internal use and storing it on self.larmorFreq). Then larmor.py::sequenceRun was copying that Hz value back into mapVals['larmorFreq']. The GUI then interpreted this as MHz and displayed and stored the corrupted value.

Fix:

  • Added _sanitize_larmor_mhz(val) helper in mriBlankSeq.py that detects and corrects values outside the valid MHz range (0.1–100 MHz)
  • Removed the double-conversion step in larmor.py::sequenceRun
  • Added self.mapVals['bw'] = self.bw * 1e3 to correctly store bandwidth in kHz for the reconstruction

I think this is already fixed in newest version


Bug 3 — Larmor Recon: RF Amplitude Displayed as 0.0000 a.u.

File: marge/recon/Larmor.py
Error: RF excitation amplitude: 0.0000 a.u. printed in recon output regardless of settings.

Root cause: The amplitude formula rfExFA * pi/180 / (rfExTime * hw.b1Efficiency) had an erroneous extra * 1e6 factor applied to rfExTime. Since rfExTime was already in µs, this inflated the denominator to ~90,000,000 instead of ~90, producing a near-zero result.

Fix: Removed the erroneous * 1e6 factor. Added a _squeeze_mat() helper for robust .mat field extraction handling various numpy array shapes (1,1), (1,n), scalar. Added a bw_true field (saved in larmor.py, loaded in Larmor.py) to preserve the actual hardware-corrected bandwidth after experiment initialisation.

I find nothing like this in current Larmor recon


Bug 4 — Larmor Raw: Not Visible in GUI

File: marge/seq/larmor_raw.py
Error: Larmor Raw sequence did not appear in the GUI sequence dropdown.

Root cause: self.addParameter(key='toMaRGE', val=False) — the flag that controls GUI visibility was set to False.

Fix: Changed val=False to val=True.

This is intentional, as only sequence 100 % compatible with all functionalities are shown by default


Bug 5 — RabiFlops: Silent Overwrite of hw.b1Efficiency

File: marge/recon/RabiFlops.py
Error: After running RabiFlops, all subsequent sequences (Larmor, Shimming, RareDoubleImage) silently used a wrong b1Efficiency, producing incorrect RF amplitudes.

Root cause: Line 63 of RabiFlops.py unconditionally overwrote hw.b1Efficiency in memory with the value calculated from the Rabi fit. This happened every time reconstruction ran, even if the user had already set a calibrated value manually. The hw module is a global singleton shared across all sequences in the session.

Fix: Removed the automatic hw.b1Efficiency = ... assignment. The reconstruction now prints a recommendation message showing the fitted value, leaving the user in control of when and whether to apply it. The recommended workflow is to manually update hw_config.py and hw_rf.csv after verifying the Rabi result.

This is intentional as this sequences is used for autocalibration


Bug 6 — Shimming: setting an array element with a sequence (two locations)

Files: marge/seq/shimmingSweep.py, marge/recon/Shimming.py

Bug 6a — sequenceRun

Error: TypeError: setting an array element with a sequence when writing the optimal shimming value back to self.shimming0.

Root cause: sxVector[np.argmax(dataFFT)] returned a 1-element (1,) array (because sxVector had shape (N,1)). Assigning this to self.shimming0[0] (a scalar slot in a numpy array) raised the error.

Fix: Changed to float(np.squeeze(sxVector[np.argmax(dataFFT)])) to force a scalar before assignment.

Bug 6b — sequenceAnalysis

Error: Same TypeError during reconstruction, in getFWHM.

Root cause: mat_data['bw'] loaded from .mat as a (1,1) 2D array. Passing it to np.linspace produced a 3D f_vector, causing np.argmax to fail.

Fix: Squeezed to scalar before use: bw = float(np.squeeze(mat_data['bw'])) * 1e-3.


Bug 7 — run_recon / data_processing.py: Fragile seqName Extraction

File: marge/recon/data_processing.py
Error: Various ValueError / AttributeError crashes when loading .mat files with certain numpy array shapes for seqName.

Root cause: seqName in .mat files can have shapes (1,1), (1,), object dtype etc. depending on how MATLAB/scipy saved it. Direct .item() calls failed on non-scalar arrays.

Fix: Wrapped extraction in try/except using str(np.squeeze(seq_raw).item()).strip(). Function now returns a safe ({}, [], {}) tuple on any failure instead of propagating an exception to the GUI.


Bug 8 — RareDoubleImage: Full Incompatibility with PyPulseq 1.5.0

Files: marge/seq/rare_double_image.py, .venv/Lib/site-packages/marga_pulseq/interpreter.py

This was the most critical issue. PyPulseq 1.5.0 (the current version on PyPI) changed the .seq file format in several incompatible ways. The marga_pulseq interpreter was written for PyPulseq 1.4 and failed to parse any sequence generated by 1.5. RareDoubleImage was completely non-functional.

Bug 8a — GUI parameter empty strings causing int('')

Error: invalid literal for int() with base 10: '' immediately on sequence run.

Root cause: Numeric parameters (etl, nScans, nPoints, add_rd_points, etc.) could arrive as empty strings when the GUI initialises parameter fields before the user enters values.

Fix: Added _to_int(v, default) and _to_float(v, default) coercion helpers at the top of sequenceRun, applied to all numeric parameters.

Bug 8b — batch.version_revision empty string

Error: int('') crash inside the interpreter version parser.

Root cause: PyPulseq 1.5's pp.Sequence sets version fields by parsing __version__. If the version string is malformed, version_revision is set to an empty string ''. The interpreter then calls int('') when reading the [VERSION] section.

Fix: After every pp.Sequence(system) call in rare_double_image.py, explicitly set:

batch.version_major = 1
batch.version_minor = 4
batch.version_revision = 0

Bug 8c — Interpreter version routing == 4 should be >= 4

Error: Interpreter used v1 (legacy) readers for all content, failing to parse v1.5 format sections.

Root cause: interpreter.py only activated v2 readers when self._version_minor == 4. PyPulseq 1.5 writes version_minor = 5, so the condition was never met.

Fix: Changed if self._version_minor == 4: to if self._version_minor >= 4:.

Bug 8d — Missing _safe_int() helper for robust integer parsing

Problem: Multiple places in the interpreter called bare int(x) on values that could be None or empty strings from malformed or newer-format .seq sections.

Fix: Added _safe_int(x, default=0) class method to the interpreter:

def _safe_int(self, x, default=0):
    if x is None or (isinstance(x, str) and x.strip() == ''):
        return default
    return int(float(x))

Applied to all version field and block field parsing.

Bug 8e — RF event format: 8 fields (v1.4) → 12 fields (v1.5)

Error: Frequency offset of ADC event 1 (0.0) doesn't match that of RF event 1 (15.0)

Root cause: _read_rf_events_v2 expected 8 fields per RF line. PyPulseq 1.5 produces 12 fields, shifting the positions of delay (now index 6), freq (now index 9), and phase (now index 10). The old reader mapped them to wrong fields, producing zero frequency offsets.

Fix: Added len(tmp) >= 12 branch with correct field remapping. Kept len(tmp) >= 8 as fallback for v1.4 files.

Bug 8f — ADC event format: 6 fields (v1.4) → 9 fields (v1.5)

Root cause: _read_adc_events expected 6 fields. PyPulseq 1.5 produces 9, shifting freq (now index 6) and phase (now index 7). Old reader read wrong values into these fields.

Fix: Added len(tmp) >= 9 branch with correct remapping. Kept len(tmp) >= 6 fallback.

Bug 8g — GRADIENTS event format: 5 fields (v1.4) → 7 fields (v1.5)

Root cause: _read_grad_events_v2 expected 5 fields. PyPulseq 1.5 produces 7, shifting shape_id (index 4), time_shape_id (index 5), and delay (index 6).

Fix: Added len(tmp) >= 7 branch with correct remapping. Kept fallbacks for 5-field and 4-field formats.

Bug 8h — SHAPES section format change

Root cause: PyPulseq 1.5 changed the SHAPES section from run-length encoded (RLE) to uncompressed format. The v1 shapes reader tried to parse uncompressed data as RLE, reading blank separator lines as repeat counts and crashing on int('').

Fix: Updated _read_shapes to use _safe_int() throughout and detect/handle both compressed (RLE) and uncompressed formats.


New Contribution — SpinEchoImage Sequence

Files added:

  • marge/seq/spin_echo_image.py
  • marge/recon/SpinEchoImage.py

A minimal 2D spin echo imaging sequence implemented using the direct flo_dict waveform approach (no PyPulseq dependency). Collects one k-space line per TR using a 90°–180° spin echo with frequency encoding (readout gradient) and phase encoding. Appears in the GUI automatically via toMaRGE=True.

Purpose:

  • Useful as a teaching and reference implementation of 2D MRI
  • Provides a working imaging sequence that bypasses all PyPulseq compatibility issues
  • Simpler starting point for users learning the MaRGE sequence framework

Files Modified

File Change
marge/seq/fid.py Empty array guards in getFWHM and sequenceAnalysis
marge/seq/larmor.py Fixed double unit conversion for larmorFreq; added bw kHz storage
marge/seq/larmor_raw.py Set toMaRGE=True to show in GUI
marge/seq/shimmingSweep.py Fixed scalar assignment for optimal shimming value
marge/seq/mriBlankSeq.py Added _sanitize_larmor_mhz() helper
marge/seq/rare_double_image.py Added parameter coercion; fixed PyPulseq version fields
marge/recon/Larmor.py Fixed RF amplitude calculation; added _squeeze_mat(); added bw_true
marge/recon/RabiFlops.py Removed automatic hw.b1Efficiency overwrite
marge/recon/Shimming.py Fixed scalar bw extraction in getFWHM
marge/recon/data_processing.py Robust seqName extraction; safe return on failure
.venv/.../marga_pulseq/interpreter.py Full PyPulseq 1.5 compatibility patch (8 sub-fixes)
marge/seq/spin_echo_image.py New: Simple spin echo imaging sequence
marge/recon/SpinEchoImage.py New: Reconstruction for SpinEchoImage

Clone this wiki locally