New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Extract values from an image based on streamline coordinates. #811
Changes from all commits
822409f
886db03
4ad9273
64e60a1
15b0483
1ab7b8a
a7005b2
038e4a0
4930625
659260e
64cfc74
f817875
ad03bc3
1c55d1b
88f7442
f8f446f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,15 +1,19 @@ | ||
from copy import deepcopy | ||
from warnings import warn | ||
import types | ||
|
||
from scipy.spatial.distance import cdist | ||
import numpy as np | ||
from nibabel.affines import apply_affine | ||
|
||
from dipy.tracking.streamlinespeed import set_number_of_points | ||
from dipy.tracking.streamlinespeed import length | ||
from dipy.tracking.streamlinespeed import compress_streamlines | ||
import dipy.tracking.utils as ut | ||
from dipy.tracking.utils import streamline_near_roi | ||
from dipy.core.geometry import dist_to_corner | ||
from scipy.spatial.distance import cdist | ||
from copy import deepcopy | ||
import dipy.align.vector_fields as vfu | ||
|
||
|
||
def unlist_streamlines(streamlines): | ||
""" Return the streamlines not as a list but as an array and an offset | ||
|
@@ -323,3 +327,138 @@ def orient_by_rois(streamlines, roi1, roi2, affine=None, copy=True): | |
new_sl[idx] = sl[::-1] | ||
|
||
return new_sl | ||
|
||
|
||
def _extract_vals(data, streamlines, affine=None, threedvec=False): | ||
""" | ||
Helper function for use with `values_from_volume`. | ||
|
||
Parameters | ||
---------- | ||
data : 3D or 4D array | ||
Scalar (for 3D) and vector (for 4D) values to be extracted. For 4D | ||
data, interpolation will be done on the 3 spatial dimensions in each | ||
volume. | ||
|
||
streamlines : ndarray or list | ||
If array, of shape (n_streamlines, n_nodes, 3) | ||
If list, len(n_streamlines) with (n_nodes, 3) array in | ||
each element of the list. | ||
|
||
affine : ndarray, shape (4, 4) | ||
Affine transformation from voxels (image coordinates) to streamlines. | ||
Default: identity. | ||
|
||
threedvec : bool | ||
Whether the last dimension has length 3. This is a special case in | ||
which we can use :func:`vfu.interpolate_vector_3d` for the | ||
interploation of 4D volumes without looping over the elements of the | ||
last dimension. | ||
|
||
Return | ||
------ | ||
array or list (depending on the input) : values interpolate to each | ||
coordinate along the length of each streamline | ||
""" | ||
data = data.astype(np.float) | ||
if (isinstance(streamlines, list) or | ||
isinstance(streamlines, types.GeneratorType)): | ||
if affine is not None: | ||
streamlines = ut.move_streamlines(streamlines, | ||
np.linalg.inv(affine)) | ||
|
||
vals = [] | ||
for sl in streamlines: | ||
if threedvec: | ||
vals.append(list(vfu.interpolate_vector_3d(data, | ||
sl.astype(np.float))[0])) | ||
else: | ||
vals.append(list(vfu.interpolate_scalar_3d(data, | ||
sl.astype(np.float))[0])) | ||
|
||
elif isinstance(streamlines, np.ndarray): | ||
sl_shape = streamlines.shape | ||
sl_cat = streamlines.reshape(sl_shape[0] * | ||
sl_shape[1], 3).astype(np.float) | ||
|
||
if affine is not None: | ||
inv_affine = np.linalg.inv(affine) | ||
sl_cat = (np.dot(sl_cat, inv_affine[:3, :3]) + | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You can also do that in only one matrix multiplication, that's why homogeneous coordinates are used. https://en.wikipedia.org/wiki/Transformation_matrix#Affine_transformations There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah - but then you have to augment the coordinates, adding a '1' to every On Tue, Jan 26, 2016 at 6:28 AM, Samuel St-Jean notifications@github.com
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Wouldn't that be faster if you have to move a whole brain tractogram? Computer vision/imaging people are using these since you can express all transformations in a single operation instead, maybe they want to pitch in about if its faster to add another dimension or go this way. |
||
inv_affine[:3, 3]) | ||
|
||
# So that we can index in one operation: | ||
if threedvec: | ||
vals = np.array(vfu.interpolate_vector_3d(data, sl_cat)[0]) | ||
else: | ||
vals = np.array(vfu.interpolate_scalar_3d(data, sl_cat)[0]) | ||
vals = np.reshape(vals, (sl_shape[0], sl_shape[1], -1)).squeeze() | ||
|
||
else: | ||
raise RuntimeError("Extracting values from a volume ", | ||
"requires streamlines input as an array, ", | ||
"a list of arrays, or a streamline generator.") | ||
|
||
return vals | ||
|
||
|
||
def values_from_volume(data, streamlines, affine=None): | ||
"""Extract values of a scalar/vector along each streamline from a volume. | ||
|
||
Parameters | ||
---------- | ||
data : 3D or 4D array | ||
Scalar (for 3D) and vector (for 4D) values to be extracted. For 4D | ||
data, interpolation will be done on the 3 spatial dimensions in each | ||
volume. | ||
|
||
streamlines : ndarray or list | ||
If array, of shape (n_streamlines, n_nodes, 3) | ||
If list, len(n_streamlines) with (n_nodes, 3) array in | ||
each element of the list. | ||
|
||
affine : ndarray, shape (4, 4) | ||
Affine transformation from voxels (image coordinates) to streamlines. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. streamlines space is world cooordinate or some other space? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's whatever you did. If you represented your streamlines in "world On Mon, Jan 25, 2016 at 8:18 AM, Samuel St-Jean notifications@github.com
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So its the streamlines which are moved (according to the function) to the 2016-01-25 17:35 GMT+01:00 Ariel Rokem notifications@github.com:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is the coordinate (0,0,0) at the center of the voxel or at its corner? I think it refers to the center, so providing integer coordinates and affine=np.eye(4) to the function, it should output the exact value of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes. That's correct. For example in the test here: https://github.com/arokem/dipy/blob/vals_from_img/dipy/tracking/tests/test_streamline.py#L834, the first value is exactly the value at There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Any suggestions for ways to make the documentation clearer on that? I'll add a couple of words. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How's that? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @arokem, yes that answered my question. |
||
Default: identity. For example, if no affine is provided and the first | ||
coordinate of the first streamline is ``[1, 0, 0]``, data[1, 0, 0] | ||
would be returned as the value for that streamline coordinate | ||
|
||
Return | ||
------ | ||
array or list (depending on the input) : values interpolate to each | ||
coordinate along the length of each streamline. | ||
|
||
Notes | ||
----- | ||
Values are extracted from the image based on the 3D coordinates of the | ||
nodes that comprise the points in the streamline, without any interpolation | ||
into segments between the nodes. Using this function with streamlines that | ||
have been resampled into a very small number of nodes will result in very | ||
few values. | ||
""" | ||
data = np.asarray(data) | ||
if len(data.shape) == 4: | ||
if data.shape[-1] == 3: | ||
return _extract_vals(data, streamlines, affine=affine, | ||
threedvec=True) | ||
if isinstance(streamlines, types.GeneratorType): | ||
streamlines = list(streamlines) | ||
vals = [] | ||
for ii in range(data.shape[-1]): | ||
vals.append(_extract_vals(data[..., ii], streamlines, | ||
affine=affine)) | ||
|
||
if isinstance(vals[-1], np.ndarray): | ||
return np.swapaxes(np.array(vals), 2, 1).T | ||
else: | ||
new_vals = [] | ||
for sl_idx in range(len(streamlines)): | ||
sl_vals = [] | ||
for ii in range(data.shape[-1]): | ||
sl_vals.append(vals[ii][sl_idx]) | ||
new_vals.append(np.array(sl_vals).T) | ||
return new_vals | ||
|
||
elif len(data.shape) == 3: | ||
return _extract_vals(data, streamlines, affine=affine) | ||
else: | ||
raise ValueError("Data needs to have 3 or 4 dimensions") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So if I don't give it an array, a list or a streamline generator, won't it crash (or do weird stuff) on trying to return vals?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That is correct. This would've raised a cryptic error ("val undefined" or somesuch), so I've added proper error handling in the
else
block and a test for that.