documentation / example / source / phantom_topographies /
Plot the topographies of HPI coils and different MEG phantoms
In this example we compute the spatial topographies of a variety of MEG phantoms: first for three magnetic dipole coils that are placed as head position indicators (HPIs) on the location of the nasion and left and right preauricular points (LPA and RPA). Subsequently, we compute and plot the topographies for different types of MEG phantoms.
All computations below are done for 144 radial OPM channels placed in a FieldLine beta 2 helmet, but they could equally well be done for any other arragmenent of the MEG sensors, either for SQUID or for OPM sensors. You can also provide your own sensor definition, see this FAQ and the documentation in the ft_datatype_sens function.
When changing the MEG sensor array to another template or to your own, please consider that the sensor arrays can be expressed relative to different head coordinaye systems. The FieldLine beta 2 helmet used below follows the Neuromag convention and has the x-axis towards the right, the y-asix to the front, and the z-axis in the superior direction.
% The following templates are located in fieldtrip/template/gradiometer
% load bti148.mat
% load bti248.mat
% load bti248grad.mat
% load ctf64.mat
% load ctf151.mat
% load ctf275.mat
% load neuromag122.mat
% load neuromag306.mat
% load yokogawa160.mat
% load yokogawa208.mat
% load fieldlinealpha1.mat
load fieldlinebeta2.mat
Since all phantoms that we will consider here for now are coils that emulate a magnetic dipole phantoms, we construct an infinite volume conductor.
headmodel = ft_headmodel_infinite();
headmodel.unit = 'm';
Construct the phantom coils
The snippets of code for the different HPI or phantom coil arrangements below are all handled in the same way. We first construct a phantom structure with the positions and orientations of the phantom coils. Those are formatted similar to the FieldTrip grad structure, so that we can use the ft_plot_sens function for visualization.
After constructing one of the phantom structures, we plot the phantom coils together with the OPM helmet and then we compute and visualise the spatial topographies of all the phantom coils.
To move or rotate either the OPM helmet or the phantom coil array, you can use ft_transform_geometry or other functions for realignment.
HPI coils on the anatomical landmarks
% this approximates where I would expect the nose and ears
nas = [0 8 0]/100;
lpa = [-8 0 0]/100;
rpa = [+8 0 0]/100;
phantom.coilpos = [
nas
lpa
rpa
];
phantom.coilori = [
0 1 0
-1 0 0
+1 0 0
];
phantom.label = {
'nas'
'lpa'
'rpa'
};
QuSpin HALO
QuSpin has designed their HALO to calibrate the OPM sensor positions and orientations. It consists of a circular printed circuit board (PCB) with 16 electromagnetic coil positions arranged in three distinct concentric layouts. Each of the 16 coils has a diameter of approximately 22 mm. The flat circular board is mounted on a base that holds it roughly 5 cm directly above the participant’s helmet or sensor array. It is described in the publication “Determining sensor geometry and gain in a wearable MEG system” by Hill et al. (2025).
% Outer Circle: 12 dipolar coils are evenly spaced every 30 degrees around a circle with a radius of 12.7 cm.
% Inner Circle: 3 dipolar coils are spaced at 120-degree intervals around a smaller circle with a radius of 6.3 cm.
% Center: 1 final dipole coil is positioned directly at the center of the circular board.
phantom.coilpos = zeros(16,3);
phantom.coilori = zeros(16,3);
phantom.coilori(:,3) = 1; % they are all pointing in the z-direction
% phantom.label = {'1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12', '13', '14', '15', '16'};
phantom.label = {'12', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '13', '16', '15', '14'};
for i=1:12
[x, y] = pol2cart(pi/2 - (i-1)*2*pi/12, 0.127);
phantom.coilpos(i,1) = x;
phantom.coilpos(i,2) = y;
phantom.coilpos(i,3) = 0.05 + max(fieldlinebeta2.coilpos(:,3)); % 5 cm above the top of the helmet
end
for i=1:3
[x, y] = pol2cart(pi/2 - (i-1)*2*pi/3, 0.063);
phantom.coilpos(12+i,1) = x;
phantom.coilpos(12+i,2) = y;
phantom.coilpos(12+i,3) = 0.05 + max(fieldlinebeta2.coilpos(:,3)); % 5 cm above the top of the helmet
end
phantom.coilpos(16,1) = 0;
phantom.coilpos(16,2) = 0;
phantom.coilpos(16,3) = 0.05 + max(fieldlinebeta2.coilpos(:,3)); % 5 cm above the top of the helmet
Cerca “Squalo”
Cerca has a PCB board with a 5x5 arrangement of magnetic dipole coils that I have seen in the MEG lab in Amsterdam. I don’t know the exact dimensions, so this is just an approximation.
% the following constructs a 20x20 cm grid placed posterior of the participant
% you can change this according to where you want the grid to be located
xgrid = linspace(-0.1, 0.1, 5);
ygrid = -0.2;
zgrid = linspace(-0.1, 0.1, 5);
[X, Y, Z] = ndgrid(xgrid, ygrid, zgrid);
phantom = [];
phantom.coilpos = [X(:) Y(:) Z(:)];
phantom.coilori = zeros(size(phantom.coilpos));
phantom.coilori(:,2) = 1;
phantom.label = arrayfun(@num2str, 1:size(phantom.coilpos,1), 'UniformOutput', false)';
Spherical HPI array
At the DCCN we constructed an array with 24 HPI coils on a 120 mm diameter sphere that we use in combination with a 24-channel phantom current driver. More details will be posted soon on the open hardware page.
phantomcoil.unit = 'mm';
phantomcoil.coilpos = [
0 0 60.0000
0.0000 36.4237 47.6793
34.6410 11.2555 47.6793
21.4093 -29.4674 47.6793
-21.4093 -29.4674 47.6793
-34.6410 11.2555 47.6793
31.5439 43.4164 26.8328
51.0390 -16.5836 26.8328
0.0000 -53.6656 26.8328
-51.0390 -16.5836 26.8328
-31.5439 43.4164 26.8328
0.0000 58.9348 11.2555
56.0503 18.2119 11.2555
34.6410 -47.6793 11.2555
-34.6410 -47.6793 11.2555
-56.0503 18.2119 11.2555
34.6410 47.6793 -11.2555
56.0503 -18.2119 -11.2555
0.0000 -58.9348 -11.2555
-56.0503 -18.2119 -11.2555
-34.6410 47.6793 -11.2555
0.0000 53.6656 -26.8328
34.6410 -11.2555 -47.6793
-34.6410 -11.2555 -47.6793
];
phantomcoil.coilori = [
0 0 1.0000
0.0000 0.6071 0.7947
0.5774 0.1876 0.7947
0.3568 -0.4911 0.7947
-0.3568 -0.4911 0.7947
-0.5774 0.1876 0.7947
0.5257 0.7236 0.4472
0.8507 -0.2764 0.4472
-0.0000 -0.8944 0.4472
-0.8507 -0.2764 0.4472
-0.5257 0.7236 0.4472
0.0000 0.9822 0.1876
0.9342 0.3035 0.1876
0.5774 -0.7947 0.1876
-0.5774 -0.7947 0.1876
-0.9342 0.3035 0.1876
0.5774 0.7947 -0.1876
0.9342 -0.3035 -0.1876
-0.0000 -0.9822 -0.1876
-0.9342 -0.3035 -0.1876
-0.5774 0.7947 -0.1876
0.0000 0.8944 -0.4472
0.5774 -0.1876 -0.7947
-0.5774 -0.1876 -0.7947
];
phantomcoil.label = { '1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12', '13', '14', '15', '16', '17', '18', '19', '20', '21', '22', '23', '24' };
Adachi sphere
Adashi and colleagues describe a spherical phantom in their 2023 paper “A Spherical Coil Array for the Calibration of Whole-Head Magnetoencephalograph Systems” that consists of large coils wrapped around a sphere. At the DCCN we constructed one based on a 3D-printed sphere with 120 mm diameter. This phantom can be described with the following geometry, where the center of each coil is teh same as the center of the sphere.
phantom = [];
phantom.unit = 'm';
phantom.coilpos = zeros(16,3);
phantom.coilori = [
0 0 1.0000
0.8944 0 0.4472
0.2764 -0.8507 0.4472
-0.7236 -0.5257 0.4472
-0.7236 0.5257 0.4472
0.2764 0.8507 0.4472
0.4911 -0.3568 0.7947
-0.1876 -0.5774 0.7947
-0.6071 0 0.7947
-0.1876 0.5774 0.7947
0.4911 0.3568 0.7947
0.7947 -0.5774 0.1876
0.3035 -0.9342 -0.1876
-0.3035 -0.9342 0.1876
-0.7947 -0.5774 -0.1876
-0.9822 0 0.1876
];
phantom.label = { '1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12', '13', '14', '15', '16'}';
Each of the 16 coils has a diameter of 120 mm. To plot these correctly with ft_plot_sens, you have to specify a a coilsize of 120 mm.
All other phantoms considered here have very small HPI-like coils that we model as a magnetic dipole. For most EEG and MEG volume conduction models the ft_compute_leadfield will assume that the source is an equivalent current dipole (ECD). When we specify the volume conductor type as as “infinite” and pass a MEG sensor array rather than an EEG electrode array, it will compute the leadfield for a magnetic dipole.
To compute the leadfield matrix (i.e. the topography for a coil in the x, y, and z-direction) for a coil of a specific size, we have to specify the size in the headmodel. So to compute the topography for coils wrapped around the 120 mm diameter sphere, you have to specify the following headmodel:
headmodel = [];
headmodel.type = 'circularcoil';
headmodel.diameter = 0.12;
headmodel.unit = 'm';
Visualize the phantom coils together with the OPM sensors
The following code can be used with all phantom structures that we created above. The coil size that is plotted is not geometrically correct, but set to 10 mm to make it easier to see the coil and orientation. For the Adachi sphere the coil size should be specified as 120 mm.
figure
ft_plot_sens(fieldlinebeta2, 'axes', false, 'fiducial', false)
ft_plot_sens(phantom, 'coil', true, 'orientation', false, 'coilsize', 0.01)
% ft_plot_sens(phantom, 'label', 'label')
ft_plot_dipole(phantom.coilpos, phantom.coilori', 'unit', 'm')
Compute and plot the phantom coil topographies
The following code (with some minor changes) can be used with all phantom structures that we created above.
% compute actual magnetic field values in SI units
diameter = 0.006;
turns = 3;
area = pi*(diameter/2)^2;
current = 5 / 200000; % 5V peak-to-peak over a 200 kOhm resistor
mom = turns * area * current;
nchans = length(fieldlinebeta2.label);
ncoils = length(phantom.label);
dat = zeros(nchans, ncoils);
for i=1:ncoils
dat(:,i) = ft_compute_leadfield(phantom.coilpos(i,:), fieldlinebeta2, headmodel) * mom * phantom.coilori(i,:)';
end
m = max(abs(dat(:)));
fprintf('max field is %f pT\n', m * 1e12)
figure
clear ax
n = ceil(sqrt(length(phantom.label)));
for i=1:ncoils
ax(i) = subplot(n,n,i);
ft_plot_topo3d(fieldlinebeta2.coilpos, dat(:,i), 'refine', 1);
ft_colormap('-RdBu')
ft_plot_sens(fieldlinebeta2, 'fiducial', false)
ft_plot_sens(phantom, 'chanindx', i, 'coil', true, 'orientation', true, 'coilsize', diameter);
ft_plot_dipole(phantom.coilpos(i,:), phantom.coilori(i,:)', 'unit', 'm')
clim([-m m])
end
linkprop(ax, {'CameraPosition', 'CameraUpVector'});
rotate3d on
This is the image you get for the HPI coils on the anatomical landmarks.
You should rotate the helmet topographies to get a correct 3D representation of the OPMs, the phantom coils, and their topographies. For the configurations with more phantoms coils this is difficult to capture in a screen shot, so please compute and plot them yourself.





