Skip to content

WIP: Improve meta data extraction from DICOM files #290

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 31 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
d62aaee
ENH: Added function for finding private element.
moloney Jul 19, 2014
b1ad71b
ENH: Add write func for CSA, reader now uses find_private_element
moloney Jul 19, 2014
ff70ad3
ENH: Add xprotocol parsing and tests
moloney Dec 3, 2014
c0085fc
CLN: Remove some unneeded imports from test_xprotocol
moloney Dec 3, 2014
98d959c
CLN: More minor cleanup in test_xprotocol
moloney Dec 3, 2014
df2940b
ENH: Add ascconv parser and tests
moloney Dec 3, 2014
c943414
ENH: Allow parsing of older XProtocol format (EvaProtocol)
moloney Dec 4, 2014
47413f0
WIP: Add extract module for pulling all meta data from a DICOM
moloney Dec 4, 2014
5d70405
BF: Fix issue with pydicom 0.9.7 and our (now non-fake) test data
moloney Dec 5, 2014
2aff015
RF: refactor ascconv parse with Python ast module
matthew-brett Dec 10, 2014
0a7168d
BF: fixes for Python 3
matthew-brett Dec 11, 2014
972f142
RF+BF+TST: refactor ElemDict to accept mapping
matthew-brett Dec 11, 2014
e551838
ENH: Improve unicode handling, allow generic filtering.
moloney Feb 17, 2015
0f39e80
RF: Swap in PLY based parser for xprotocol
moloney Feb 17, 2015
86da916
CLN: Remove generate_uid function as that is being added to pydicom
moloney Feb 17, 2015
b5baec5
TST: Remove outdated tests
moloney Apr 23, 2015
078d775
BF: Fix issue with externals.ply not being installed.
moloney Apr 24, 2015
e67ac6b
RF: Build meta data extraction logic into dicom wrapper objects.
moloney Apr 24, 2015
418d958
TST: Fix ascconv test, make more independent.
moloney Apr 24, 2015
b14d0a8
ENH: Added more complex array test case.
moloney May 27, 2015
471662b
ENH: Update ply to 3.6 and update test_xpparse.
moloney May 27, 2015
af3f7b8
RF: Add old sample file, make parser more flexible to accomodate.
moloney Jun 10, 2015
14b1292
CLN: Remove unneeded class
moloney Jun 10, 2015
d65151b
ENH: Add element containers with tests.
moloney Jun 22, 2015
4dd044e
ENH: Add slicing functionality to ElemList
moloney Jun 25, 2015
9e64b45
BF+ENH: Improve elem container functionality.
moloney Jun 25, 2015
f69f66a
CLN: whitespace
moloney Jun 25, 2015
290549c
RF: Use the elemcont classes in xpparse, fix some parsing.
moloney Jul 7, 2015
a29832b
TST: Improve test_sample_file by adding more checks.
moloney Jul 7, 2015
3764297
ENH: Allow attributes on 'ASCCONV BEGIN' line.
moloney Jul 7, 2015
3574455
RF+TST: Move attribute parsing logic into ascconv module.
moloney Aug 12, 2015
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ dist/
.shelf
.tox/
.coverage
parser.out
parsetab.py

# Logs and databases #
######################
Expand Down
189 changes: 189 additions & 0 deletions nibabel/elemcont.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
'''Containers for storing "elements" which have both a core data value as well
as some additional meta data. When indexing into these containers it is this
core value that is returned, which allows for much cleaner and more readable
access to nested structures.

Each object stored in these containers must have an attribute `value` which
provides the core data value for the element. To get the element object itself
the `get_elem` method must be used.
'''
from collections import MutableMapping, MutableSequence

from .externals import OrderedDict
from .externals.six import iteritems


class Elem(object):
'''Basic element type has a `value` and a `meta` attribute.'''
def __init__(self, value, meta=None):
self.value = value
self.meta = {} if meta is None else meta


class InvalidElemError(Exception):
'''Raised when trying to add an object without a `value` attribute to an
`ElemDict`.'''
def __init__(self, invalid_val):
self.invalid_val = invalid_val
message = ("Provided value '%s' of type %s does not have a 'value' "
"attribute" % (self.invalid_val, type(invalid_val)))
super(InvalidElemError, self).__init__(message)


class ElemDict(MutableMapping):
'''Ordered dict-like where each value is an "element", which is defined as
any object which has a `value` attribute.

When looking up an item in the dict, it is this `value` attribute that
is returned. To get the element itself use the `get_elem` method.
'''

def __init__(self, *args, **kwargs):
if len(args) > 1:
raise TypeError("At most one arg expected, got %d" % len(args))
self._elems = OrderedDict()
if len(args) == 1:
if hasattr(args[0], 'iter_elems'):
it = args[0].iter_elems()
elif hasattr(args[0], 'items'):
it = iteritems(args[0])
else:
it = args[0]
for key, val in it:
self[key] = val
for key, val in iteritems(kwargs):
self[key] = val

def __getitem__(self, key):
return self._elems[key].value

def __setitem__(self, key, val):
if not hasattr(val, 'value'):
raise InvalidElemError(val)
self._elems[key] = val

def __delitem__(self, key):
del self._elems[key]

def __iter__(self):
return iter(self._elems)

def __len__(self):
return len(self._elems)

def __repr__(self):
return ('ElemDict(%s)' %
', '.join(['%r=%r' % x for x in self.items()]))

def update(self, other):
if isinstance(other, self.__class__):
for key, elem in other.iter_elems():
self[key] = elem
else:
for key, elem in iteritems(other):
self[key] = elem

def get_elem(self, key):
return self._elems[key]

def iter_elems(self):
for key in self:
yield (key, self._elems[key])


class ElemList(MutableSequence):
'''A list-like container where each value is an "element", which is
defined as any object which has a `value` attribute.

When looking up an item in the list, it is this `value` attribute that
is returned. To get the element itself use the `get_elem` method.
'''
def __init__(self, data=None):
self._elems = list()
if data is not None:
if isinstance(data, self.__class__):
for idx in range(len(data)):
self.append(data.get_elem(idx))
else:
for elem in data:
self.append(elem)

def _tuple_from_slice(self, slc):
'''Get (start, end, step) tuple from slice object.
'''
(start, end, step) = slc.indices(len(self))
# Replace (0, -1, 1) with (0, 0, 1) (misfeature in .indices()).
if step == 1:
if end < start:
end = start
step = None
if slc.step == None:
step = None
return (start, end, step)

def __getitem__(self, idx):
if isinstance(idx, slice):
return ElemList(self._elems[idx])
else:
return self._elems[idx].value

def __setitem__(self, idx, val):
if isinstance(idx, slice):
(start, end, step) = self._tuple_from_slice(idx)
if step != None:
# Extended slice
indices = range(start, end, step)
if len(val) != len(indices):
raise ValueError(('attempt to assign sequence of size %d' +
' to extended slice of size %d') %
(len(value), len(indices)))
for j, assign_val in enumerate(val):
self.insert(indices[j], assign_val)
else:
# Normal slice
for j, assign_val in enumerate(val):
self.insert(start + j, assign_val)
else:
self.insert(idx, val)

def __delitem__(self, idx):
del self._elems[idx]

def __len__(self):
return len(self._elems)

def __repr__(self):
return ('ElemList([%s])' % ', '.join(['%r' % x for x in self]))

def __add__(self, other):
result = self.__class__(self)
if isinstance(other, self.__class__):
for idx in range(len(other)):
result.append(other.get_elem(idx))
else:
for e in other:
result.append(e)
return result

def __radd__(self, other):
result = self.__class__(other)
for idx in range(len(self)):
result.append(self.get_elem(idx))
return result

def __iadd__(self, other):
if isinstance(other, self.__class__):
for idx in range(len(other)):
self.append(other.get_elem(idx))
else:
for e in other:
self.append(e)
return self

def insert(self, idx, val):
if not hasattr(val, 'value'):
raise InvalidElemError(val)
self._elems.insert(idx, val)

def get_elem(self, idx):
return self._elems[idx]
5 changes: 5 additions & 0 deletions nibabel/externals/ply/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# PLY package
# Author: David Beazley ([email protected])

__version__ = '3.6'
__all__ = ['lex','yacc']
Loading