Skip to content

BF: if voxel order in header is lowercase, .trk can't load #590

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

Closed
wants to merge 8 commits into from

Conversation

kesshijordan
Copy link
Contributor

If the voxel_order field of the header is lowercase in a track file (this is the case when I re-save streamlines using trackvis on my mac: Trackvis Version 0.6.1 (Build 2017.01.17) Public), it will not load using the new nib.streamlines.load because axcodes2ornt returns nan's when given a lower case voxel order.

@coveralls
Copy link

Coverage Status

Coverage increased (+0.004%) to 96.337% when pulling 419998a on kesshijordan:fix_vo_case into 6d54618 on nipy:master.

@matthew-brett
Copy link
Member

Thanks a lot for this - @MarcCote - any reason we should insist on lower-case voxel orientations?

@kesshijordan - any chance of adding a test?

@kesshijordan
Copy link
Contributor Author

Thanks, @matthew-brett. Sure, though do you know how to fix the travis failures, above? The spacing on the arrays disagrees on expected vs got (though the numbers match) in the DOC_DOC_TEST=1 builds.

@effigies
Copy link
Member

Looks like that's an effect of numpy releasing 1.14 (related: #556, #582), and us not running DOC_DOC_TEST on prereleases. I wouldn't worry about it in this PR.

@codecov-io
Copy link

codecov-io commented Jan 11, 2018

Codecov Report

Merging #590 into master will decrease coverage by 0.11%.
The diff coverage is 100%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master     #590      +/-   ##
==========================================
- Coverage   94.58%   94.46%   -0.12%     
==========================================
  Files         177      177              
  Lines       25533    24949     -584     
  Branches     2813     2661     -152     
==========================================
- Hits        24150    23569     -581     
  Misses        908      908              
+ Partials      475      472       -3
Impacted Files Coverage Δ
nibabel/tests/test_orientations.py 100% <100%> (ø) ⬆️
nibabel/streamlines/tests/test_trk.py 100% <100%> (ø) ⬆️
nibabel/orientations.py 97.5% <100%> (+0.02%) ⬆️
nibabel/streamlines/trk.py 94.29% <0%> (-1.97%) ⬇️
nibabel/streamlines/tests/test_tractogram.py 97.75% <0%> (-0.68%) ⬇️
nibabel/streamlines/tests/test_array_sequence.py 99.5% <0%> (-0.5%) ⬇️
nibabel/streamlines/tck.py 98.89% <0%> (-0.04%) ⬇️
nibabel/tests/test_volumeutils.py 98.94% <0%> (ø) ⬆️
nibabel/streamlines/utils.py 100% <0%> (ø) ⬆️
nibabel/streamlines/tractogram.py 99.66% <0%> (+0.65%) ⬆️
... and 1 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 6d54618...9a74880. Read the comment docs.

@kesshijordan
Copy link
Contributor Author

Thanks, @effigies

@matthew-brett I added a test comparing the trackvis to rasmm affines between two simulated trk files (voxel order las and LAS). I put it in the test_load_file_with_wrong_information() set. Is that fine?

@coveralls
Copy link

coveralls commented Jan 11, 2018

Coverage Status

Coverage increased (+0.03%) to 96.361% when pulling 9a74880 on kesshijordan:fix_vo_case into 6d54618 on nipy:master.

@MarcCote
Copy link
Contributor

@matthew-brett no, there is no reason why we couldn't support both. That said, shouldn't we fix that in nibabel.orientations.axcodes2ornt? Is there any reason why we couldn't support lower case letter for the orientation?

@matthew-brett
Copy link
Member

Good point - lower case should be OK for the axis codes in orientations.py ...

@kesshijordan
Copy link
Contributor Author

@matthew-brett and @MarcCote : I moved the change to nibabel.orientations.axcodes2ornt by inserting axcodes = axcodes.upper() as the first line. I think the test I added before should still work, since it would break if 'las' and 'LAS' were treated differently. Is this okay?

@@ -349,6 +349,7 @@ def axcodes2ornt(axcodes, labels=None):
[ 0., -1.],
[ 2., 1.]])
"""
axcodes = axcodes.upper()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

According to the docsring, axcodes is a tuple not a str. So, .upper() does not exist.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks; I hadn't noticed that : ) It fixed my loading issue because the .trk files I'm trying to load have orientation stored as a string, but that does break on tuples. I exchanged that line for a loop that generates an upper-case tuple from either a string or a tuple (see below). How's this?

upper_str = []
    for c in axcodes:
        upper_str.append(c.upper())
    axcodes = tuple(upper_str)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about the following?

axcodes = tuple(c.upper() for c in axcodes)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, @effigies

@effigies
Copy link
Member

Sorry to push more work on you, but do you think you could quickly add some test cases to axcodes2ornt?

def test_axcodes2ornt():
# Go from axcodes back to orientations
labels = (('left', 'right'), ('back', 'front'), ('down', 'up'))
assert_array_equal(axcodes2ornt(('right', 'front', 'up'), labels),
[[0, 1],
[1, 1],
[2, 1]]
)
assert_array_equal(axcodes2ornt(('left', 'back', 'down'), labels),
[[0, -1],
[1, -1],
[2, -1]]
)
assert_array_equal(axcodes2ornt(('down', 'back', 'left'), labels),
[[2, -1],
[1, -1],
[0, -1]]
)
assert_array_equal(axcodes2ornt(('front', 'down', 'right'), labels),
[[1, 1],
[2, -1],
[0, 1]]
)
# default is RAS output directions
assert_array_equal(axcodes2ornt(('R', 'A', 'S')),
[[0, 1],
[1, 1],
[2, 1]]
)
# dropped axes produce None
assert_array_equal(axcodes2ornt(('R', None, 'S')),
[[0, 1],
[np.nan, np.nan],
[2, 1]]
)

Something like:

lia_ornt = [[0, -1], [2, -1], [1, 1]]
for lia_axcodes in ('LIA', 'lia', ('L', 'I', 'A'), ('l', 'i', 'a')):
    assert_array_equal(axcodes2ornt(lia_axcodes), lia_ornt)

@@ -349,6 +349,9 @@ def axcodes2ornt(axcodes, labels=None):
[ 0., -1.],
[ 2., 1.]])
"""

axcodes = tuple(c.upper() for c in axcodes)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is breaking on labels is not None. Probably the least disruptive approach is to move this into the if labels is None branch immediately below.

My reasoning is that, if somebody's passing labels, they'll also be passing axis codes with labels selected from that set. (The alternative would be to also apply .upper() to the labels, but I don't like the smell of that.)

@effigies
Copy link
Member

Hi @kesshijordan, just checking in. I think this is very nearly ready, if you have a few minutes. If you're swamped, I can submit a PR to your branch with my suggested changes.

@kesshijordan
Copy link
Contributor Author

Hi, @effigies. Thanks for checking in; I've been swamped but I have a bit of time now. I'll submit changes soon.

Copy link
Member

@effigies effigies left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for your work on this. I think this final fix should get the tests working.

@@ -349,7 +349,9 @@ def axcodes2ornt(axcodes, labels=None):
[ 0., -1.],
[ 2., 1.]])
"""

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just for a clean diff, could you remove this empty line?

if labels is None:
axcodes = tuple(c.upper() for c in axcodes)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like we did not consider c is None. Maybe update this to:

axcodes = tuple(c.upper() if c is not None else None for c in axcodes)

@kesshijordan
Copy link
Contributor Author

Absolutely. Thanks for all the pointers, @effigies. I have made those corrections and resubmitted.

@effigies
Copy link
Member

Looks ready to me. @MarcCote @matthew-brett any further comments?

Copy link
Member

@matthew-brett matthew-brett left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At least we should document that the labels / codes are not case sensitive...

@@ -350,6 +350,7 @@ def axcodes2ornt(axcodes, labels=None):
[ 2., 1.]])
"""
if labels is None:
axcodes = tuple(c.upper() if c is not None else None for c in axcodes)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be run only when labels is None?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. Unless the labels themselves are also to be filtered. See #590 (comment).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't that smell off though - to be case insensitive only when the labels are not specified?

I guess the ideal would be to be case insensitive only for codes labels in LRAPIS.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see, so the issue is if someone passes an alternative default ordering of the normal codes, but not custom labels, we want the overall behavior to be unchanged. I was under the impression that the only time you wanted to specify labels would be to use some custom labeling.

Then yeah, that might make sense. What if it becomes:

if labels is None:
    labels = list(zip('LPI', 'RAS'))

# Enable case-insensitive axis codes for standard direction labels
if set(elem for pair in labels for elem in pair) == set('LRAPIS'):
    axcodes = tuple(...)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@matthew-brett Can you please confirm whether I've summarized your concern and proposed a reasonable solution? If so, I think we can get this fix in pretty soon.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry to be a bit imprecise.

Now I think about this more, it still seems ugly to me to special case for characters LRAPIS. Why these and not other codes? For example, what if someone uses F and B for front and back? Wouldn't they expect the same behavior?

I've very sorry to keep changing, but can't we go back to expecting upper case for the codes from the caller, and, in particular, fixing this in the trackvis caller rather than here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@matthew-brett would it make sense to put in a "header format check" that returns a more informative error? That way the user can see why the load is failing and modify the header themself?

@matthew-brett
Copy link
Member

@kesshijordan - I've made an alternative PR, as a suggestion. It's got your initial changes, plus (I think) some code to give a more informative error, as you suggested above. Is that OK with you?

@matthew-brett
Copy link
Member

Sorry - the PR is linked below, but for completeness - #600

@kesshijordan
Copy link
Contributor Author

That sounds good, @matthew-brett. Thanks!

@nibotmi
Copy link
Contributor

nibotmi commented Feb 20, 2018

☔ The latest upstream changes (presumably #600) made this pull request unmergeable. Please resolve the merge conflicts.

@matthew-brett
Copy link
Member

@kesshijordan - OK to close this one?

@MarcCote
Copy link
Contributor

Yes, I'm good with the changes of #600.

@kesshijordan
Copy link
Contributor Author

Yes, @matthew-brett. Okay to close this one.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants