bids-standard / pybv

A lightweight I/O utility for the BrainVision data format, written in Python.

Home Page:https://pybv.readthedocs.io/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Let pybv write [coordinates] section to vhdr files

sappelhoff opened this issue · comments

See BrainVision data file specification.

The OPTIONAL [Coordinates] section in a vhdr file comes right after the REQUIRED [Channels] section, and it looks like this:

[Coordinates]
; Each entry: Ch<Channel number>=<Radius>,<Theta>,<Phi>
Ch1=1,-60,-51
Ch2=1,-49,-29
Ch3=1,-45,0
Ch4=1,-49,29

For the radius, the spec say the following [emphasis by me]:

“The radius r specifies the distance (in millimeters) between point P and the origin of the coordinate system. The only exceptions are r = 0 and r = 1. r = 0 signifies an invalid position, for instance when the position of an electrode is not known. When realistic electrode coordinates are used, r can have a different value for each channel. In other cases, the value of r should be the same for all the channels if a spherical head model is used. For instance, in the Analyzer's standard coordinate system, r = 1”.

All coordinates that pybv writes to the [Coordinates] section must first be transformed to the Analyzer's standard coordinate system, which happens to be the CapTrak system (more info in FieldTrip wiki). For this conversion to work, we will need the Nasion, left, and right preauricular point coordinates (to rescale the coordsystem).

I suggest that furthermore, pybv writes a more extensive comment in the [Coordinates] section:

[Coordinates]
; Each entry: Ch<Channel number>=<Radius>,<Theta>,<Phi>
; Radius defined in millimeters (mm).
; Radius of 0 signifies invalid position for a given electrode.
; If all coordinates are on an (idealized) spherical head model, Radius will be 1 for all entries.
; The coordinate system is "Captrak", see definition of axes below.
; The X-axis goes from the left preauricular point towards (through) the right preauricular point.
; The Y-axis goes orthogonally to the X-axis and towards (through) the nasion.
; The Z-axis goes orthogonally to the X-Y-plane upwards.
; Below are the coordinates for left and right preauricular points, and the nasion.
; LPA=1,44,55
; RPA=1,44,55
; Nasion=1,44,55
Ch1=1,-60,-51
Ch2=1,-49,-29
Ch3=1,-45,0
Ch4=1,-49,29

this will be particularly interesting once MNE starts to export to pybv ... because we'll want to capture as much of the FIF data as possible for a round-trip.

as discussed in #77, the input for a coordinates parameter in write_brainvision could be a list of dicts:

[
    {
        "name": "LPA",
        "x": 0.3,
        "y": 4.5,
        "z": 6.1,
    },
    {
        "name": "Fpz",
        "radius": 3.1,
        "theta": 22,
        "phi": 33,
    }
]

pybv would then:

  • check that an entry has either a complete set of "x, y, z" OR a complete set of "radius, theta, phi"
  • convert XYZ to radius, theta phi (because we write radius, theta, phi to VMRK)
  • check for "LPA", "RPA", and "Nasion", and write them as "comment" (because VHDR only supports existing data channels in their coordinate section; see my example)
    • also use LPA, RPA, NAS for transforming the coordinate system to "head" (=captrak, =analyzer)
  • check for all other "names" and map them to channel indices as present in the data (we write indices, not names to VHDR)
  • raise a warning if there are more or less names than in the data
  • if less names, the missing names are entered with zeros (that is the "NaN" representation in VHDR)
  • if more names, the superfluous names are just ignored

PS: I have a very lengthy email discussion on this topic with Brain Products that I didn't post here ... But I think what we plan here would be spec conformant: Especially if we put coordsystem info, units, and landmarks as "comments".

BV Readers can then optionally check the VHDR for whether the file was created by "pybv" and if it was, make use of that "comment".

BV Readers that dont check for that, will still get sensible data: At least as sensible as the underspecified "Coordinates" section allows :-)

This sounds great!

we'd probably need to look at these three functions in mne

  • get_ras_to_neuromag_trans
  • compute_native_head_t
  • transform_to_head

and simplify them for use here without adding mne as a dependency.