Skip to content

RF: Use XmlSerializable, XmlParser for Gifti load/save #365

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 17 commits into from
Nov 22, 2015
Merged

RF: Use XmlSerializable, XmlParser for Gifti load/save #365

merged 17 commits into from
Nov 22, 2015

Conversation

bcipolli
Copy link
Contributor

CiftiImage (#249) and GiftiImage share a lot of logic. Both have functions for parsing, both will use similar logic for loading and saving. XmlBasedImage and XmlImageParser encapsulates the shared code, and defines a (hopefully) clean object model for any future images that are defined in XML.

  • XmlImageParser - a new abstract class that does most of the work here. Defines a structure for parsing XML files; every xml-based image will have to define one, specific to that image format. This essentially generalizes the object model used in parse_gifti_fast.py.
  • XmlBasedImage - a light wrapper around FileBasedImage that calls into the XmlImageParser for the image class on read, and takes care of small details on write (call into to_xml()). New required class property: parser (similar usage to header)

Things done:

  • Define XmlImageParser and XmlBasedImage
  • Migrate GiftiImage to use XmlBasedImage, and parse_gifti_fast to use XmlImageParser
  • Deprecate parse_gifti_file and related objects.

Things to do:

  • Determine the file structure; there is an unresolvable dependency between gifti.py and parse_gifti_fast.py; each refers to objects in the other. Perhaps we can simply combine them?
  • Write tests
  • Migrate parse_gifti_fast tests to new object model.
  • Add deprecation tests for parse_gifti_file
  • Add documentation
  • Scrub GIFTI parser code
  • Add test to test buffer_size passing via nibabel.load()

I'm pushing this PR out there now as (1) it seems to pass tests and (2) to get feedback on the object model / design as early in the development process as possible.

I'm not in a big rush on this; just wanted to get it out there while it was on my mind.

class GiftiImage(FileBasedImage, xml.XmlSerializable):
class GiftiImage(xml.XmlBasedImage):
"""
The Gifti spec suggests using the following suffixes to your
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Here, I moved the docstring from GiftiImage.to_file_map to GiftiImage, so that to_file_map could be moved to XmlBasedImage.

@bcipolli
Copy link
Contributor Author

Checked off all items but one: not sure the best way to avoid the recursive imports here. Should be ready for a looksie anytime--I even tried scrubbing some of the old code :)

@bcipolli
Copy link
Contributor Author

@effigies I put CiftiImage on top of this code this morning. Turns out that CiftiImage is a Nifti2Image with an xml-based extension; it is not an Xml-based image. This changed two things:

  • CiftiImage will not subclass XmlBasedImage; only GiftiImage will.
  • The CiftiImage extension will need an xml parser. Using the object model here was great, but XmlImageParser is too specific a name now. I am thinking to change to XmlParser.

I think the object model and abstractions here are good; I'd like to keep them. But, since GiftiImage is the only one using XmlBasedImage, it could be considered overkill. Alternately, I could remove XmlBasedImage, move logic back to GiftiImage, keepxmlutils.py`, and simply add the parser there.

@effigies
Copy link
Member

I suppose I'm inclined to not bother with XMLBasedImage if GiftiImage is its only subclass, but I should make it clear that I have not even started to look at this PR. (Busy week. I don't expect to be useful on here until Monday at the earliest.)

@np.deprecate_with_doc("Use GiftiImageParser instead.")
def __init__(self, *args, **kwargs):
super(Outputter, self).__init__(*args, **kwargs)
self.img = None
Copy link
Member

Choose a reason for hiding this comment

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

Outputter had an initialize method that should be preserved.

@effigies
Copy link
Member

effigies commented Nov 4, 2015

Sorry for the delay. This looks reasonable to me.

The main design issue I see is still whether or not we want an XMLBasedImage with only one subclass. In favor of XMLBasedImage, the decoupling work has already been done, and the alternative is to stick generic code into GiftiImage. In opposition, now you have to read another class to understand GiftiImage.

I think I can go either way. If anybody else has strong opinions, I can probably be pushed to one side or the other.

@bcipolli
Copy link
Contributor Author

bcipolli commented Nov 4, 2015

I would prefer to move the code back into GiftiImage, rename XmlImageParser to XmlParser (as it's the CIFTI header that's XML-based), and move xmlimages.py back to xmlutils.py.

@effigies
Copy link
Member

effigies commented Nov 4, 2015

That sounds good to me.

@bcipolli
Copy link
Contributor Author

bcipolli commented Nov 4, 2015

@effigies I did as outlined above, and deprecated Outputter.initialize. Tests pass as well.

out.img.filename = fname
return out.img
def initialize(self, *args, **kwargs):
self.__init__(*args, **kwargs)
Copy link
Member

Choose a reason for hiding this comment

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

Outputter.__init__ doesn't quite do the same thing as Outputter.initialize, since it also calls super(GiftiImageParser, self).__init__, which could change the buffer size and all that. I would just do the original contents, but short, like:

def initialize(self):
    """ Initialize outputter
    """
    self.fsm_state = []
    self.nvpair = self.da = self.coordsys = self.lata = self.label = None
    self.meta_global = self.meta_da = self.write_to = None
    self._char_blocks = None
    self.count_da = True

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Setting encoding and buffer_size is new. I figured since initialize is deprecated, nobody will call it with those args, nor would those args be non-defaults. I'd prefer to keep the # of lines for deprecated functions minimal.

As a side note, I could remove the arguments from initialize and __init__ to match the original definitions.

LMK what you think.

Copy link
Member

Choose a reason for hiding this comment

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

I would keep the original prototypes and docstring on initialize. But point taken on only defaults being used in any case, so revert the prototypes and I'm okay here.

@bcipolli
Copy link
Contributor Author

bcipolli commented Nov 5, 2015

Done.

@bcipolli bcipolli changed the title WIP: XmlBasedImage, XmlImageParser MRG: XmlBasedImage, XmlImageParser Nov 5, 2015
else:
datasource = fptr
class Outputter(GiftiImageParser):
@np.deprecate_with_doc("Use GiftiImageParser instead.")
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 deprecation be on the class instead of __init__?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I probably don't understand what deprecating the class would do; could you help make that clear to me?

In previous cases I remember deprecating __init__ so that the deprecation is lazy--it only triggers if someone instantiates the class, and not when the file is simply imported. That's the only goal here.

Copy link
Member

Choose a reason for hiding this comment

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

I thought the @np.deprecate functions modify documentation. Do they also raise warnings? If so, this makes sense.

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, they raise warnings--I add a deprecation warning test case each time we add this decorator, to make sure.

@effigies
Copy link
Member

effigies commented Nov 5, 2015

Okay, LGTM then.

Anybody else have comments?

@bcipolli bcipolli changed the title MRG: XmlBasedImage, XmlImageParser RF: Use XmlSerializable, XmlParser for Gifti load/save Nov 20, 2015
@bcipolli
Copy link
Contributor Author

Any chance we can merge this one?

@effigies
Copy link
Member

I think people have had a chance to voice objections. In it goes.

effigies added a commit that referenced this pull request Nov 22, 2015
RF: Use XmlSerializable, XmlParser for Gifti load/save
@effigies effigies merged commit fde94f1 into nipy:master Nov 22, 2015
@matthew-brett
Copy link
Member

Yes, sorry I haven't had a chance to review. Next week is better for me, by Tuesday afternoon I will have more time.

file_map=file_map)
super(GiftiImage, self).__init__(header=header, extra=extra,
file_map=file_map)
# placed here temporarily for git diff purposes
Copy link
Member

Choose a reason for hiding this comment

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

Rewrite comment now you've decided in this implementation?

@effigies
Copy link
Member

@matthew-brett Should I revert this merge?

@matthew-brett
Copy link
Member

Oh - no - sorry - you did good to merge, thanks for doing it. I was only adding post-merge suggestions. Happy to do that as a PR if it's easier.

grlee77 pushed a commit to grlee77/nibabel that referenced this pull request Mar 15, 2016
RF: Use XmlSerializable, XmlParser for Gifti load/save
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.

4 participants