-
Notifications
You must be signed in to change notification settings - Fork 258
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
RF: Simplify MGHImage and add footer fields #569
Conversation
5241b1b
to
832f584
Compare
Codecov Report
@@ Coverage Diff @@
## master #569 +/- ##
==========================================
+ Coverage 94.32% 94.41% +0.09%
==========================================
Files 177 177
Lines 24697 24884 +187
Branches 2640 2656 +16
==========================================
+ Hits 23295 23495 +200
+ Misses 925 913 -12
+ Partials 477 476 -1
Continue to review full report at Codecov.
|
This is ready for review, if anybody's got a few minutes. |
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.
Main question - is it really worth renaming Mdc and the other fields?
nibabel/freesurfer/mghformat.py
Outdated
@@ -84,6 +92,7 @@ class MGHHeader(object): | |||
|
|||
def __init__(self, | |||
binaryblock=None, | |||
endianness='>', |
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.
Does it make sense to allow passing this flag? It also changes the API.
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.
I did this because of places where LabeledWrapStruct
calls __init__
assuming the type signature of the base class. I can instead make sure to override any methods that make such calls, just figured this would be a little shorter.
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.
I played with the idea of making WrapStruct have a parent or similar that had forced endianness, but I seem to remember it got a bit messy.
|
||
def check_fix(self): | ||
''' Pass. maybe for now''' | ||
return klass(hdr_str + ftr_str, check=check) | ||
|
||
def get_affine(self): | ||
''' Get the affine transform from the header information. |
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.
Blank line after first line (sorry, I know that wasn't present before either).
nibabel/freesurfer/mghformat.py
Outdated
MdcD = np.hstack((hdr['x_ras'], hdr['y_ras'], hdr['z_ras'])) * hdr['voxelsize'] | ||
vol_center = MdcD.dot(hdr['dims'][:3].reshape(-1, 1)) / 2 | ||
affine[:3, :3] = MdcD | ||
affine[:3, [3]] = hdr['c_ras'] - vol_center |
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.
3
rather than [3]
?
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.
The RHS is a column, so the [3]
keeps the LHS expecting a column. Is there a trick I don't know here?
hdr = self._header_data | ||
zooms = hdr['delta'] | ||
return tuple(zooms[:]) | ||
# Do not return time zoom (TR) if 3D image |
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.
Maybe worth a note in the docstring about the time zoom and 3D?
nibabel/freesurfer/mghformat.py
Outdated
('dims', '>i4', (4,)), # 4; width, height, depth, nframes | ||
('type', '>i4'), # 20; data type | ||
('dof', '>i4'), # 24; degrees of freedom | ||
('ras_good', '>i2'), # 28; *_ras fields valid |
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.
I guess these renamings are API changes - our our Freesurfer colleagues happy with these name changes? Any way to offer the old names as alternatives for a while?
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.
Yeah, I can re-add a _header_data
structure (which I've replaced with LabeledWrapStruct._structarr
) that will expose the old fields and raise a DeprecationWarning
.
nibabel/freesurfer/mghformat.py
Outdated
('dof', '>i4'), # 24; degrees of freedom | ||
('ras_good', '>i2'), # 28; *_ras fields valid | ||
('voxelsize', '>f4', (3,)), # 30; zooms (X, Y, Z) | ||
('x_ras', '>f4', (3, 1)), # 42; X direction cosine column |
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.
(3, 1)
rather than (3,)
?
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.
Is this split into three columns really useful? Compared to the native 3, 3 size?
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.
By including all three columns as a single, row-ordered Mdc
matrix, we're exposing a transposed matrix. It was being transposed correctly in and out, but if someone does decide to use it directly, they need to know this. I prefer explicit to silently misleading...
I could use (3,)
if that's important. But it will mean users knowing that they're columns and not row vectors (can add to the docs).
For context, this is the comparable portion of the C data structure, which is just 16 individual float
fields: https://github.com/freesurfer/freesurfer/blob/5367eec84621b331271e97f4e1f1502c3f6d0d87/include/mri.h#L184-L191
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.
OK - fine to leave as is (3,1) etc.
nibabel/freesurfer/mghformat.py
Outdated
hdr = self._structarr | ||
MdcD = np.hstack((hdr['x_ras'], hdr['y_ras'], hdr['z_ras'])) * hdr['voxelsize'] | ||
vol_center = MdcD.dot(hdr['dims'][:3].reshape(-1, 1)) / 2 | ||
affine[:3, :3] = MdcD |
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.
return nib.affines.from_matvec(MdcD, hdr['c_ras'] - vol_center)
?
nibabel/freesurfer/mghformat.py
Outdated
|
||
Ignores byte order; always big endian | ||
''' | ||
structarr = super(MGHHeader, klass).default_structarr() |
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.
Here might be worth checking and error if endian not big.
nibabel/freesurfer/mghformat.py
Outdated
|
||
# Assign after we've had a chance to raise exceptions | ||
hdr['voxelsize'] = voxelsize | ||
hdr['x_ras'] = Mdc[:, [0]] |
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.
hdr['x_ras'], hdr['y_ras'], hdr['z_ras'] = Mdc.T
?
Re: Renaming, the biggest motivation is that a lot of the field names seem only to align with one slideshow that documents the spaces/transforms, and not at all with either the FreeSurfer source code or the outputs of So my strong preference is to move to a more consistent naming scheme (I'm open to alternative suggestions), but I'm also perfectly happy to provide a backwards-compatible interface to access the data using the old field names to get back the old shapes. |
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.
I guess it would be good to get more feedback from Freesurferers out there.
nibabel/freesurfer/mghformat.py
Outdated
('dof', '>i4'), # 24; degrees of freedom | ||
('ras_good', '>i2'), # 28; *_ras fields valid | ||
('voxelsize', '>f4', (3,)), # 30; zooms (X, Y, Z) | ||
('x_ras', '>f4', (3, 1)), # 42; X direction cosine column |
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.
OK - fine to leave as is (3,1) etc.
nibabel/freesurfer/mghformat.py
Outdated
@@ -84,6 +92,7 @@ class MGHHeader(object): | |||
|
|||
def __init__(self, | |||
binaryblock=None, | |||
endianness='>', |
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.
I played with the idea of making WrapStruct have a parent or similar that had forced endianness, but I seem to remember it got a bit messy.
Okay. I've pinged the Python neuroimaging list and the FreeSurfer support list. Hopefully we can find somebody willing to share their opinions on the Internet. :-) |
I don't have a strong opinion here, but I would guess it's more likely that a user has encountered the fscoordinates slide deck than read through the freesurfer source code. |
@effigies - perhaps chatting with doug would be a good idea as well. also folks at corticometrics may have some suggestions as well. however, i do not have a strong opinion either. since i rarely do much with those files than reading into an array, doing some processing, and writing. or just visualization. |
@satra Can I expect them to respond to the post on the FreeSurfer list, or is there a preferred way of contacting them more directly? |
@effigies - i hadn't seen that the mail was on the freesurfer list. hopefully they respond - otherwise it's your call :) |
Okay, it sounds like there's a slight preference from @mwaskom to follow the FS documentary slideshow and a preference (of unknown strength) from @matthew-brett to avoid changing names without a Very Good Reason. I'll defer to this for now and revert However, I do think I would like to move to more "standard" FreeSurfer naming, because this construct ("volume geometry", or dimensions + x/y/z/c_ras) shows up in several places, including nibabel/nibabel/freesurfer/io.py Lines 50 to 73 in 2139ce0
nibabel/nibabel/freesurfer/io.py Lines 462 to 488 in 2139ce0
But I think it does make sense to postpone the discussion of field naming to an actual attempt to provide a common interface to volume geometries (which is actually re-implemented in several FreeSurfer data structures). |
Chris - sorry - I should have said - I don't care too much about the name changes, especially if there's a deprecation route. I should have said before, but if you think these names are more canonical, that's OK - we can always explain in the docstring and comments what the relationship is to the slide deck. |
Okay. I think I'll still push it off for now. If/when we add support for LTA/M3Z etc, it will be time enough to consider a consistent naming scheme, rather than deprecating the old one now and finding later that we would have preferred something slightly different. I will add docs though noting that |
@matthew-brett Got back to this. I think I've addressed all of your comments, and I've fleshed out some more tests. Ready for another review. |
Hi @matthew-brett, just bugging you about this. No worries if you're busy. |
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.
Just a few small comments.
nibabel/freesurfer/mghformat.py
Outdated
|
||
def set_zooms(self, zooms): | ||
''' Set zooms into header fields | ||
|
||
See docstring for ``get_zooms`` for examples | ||
Sets the spaing of voxels in the x, y, and z 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.
Typo 'spacing'
nibabel/freesurfer/mghformat.py
Outdated
|
||
def set_zooms(self, zooms): | ||
''' Set zooms into header fields | ||
|
||
See docstring for ``get_zooms`` for examples | ||
Sets the spaing of voxels in the x, y, and z dimensions. | ||
For four-dimensional files, a temporal zoom (repetition time, or TR, in |
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.
Ouch - it's really in milliseconds? That's unfortunate (compared to nifti).
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.
Annoyingly. Fortunately #567 proposes a way to get normalized zooms. (It was working on that that led to this PR in the first place.)
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.
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.
Add reference to docstring?
Thanks for your patience, in it goes. |
Oops - a couple of PEP8 failures : https://travis-ci.org/nipy/nibabel/builds/318192252 |
This PR includes a number of simplifications to the
MGHHeader
andMGHImage
data structures.The most significant is subclassing
MGHHeader
fromLabeledWrapStruct
. It appeared to have been a reimplementation ofLabeledWrapStruct
in many places without theendianness
option. I have simply raised an exception on attempts to instantiate as little-endian.The next most significant change is moving from
Mdc
(matrix of direction cosines) andPxyz_c
(center point in world coordinates) to the more explicitx/y/z/c_ras
column vectors. These are consistent with most FreeSurfer literature and program outputs, and correctly encode the shape of the direction cosines as column-major rather than row-major (needing to be transposed when reading/writing).Somewhat confusingly, there are two ways of referring to the voxel and world coordinates, and
XYZ
means world in one, and voxel in the other, and in both cases the alternative includes "R" and "S".As CRS/XYZ appears to be FreeSurfer-unique, I'd prefer to settle on XYZ/RAS, and perhaps consider non-XYZ variable names when they can reduce confusion.
TODO:
get_data_shape
handling (see ENH: Add image slicing #550)