Head radius discrepancies

Hi,

Iā€™ve been having some closely-related issues where the head radius is apparently considerably larger than it should be, but Iā€™m unsure why as I assume the radius to be the default/9.5cm. I think part of it may be the way MNE calculates radius when there arenā€™t enough channels/fewer than in the montage file, but even so some of whatā€™s occurring seems quite odd.

The main situation is:
-I input my data (32 channel, 10-20 scalp EEG data).
-I set the montage to the standard 10-20 montage.
-I use matlab.eng to open EEGLAB, and run cleanRawData on the data (saving it as a .set file before and after as an ā€˜intermediateā€™).
-After this has run, I receive a warning along the lines of " RuntimeWarning: Estimated head radius (11.6 cm) is above the 99th percentile for adult head size. Check if the montage_units argument is correct (the default is ā€œmmā€, but your channel positions may be in different units).", with the radius possibly varying a little from that.
-If I calculate the difference between channel coordinates (before or after running the EEGLAB fn), I get values on the order of several cm. A typical example: ā€œO1: [ 0.00216017 -0.03188065 -0.04595065]ā€.
-Also, if I interpolate channels, there is a message along the lines of ā€œAutomatic origin fit: head of radius 93.9 mmā€, generally 93-98mm. Not problematic in itself, but strange how this disagrees with both the warning message, and the montage value of 9.5cm slightly.

Additional notes:
-The magnitude seems to vary a little with how many channels are removed. E.g, for a particularly bad recording I had four flat channels. With these removed the head radius would be reported as 11.6cm, and with additional three noisy ones removed it would be reported as 11.8cm. It also seems to be partly random, having run the code a few times on the same file.
-I am also working with cEEGrid files, where I created my own approximate montage. For now I have assumed the ears are about 9cm from the head centre, and yet I still receive the same error (in fact here it is worse, it claims the head radius is ~15.5cm). I suppose it makes sense this would be inaccurate, it has limited channels to work with, and they are only around the ears (plus Fpz), but Iā€™m still a little surprised it is so far out.
-I know that the default head radius for set_montage() is meant to be 9.5cm, but for some reason explicitly inputting ā€œhead_radius = 0.095ā€ seems to slightly help. E.g, the O1 difference from above becomes ā€œO1: [ 0.00207045 -0.03055664 -0.0440423 ]ā€, i.e slightly smaller differences between my O1 coordinates and the standard 10-20 values.

As I only seem to receive the error warning when using this EEGLab function, I had thought perhaps it is a conversion issue. However Iā€™ve checked and they both seem to use the exact same file, ā€œstandard_1020.elcā€.

Iā€™d also thought that perhaps as the standard_1020 montage file contains 90 electrodes and we only have 32 (or fewer, where they are removed), maybe MNE is at some point trying to calculate the radius and getting confused. However I couldnā€™t find any reports of this here or elsewhere. Also this still doesnā€™t really explain the discrepancy between the warning value vs. the ā€œautomatic origin fitā€ value, or why inputting ā€œhead_radius = 0.095ā€ makes a difference.

  • MNE version: 1.6.1
  • operating system: Windows 11
    -(EEGLab 2023.1 if it is of any relevance)

Iā€™ve attached the code below (for terseness Iā€™ve not included the interpolation code, but essentially Iā€™d add placeholder zeroes for the removed channels, set them as ā€˜badsā€™ and use interpolate_bads).

Any help would be hugely appreciated.

standard_1020 = mne.channels.make_standard_montage("standard_1020")#,head_size=0.095)
eeg_fullyFiltered.set_montage(standard_1020)

# Get channel names from montage and EEG data
montage_channel_names = set(standard_1020.ch_names)
eeg_channel_names = set(eeg_fullyFiltered.ch_names)

# Get channels present in both montage and EEG data
common_channel_names = montage_channel_names.intersection(eeg_channel_names)

# Print differences in electrode positions
print("Differences in electrode positions (montage - EEG data):")
for ch_name in common_channel_names:
    montage_pos = standard_1020.get_positions()['ch_pos'].get(ch_name)
    eeg_pos = eeg_fullyFiltered.get_montage().get_positions()['ch_pos'].get(ch_name)
    if montage_pos is not None and eeg_pos is not None:
        diff = montage_pos - eeg_pos
        if not np.allclose(diff, 0):  # Check if all elements are close to zero
            print(f"{ch_name}: {diff}")
    else:
        print(f"Positions not available for channel: {ch_name}")

#cleanRawData, with only flats removed:
filePath = basePath + "/Intermediate Files/"
fileNamePartialCleanVersion = "P" + participantNumber + "_" + eeg_type + "_" + task + "_PartialCleanVers.set"
fullFilePath = filePath + fileNamePartialCleanVersion
eeg_fullyFiltered.export(fullFilePath, overwrite=True)
eng = matlab.engine.start_matlab()
eng.CRD_IntermedFn_KeepsChans(fullFilePath, nargout=0)
eng.quit()