mrphy.mobjs
mrphy.mobjs
Classes for MRI excitation simulations
mrphy.mobjs.Pulse
- class mrphy.mobjs.Pulse(rf: Tensor | None = None, gr: Tensor | None = None, *, dt: Tensor = tensor(4.0000e-06, dtype=torch.float64), gmax: Tensor = tensor(5., dtype=torch.float64), smax: Tensor = tensor(12000., dtype=torch.float64), rfmax: Tensor = tensor(0.2500, dtype=torch.float64), desc: str = 'generic pulse', device: device = device(type='cpu'), dtype: dtype = torch.float32)
Pulse object of RF and GR
- Usage:
pulse = Pulse(rf, gr, *, dt, gmax, smax, rfmax, desc, device,
`` dtype)``- Inputs:
rf
: (N,xy, nT,(nCoils)) “Gauss”,xy
for separating real and imag part.gr
: (N,xyz,nT), “Gauss/cm”dt
: () ⊻ (N ⊻ 1,), “Sec”, dwell time.gmax
: () ⊻ (N ⊻ 1, xyz ⊻ 1), “Gauss/cm”, max |gradient|.smax
: () ⊻ (N ⊻ 1, xyz ⊻ 1), “Gauss/cm/Sec”, max |slew rate|.rfmax
: () ⊻ (N ⊻ 1,(nCoils)), “Gauss”, max |RF|.desc
: str, an description of the pulse to be constructed.device
: torch.device.dtype
: torch.dtype.
- Properties:
device
dtype
is_cuda
shape
:(N,1,nT)
gmax
: (N ⊻ 1, xyz), “Gauss/cm”, max |gradient|.smax
: (N ⊻ 1, xyz), “Gauss/cm/Sec”, max |slew rate|.rfmax
: (N ⊻ 1,(nCoils)), “Gauss”, max |RF|.rf
: (N,xy, nT,(nCoils)), “Gauss”,xy
for separating real and imag part.gr
: (N,xyz,nT), “Gauss/cm”dt
: (N ⊻ 1,), “Sec”, dwell time.desc
: str, an description of the pulse to be constructed.
- asdict(*, toNumpy: bool = True) dict
Convert mrphy.mobjs.Pulse object to dict
- Usage:
d = pulse.asdict(*, toNumpy)
- Inputs:
toNumpy
: [T/f], convert Tensor to Numpy arrays.
- Outputs:
d
: dict, dictionary with detached data identical to the object.
- beff(loc: Tensor, *, Δf: Tensor | None = None, b1Map: Tensor | None = None, γ: Tensor = tensor(4257.6000, dtype=torch.float64)) Tensor
Compute B-effective of provided location from the pulse
- Usage:
beff = pulse.beff(loc, *, Δf, b1Map, γ)
- Inputs:
loc
: (N,*Nd,xyz), “cm”, locations.
- Optionals:
Δf
: (N,*Nd,), “Hz”, off-resonance.b1Map
: (N,*Nd,xy,(nCoils)), a.u., transmit sensitivity.γ
: (N,*Nd), “Hz/Gauss”, gyro-ratio
- Outputs:
beff
: (N,*Nd,xyz,nT)
- interpT(dt: Tensor, *, kind: str = 'linear') Pulse
Interpolate pulse of dt by kind.
- Usage:
new_pulse = pulse.interpT(dt, *, kind)
- Inputs:
dt
: (1,), “Sec”, new simulation dwell time.kind
: str, passed to scipy.interpolate.interp1d.
- Outputs:
new_pulse
: mrphy.mobjs.Pulse object.
Note
This method requires both dt and self.dt to be unique/global, i.e., of shape
(1,)
, which ensures pulse length to be the same within a batch after interpolation.
mrphy.mobjs.SpinArray
- class mrphy.mobjs.SpinArray(shape: tuple, mask: Tensor | None = None, *, T1: Tensor | None = None, T1_: Tensor | None = None, T2: Tensor | None = None, T2_: Tensor | None = None, γ: Tensor | None = None, γ_: Tensor | None = None, M: Tensor | None = None, M_: Tensor | None = None, device: device = device(type='cpu'), dtype: dtype = torch.float32)
mrphy.mobjs.SpinArray object
- Usage:
spinarray = SpinArray(shape, mask, *, T1_, T2_, γ_, M_, device,
`` dtype)``spinarray = SpinArray(shape, mask, *, T1, T2, γ, M, device, dtype)
- Inputs:
shape
: tuple, e.g.,(N, nx, ny, nz)
.
- Optionals:
mask
: (1, *Nd), where does compact attributes locate in Nd.T1
⊻T1_
: (N, *Nd ⊻ nM), “Sec”, T1 relaxation coeff.T2
⊻T2_
: (N, *Nd ⊻ nM), “Sec”, T2 relaxation coeff.γ
⊻γ_
: (N, *Nd ⊻ nM), “Hz/Gauss”, gyro ratio.M
⊻M_
: (N, *Nd ⊻ nM, xyz), spins, equilibrium[0 0 1]
.device
: torch.device.dtype
: torch.dtype
- Properties:
shape
: (N, *Nd).mask
: (1, *Nd).device
.dtype
.ndim
:len(shape)
nM
:nM = torch.count_nonzero(mask).item()
.T1_
: (N, nM), “Sec”, T1 relaxation coeff.T2_
: (N, nM), “Sec”, T2 relaxation coeff.γ_
: (N, nM), “Hz/Gauss”, gyro ratio.M_
: (N, nM, xyz), spins, equilibrium [0 0 1]
Warning
Do NOT modify the
mask
of an object, e.g.,spinarray.mask[0] = True
.Do NOT proceed indexed/masked assignments over any non-compact attribute, e.g.,
spinarray.T1[0] = T1G
orspinarray.T1[mask] = T1G
. The underlying compact attributes will NOT be updated, since they do not share memory. The only exception is whentorch.all(mask == True)
and the underlying compact is contiguous, where the non-compact is just aview((N, *Nd, ...))
. Checkoutcrds_()
andmask_()
for indexed/masked access to compacts.
Tip
mask
is GLOBAL for a batch, in other words, one cannot specify distinct masks w/in a batch. This design is to reduce storage/computations in, e.g.,applypulse
(blochsim
), avoiding extra allocations. For DNN applications where an in-batch variation ofmask
may seemingly be of interest, havingtorch.all(mask == True)
and postponing the variations to eventual losses evaluation can be a better design, which allows reuse ofM_
, etc., avoiding repetitive allocations.
- applypulse(pulse: Pulse, *, doEmbed: bool = False, doRelax: bool = True, doUpdate: bool = False, loc: Tensor | None = None, loc_: Tensor | None = None, Δf: Tensor | None = None, Δf_: Tensor | None = None, b1Map: Tensor | None = None, b1Map_: Tensor | None = None) Tensor
Apply a pulse to the spinarray object
- Typical usage:
M = spinarray.applypulse(pulse, *, loc, doEmbed=True, doRelax,
`` doUpdate, Δf, b1Map)``M_ = spinarray.applypulse(pulse, *, loc_, doEmbed=False, `` \ ``doRelax, doUpdate, Δf_, b1Map_)
- Inputs:
pulse
: mrphy.mobjs.Pulse.loc
⊻loc_
: (N,*Nd ⊻ nM,xyz), “cm”, locations.
- Optionals:
doEmbed
: [t/F], returnM
orM_
doRelax
: [T/f], do relaxation during Bloch simulation.doUpdate
: [t/F], updateself.M_
Δf``⊻ ``Δf_
: (N,*Nd ⊻ nM), “Hz”, off-resonance.b1Map
⊻b1Map_
: (N,*Nd ⊻ nM,xy,(nCoils)), transmit sensitivity.
- Outputs:
M
⊻M_
: (N,*Nd ⊻ nM,xyz)
Note
When
doUpdate == True and doEmbed == False
, the output compact magnetization Tensor is a reference toself.M_
, and needs caution when being accessed.
- asdict(*, toNumpy: bool = True, doEmbed: bool = True) dict
Convert mrphy.mobjs.SpinArray object to dict
- Usage:
d = spinarray.asdict(*, toNumpy, doEmbed)
- Inputs:
toNumpy
: [T/f], convertTensor
to Numpy arrays.doEmbed
: [T/f], embed compactly stored (nM) data to the mask (*Nd).
- Outputs:
d
: dict, dictionary with detached data identical to the object.
- crds_(crds: list) list
Compute crds for compact attributes
Data in a SpinArray object is stored compactly, such that only those correspond to
1
on thespinarray.mask
is kept. This function is provided to facilitate indexing the compact data from regular indices, by computing (ix, iy, iz) -> iM- Usage:
crds_ = spinarray.crds_(crds)
- Inputs:
crds
: indices for indexing non-compact attributes.
- Outputs:
crds_
: list,len(crds_) == 2+len(crds)-self.ndim
.
v_[crds_] == v[crds]
, whenv_[crds_]=new_value
is effective.
- dim() int
Nd of the spinarray object, syntax sugar for len(spinarray.shape)
- Usage:
Nd = spinarray.dim()
- embed(v_: Tensor, *, out: Tensor | None = None) Tensor
Embed compact data into the spinarray.mask
- Usage:
out = spinarray.embed(v_, *, out)
- Inputs:
v_
: (N, nM, …), must be contiguous.
- Optionals:
out
: (N, *Nd, …), in-place holder.
- Outputs:
out
: (N, *Nd, …).
- extract(v: Tensor, *, out_: Tensor | None = None) Tensor
Extract data with the spinarray.mask, making it compact
- Usage:
out_ = spinarray.extract(v, *, out_)
- Inputs:
v
: (N, *Nd, …).
- Optionals:
out_
: (N, nM, …), in-place holder, must be contiguous.
- Outputs:
out_
: (N, nM, …).
- freeprec(dur: Tensor, *, doEmbed: bool = False, doRelax: bool = True, doUpdate: bool = False, Δf: Tensor | None = None, Δf_: Tensor | None = None) Tensor
Free precession of duration
dur
- Typical usage:
M = obj.freeprec(dur, doEmbed=True, doRelax, doUpdate, Δf)
M_ = obj.applypulse(dur, doEmbed=False, doRelax, doUpdate, Δf_)
- Inputs:
dur
: () ⊻ (N ⊻ 1,), “Sec”, duration of free-precession.
- Optionals:
doEmbed
: [t/F], returnM
orM_
doRelax
: [T/f], do relaxation during free precession.doUpdate
: [t/F], updateself.M_
Δf``⊻ ``Δf_
: (N ⊻ 1,*Nd ⊻ nM), “Hz”, off-resonance.
- Outputs:
M
⊻M_
: (N,*Nd ⊻ nM,xyz)
Note
When
doUpdate == True and doEmbed == False
, the output compact magnetization Tensor is a reference toself.M_
, and needs caution when being accessed.
- mask_(*, mask: Tensor) Tensor
Extract the compact region of an input external
mask
.- Usage:
mask_ = spinarray.mask_(mask)
- Inputs:
mask
: (1, *Nd).
- Outputs:
mask_
: (1, nM),mask_
can be used on compact attributes.
- numel() int
Number of spins for the spinarray object, incompact.
Syntax sugar of
spinarray.mask.numel()
, effectivelyprod(spinarray.size())
.- Usage:
res = spinarray.numel()
- pulse2beff(pulse: Pulse, *, doEmbed: bool = False, loc: Tensor | None = None, loc_: Tensor | None = None, Δf: Tensor | None = None, Δf_: Tensor | None = None, b1Map: Tensor | None = None, b1Map_: Tensor | None = None) Tensor
Compute B-effective of
pulse
with the spinarray’s parameters- Typical usage:
beff = spinarray.pulse2beff(pulse, *, loc, doEmbed=True, Δf, ``\ ``b1Map)
beff_ = spinarray.pulse2beff(pulse, *, loc_, doEmbed=False, ``\ ``Δf_, b1Map_)
- Inputs:
pulse
: mrphy.mobjs.Pulse.loc
⊻loc_
: (N,*Nd ⊻ nM,xyz), “cm”, locations.
- Optionals:
doEmbed
: [t/F], returnbeff
orbeff_
Δf
⊻Δf_
: (N,*Nd ⊻ nM), “Hz”, off-resonance.b1Map
⊻b1Map_
: (N,*Nd ⊻ nM,xy,(nCoils)), transmit sensitivity.
- Outputs:
beff
⊻beff_
: (N,*Nd ⊻ nM,xyz,nT).
- size() tuple
Size of the spinarray object.
Syntax sugar of
spinarray.shape
.- Usage:
sz = spinarray.size()
mrphy.mobjs.SpinCube
- class mrphy.mobjs.SpinCube(shape: tuple, fov: Tensor, *, mask: Tensor | None = None, ofst: Tensor = tensor([[0., 0., 0.]]), Δf: Tensor | None = None, Δf_: Tensor | None = None, T1: Tensor | None = None, T1_: Tensor | None = None, T2: Tensor | None = None, T2_: Tensor | None = None, γ: Tensor | None = None, γ_: Tensor | None = None, M: Tensor | None = None, M_: Tensor | None = None, device: device = device(type='cpu'), dtype: dtype = torch.float32)
Bases:
SpinArray
mrphy.mobjs.SpinCube object
- Usage:
SpinCube(shape, fov, mask, *, ofst, Δf_, T1_, T2_, γ_, M_, device,
`` dtype)``SpinCube(shape, fov, mask, *, ofst, Δf, T1, T2, γ, M, device,
‘’ dtype)``- Inputs:
shape
: tuple, e.g.,(N, nx, ny, nz)
.fov
: (N, xyz), “cm”, field of view.
- Optionals:
mask
: (1, *Nd), where does compact attributes locate in Nd.ofst
: (N, xyz), Tensor “cm”, fov offset from iso-center.Δf
⊻Δf_
: (N, *Nd ⊻ nM), “Hz”, off-resonance map.T1
⊻T1_
: (N, *Nd ⊻ nM), “Sec”, T1 relaxation coeff.T2
⊻T2_
: (N, *Nd ⊻ nM), “Sec”, T2 relaxation coeff.γ
⊻γ_
: (N, *Nd ⊻ nM), “Hz/Gauss”, gyro ratio.M
⊻M_
: (N, *Nd ⊻ nM, xyz), spins, equilibrium[0 0 1]
.device
: torch.device.dtype
: torch.dtype
- Properties:
spinarray
: SpinArray object.Δf_
: (N, nM), “Hz”, off-resonance map.loc_
: (N, nM, xyz), “cm”, location of spins.fov
: (N, xyz), “cm”, field of view.ofst
: (N, xyz), “cm”, fov offset from iso-center.
- applypulse(pulse: Pulse, *, doEmbed: bool = False, doRelax: bool = True, doUpdate: bool = False, b1Map: Tensor | None = None, b1Map_: Tensor | None = None) Tensor
Apply a pulse to the spincube object
- Usage:
M = spincube.applypulse(pulse, *, doEmbed=True, doRelax, b1Map)
M_ = spincube.applypulse(pulse, *, doEmbed=False, doRelax,
`` b1Map_)``- Inputs:
pulse
: mobjs.Pulse object.
- Optionals:
doEmbed
: [t/F], returnM
orM_
.doRelax
: [T/f], do relaxation during Bloch simulation.b1Map
⊻b1Map_
: (N,*Nd ⊻ nM,xy,(nCoils)), transmit sensitivity.
- Outputs:
M
⊻M_
: (N,*Nd ⊻ nM,xyz).
- asdict(*, toNumpy: bool = True, doEmbed: bool = True) dict
Convert mrphy.mobjs.SpinCube object to dict
- Usage:
d = spincube.asdict(*, toNumpy, doEmbed)
- Inputs:
toNumpy
: [T/f], convertTensor
to Numpy arrays.doEmbed
: [T/f], embed compactly stored (nM) data to the mask (*Nd).
- Outputs:
d
: dict, dictionary with detached data identical to the object.
- freeprec(dur: Tensor, *, doEmbed: bool = False, doRelax: bool = True, doUpdate: bool = False) Tensor
Free precession of duration
dur
- Typical usage:
M = obj.freeprec(dur, doEmbed=True, doRelax, doUpdate)
M_ = obj.applypulse(dur, doEmbed=False, doRelax, doUpdate)
- Inputs:
dur
: () ⊻ (N ⊻ 1,), “Sec”, duration of free-precession.
- Optionals:
doEmbed
: [t/F], returnM
orM_
doRelax
: [T/f], do relaxation during free precession.doUpdate
: [t/F], updateself.M_
- Outputs:
M
⊻M_
: (N,*Nd ⊻ nM,xyz)
Note
When
doUpdate == True and doEmbed == False
, the output compact magnetization Tensor is a reference toself.M_
, and needs caution when being accessed.
- pulse2beff(pulse: Pulse, *, doEmbed: bool = False, b1Map: Tensor | None = None, b1Map_: Tensor | None = None) Tensor
Compute B-effective of
pulse
with the spincube’s parameters- Typical usage:
beff = spincube.pulse2beff(pulse, *, doEmbed=True, b1Map)
beff_ = spincube.pulse2beff(pulse, *, doEmbed=False, b1Map_)
- Inputs:
pulse
: mrphy.mobjs.Pulse.
- Optionals:
doEmbed
: [t/F], returnbeff
orbeff_
.b1Map
⊻b1Map_
: (N,*Nd ⊻ nM,xy,(nCoils)), transmit sensitivity.
- Outputs:
beff
⊻beff_
: (N,*Nd ⊻ nM,xyz,nT).