Surf2VolCoord get RAI coordinates from node indices - faster way?

AFNI version info (afni -ver):
Precompiled binary linux_ubuntu_24_64: Oct 1 2024 (Version AFNI_24.3.00 'Elagabalus')

Hi there,

I am trying to get the RAI coordinates of all the nodes in my ROI.

I am using Surf2VolCoord to do so, calling the function for each node in a loop (code below). However, some ROIs have 30,000 nodes, and the function call takes several seconds, so getting the RAI coordinates is taking several days.

Is there a faster way?

I also tried the below which gave me all the topographic facesets in a file, and the RAI coordinates in a file. But I wasn't sure if the RAI coordinates were for the middle of the faceset? and therefore how close they would be to the coordinates for the individual nodes in the facesets...

ConvertSurface -i ${sub_dir}/SUMA/lh.sphere.gii -sv ${sub_dir}/SUMA/${sv} -o_1D coords_sph mesh_sph

Thank you for your help!

H

#!/bin/bash

# Set sub details
sub_num="P002"
sub_init="HW"

# Get sub stuff from sub details
sv="${sub_init}_columns_SurfVol_Alnd_Exp+orig"

# Set other details
roi=fMap_Q001_postAl

# Change dir
cd ${working_dir}/${sub_init}


# Load afni
ml afni


# Get RAI cartesian coordinates for each node
input_dset="${roi}.1D.dset"
output_file="node_coordinates_spherical.txt"

# Clear or create output file
echo "Node R A I r theta phi" > "$output_file"

# Loop through each node in the input 1D.dset file
while read -r line; do
    # Skip lines that start with '#'
    if [[ "$line" == \#* ]]; then
        continue
    fi

    # Read node number
    node=$(echo "$line" | awk '{print $1}')
    echo "node = ${node}"

    # Extract RAI coordinates using Surf2VolCoord
    output=$(Surf2VolCoord -i "$sub_dir/SUMA/lh.sphere.gii" -sv "$sub_dir/SUMA/$sv" -one_node "$node")

    # Parse the output to get RAI values
    RAI=$(echo "$output" | grep "RAImm" | awk -F 'RAImm ' '{print $2}' | awk -F ' :' '{print $1}')
    # RAI=$(echo "$output" | grep "RAImm" | awk '{print $3, $4, $5}')
    R=$(echo "$RAI" | awk '{print $1}')
    A=$(echo "$RAI" | awk '{print $2}')
    I=$(echo "$RAI" | awk '{print $3}')

    # Check if we have valid RAI coordinates
    if [[ -z "$R" || -z "$A" || -z "$I" ]]; then
        echo "Skipping node $node (missing RAI values)"
        continue
    fi

    # Compute spherical coordinates using bc
    r=$(echo "scale=6; sqrt($R^2 + $A^2 + $I^2)" | bc -l)
    if (( $(echo "$r == 0" | bc -l) )); then
        theta=0
    else
        theta=$(echo "scale=6; a(1) * 180 / 3.141592653589793 * a($I / $r)" | bc -l)
    fi
    phi=$(echo "scale=6; a(1) * 180 / 3.141592653589793 * a($A / $R)" | bc -l)

    # Append to output file
    echo "$node $R $A $I $r $theta $phi" >> "$output_file"

done < "$input_dset"

echo "Processing complete. Output saved to $output_file."

Your ConvertSurface command looks good to me. The output in the coord text file is in RAI coordinates, with one row of xyz coordinates per node. You can verify by clicking on the nodes in suma and looking at the coordinates there. For anatomical surfaces like pial or white matter, these will make more sense. What will you do with the sphere?

Hi Daniel, thanks for getting back.

The output in the coord file I understand, the columns are R, A, I, respectively.
E.g.:
14.324354 -129.218781 -71.369087
14.117275 -129.250671 -71.400818
13.891945 -129.241180 -71.577080
14.193821 -129.294434 -71.207603
13.908064 -129.320770 -71.307648

What I don't quite understand is the contents of the mesh file
E.g.:
0 1058991 1058990
1058991 264750 1058992
1058992 264749 1058990
1058991 1058992 1058990
264750 1058994 1058993

I don't understand the three numbers. Are they the 'faceset' for the RAI value? Where the faceset is three nodes that are the closest to each other on the surface, forming a triangle in the mesh?

If I wanted to match the node numbers with the RAI coordinates, i.e., to find a single node which is closest to the RAI coord, which of the three numbers do I use? Or, does it mean that there are three nodes that have the same RAI coordinates, because they are so close together they have nearly the same coordinate values?

Sorry, I am probably missing something obvious. Thank you for your help

The row of the coord file is the node index, so row 0 has the coordinates for node 0; row 1 for node 1,.... The topo file has the nodes for each triangle in the mesh, showing how the nodes are linked together.

Ahh! I did not understand that from the documentation where that was written about node indices. Thanks for the clarification

Hi again,

I just tried using indexing to pull out the RAI values from coords_sph.1D.coord for each node number in my ROI (called 'fMap_Q001_postAl.1D.dset').

awk '
NR==FNR {coords[NR] = $0; next} 
!/^#/ {print $1, coords[$1]}
' coords_sph.1D.coord fMap_Q001_postAl.1D.dset > node_coordinates.txt

It does not appear to work in the way I expected. The RAI values do not look continuous, i.e., they don't seem to suggest that the nodes are close to each other when they have similar node numbers. Example below:
Nodes R A I
104905 6.838706 -64.290459 52.448975
104906 -36.949661 -78.614204 41.592079
104907 -36.897079 -78.939873 41.444092
104908 -37.302757 -79.159378 41.172813
104916 4.883001 -64.662018 52.551338
104917 -36.285500 -78.855682 41.719765
106453 -4.143100 -57.463615 54.943062
106454 -38.505543 -78.183655 41.199921
106467 -5.381677 -57.772186 54.871140

I know the node numbers do not have complete spatial information, but when I get the RAI values for the nodes the long slow way, using the code below, looping for each node:

output=$(Surf2VolCoord -i "$sub_dir/SUMA/lh.sphere.gii" -sv "$sub_dir/SUMA/$sv" -one_node "$node")

It outputs something like this:
Node R A I
104905 -36.950 -78.614 41.592
104906 -36.897 -78.940 41.444
104907 -37.303 -79.159 41.173
104908 -37.617 -79.378 40.934
104916 -36.285 -78.856 41.720
104917 -36.022 -79.370 41.551
106453 -38.506 -78.184 41.200
106454 -38.616 -78.350 41.070
106467 -37.062 -78.298 41.711

These RAI values appear to make sense, as they appear close to each other in space.

Do you have any ideas why the two approaches to doing, what I thought was the same thing, return different outcomes? I am still keen to get the fast indexing version working if possible.

Thank you so much for your help

There is no guarantee the indices should be near each other spatially. In the suma interface, you can open the Surface Object Controller with Ctrl-n. Then select the Node directly on the left side of that menu and type the node index there. You should find the coordinates reported there will match the information from the coords file.

Yes, I understand that, and thanks for the image. But, I don't get how indexing the RAI coordinates out of the coords file (gained with ConvertSurface) does not return the same RAI values for the same nodes as I got with the Surf2VolCoord...?

I checked, and the indexing script did work correctly, so it's not that...

I'm not seeing this on my system. Is it possible you've switched hemispheres between the two commands? The -sv volume can apply a transformation for alignment from a surface to a session if it exists in the header of the dataset. If the two volumes are different (with different transformations), then that would give different results. The node locations with the same -sv volume in the suma GUI should give the right coordinates.

Surf2VolCoord is used mostly for providing the closest nodes to some xyz coordinate as its input, but you can get xyz coordinates as output. If you just leave out the "-one_node" option, it will give all the node coordinates. Just redirect the output to a file.

Surf2VolCoord -i std.141.lh.sphere.gii -RAI -sv FT_SurfVol.nii > s2v_coords.txt

Another way to get surface node coordinates is the SurfaceMetrics command:

SurfaceMetrics -i std.141.lh.sphere.gii -coords

Hi Daniel,

Thanks for getting back.

I don't think I am switching hemispheres - I don't actually have functional data for the other hemisphere as we are using a slab centred over left hemi. How would I best check this (aside from looking at my code, which looks fine to me). Which dataset woul I need to look in the header for? Would I do this with 3dinfo?

I tried those two other ways of getting RAI coordinates. They appear to output identical RAI coords as my first ConvertSurface method (see below).

All I can tell you is I am getting different output with Surf2VolCoord and I don't know why.

#!/bin/bash

sub_init=LF

sv="${sub_init}_columns_SurfVol_Alnd_Exp+orig"

surf="lh.sphere.gii"

# node=103426

node=104905

output=$(Surf2VolCoord -i "${surf}" -sv "${sv}" -one_node "$node")

# Output

# Node Index 103426: RAImm -36.344 -79.584 41.318 : 3dfind 97.7 98.1 209.0 : 3dind 98 98 209 : Val 7.000000

# Node Index 104905: RAImm -36.950 -78.614 41.592 : 3dfind 96.9 97.8 207.7 : 3dind 97 98 208 : Val 13.000000

ConvertSurface -i "${surf}" -sv "${sv}" -o_1D coords_sph_DELETE mesh_sph_DELETE

# Output to terminal

# Reading lh.sphere.gii ...

# Writing surface...

# output from file

# 103426 6.474477 -64.664757 52.377556

# 104905 6.838706 -64.290459 52.448975

The coordinates should be the same ones you see in the GUI when you right click on the surface. The coordinates appear in the GUI in the Surface Object Controller menu and in the terminal output. The output you have above shows a negative for the Surf2Vol output. For an anatomical surface, that's not likely for a location in the left hemisphere where coordinates should be positive in the RAI x coordinate. For a non-anatomical surface, like the sphere, coordinates vary from positive to negative around the sphere for either left or right spheres, but the coordinates shouldn't be different among programs. I've tried with a few surfaces, and so far, I see the same results no matter which way I try. Maybe the best solution is to send me the surfaces and the sv volume you are using.

Hi Daniel,

I just did a test and looping Surf2VolCoord -one_node appears to produce nearly identical coordinates as when I click around on the spherical surface (see below).

When I get the full list of coords file with ConvertSurface, the numbers are mostly similar to clicking around, but just a bit off. In the example I send previously, for a different participant, they seemed to be a bit more off...

There are negatives I think because I was pulling coordinates for the spherical surface? But yes, can't explain why different commands pull different values. Happy to send you a surface and sv, you would want these files (below), anything else?
sv="AF_columns_SurfVol_Alnd_Exp+orig"
spec="P00_AF_antsTemp.dense.fs_lh.spec"
surf="lh.sphere.gii"

Where should I send them?

Thank you once again for your help!

# Nodes and coords when I click around in different surfaces
# Sphere surface
vvvvvvvvvvvvvvvvvvvvvvvvvvvv
Selected surface lh.sphere.gii (Focus_DO_ID # 15).
FaceSet 3587825, Closest Node 573340
Nodes forming closest FaceSet:
2291500, 573340, 2291507
Coordinates of Nodes forming closest FaceSet:
-58.048325, -64.669632, 56.284401
-58.238316, -64.622971, 56.167091
-58.191349, -64.825668, 56.151688

Indexing coords file from ConvertSurface
573340 -58.162308  -64.265526  56.300217

Loop Surf2VolCoord -one_node
573340 -58.238 -64.623 56.167
^^^^^^^^^^^^^^^^^^^^^^^^^^^^

vvvvvvvvvvvvvvvvvvvvvvvvvvvv
Selected surface lh.sphere.gii (Focus_DO_ID # 15).
FaceSet 3539664, Closest Node 2273402
Nodes forming closest FaceSet:
110528, 2273402, 2273297
Coordinates of Nodes forming closest FaceSet:
-52.402744, -65.772827, 59.557503
-52.771648, -65.779770, 59.340805
-52.478310, -65.966782, 59.467667

^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Indexing coords file from ConvertSurface
2273402 -52.665333  -65.581055  59.449684

Loop Surf2VolCoord -one_node
2273402 -52.772 -65.780 59.341

vvvvvvvvvvvvvvvvvvvvvvvvvvvv
Selected surface lh.sphere.gii (Focus_DO_ID # 15).
FaceSet 3445974, Closest Node 2238097
Nodes forming closest FaceSet:
2238097, 559969, 2238236
Coordinates of Nodes forming closest FaceSet:
-46.000103, -70.812546, 61.665909
-45.799137, -70.927765, 61.731323
-45.727364, -70.820511, 61.797340

^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Indexing coords file from ConvertSurface
2238097 -45.719357  -71.330841  61.652878

Loop Surf2VolCoord -one_node
2238097 -46.000 -70.813 61.666


Messaged you with details.

I'm still seeing the same results with either ConvertSurface or Surf2VolCoord, subject to slight rounding at the third decimal place. Here's are a couple examples:

head -2273403 coords_sph_DELETE.1D.coord|tail -1
-20.379456  -75.982613  47.428864 

Surf2VolCoord -i lh.sphere.gii -sv LF_columns_SurfVol_Alnd_Exp+orig. -one_node 2273402
Node Index 2273402: RAImm -20.379 -75.983 47.429 : 3dfind 119.0 90.0 204.2 : 3dind 119 90 204 : Val 6.000000

head -2238098 coords_sph_DELETE.1D.coord|tail -1                             
-63.305347  -107.534691  -5.852222 

Surf2VolCoord -i lh.sphere.gii -sv LF_columns_SurfVol_Alnd_Exp+orig. -one_node 2238097
Node Index 2238097: RAImm -63.305 -107.535 -5.852 : 3dfind 61.8 161.0 246.2 : 3dind 62 161 246 : Val 0.000000

Note, I am not seeing the coordinates you show, so this seems to be a different dataset. Another thing to note is the indexing if you use head is that is 1-based, while the Surf2VolCoord command deals with the 0-based node indices.

The sphere here is very large, so it is a little slow to deal with it. At more than 4 million nodes, I'm not sure how you are using this surface. Our typical high-resolution surface has fewer than 200 thousand nodes.