How to make an experiment class with a custom pulse sequence?

Hi Resonint Team,

I’m trying to make an experiment class using a custom pulse sequence (nothing special, just a PGSE-CPMG).

I’ve used what’s in ilumr-courseware/courses/MRI Fundamentals/Lab 1/Lab 1 Intro to NMR.ipynb at main · Resonint/ilumr-courseware · GitHub as a basis to create my own class.

However, I’m getting an error when trying to instantiate the object, the issue seems to be that it doesn’t like the sequence object

class CpmgExperiment(BaseExperiment):
    def setup(self):
        self.title = "CPMG Signal Experiment",
        self.seq = Sequence('/home/aaron/gsp-ilumr/programs/PGSE_CPMG.py'),
        self.enable_partialplot = True,
        self.enable_progressbar = True,
        self.plots = {
            'signal': SignalPlot(show_magnitude=True),
            'spectrum': SpectrumPlot()
        }      
        
        self.inputs = auto_inputs(self.seq, {
            'n_scans': 2, # number of repeated scans (averages)
            'n_samples': 2048, # number of samples to acquire per echo
            't_dw': 50e-6, # dwell time: duration of each sample in seconds
            't_repeat': 1, # repeat time (TR) in seconds
            't_echo': 100e-3 # echo time (TE) in seconds
        })
        
        # set custom labels
        self.inputs['n_scans'].name = "Number of averages"
        self.inputs['n_samples'].name = "Number of samples per echo"
        self.inputs['t_dw'].name = "Dwell Time (seconds)"
        self.inputs['t_repeat'].name = "Repeat Time, TR (seconds)"
        self.inputs['t_echo'].name = "Echo Time, TE (seconds)"
        
        
    def update_par(self):
        # use set timings to calculate t_diff and diffusion_time
        t_180 = self.seq.par.t_180 # duration of the 180° refocusing pulse
        t_90 = self.seq.par.t_90 # duration of the 90° excitation pulse
        acquisition_duration = self.seq.n_samples*self.seq.t_dw
        t_diff = (t_echo - t_90/2 - t_180 - acquisition_duration/2)/2 # calculate t_diff to use the available space
        g_diff = (0,0,0)
        diffusion_time = t_180 + t_diff
        
        if t_echo < t_90 + t_180 + acquisition_duration/2:
            raise Exception("t_echo is too short")
        
        self.seq.setpar(
            n_echo = 1,
            t_diff = t_diff,
            g_diff = g_diff,
            diffusion_time = diffusion_time,
            b_value = 0 # no diffusion
        )
        
    async def update_plots(self, *args, **kwargs):
        await self.seq.fetch_data()
        self.plots['signal'].update(seqdata=self.seq.data, t_dw=self.seq.par.t_dw)
        self.plots['spectrum'].update(seqdata=self.seq.data, t_tw=self.seq.par.t_dw)

experiment = CpmgExperiment(state_id='exp1')  

I get this error:

---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
Cell In[2], line 53
     50         self.plots['signal'].update(seqdata=self.seq.data, t_dw=self.seq.par.t_dw)
     51         self.plots['spectrum'].update(seqdata=self.seq.data, t_tw=self.seq.par.t_dw)
---> 53 experiment = CpmgExperiment(state_id='exp1')  

File /usr/local/lib/python3.10/site-packages/matipo/experiment/base_experiment.py:335, in BaseExperiment.__init__(self, title, seq, inputs, plots, enable_runloop, enable_progressbar, enable_partialplot, auto_save_default, partialplot_cooldown, progress_cooldown, workspace, state_id, default_par_files)
    332 self.log_handler = CardLogHandler()
    333 self.log.addHandler(self.log_handler)
--> 335 self.setup()
    337 if self.title is None:
    338     # use class name by default, adding spaces
    339     self.title = re.sub(r'(?<!^)(?=[A-Z])', ' ', self.__class__.__name__)

Cell In[2], line 12, in CpmgExperiment.setup(self)
      6 self.enable_progressbar = True,
      7 self.plots = {
      8     'signal': SignalPlot(show_magnitude=True),
      9     'spectrum': SpectrumPlot()
     10 }      
---> 12 self.inputs = auto_inputs(self.seq, {
     13     'n_scans': 2, # number of repeated scans (averages)
     14     'n_samples': 2048, # number of samples to acquire per echo
     15     't_dw': 50e-6, # dwell time: duration of each sample in seconds
     16     't_repeat': 1, # repeat time (TR) in seconds
     17     't_echo': 100e-3 # echo time (TE) in seconds
     18 })
     20 # set custom labels
     21 self.inputs['n_scans'].name = "Number of averages"

File /usr/local/lib/python3.10/site-packages/matipo/experiment/base_experiment.py:67, in auto_inputs(seq, inputs)
     64 pardef = None
     65 dtype = None
---> 67 if parname in seq._pardefmap:
     68     pardef = seq._pardefmap[parname]
     70 # default input takes priority if defined

AttributeError: 'tuple' object has no attribute '_pardefmap'

If I run the sequence normally, it executes fine, i.e.

seq = Sequence('/home/aaron/gsp-ilumr/programs/PGSE_CPMG.py')

#set the parameters

await seq.run()

Any help would be greatly appreciated.

Cheers,

Aaron

Hi Aaron,

Thats a pretty cryptic error, but it’s caused by a small typo: there are some commas at the end of the following lines that should be removed:

These commas are putting the values into tuples of length 1, which is breaking things.

Also some tips:

You may want to use different units to make the user input cleaner; if so you need to define the units of parameters in the pulse sequence like s:

    ParDef('t_dw', float, 1e-6, min=0.1e-6, max=80e-6, unit='s'),

See the system/programs/ files for reference, they should all have units defined.

Then you can use the Unit function to set the inputs to a different unit, and it will be converted automatically before being passed to the sequence object:

from matipo import Unit

self.inputs = auto_inputs(self.seq, {
    ...
    't_dw': 50*Unit('us'), # dwell time: duration of each sample in microseconds
    ...
})

You can also use self.log.info('Hello World!') inside the experiment class to print anything to the status box, which can help in debugging. self.log is a standard python logging object: logging — Logging facility for Python — Python 3.14.2 documentation

Hope this helps!
Cameron

Hi Cameron,

Thanks for looking at this for me. I was not intending to use this forum for code reviews to spot such a silly mistake! Clearly I have placed too much reliance on all the AI, linting, auto-formatting tools in VS code to catch these things.

Looking at the docs, particularly the API (Quick Reference Lists — Matipo 2.0 documentation) it would be really helpful if there was more content here, and also some examples, particularly for using the experiment class.

Cheers!

Aaron

1 Like