Skip to content

RF: move load/save logic out of SpatialImage #364

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

Merged
merged 10 commits into from
Oct 23, 2015
Merged

RF: move load/save logic out of SpatialImage #364

merged 10 commits into from
Oct 23, 2015

Conversation

bcipolli
Copy link
Contributor

This PR precedes #360, which builds off of this work. I'll copy some text from that PR; there's quite a bit of discussion about how this fits into forward-looking design of surface vs. spatial, binary vs. xml object models.

Issue:
The final issue blocking streamlined GIFTI/CIFTI in nibabel is: image file I/O functions are tied directly to the SpatialImage class; non-volumetric images (GIFTI/CIFTI) can't extend SpatialImage and so can't easily get in the load/save patheway.

Proposed Solution:
The good news is, the file I/O code actually is quite general. We can easily abstract the file I/O into a higher-level class (which I've called FileBasedHeader/FileBasedImage, as the more generic Header was already taken) that SpatialImage and GiftiImage inherit from.

To do:

  • Move file I/O from SpatialImage into FileBasedHeader/FileBasedImage
  • Update tests that assume all image types are of type SpatialImage

Notes:

class ImageDataError(Exception):
pass
class Header(SpatialHeader):
'''Alias for SpatialHeader; kept for backwards compatibility.'''
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 marked deprecated? e.g.

def __init__(self, *args, **kwargs):
    warnings.warn('Header is deprecated, use SpatialHeader', DeprecationWarning)
    SpatialHeader.__init__(self, *args, **kwargs)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

👍

Copy link
Member

Choose a reason for hiding this comment

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

I should probably note that you'll want stacklevel=2 in that warning.

@bcipolli
Copy link
Contributor Author

Thanks @effigies!

A question: for some comments, it's about old code. Most of the code is copy-pasted from spatialimage.py; it seemed dangerous to move a bunch of code, and make changes at the same time (as git doesn't really track those changes as different).

I'm happy to deal with those things here, but I wanted to double-check first. Thanks!

@effigies
Copy link
Member

Could be a separate PR, but my thought is that while people are looking at code is a good time to make changes. And the changes will be tracked in the commits, if not the full PR diff.

### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##
''' Common interface for any image format--volume or surface, binary or xml.'''

from ..externals.six import string_types
Copy link
Member

Choose a reason for hiding this comment

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

.externals, not ..externals

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yep. I pushed before running tests; give me a min..

@bcipolli
Copy link
Contributor Author

OK @effigies ; cleaned up and ran tests; pls take a look!

@bcipolli
Copy link
Contributor Author

doctests are failing; will work on those now.

@bcipolli
Copy link
Contributor Author

Made the code updates. A bit worried about deprecating Header now; should we simply document that they're equivalent and it will be deprecated (a la get_header())?

Also a bit concerned about changing the base class of images from Header to SpatialHeader. Would this break pickled headers?

@effigies
Copy link
Member

@bcipolli Regarding 9cc27ea: For images where there's a filename interface (basically, if there CAN be multiple files, then return the canonical file), then {get,set}_ are preferred. So that commit can be stripped out.

Is there any possibility that XML-based datasets will have multiple files (so a canonical filename is needed)? If not, then {get,set}_filename could be moved back into SpatialImage. But I don't think there's anything wrong with leaving them in FileBasedImage, either.

@effigies
Copy link
Member

A bit worried about deprecating Header now; should we simply document that they're equivalent and it will be deprecated (a la get_header())?

To be honest, I'm not quite sure what the difference is between posting an intent to deprecate and actually deprecating but not removing. Documentation should be updated ASAP, and DeprecationWarnings are silent by default.

Also a bit concerned about changing the base class of images from Header to SpatialHeader. Would this break pickled headers?

Good question. Is pickling really a thing we care about? The only case I can see is in tests/test_analyze.py. I guess I'm confused why you'd pickle an image rather than save the image. But we can try pickling under Header and loading under SpatialHeader, to be safe.

In any event, I'm also okay with leaving things as subclassed from Header and not SpatialHeader.

@bcipolli
Copy link
Contributor Author

that commit can be stripped out.

Done

Is there any possibility that XML-based datasets will have multiple files

I don't see any reason to keep it off of FileBasedImage; there's nothing to suggest surface images couldn't use a similar approach at some point. CIFTI does something close (using multiple sub-extensions to specify different data types related to a study), but not quite.

Is pickling really a thing we care about?

I'm not sure; perhaps some people or tools use pickle or shelve to store working sessions.

In any event, I'm also okay with leaving things as subclassed from Header and not SpatialHeader.

I'd prefer not to do that; I think that introduces complexity without much of the benefits. I suggest we either use SpatialHeader and deprecate Header, or just have Header without `SpatialHeader.

I'll try this out.

@bcipolli
Copy link
Contributor Author

@effigies I removed the commit and tested shelve (which uses pickle) manually (create a Minc1Header in master, load up in this branch. No issue.

@effigies
Copy link
Member

Okay. So no barrier to deprecating Header for SpatialHeader?

@bcipolli
Copy link
Contributor Author

Not that I see. I agree that deprecating directly is a fine user experience, from my POV

work:
'''
files_types = (('image', None),)
alternate_exts = () # Modified by @ImageOpener.register_ext_from_image
Copy link
Member

Choose a reason for hiding this comment

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

valid_exts

No _meta_sniff_len?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

_meta_sniff_len is optional, and on the header.

valid_exts is required, but is also required to be defined by each subclass. Defining it here makes it easier to forget to do that, so I excluded it.

Copy link
Member

Choose a reason for hiding this comment

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

sizeof_hdr is on the header, _meta_sniff_len is on the image and used in path_maybe_image without being checked to exist, first. (It does check whether the header class defines may_contain_header, but that's not tied to presence of _meta_sniff_len, AFAICT.)

files_types must also be defined by subclasses, correct? I think its purpose here is documentary. But the point of my comment was that alternate_exts has been removed from SpatialImage.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

👍

@classmethod
def is_valid_extension(klass, ext):
valid = tuple(ft[1] for ft in klass.files_types) + klass.alternate_exts
return ext.lower() in valid
Copy link
Member

Choose a reason for hiding this comment

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

is_valid_extension was removed from SpatialImage in 0c06464.

@effigies
Copy link
Member

Had another read through, and once we agree on the above issues, I think this can go in.

@bcipolli
Copy link
Contributor Author

@effigies Made the tweaks requested, tests pass... I think we're good to go! Excited! This is the last refactoring needed to really jump into #249 for CIFTI :)

@effigies
Copy link
Member

Pushed a PEP8 cleanup for filebasedimages.py, as that was easier than pointing them out. Have a suspicion removing the numpy import will break the doctests. If so, I'll put it into the doctests themselves.

@bcipolli
Copy link
Contributor Author

Pushed a PEP8 cleanup for filebasedimages.py, as that was easier than pointing them out.

Agree, thanks for doing that!

@effigies
Copy link
Member

Alright. If you're happy with that commit (and the tests pass), in it goes.

@bcipolli
Copy link
Contributor Author

👍

effigies added a commit that referenced this pull request Oct 23, 2015
RF: Move load/save logic out of SpatialImage into FileBasedImage

Prepares for XML-based images that do not store data as binary arrays.
Spatial{Image,Header} now subclass FileBased{Image,Header}
Header is a deprecated alias for SpatialHeader
@effigies effigies merged commit 9a67e76 into nipy:master Oct 23, 2015
@bcipolli bcipolli deleted the filebasedimage branch February 5, 2016 16:54
grlee77 pushed a commit to grlee77/nibabel that referenced this pull request Mar 15, 2016
RF: Move load/save logic out of SpatialImage into FileBasedImage

Prepares for XML-based images that do not store data as binary arrays.
Spatial{Image,Header} now subclass FileBased{Image,Header}
Header is a deprecated alias for SpatialHeader
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.

3 participants