-
Notifications
You must be signed in to change notification settings - Fork 266
NF: GiftiImage method agg_data to return usable data arrays #793
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
Changes from all commits
6feb581
72471a7
98c68af
448e073
15d1dd7
85f65a4
de8f028
154321c
0530484
8c6cd7d
c54b696
641feaa
bee6065
bd95ce8
ad3316f
aef5a4f
18501c4
51ff298
b3adb15
785a8d3
995d834
7ea7dec
f633676
bf6eb97
a9471a0
9a52b78
0447bbc
1ecaa26
384475b
3fb7003
08a8752
bb7517d
033ca51
76efe61
c8c2c43
654ee5b
64b019a
5ea1e88
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 |
---|---|---|
|
@@ -678,6 +678,141 @@ def get_arrays_from_intent(self, intent): | |
it = intent_codes.code[intent] | ||
return [x for x in self.darrays if x.intent == it] | ||
|
||
def agg_data(self, intent_code=None): | ||
""" | ||
Aggregate GIFTI data arrays into an ndarray or tuple of ndarray | ||
|
||
In the general case, the numpy data array is extracted from each ``GiftiDataArray`` | ||
object and returned in a ``tuple``, in the order they are found in the GIFTI image. | ||
|
||
If all ``GiftiDataArray`` s have ``intent`` of 2001 (``NIFTI_INTENT_TIME_SERIES``), | ||
then the data arrays are concatenated as columns, producing a vertex-by-time array. | ||
If an ``intent_code`` is passed, data arrays are filtered by the selected intents, | ||
before being aggregated. | ||
This may be useful for images containing several intents, or ensuring an expected | ||
data type in an image of uncertain provenance. | ||
If ``intent_code`` is a ``tuple``, then a ``tuple`` will be returned with the result of | ||
``agg_data`` for each element, in order. | ||
This may be useful for ensuring that expected data arrives in a consistent order. | ||
|
||
Parameters | ||
---------- | ||
intent_code : None, string, integer or tuple of strings or integers, optional | ||
code(s) specifying nifti intent | ||
|
||
Returns | ||
------- | ||
tuple of ndarrays or ndarray | ||
If the input is a tuple, the returned tuple will match the order. | ||
|
||
Examples | ||
-------- | ||
|
||
Consider a surface GIFTI file: | ||
|
||
>>> import nibabel as nib | ||
>>> from nibabel.testing import test_data | ||
>>> surf_img = nib.load(test_data('gifti', 'ascii.gii')) | ||
|
||
The coordinate data, which is indicated by the ``NIFTI_INTENT_POINTSET`` | ||
intent code, may be retrieved using any of the following equivalent | ||
calls: | ||
|
||
>>> coords = surf_img.agg_data('NIFTI_INTENT_POINTSET') | ||
>>> coords_2 = surf_img.agg_data('pointset') | ||
>>> coords_3 = surf_img.agg_data(1008) # Numeric code for pointset | ||
>>> print(np.array2string(coords, precision=3)) | ||
[[-16.072 -66.188 21.267] | ||
[-16.706 -66.054 21.233] | ||
[-17.614 -65.402 21.071]] | ||
>>> np.array_equal(coords, coords_2) | ||
True | ||
>>> np.array_equal(coords, coords_3) | ||
True | ||
|
||
Similarly, the triangle mesh can be retrieved using various intent | ||
specifiers: | ||
|
||
>>> triangles = surf_img.agg_data('NIFTI_INTENT_TRIANGLE') | ||
>>> triangles_2 = surf_img.agg_data('triangle') | ||
>>> triangles_3 = surf_img.agg_data(1009) # Numeric code for pointset | ||
>>> print(np.array2string(triangles)) | ||
[0 1 2] | ||
>>> np.array_equal(triangles, triangles_2) | ||
True | ||
>>> np.array_equal(triangles, triangles_3) | ||
True | ||
|
||
All arrays can be retrieved as a ``tuple`` by omitting the intent | ||
code: | ||
|
||
>>> coords_4, triangles_4 = surf_img.agg_data() | ||
>>> np.array_equal(coords, coords_4) | ||
True | ||
>>> np.array_equal(triangles, triangles_4) | ||
True | ||
|
||
Finally, a tuple of intent codes may be passed in order to select | ||
the arrays in a specific order: | ||
|
||
>>> triangles_5, coords_5 = surf_img.agg_data(('triangle', 'pointset')) | ||
>>> np.array_equal(triangles, triangles_5) | ||
True | ||
>>> np.array_equal(coords, coords_5) | ||
True | ||
|
||
The following image is a GIFTI file with ten (10) data arrays of the same | ||
size, and with intent code 2001 (``NIFTI_INTENT_TIME_SERIES``): | ||
|
||
>>> func_img = nib.load(test_data('gifti', 'task.func.gii')) | ||
|
||
When aggregating time series data, these arrays are concatenated into | ||
a single, vertex-by-timestep array: | ||
|
||
>>> series = func_img.agg_data() | ||
>>> series.shape | ||
(642, 10) | ||
|
||
In the case of a GIFTI file with unknown data arrays, it may be preferable | ||
to specify the intent code, so that a time series array is always returned: | ||
|
||
>>> series_2 = func_img.agg_data('NIFTI_INTENT_TIME_SERIES') | ||
>>> series_3 = func_img.agg_data('time series') | ||
>>> series_4 = func_img.agg_data(2001) | ||
>>> np.array_equal(series, series_2) | ||
True | ||
>>> np.array_equal(series, series_3) | ||
True | ||
>>> np.array_equal(series, series_4) | ||
True | ||
|
||
Requesting a data array from a GIFTI file with no matching intent codes | ||
will result in an empty tuple: | ||
|
||
>>> surf_img.agg_data('time series') | ||
() | ||
>>> func_img.agg_data('triangle') | ||
() | ||
""" | ||
|
||
# Allow multiple intents to specify the order | ||
# e.g., agg_data(('pointset', 'triangle')) ensures consistent order | ||
|
||
if isinstance(intent_code, tuple): | ||
return tuple(self.agg_data(intent_code=code) for code in intent_code) | ||
|
||
darrays = self.darrays if intent_code is None else self.get_arrays_from_intent(intent_code) | ||
all_data = tuple(da.data for da in darrays) | ||
all_intent = {intent_codes.niistring[da.intent] for da in darrays} | ||
|
||
if all_intent == {'NIFTI_INTENT_TIME_SERIES'}: # stack when the gifti is a timeseries | ||
return np.column_stack(all_data) | ||
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. I'm not sure about this one. It's not clear to me that:
I'm hesitant to make concatenation contingent on the shapes working out, as the output type will then be an even more complex function of the input data than we're already proposing. I see a few options:
I'm inclined toward (2), with an option to move to (3) if people actually want control over that. Not sure they will, as they can always do 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. I wrote something but for got to hit reply..... Anyway, the TLDR of my original comment was, these are great suggestions! I didn't think about those weird but totally legit use of gifti format. I agreed to go for 2 for now and move to 3. I think we will need more people to use these functions to know what is common for users. |
||
|
||
if len(all_data) == 1: | ||
all_data = all_data[0] | ||
|
||
return all_data | ||
|
||
@deprecate_with_version( | ||
'getArraysFromIntent method deprecated. ' | ||
"Use get_arrays_from_intent instead.", | ||
|
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.
It would be good to add some doctests that demonstrate how to use this. If we include a time series and a surface file, we could follow the examples of
get_fdata()
:nibabel/nibabel/dataobj_images.py
Lines 287 to 342 in 22fe8c2
I would show aggregating data:
()
)