Skip to content

mariepy

Electromagnetic simulation of MRI transmit coils and virtual observation points for SAR, ported from MARIE 3.0.

Tests codecov PyPI License: GPL v3+

Install

pip install mariepy

Usage

Build a coil and a body, and solve them together at the Larmor frequency of the field strength you name. Both are built in code here. A case laid out as MARIE lays it out — a simulation file in data/inputs/, its body in data/bodies/, its coil in data/coils/coil_files/ — is read whole:

from mariepy.inputs import read_case

case = read_case("data/inputs/my_case.json")
result = solve(
    case.body, case.coil, case.medium, linear=case.linear, shield=case.shield
)

VoxelBody.read_marie, SurfaceMesh.read_gmsh22 and read_lumped_elements read each file on its own.

from mariepy.body import VoxelBody
from mariepy.coil import Port, SurfaceCoil
from mariepy.constants import Medium
from mariepy.mesh import SurfaceMesh
from mariepy.solver import solve

medium = Medium(3.0)  # 1H at 3 T
body = VoxelBody.sphere(0.07, 0.005, 52.0, 0.55, padding=2)
coil = SurfaceCoil.build(
    SurfaceMesh.loop(radius=0.12, width=0.01, n_around=48, n_across=2),
    (Port(tag=1, kind="port", load="none", value=0.0, quality=1.0, voltage=1.0),),
)

result = solve(body, coil, medium)

result.impedance, result.admittance and result.scattering are the port matrices; result.fields.electric and result.fields.magnetic are each port's field over the body grid, shaped (n_ports, 3, n1, n2, n3). From there:

from mariepy.fields import absorbed_power, circular_components

watts = absorbed_power(result.operator, result.fields)
b1_plus, b1_minus = circular_components(result.operator, result.fields)

A wire coil, WireCoil.read_gmsh22 or WireCoil.loop, goes to solve in the same place, and so does a CombinedCoil of a wire coil and a surface coil; a simulation file naming a WireFile, with or without a CoilFile, reads into one of them.

linear=True gives the body the piecewise-linear basis, twelve unknowns per voxel, which carries the field's variation inside each voxel; the fields then come back as those coefficients, and fields.at_centres gives their values at the voxel centres.

The solve runs on either device: build the body and the coil with device="cuda" and everything downstream follows.

Tuning, matching and calibration

A coil's lumped elements are closed by co-simulation, as MARIE does it. Solve the coil with its tunable elements opened into ports ("TMD": 1 in the simulation file), then search their values, the matching networks' and the decoupling, and calibrate the fields to the wave driving each matched port:

from mariepy.cosim import calibrate, co_simulate

closed = co_simulate(case.network, result.admittance, case.medium.angular_frequency)
electric = calibrate(result.fields.electric, closed.transmit)

With "TMD": 0 the file's values are placed as they are. closed.transmit has a column per transmitting port and closed.receive one per receiving port, as the element file assigns Tx, Rx and TxRx; closed.scattering is the transmitting ports' reflection and coupling; cosim.sweep gives the matched ports across a band. The searches need scipy: pip install "mariepy[cosim]".

Field bases, SNR and figures

A body's field basis is built once from a support surface around it (basis.surface_basis) or from a shell of currents around it (basis.dipole_basis), and any coil near that support is then solved through it:

from mariepy import basis, metrics, plot
from mariepy.solver import assemble_coil

incident = basis.surface_basis(case.body, case.basis_support, case.medium)
solved = basis.solve(incident, case.body, case.medium)
system = assemble_coil(case.coil, case.medium)
reduced = basis.solve_coil(case.coil, system, solved, case.body, case.medium)
ultimate_snr, ultimate_efficiency = basis.ultimate_maps(solved, case.body, case.medium)

metrics.noise_covariance, metrics.snr, metrics.transmit_efficiency and metrics.g_factor map a coil's performance from its fields; plot.geometry, plot.coil_currents, plot.scattering, plot.sweep and plot.slices draw the model and the maps (pip install "mariepy[plot]").

Head models, body coils, arrays and field maps

A segmentation that gives each voxel the fraction every tissue fills is averaged onto the solver's grid and mixed into a body; a body coil enters as the modes of an infinitely long birdcage; an array is a set of loops, one port each; and each coil's circular field components are written for a simulator:

import math
from mariepy import fields, incident, maps, tissue
from mariepy.solver import solve, solve_incident
from mariepy.wire import WireCoil

head = tissue.mix(tissue.coarsen(fractions, 5), table, medium, 5e-3)
modes = [incident.birdcage(head.body, medium, angle=a) for a in (0.0, math.pi / 2)]
body_coil = solve_incident(
    head.body,
    medium,
    torch.stack([e for e, _ in modes]),
    torch.stack([h for _, h in modes]),
)
array = solve(head.body, WireCoil.loops(centres, normals, 0.04, 36), medium)
open_ports = fields.combine(array.fields, array.impedance)  # 1 A each, the rest open
plus, minus = fields.circular_components(medium, open_ports)

examples/brainweb.py does this for BrainWeb's normal brain in a body coil and three head arrays, and writes each coil's maps with maps.write and the transmit coils' VOPs with vop.write.

Command line

mariepy solve my_case.json --data marie-tools/data --out ports.npz
mariepy vops my_case.json --data marie-tools/data --labels labels.npy \
    --table tissue.csv --out vops.npz
mariepy vops --sphere --out vops.npz   # a loop coil around a ball

solve prints the port solve, reciprocity, power balance, co-simulation and B1+ and SNR summaries, and --out keeps the admittance and scattering matrices. vops writes the file vop.read reads; without --labels and --table the case's own tissue properties are used with --density. examples/tissue_gabriel.csv shows the table's columns.

Development

See CONTRIBUTING.md.

Licence

Copyright (C) 2026 Matteo Cencini. mariepy is free software under the GNU General Public License, version 3 or any later version; see LICENSE. Code it carries from other projects, and their notices, are listed in THIRD_PARTY.md.

The licence covers the program. The files it writes, such as VOP files and field maps, are the user's and carry whatever terms their body models impose (see PLAN.md), so projects under any licence can read them.

About

Electromagnetic simulation of MRI transmit coils and virtual observation points for SAR, ported from MARIE 3.0.

Resources

Code of conduct

Contributing

Security policy

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages