@@ -2073,3 +2073,47 @@ def apply_volume_registration_points(
20732073 info2 .set_montage (montage2 ) # converts to head coordinates
20742074
20752075 return info2 , trans2
2076+
2077+
2078+ class _MatchedDisplacementFieldInterpolator :
2079+ """Interpolate from matched points using a displacement field in ND.
2080+
2081+ For a demo, see
2082+ https://gist.github.com/larsoner/fbe32d57996848395854d5e59dff1e10
2083+ and related tests.
2084+ """
2085+
2086+ def __init__ (self , fro , to ):
2087+ from scipy .interpolate import LinearNDInterpolator
2088+
2089+ fro = np .array (fro , float )
2090+ to = np .array (to , float )
2091+ assert fro .shape == to .shape
2092+ assert fro .ndim == 2
2093+ # this restriction is only necessary because it's what
2094+ # _fit_matched_points requires
2095+ assert fro .shape [1 ] == 3
2096+
2097+ # Prealign using affine + uniform scaling
2098+ trans , scale = _fit_matched_points (fro , to , scale = True )
2099+ trans = _quat_to_affine (trans )
2100+ trans [:3 , :3 ] *= scale
2101+ self ._affine = trans
2102+ fro = apply_trans (trans , fro )
2103+
2104+ # Add points at extrema
2105+ delta = (to .max (axis = 0 ) - to .min (axis = 0 )) / 2.0
2106+ extrema = np .array ([fro .min (axis = 0 ) - delta , fro .max (axis = 0 ) + delta ])
2107+ self ._extrema = np .array (np .meshgrid (* extrema .T )).T .reshape (- 1 , fro .shape [- 1 ])
2108+ fro_concat = np .concatenate ((fro , self ._extrema ))
2109+ to_concat = np .concatenate ((to , self ._extrema ))
2110+
2111+ # Compute the interpolator (which internally uses Delaunay)
2112+ self ._interp = LinearNDInterpolator (fro_concat , to_concat )
2113+
2114+ def __call__ (self , x ):
2115+ assert x .ndim in (1 , 2 ) and x .shape [- 1 ] == 3
2116+ singleton = x .ndim == 1
2117+ out = self ._interp (apply_trans (self ._affine , x ))
2118+ out = out [0 ] if singleton else out
2119+ return out
0 commit comments