Hello, am writing a module which uses FreeSurfer segmentations and surface files. Empirically and based on another discourse post (Nifti file and freesurfer pial file mismatch in slicer) I had previously developed a transform to put the pial surface files back in the coordinate system of the segmented labelmap, based on the idea of ‘uncentering’.
def createSurfTransform(self, referenceNode, transformName='FreeSurfer_to_Parcellation_Transform'):
'''Create the transform which takes the FreeSurfer surfaces into the coordinate system of the
reference node. The transform is a translation from the origin to the center of the reference
volume.
'''
bounds = [0,0,0,0,0,0] # xmin,xmax,ymin,ymax,zmin,zmax, preallocated
referenceNode.GetBounds(bounds) # this is always the bounds of the untransformed object
tr = [(bounds[1]+bounds[0])/2,(bounds[3]+bounds[2])/2, (bounds[5]+bounds[4])/2]
# Create new transformation matrix with this translation
transformMatrix = vtk.vtkMatrix4x4()
transformMatrix.SetElement(0,3,tr[0])
transformMatrix.SetElement(1,3,tr[1])
transformMatrix.SetElement(2,3,tr[2])
# Create transform node with given name
transformNode = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLTransformNode',transformName)
transformNode.SetAndObserveMatrixTransformToParent(transformMatrix)
return transformNode
However, the transform this code generates seems to differ from the one generated by SlicerFreeSurfer by exactly 0.5 in each dimension. The SlicerFreeSurfer transform does appear to line up the surface better than my transform. I see there is a note in the code about a shift of 0.5 to move to pixel centers (https://github.com/PerkLab/SlicerFreeSurfer/blob/910ee2c8adbce1c2d472e658f4cbffa982618cd9/FreeSurferImporter/MRML/vtkMRMLFreeSurferModelStorageNode.cxx#L37-L39). However, I don’t see any actual code associated with this comment. Also, I don’t understand why the shift would be 0.5 rather than 0.5*voxel dimension. The reference volumes I am using are usually approximately but not exactly 1mm isotropic voxels. For example, the one I am looking at now has voxels which are .97x.97x1mm, but the offset between the transform I generate and the one SlicerFreeSurfer generates is exactly 0.5mm in all 3 dimensions.
I don’t currently understand the logic of why a half-voxel shift is needed, and it sounds wrong to me if the shift is half a mm regardless of voxel size. I don’t argue with the fact that the surfaces line up better with the image with an offset than without, but I’d like to understand why.
In a related issue, I don’t see how to load a FreeSurfer surface file from a scripted module and specify the reference volume. I’d like to have the same thing happen as dropping a (for example) “lh.pial” file on the Slicer window and choosing the appropriate reference volume node. I have tried
slicer.util.loadNodeFromFile('lh.pial', 'FreeSurferModelFile')
but while this loads the model, it does not generate the transform. I am guessing that I could specify the reference volume somehow through the properties argument, but I haven’t been able to guess the right property name. I’ve tried properties={‘ReferenceVolume’: ‘myReferenceNodeName’}, and similar, trying ‘Referencevolume’, ‘Reference volume’, and I think a few other variations, none of which seemed to have any effect.
Bottom line: I’d love to understand why the SlicerFreeSurfer-generated transform is correct, but if I can’t manage that, I’d at least like to be able to be able to generate the same transform from a scripted module that is generated by dragging and dropping a specified surface file onto the slicer main window and choosing the appropriate reference volume.