-
Notifications
You must be signed in to change notification settings - Fork 264
ENH: Add Nifti1DicomExtension + test #296
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
Conversation
Thanks a lot for doing this. Absolutely fine for the pydicom optional dependency - we already have that. Have a look at Did you see: http://nipy.org/nibabel/devel/add_test_data.html for adding test data? For the tests - the trick is to go through each function / method and ask yourself 'how would I know if this method was doing the wrong thing'. It is a rather tiring process, but it also proves very helpful in learning, at least in my experience. |
I did see the page for adding test data, but I wasn't sure if it was appropriate to add another sample image (even a small one) for a test like this. Thanks for the tip on test_utils; that will work. Looks like I'm also failing on py3; I'll fix that as well. |
For a test image - if it is smaller than 50 compressed - sure - go ahead and add it to the main repo. Otherwise, a submodule would better. No problem for a very small submodule with only a few images. |
Ironically, my actual use-case is a PET volume that's 1.6GB, so I'll definitely be making a new image for the test. See what I can do! |
Also, fix _guess_implicit_VR method.
* Remove redundant get size method (inherited) * Remove unnecessary super()
Thanks for keeping up the work on this. Are there any good docs on what the DICOM extension format should contain. I see this : http://nifti.nimh.nih.gov/nifti-1/documentation/nifti1fields/nifti1fields_pages/extension.html - which only says:
Do you know of anyone else writing these extensions? |
is_implicit_VR = False | ||
is_little_endian = False | ||
elif transfer_syntax == dicom.UID.DeflatedExplicitVRLittleEndian: | ||
zipped = fileobj.read() |
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.
Have you got pyflakes or similar running in your editor? Pyflakes tells me that fileobj
and zlib
are not defined - is this piece of code tested?
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.
You're right - I took this directly from pydicom, although it wasn't abstracted in a method I could call from nibabel. The only transfer syntax tested here is ImplicitVRLittleEndian, not the zipped or big endian ones. I'll add tests and fix this.
I haven't been using Pyflakes, but just grabbed a bundle to enable it. Thanks for the suggestion!
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.
Pyflakes is huge - it really helps picking up this kind of thing.
I've only ever needed this once, for PMOD, a closed-source tool to model PET time activity curves. They write dicom tags to indicate the start time and duration of PET frames in a 4d nifti; the closet I could find for documentation is a listing of what they support for nifti. I only noticed they were using the DICOM extension when I saw they were able to read the timing from PMOD-created files and started looking around. The tags are written with an explicit VR syntax without any metadata. An example header looks like this:
Everyone else I know that wants DICOM info in NiFtI files uses @moloney 's dcmstack xml encoding; and I see there's already effort for that w/ nibabel (e.g. #232, #290). However, I figured that since I was implementing this, I might as well put in a way of storing full DICOM datasets in case someone wanted to use it. |
Well - I guess we can define the standard format. So would that be the explicit VR syntax? Maybe with little-endian byte order? Do the PMOD files always have little-endian order? And then try our best to read DICOM extensions that are written with implicit VR and big-endian. |
@@ -380,6 +380,113 @@ def write_to(self, fileobj, byteswap): | |||
# next 16 byte border | |||
fileobj.write(b'\x00' * (extstart + rawsize - fileobj.tell())) | |||
|
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.
PEP8 - two lines between classes, two lines between functions one line between methods.
That sounds like a plan; little-endian seems to be preferred from everything I've seen (I'm not even sure how to guess endianness without some magic or prior, which meta-less tags don't have). And I think that being explicit about the VR is preferable to implicit - the intended use-case is archival, not transmission over the network, so clarity has the premium over compression or compactness. I'm not sure about the endianness of PMOD files - everything I've seen has been little, but there's no doc or guarantee. I could write to the developers if you think it's worth it? Should we toss reading full metadata dicom datasets until someone wants to use them? Seems like we should honor it if it's present... |
Thinking more - I wonder if we should default to writing as the byte order of the header ( I guess we could write explicit VR, header endianness always, and read any endianness, with or without full metadata. I'm neutral about allowing full metadata if you have already implemented it, just because we haven't got an example of anyone using it, and it's more code maintenance - the YAGNI principle. |
There's two parts to this, the way that the header is actually written and what the header says. The dicom standard says the header is always written ExplicitVR-LittleEndian, but it can list a different method (TransferSyntax) to use for the rest of the file. However, in the case where there is no header (e.g. naked tags like PMOD) there's no TransferSyntax and no way to know the correct encoding; we can only guess it. How about this? If there is metadata and a TransferSyntax attribute is present, write using whatever the transfer syntax says. If no TransferSyntax, always use little endian. If the dataset was inferred to be Implicit, then write with ImplicitVR, otherwise write with ExplicitVR. That way you're returning as close to what you got as possible. I agree with you concerning YAGNI, but if I were going to use it, for archiving tags, I would use the whole dataset with metadata, and I would expect the writing to follow the Syntax - not doing so would definitely be a surprise. Plus, I've created example dicoms (~360Bytes) for testing different syntaxes directly from Nibabel's tests, so that reduces maintenance cost a little. |
Sorry - when I was talking about the 'header' I meant the nifti header. Do we care about what the input transfer syntax is? I mean, endian, or implicit VR? If we specified nifti-header-endian and explicit VR was the standard, could we persuade people that was the right way to save this stuff out? |
Take a look at this one, @matthew-brett , and see if makes sense. Sorry again for the delay! Nifti1DicomExtension subclasses Nifti1Extension, but defines its own Also, it looks like the environment is failing. Do we need to rebase now for the CI to build? |
I just did a merge of the master branch (makes it simpler for you to merge into your branch). I also did a refactor we need to do anyway to clean up importing dicom / pydicom in various places. I made a pull request into this branch (I hope): kastman#1 Please do check what I did, comments welcome. |
self._is_little_endian = parent_hdr.endianness == '<' | ||
else: | ||
self._is_little_endian = True | ||
if content.__class__ == Dataset: |
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.
How about if isinstance(content, Dataset):
?
Converting everything to the same endianness of the header sounds good to me. |
Also fine to override the |
Use externals version of OrderedDict for parrec. Use ``next(something)`` instead of ``something.__next__``.
* tiny-fixes: (333 commits) BF: a couple of tiny fixes MAINT: add comment specifying behaviour of shape=None to _hdr_key_dict TST: update parrec volume_labels tests to check the specific key order TST: expand dualTR parrec test to check warning FIX: fix bug in OrderedDict call within parrec get_volume_labels TST: add a test using a dummy dual TR .PAR file MAINT: change sort_info to an OrderedDict to enforce a consistent ordering for a given .PAR file ENH: support multiple TR values in PARREC headers TST: add test for parrec2nii CSV output MAINT: change parrec2nii volume label output from JSON to CSV MAINT: simplify get_volume_labels by removing per-slice attributes STY: rename get_dimension_labels to get_volume_labels DOC: fix typo in parrec2nii docstring TST: add get_dimension_label tests to the 5D and 6D data sets used for testing strict_sort STY: trim dynamic_keys via list comprehension TST: test_header_dimension_labels() added STY: clarify/streamline code based on feedback FIX: bugfix to replace np.unique() with _unique_rows() for 2D inputs needed for sorting vector properties. proper 2D ndarray to list of lists for JSON export in parrec2nii TST: add sorted_labels property to FakeHeader in test_parrec.py ENH: add sorted_labels function to PARRECHeader and corresponding info to PARRECArrayProxy ... Conflicts: Changelog nibabel/tests/test_nifti1.py
Move logic for conditionally importing dicom or pydicom into own module, and use this module where we are using dicom routines.
My my pep8 is picky.
MRG: merged current master, refactor dicom import
Not sure the docstring is super-helpful - take a look? Also, fixed the value import for pydicom <1. |
""" | ||
Parameters | ||
---------- | ||
code : int|str |
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.
Detail point, but I think these should be int or str
: see https://github.com/numpy/numpy/blob/master/doc/HOWTO_DOCUMENT.rst.txt#id5
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.
Sure, I was just following the docstring convention from Nifti1Extension (nifti1.py#L262
). I'm fixing that here too. Thanks for the detailed numpy docstring link; it was helpful (I know it's linked at the nibabel developer help as well, but hadn't gone back to that yet).
@matthew-brett Anything left that I can tweak or help you with on this one? |
Sorry to be a bit slow - looks good - thanks a lot for your persistence. |
Hi all,
Here's a thin wrapper class to read DICOM binary header information as encoded with the DICOM extended header code 2, instead of just providing the ugly & useless byte string. The extension code already written was quite helpful; thanks!
This depends on pydicom, but should fall back to a standard
Nifti1Extension
in the case of anImportError
. This is my first commit to nipy, so I'm not sure how you feel about provisional dependencies like this, but I think delegating the work to pydicom is the right approach.Few things still to be worked out before you merge:
import struct
in the test to create DICOM byte-strings, but it looked out of place. Is there a better spot / method?Hope this is helpful; let me know if there are any concerns about it! Cheers,
Erik