forked from kspaceKelvin/python-ismrmrd-server
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathbartfire.py
executable file
·209 lines (169 loc) · 8.72 KB
/
bartfire.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
import ismrmrd
import os
import logging
import traceback
import numpy as np
import ctypes
import constants
import mrdhelper
import tempfile
from bart import bart
# Folder for debug output files
debugFolder = "/tmp/share/debug"
def process(connection, config, metadata):
logging.info("Config: \n%s", config)
# Metadata should be MRD formatted header, but may be a string
# if it failed conversion earlier
try:
# Disabled due to incompatibility between PyXB and Python 3.8:
# https://github.com/pabigot/pyxb/issues/123
# # logging.info("Metadata: \n%s", metadata.toxml('utf-8'))
logging.info("Incoming dataset contains %d encodings", len(metadata.encoding))
logging.info("First encoding is of type '%s', with a matrix size of (%s x %s x %s) and a field of view of (%s x %s x %s)mm^3",
metadata.encoding[0].trajectory,
metadata.encoding[0].encodedSpace.matrixSize.x,
metadata.encoding[0].encodedSpace.matrixSize.y,
metadata.encoding[0].encodedSpace.matrixSize.z,
metadata.encoding[0].encodedSpace.fieldOfView_mm.x,
metadata.encoding[0].encodedSpace.fieldOfView_mm.y,
metadata.encoding[0].encodedSpace.fieldOfView_mm.z)
except:
logging.info("Improperly formatted metadata: \n%s", metadata)
# Continuously parse incoming data parsed from MRD messages
acqGroup = []
try:
for item in connection:
# ----------------------------------------------------------
# Raw k-space data messages
# ----------------------------------------------------------
if isinstance(item, ismrmrd.Acquisition):
# Accumulate all imaging readouts in a group
if (not item.is_flag_set(ismrmrd.ACQ_IS_NOISE_MEASUREMENT) and
not item.is_flag_set(ismrmrd.ACQ_IS_PARALLEL_CALIBRATION) and
not item.is_flag_set(ismrmrd.ACQ_IS_PHASECORR_DATA)):
acqGroup.append(item)
# When this criteria is met, run process_raw() on the accumulated
# data, which returns images that are sent back to the client.
if item.is_flag_set(ismrmrd.ACQ_LAST_IN_SLICE):
logging.info("Processing a group of k-space data")
image = process_raw(acqGroup, config, metadata)
connection.send_image(image)
acqGroup = []
# ----------------------------------------------------------
# Image and waveform data messages are not supported
# ----------------------------------------------------------
elif isinstance(item, ismrmrd.Image):
logging.info("Received an image, but this is not supported -- discarding")
continue
elif isinstance(item, ismrmrd.Waveform):
logging.info("Received a waveform, but this is not supported -- discarding")
continue
elif item is None:
break
else:
logging.error("Unsupported data type %s", type(item).__name__)
# Process any remaining groups of raw or image data. This can
# happen if the trigger condition for these groups are not met.
# This is also a fallback for handling image data, as the last
# image in a series is typically not separately flagged.
if len(acqGroup) > 0:
logging.info("Processing a group of k-space data (untriggered)")
image = process_raw(acqGroup, config, metadata)
connection.send_image(image)
acqGroup = []
except Exception as e:
logging.error(traceback.format_exc())
connection.send_logging(constants.MRD_LOGGING_ERROR, traceback.format_exc())
finally:
connection.send_close()
def process_raw(group, config, metadata):
if len(group) == 0:
return []
# Create folder, if necessary
if not os.path.exists(debugFolder):
os.makedirs(debugFolder)
logging.debug("Created folder " + debugFolder + " for debug output files")
# Format data into single [cha PE RO phs] array
lin = [acquisition.idx.kspace_encode_step_1 for acquisition in group]
phs = [acquisition.idx.phase for acquisition in group]
# Use the zero-padded matrix size
data = np.zeros((group[0].data.shape[0],
metadata.encoding[0].encodedSpace.matrixSize.y,
metadata.encoding[0].encodedSpace.matrixSize.x,
max(phs)+1),
group[0].data.dtype)
rawHead = [None]*(max(phs)+1)
for acq, lin, phs in zip(group, lin, phs):
if (lin < data.shape[1]) and (phs < data.shape[3]):
# TODO: Account for asymmetric echo in a better way
data[:,lin,-acq.data.shape[1]:,phs] = acq.data
# center line of k-space is encoded in user[5]
if (rawHead[phs] is None) or (np.abs(acq.getHead().idx.kspace_encode_step_1 - acq.getHead().idx.user[5]) < np.abs(rawHead[phs].idx.kspace_encode_step_1 - rawHead[phs].idx.user[5])):
rawHead[phs] = acq.getHead()
# Flip matrix in RO/PE to be consistent with ICE
data = np.flip(data, (1, 2))
# Format as [row col phs cha] for BART
data = data.transpose((1, 2, 3, 0))
logging.debug("Raw data is size %s" % (data.shape,))
np.save(debugFolder + "/" + "raw.npy", data)
# Fourier Transform with BART
logging.info("Calling BART FFT")
data = bart(1, 'fft -u -i 3', data)
# Re-format as [cha row col phs]
data = data.transpose((3, 0, 1, 2))
# Sum of squares coil combination
# Data will be [PE RO phs]
data = np.abs(data)
data = np.square(data)
data = np.sum(data, axis=0)
data = np.sqrt(data)
logging.debug("Image data is size %s" % (data.shape,))
np.save(debugFolder + "/" + "img.npy", data)
# Determine max value (12 or 16 bit)
BitsStored = 12
if (mrdhelper.get_userParameterLong_value(metadata, "BitsStored") is not None):
BitsStored = mrdhelper.get_userParameterLong_value(metadata, "BitsStored")
maxVal = 2**BitsStored - 1
# Normalize and convert to int16
data *= maxVal/data.max()
data = np.around(data)
data = data.astype(np.int16)
# Remove readout oversampling
offset = int((data.shape[1] - metadata.encoding[0].reconSpace.matrixSize.x)/2)
data = data[:,offset:offset+metadata.encoding[0].reconSpace.matrixSize.x]
# Remove phase oversampling
offset = int((data.shape[0] - metadata.encoding[0].reconSpace.matrixSize.y)/2)
data = data[offset:offset+metadata.encoding[0].reconSpace.matrixSize.y,:]
logging.debug("Image without oversampling is size %s" % (data.shape,))
np.save(debugFolder + "/" + "imgCrop.npy", data)
# Format as ISMRMRD image data
imagesOut = []
for phs in range(data.shape[2]):
# Create new MRD instance for the processed image
# data has shape [PE RO phs], i.e. [y x].
# from_array() should be called with 'transpose=False' to avoid warnings, and when called
# with this option, can take input as: [cha z y x], [z y x], or [y x]
tmpImg = ismrmrd.Image.from_array(data[...,phs], transpose=False)
# Set the header information
tmpImg.setHead(mrdhelper.update_img_header_from_raw(tmpImg.getHead(), rawHead[phs]))
tmpImg.field_of_view = (ctypes.c_float(metadata.encoding[0].reconSpace.fieldOfView_mm.x),
ctypes.c_float(metadata.encoding[0].reconSpace.fieldOfView_mm.y),
ctypes.c_float(metadata.encoding[0].reconSpace.fieldOfView_mm.z))
tmpImg.image_index = phs
# Set ISMRMRD Meta Attributes
tmpMeta = ismrmrd.Meta()
tmpMeta['DataRole'] = 'Image'
tmpMeta['ImageProcessingHistory'] = ['PYTHON', 'BART']
tmpMeta['WindowCenter'] = str((maxVal+1)/2)
tmpMeta['WindowWidth'] = str((maxVal+1))
tmpMeta['Keep_image_geometry'] = 1
# Add image orientation directions to MetaAttributes if not already present
if tmpMeta.get('ImageRowDir') is None:
tmpMeta['ImageRowDir'] = ["{:.18f}".format(tmpImg.getHead().read_dir[0]), "{:.18f}".format(tmpImg.getHead().read_dir[1]), "{:.18f}".format(tmpImg.getHead().read_dir[2])]
if tmpMeta.get('ImageColumnDir') is None:
tmpMeta['ImageColumnDir'] = ["{:.18f}".format(tmpImg.getHead().phase_dir[0]), "{:.18f}".format(tmpImg.getHead().phase_dir[1]), "{:.18f}".format(tmpImg.getHead().phase_dir[2])]
xml = tmpMeta.serialize()
logging.debug("Image MetaAttributes: %s", xml)
tmpImg.attribute_string = xml
imagesOut.append(tmpImg)
return imagesOut