Skip to content

Commit 7ec97e3

Browse files
authored
Significantly improved acquisition, waveform and image creation speed (#66)
* Significantly improved Acquisition, waveform and image creation speed, resulting in faster deserialization * Updated version
1 parent 0742456 commit 7ec97e3

File tree

8 files changed

+113
-163
lines changed

8 files changed

+113
-163
lines changed

ismrmrd/acquisition.py

Lines changed: 20 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from .constants import *
77
from .flags import FlagsMixin
88
from .equality import EqualityMixin
9+
from . import decorators
910

1011

1112
class EncodingCounters(EqualityMixin, ctypes.Structure):
@@ -58,7 +59,6 @@ class AcquisitionHeader(FlagsMixin, EqualityMixin, ctypes.Structure):
5859
("idx", EncodingCounters),
5960
("user_int", ctypes.c_int32 * USER_INTS),
6061
("user_float", ctypes.c_float * USER_FLOATS)]
61-
6262
def __str__(self):
6363
retstr = ''
6464
for field_name, field_type in self._fields_:
@@ -70,8 +70,9 @@ def __str__(self):
7070
return retstr
7171

7272

73+
@decorators.expose_header_fields(AcquisitionHeader)
7374
class Acquisition(FlagsMixin):
74-
__readonly = ('number_of_samples', 'active_channels', 'trajectory_dimensions')
75+
_readonly = ('number_of_samples', 'active_channels', 'trajectory_dimensions')
7576

7677
@staticmethod
7778
def deserialize_from(read_exactly):
@@ -97,7 +98,7 @@ def deserialize_from(read_exactly):
9798
return acquisition
9899

99100
def serialize_into(self, write):
100-
write(self.__head)
101+
write(self._head)
101102
write(self.__traj.tobytes())
102103
write(self.__data.tobytes())
103104

@@ -146,7 +147,7 @@ def __init__(self, head=None, data=None, trajectory=None):
146147
def generate_header():
147148
if head is None:
148149
if data is None:
149-
return AcquisitionHeader()
150+
return AcquisitionHeader()
150151
else:
151152
nchannels, nsamples = data.shape
152153
trajectory_dimensions = trajectory.shape[1] if trajectory is not None else 0
@@ -170,55 +171,26 @@ def generate_trajectory_array(header):
170171
return trajectory if trajectory is not None else np.zeros(
171172
shape=(header.number_of_samples, header.trajectory_dimensions), dtype=np.float32)
172173

173-
self.__head = generate_header()
174-
175-
self.__data = generate_data_array(self.__head)
176-
self.__traj = generate_trajectory_array(self.__head)
177-
178-
for (field, _) in self.__head._fields_:
179-
try:
180-
g = '__get_' + field
181-
s = '__set_' + field
182-
setattr(Acquisition, g, self.__getter(field))
183-
setattr(Acquisition, s, self.__setter(field))
184-
p = property(getattr(Acquisition, g), getattr(Acquisition, s))
185-
setattr(Acquisition, field, p)
186-
except TypeError:
187-
# e.g. if key is an `int`, skip it
188-
pass
189-
190-
def __getter(self, name):
191-
if name in self.__readonly:
192-
def fn(self):
193-
return copy.copy(self.__head.__getattribute__(name))
194-
else:
195-
def fn(self):
196-
return self.__head.__getattribute__(name)
197-
return fn
198-
199-
def __setter(self, name):
200-
if name in self.__readonly:
201-
def fn(self, val):
202-
raise AttributeError(name + " is read-only. Use resize instead.")
203-
else:
204-
def fn(self, val):
205-
self.__head.__setattr__(name, val)
206-
207-
return fn
174+
self._head = generate_header()
175+
176+
self.__data = generate_data_array(self._head)
177+
self.__traj = generate_trajectory_array(self._head)
178+
179+
208180

209181
def resize(self, number_of_samples=0, active_channels=1, trajectory_dimensions=0):
210182
self.__data = np.resize(self.__data, (active_channels, number_of_samples))
211183
self.__traj = np.resize(self.__traj, (number_of_samples, trajectory_dimensions))
212-
self.__head.number_of_samples = number_of_samples
213-
self.__head.active_channels = active_channels
214-
self.__head.trajectory_dimensions = trajectory_dimensions
184+
self._head.number_of_samples = number_of_samples
185+
self._head.active_channels = active_channels
186+
self._head.trajectory_dimensions = trajectory_dimensions
215187

216188
def getHead(self):
217-
return copy.deepcopy(self.__head)
189+
return copy.deepcopy(self._head)
218190

219191
def setHead(self, hdr):
220-
self.__head = self.__head.__class__.from_buffer_copy(hdr)
221-
self.resize(self.__head.number_of_samples, self.__head.active_channels, self.__head.trajectory_dimensions)
192+
self._head = self._head.__class__.from_buffer_copy(hdr)
193+
self.resize(self._head.number_of_samples, self._head.active_channels, self._head.trajectory_dimensions)
222194

223195
@property
224196
def data(self):
@@ -230,7 +202,7 @@ def traj(self):
230202

231203
def __str__(self):
232204
retstr = ''
233-
retstr += 'Header:\n %s\n' % (self.__head)
205+
retstr += 'Header:\n %s\n' % (self._head)
234206
retstr += 'Trajectory:\n %s\n' % (self.traj)
235207
retstr += 'Data:\n %s\n' % (self.data)
236208
return retstr
@@ -240,7 +212,8 @@ def __eq__(self, other):
240212
return False
241213

242214
return all([
243-
self.__head == other.__head,
215+
self._head == other._head,
244216
np.array_equal(self.__data, other.__data),
245217
np.array_equal(self.__traj, other.__traj)
246218
])
219+

ismrmrd/decorators.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import copy
2+
3+
4+
class expose_header_fields:
5+
def __init__(self,header_cls) -> None:
6+
self.header_cls = header_cls
7+
8+
def __call__(self,cls):
9+
def create_getter_and_setter(field):
10+
if field in cls._readonly:
11+
def getter(self):
12+
return copy.copy(self._head.__getattribute__(field))
13+
def setter(self,val):
14+
raise AttributeError(field+ " is read-only. Use resize instead.")
15+
else:
16+
def getter(self):
17+
return self._head.__getattribute__(field)
18+
def setter(self, val):
19+
self._head.__setattr__(field, val)
20+
21+
return getter,setter
22+
23+
ignore_list = cls._ignore if hasattr(cls,"_ignore") else []
24+
25+
for (field, _) in self.header_cls._fields_:
26+
if field in ignore_list:
27+
continue
28+
try:
29+
g = '__get_' + field
30+
s = '__set_' + field
31+
32+
getter,setter = create_getter_and_setter(field)
33+
setattr(cls, g, getter)
34+
setattr(cls, s, setter)
35+
p = property(getattr(cls, g), getattr(cls, s))
36+
setattr(cls, field, p)
37+
except TypeError:
38+
# e.g. if key is an `int`, skip it
39+
pass
40+
41+
return cls
42+

ismrmrd/file.py

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ def __len__(self):
1818

1919
def __iter__(self):
2020
for raw in self.data:
21-
yield self.from_numpy(raw)
21+
yield self.from_numpy(raw)
2222

2323
def __getitem__(self, key):
2424
if isinstance(key, slice):
@@ -65,17 +65,14 @@ def __init__(self, data):
6565

6666
@classmethod
6767
def from_numpy(cls, raw):
68-
acquisition = Acquisition(raw['head'])
69-
70-
acquisition.data[:] = raw['data'].view(np.complex64).reshape(
71-
(acquisition.active_channels,
72-
acquisition.number_of_samples)
73-
)[:]
74-
75-
acquisition.traj[:] = raw['traj'].reshape(
76-
(acquisition.number_of_samples,
77-
acquisition.trajectory_dimensions)
78-
)[:]
68+
acquisition = Acquisition(raw['head'],raw['data'].view(np.complex64).reshape(
69+
(raw['head']['active_channels'],
70+
raw['head']['number_of_samples'])
71+
),
72+
raw['traj'].reshape(
73+
(raw['head']['number_of_samples'],
74+
raw['head']['trajectory_dimensions']
75+
)))
7976

8077
return acquisition
8178

@@ -378,11 +375,14 @@ def has_acquisitions(self):
378375
class File(Folder):
379376

380377
def __init__(self, filename, mode='a'):
381-
self.__file = h5py.File(filename, mode)
378+
self.__file = h5py.File(filename, mode,driver='stdio')
382379
super().__init__(self.__file)
383380

384381
def __enter__(self):
385382
return self
386383

387384
def __exit__(self, exc_type, exc_value, traceback):
388385
self.__file.close()
386+
387+
def close(self):
388+
self.__file.close()

ismrmrd/image.py

Lines changed: 20 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
from .flags import FlagsMixin
1313
from .equality import EqualityMixin
1414
from .constants import *
15+
from . import decorators
1516

1617
dtype_mapping = {
1718
DATATYPE_USHORT: np.dtype('uint16'),
@@ -125,9 +126,10 @@ def __repr__(self):
125126

126127

127128
# Image class
129+
@decorators.expose_header_fields(ImageHeader)
128130
class Image(FlagsMixin):
129-
__readonly = ('data_type', 'matrix_size', 'channels')
130-
__ignore = ('matrix_size', 'attribute_string_len')
131+
_readonly = ('data_type', 'matrix_size', 'channels')
132+
_ignore = ('matrix_size', 'attribute_string_len')
131133

132134
@staticmethod
133135
def deserialize_from(read_exactly):
@@ -154,9 +156,9 @@ def calculate_number_of_entries(nchannels, xs, ys, zs):
154156
def serialize_into(self, write):
155157

156158
attribute_bytes = self.attribute_string.encode('utf-8')
157-
self.__head.attribute_string_len = len(attribute_bytes)
159+
self._head.attribute_string_len = len(attribute_bytes)
158160

159-
write(self.__head)
161+
write(self._head)
160162

161163
write(ctypes.c_uint64(len(attribute_bytes)))
162164
write(attribute_bytes)
@@ -229,15 +231,15 @@ def create_consistent_header(header, data):
229231
if head is None:
230232
if data is None:
231233
data = np.empty((1, 1, 1, 0), dtype=np.complex64)
232-
self.__head = create_consistent_header(ImageHeader(), data)
234+
self._head = create_consistent_header(ImageHeader(), data)
233235
else:
234-
self.__head = ImageHeader.from_buffer_copy(head)
236+
self._head = ImageHeader.from_buffer_copy(head)
235237
if data is None:
236-
data = np.empty(shape=(self.__head.channels, self.__head.matrix_size[2],
237-
self.__head.matrix_size[1], self.__head.matrix_size[0]),
238-
dtype=get_dtype_from_data_type(self.__head.data_type))
238+
data = np.empty(shape=(self._head.channels, self._head.matrix_size[2],
239+
self._head.matrix_size[1], self._head.matrix_size[0]),
240+
dtype=get_dtype_from_data_type(self._head.data_type))
239241
else:
240-
self.__head = create_consistent_header(self.__head, data)
242+
self._head = create_consistent_header(self._head, data)
241243
self.__data = data
242244

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

252-
for (field, type) in self.__head._fields_:
253-
if field in self.__ignore:
254-
continue
255-
else:
256-
try:
257-
g = '__get_' + field
258-
s = '__set_' + field
259-
setattr(Image, g, self.__getter(field))
260-
setattr(Image, s, self.__setter(field))
261-
p = property(getattr(Image, g), getattr(Image, s))
262-
setattr(Image, field, p)
263-
except TypeError:
264-
# e.g. if key is an `int`, skip it
265-
pass
266-
267-
def __getter(self, name):
268-
if name in self.__readonly:
269-
def fn(self):
270-
return copy.copy(self.__head.__getattribute__(name))
271-
else:
272-
def fn(self):
273-
return self.__head.__getattribute__(name)
274-
return fn
275-
276-
def __setter(self, name):
277-
if name in self.__readonly:
278-
def fn(self, val):
279-
raise AttributeError(name + " is read-only.")
280-
else:
281-
def fn(self, val):
282-
self.__head.__setattr__(name, val)
283-
284-
return fn
285-
286254
def getHead(self):
287-
return copy.deepcopy(self.__head)
255+
return copy.deepcopy(self._head)
288256

289257
def setHead(self, hdr):
290-
self.__head = self.__head.__class__.from_buffer_copy(hdr)
291-
self.setDataType(self.__head.data_type)
292-
self.resize(self.__head.channels, self.__head.matrix_size[2], self.__head.matrix_size[1],
293-
self.__head.matrix_size[0])
258+
self._head = self._head.__class__.from_buffer_copy(hdr)
259+
self.setDataType(self._head.data_type)
260+
self.resize(self._head.channels, self._head.matrix_size[2], self._head.matrix_size[1],
261+
self._head.matrix_size[0])
294262

295263
def setDataType(self, val):
296264
self.__data = self.__data.astype(get_dtype_from_data_type(val))
@@ -340,18 +308,18 @@ def attribute_string_len(self):
340308
return len(self.attribute_string)
341309

342310
def __str__(self):
343-
return "Header:\n {}\nAttribute string:\n {}\nData:\n {}\n".format(self.__head, self.attribute_string,
311+
return "Header:\n {}\nAttribute string:\n {}\nData:\n {}\n".format(self._head, self.attribute_string,
344312
self.__data)
345313

346314
def __repr__(self):
347-
return f"Image(head={self.__head.__repr__()},meta={self.__meta.__repr__()},data={self.__data.__repr__()})"
315+
return f"Image(head={self._head.__repr__()},meta={self.__meta.__repr__()},data={self.__data.__repr__()})"
348316

349317
def __eq__(self, other):
350318
if not isinstance(other, Image):
351319
return False
352320

353321
return all([
354-
self.__head == other.__head,
322+
self._head == other._head,
355323
np.array_equal(self.__data, other.__data),
356324
np.array_equal(self.attribute_string, other.attribute_string)
357325
])

0 commit comments

Comments
 (0)