Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
67 changes: 20 additions & 47 deletions ismrmrd/acquisition.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from .constants import *
from .flags import FlagsMixin
from .equality import EqualityMixin
from . import decorators


class EncodingCounters(EqualityMixin, ctypes.Structure):
Expand Down Expand Up @@ -58,7 +59,6 @@ class AcquisitionHeader(FlagsMixin, EqualityMixin, ctypes.Structure):
("idx", EncodingCounters),
("user_int", ctypes.c_int32 * USER_INTS),
("user_float", ctypes.c_float * USER_FLOATS)]

def __str__(self):
retstr = ''
for field_name, field_type in self._fields_:
Expand All @@ -70,8 +70,9 @@ def __str__(self):
return retstr


@decorators.expose_header_fields(AcquisitionHeader)
class Acquisition(FlagsMixin):
__readonly = ('number_of_samples', 'active_channels', 'trajectory_dimensions')
_readonly = ('number_of_samples', 'active_channels', 'trajectory_dimensions')

@staticmethod
def deserialize_from(read_exactly):
Expand All @@ -97,7 +98,7 @@ def deserialize_from(read_exactly):
return acquisition

def serialize_into(self, write):
write(self.__head)
write(self._head)
write(self.__traj.tobytes())
write(self.__data.tobytes())

Expand Down Expand Up @@ -146,7 +147,7 @@ def __init__(self, head=None, data=None, trajectory=None):
def generate_header():
if head is None:
if data is None:
return AcquisitionHeader()
return AcquisitionHeader()
else:
nchannels, nsamples = data.shape
trajectory_dimensions = trajectory.shape[1] if trajectory is not None else 0
Expand All @@ -170,55 +171,26 @@ def generate_trajectory_array(header):
return trajectory if trajectory is not None else np.zeros(
shape=(header.number_of_samples, header.trajectory_dimensions), dtype=np.float32)

self.__head = generate_header()

self.__data = generate_data_array(self.__head)
self.__traj = generate_trajectory_array(self.__head)

for (field, _) in self.__head._fields_:
try:
g = '__get_' + field
s = '__set_' + field
setattr(Acquisition, g, self.__getter(field))
setattr(Acquisition, s, self.__setter(field))
p = property(getattr(Acquisition, g), getattr(Acquisition, s))
setattr(Acquisition, field, p)
except TypeError:
# e.g. if key is an `int`, skip it
pass

def __getter(self, name):
if name in self.__readonly:
def fn(self):
return copy.copy(self.__head.__getattribute__(name))
else:
def fn(self):
return self.__head.__getattribute__(name)
return fn

def __setter(self, name):
if name in self.__readonly:
def fn(self, val):
raise AttributeError(name + " is read-only. Use resize instead.")
else:
def fn(self, val):
self.__head.__setattr__(name, val)

return fn
self._head = generate_header()

self.__data = generate_data_array(self._head)
self.__traj = generate_trajectory_array(self._head)



def resize(self, number_of_samples=0, active_channels=1, trajectory_dimensions=0):
self.__data = np.resize(self.__data, (active_channels, number_of_samples))
self.__traj = np.resize(self.__traj, (number_of_samples, trajectory_dimensions))
self.__head.number_of_samples = number_of_samples
self.__head.active_channels = active_channels
self.__head.trajectory_dimensions = trajectory_dimensions
self._head.number_of_samples = number_of_samples
self._head.active_channels = active_channels
self._head.trajectory_dimensions = trajectory_dimensions

def getHead(self):
return copy.deepcopy(self.__head)
return copy.deepcopy(self._head)

def setHead(self, hdr):
self.__head = self.__head.__class__.from_buffer_copy(hdr)
self.resize(self.__head.number_of_samples, self.__head.active_channels, self.__head.trajectory_dimensions)
self._head = self._head.__class__.from_buffer_copy(hdr)
self.resize(self._head.number_of_samples, self._head.active_channels, self._head.trajectory_dimensions)

@property
def data(self):
Expand All @@ -230,7 +202,7 @@ def traj(self):

def __str__(self):
retstr = ''
retstr += 'Header:\n %s\n' % (self.__head)
retstr += 'Header:\n %s\n' % (self._head)
retstr += 'Trajectory:\n %s\n' % (self.traj)
retstr += 'Data:\n %s\n' % (self.data)
return retstr
Expand All @@ -240,7 +212,8 @@ def __eq__(self, other):
return False

return all([
self.__head == other.__head,
self._head == other._head,
np.array_equal(self.__data, other.__data),
np.array_equal(self.__traj, other.__traj)
])

42 changes: 42 additions & 0 deletions ismrmrd/decorators.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import copy


class expose_header_fields:
def __init__(self,header_cls) -> None:
self.header_cls = header_cls

def __call__(self,cls):
def create_getter_and_setter(field):
if field in cls._readonly:
def getter(self):
return copy.copy(self._head.__getattribute__(field))
def setter(self,val):
raise AttributeError(field+ " is read-only. Use resize instead.")
else:
def getter(self):
return self._head.__getattribute__(field)
def setter(self, val):
self._head.__setattr__(field, val)

return getter,setter

ignore_list = cls._ignore if hasattr(cls,"_ignore") else []

for (field, _) in self.header_cls._fields_:
if field in ignore_list:
continue
try:
g = '__get_' + field
s = '__set_' + field

getter,setter = create_getter_and_setter(field)
setattr(cls, g, getter)
setattr(cls, s, setter)
p = property(getattr(cls, g), getattr(cls, s))
setattr(cls, field, p)
except TypeError:
# e.g. if key is an `int`, skip it
pass

return cls

26 changes: 13 additions & 13 deletions ismrmrd/file.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ def __len__(self):

def __iter__(self):
for raw in self.data:
yield self.from_numpy(raw)
yield self.from_numpy(raw)

def __getitem__(self, key):
if isinstance(key, slice):
Expand Down Expand Up @@ -65,17 +65,14 @@ def __init__(self, data):

@classmethod
def from_numpy(cls, raw):
acquisition = Acquisition(raw['head'])

acquisition.data[:] = raw['data'].view(np.complex64).reshape(
(acquisition.active_channels,
acquisition.number_of_samples)
)[:]

acquisition.traj[:] = raw['traj'].reshape(
(acquisition.number_of_samples,
acquisition.trajectory_dimensions)
)[:]
acquisition = Acquisition(raw['head'],raw['data'].view(np.complex64).reshape(
(raw['head']['active_channels'],
raw['head']['number_of_samples'])
),
raw['traj'].reshape(
(raw['head']['number_of_samples'],
raw['head']['trajectory_dimensions']
)))

return acquisition

Expand Down Expand Up @@ -378,11 +375,14 @@ def has_acquisitions(self):
class File(Folder):

def __init__(self, filename, mode='a'):
self.__file = h5py.File(filename, mode)
self.__file = h5py.File(filename, mode,driver='stdio')
super().__init__(self.__file)

def __enter__(self):
return self

def __exit__(self, exc_type, exc_value, traceback):
self.__file.close()

def close(self):
self.__file.close()
72 changes: 20 additions & 52 deletions ismrmrd/image.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from .flags import FlagsMixin
from .equality import EqualityMixin
from .constants import *
from . import decorators

dtype_mapping = {
DATATYPE_USHORT: np.dtype('uint16'),
Expand Down Expand Up @@ -125,9 +126,10 @@ def __repr__(self):


# Image class
@decorators.expose_header_fields(ImageHeader)
class Image(FlagsMixin):
__readonly = ('data_type', 'matrix_size', 'channels')
__ignore = ('matrix_size', 'attribute_string_len')
_readonly = ('data_type', 'matrix_size', 'channels')
_ignore = ('matrix_size', 'attribute_string_len')

@staticmethod
def deserialize_from(read_exactly):
Expand All @@ -154,9 +156,9 @@ def calculate_number_of_entries(nchannels, xs, ys, zs):
def serialize_into(self, write):

attribute_bytes = self.attribute_string.encode('utf-8')
self.__head.attribute_string_len = len(attribute_bytes)
self._head.attribute_string_len = len(attribute_bytes)

write(self.__head)
write(self._head)

write(ctypes.c_uint64(len(attribute_bytes)))
write(attribute_bytes)
Expand Down Expand Up @@ -229,15 +231,15 @@ def create_consistent_header(header, data):
if head is None:
if data is None:
data = np.empty((1, 1, 1, 0), dtype=np.complex64)
self.__head = create_consistent_header(ImageHeader(), data)
self._head = create_consistent_header(ImageHeader(), data)
else:
self.__head = ImageHeader.from_buffer_copy(head)
self._head = ImageHeader.from_buffer_copy(head)
if data is None:
data = np.empty(shape=(self.__head.channels, self.__head.matrix_size[2],
self.__head.matrix_size[1], self.__head.matrix_size[0]),
dtype=get_dtype_from_data_type(self.__head.data_type))
data = np.empty(shape=(self._head.channels, self._head.matrix_size[2],
self._head.matrix_size[1], self._head.matrix_size[0]),
dtype=get_dtype_from_data_type(self._head.data_type))
else:
self.__head = create_consistent_header(self.__head, data)
self._head = create_consistent_header(self._head, data)
self.__data = data

if attribute_string is not None:
Expand All @@ -249,48 +251,14 @@ def create_consistent_header(header, data):
else:
self.__meta = Meta()

for (field, type) in self.__head._fields_:
if field in self.__ignore:
continue
else:
try:
g = '__get_' + field
s = '__set_' + field
setattr(Image, g, self.__getter(field))
setattr(Image, s, self.__setter(field))
p = property(getattr(Image, g), getattr(Image, s))
setattr(Image, field, p)
except TypeError:
# e.g. if key is an `int`, skip it
pass

def __getter(self, name):
if name in self.__readonly:
def fn(self):
return copy.copy(self.__head.__getattribute__(name))
else:
def fn(self):
return self.__head.__getattribute__(name)
return fn

def __setter(self, name):
if name in self.__readonly:
def fn(self, val):
raise AttributeError(name + " is read-only.")
else:
def fn(self, val):
self.__head.__setattr__(name, val)

return fn

def getHead(self):
return copy.deepcopy(self.__head)
return copy.deepcopy(self._head)

def setHead(self, hdr):
self.__head = self.__head.__class__.from_buffer_copy(hdr)
self.setDataType(self.__head.data_type)
self.resize(self.__head.channels, self.__head.matrix_size[2], self.__head.matrix_size[1],
self.__head.matrix_size[0])
self._head = self._head.__class__.from_buffer_copy(hdr)
self.setDataType(self._head.data_type)
self.resize(self._head.channels, self._head.matrix_size[2], self._head.matrix_size[1],
self._head.matrix_size[0])

def setDataType(self, val):
self.__data = self.__data.astype(get_dtype_from_data_type(val))
Expand Down Expand Up @@ -340,18 +308,18 @@ def attribute_string_len(self):
return len(self.attribute_string)

def __str__(self):
return "Header:\n {}\nAttribute string:\n {}\nData:\n {}\n".format(self.__head, self.attribute_string,
return "Header:\n {}\nAttribute string:\n {}\nData:\n {}\n".format(self._head, self.attribute_string,
self.__data)

def __repr__(self):
return f"Image(head={self.__head.__repr__()},meta={self.__meta.__repr__()},data={self.__data.__repr__()})"
return f"Image(head={self._head.__repr__()},meta={self.__meta.__repr__()},data={self.__data.__repr__()})"

def __eq__(self, other):
if not isinstance(other, Image):
return False

return all([
self.__head == other.__head,
self._head == other._head,
np.array_equal(self.__data, other.__data),
np.array_equal(self.attribute_string, other.attribute_string)
])
Loading