def test_serialize(): meta = ismrmrd.Meta(META) xml = meta.serialize() root = ET.fromstring(xml) assert root.tag == 'ismrmrdMeta' children = root.findall('meta') # check that all expected Name-Value pairs can be found for k, v in META.items(): values = None for child in children: name = child.find('name') assert name is not None _values = child.findall('value') assert _values is not None if name.text == k: # found! sort it for later! values = [x.text for x in _values] break assert values is not None # make the META value a list if type(v) != list: v = [v] # sort both lists for simpler comparison v = sorted([str(x) for x in v]) values = sorted(values) assert v == values
def put_next(self, *args): if self.next_gadget is not None: if isinstance(self.next_gadget, Gadget): if len(args) == 3 and not isinstance( args[2], ismrmrd.Meta): #Data with meta data we assume meta = ismrmrd.Meta() meta = ismrmrd.Meta.deserialize(args[2]) new_args = (args[0], args[1], meta) self.next_gadget.process(*new_args) else: self.next_gadget.process(*args) elif isinstance(self.next_gadget, GadgetronPythonMRI.GadgetReference): if len(args) > 3: raise Exception( "Only two or 3 return arguments are currently supported when returning to Gadgetron framework" ) if isinstance(args[0], ismrmrd.AcquisitionHeader): self.next_gadget.return_acquisition( args[0], args[1].astype('complex64')) elif isinstance(args[0], IsmrmrdImageArray): self.next_gadget.return_ismrmrd_image_array(args[0]) elif isinstance(args[0], ismrmrd.ImageHeader): header = args[0] if (args[1].dtype == np.uint16): if len(args) == 3: self.next_gadget.return_image_ushort_attr( header, args[1], args[2].serialize()) else: self.next_gadget.return_image_ushort( header, args[1]) elif (args[1].dtype == np.float32): if len(args) == 3: self.next_gadget.return_image_float_attr( header, args[1], args[2].serialize()) else: self.next_gadget.return_image_float( header, args[1]) else: if len(args) == 3: self.next_gadget.return_image_cplx_attr( header, args[1].astype('complex64'), args[2].serialize()) else: self.next_gadget.return_image_cplx( header, args[1].astype('complex64')) elif len(args[0]) > 0 and isinstance(args[0][0], IsmrmrdReconBit): self.next_gadget.return_recondata(args[0]) else: raise ( "Unsupported types when returning to Gadgetron framework" ) else: raise ("next_gadget is set to unsupported type") else: self.results.append(list(args))
def process_group(group, config, metadata): # 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 RO PE] array data = [acquisition.data for acquisition in group] data = np.stack(data, axis=-1) logging.debug("Raw data is size %s" % (data.shape, )) np.save(debugFolder + "/" + "raw.npy", data) # Fourier Transform data = fft.fftshift(data, axes=(1, 2)) data = fft.ifft2(data) data = fft.ifftshift(data, axes=(1, 2)) # Sum of squares coil combination 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) # Normalize and convert to int16 data *= 32767 / data.max() data = np.around(data) data = data.astype(np.int16) # Remove phase oversampling nRO = np.size(data, 0) data = data[int(nRO / 4):int(nRO * 3 / 4), :] logging.debug("Image without oversampling is size %s" % (data.shape, )) np.save(debugFolder + "/" + "imgCrop.npy", data) # Format as ISMRMRD image data image = ismrmrd.Image.from_array(data, acquisition=group[0]) image.image_index = 1 # Set ISMRMRD Meta Attributes meta = ismrmrd.Meta({ 'DataRole': 'Image', 'ImageProcessingHistory': ['FIRE', 'PYTHON'], 'WindowCenter': '16384', 'WindowWidth': '32768' }) xml = meta.serialize() logging.debug("Image MetaAttributes: %s", xml) logging.debug("Image data has %d elements", image.data.size) image.attribute_string = xml return image
def process_image(image, config, metadata): # Create folder, if necessary if not os.path.exists(debugFolder): os.makedirs(debugFolder) logging.debug("Created folder " + debugFolder + " for debug output files") logging.debug("Incoming image data of type %s", ismrmrd.get_dtype_from_data_type(image.data_type)) # Extract image data itself data = image.data logging.debug("Original image data is size %s" % (data.shape, )) np.save(debugFolder + "/" + "imgOrig.npy", data) # Normalize and convert to int16 data = data.astype(np.float64) data *= 32767 / data.max() data = np.around(data) data = data.astype(np.int16) # Invert image contrast data = 32767 - data data = np.abs(data) data = data.astype(np.int16) np.save(debugFolder + "/" + "imgInverted.npy", data) # Create new MRD instance for the inverted image imageInverted = ismrmrd.Image.from_array(data.transpose()) data_type = imageInverted.data_type np.save(debugFolder + "/" + "imgInvertedMrd.npy", imageInverted.data) # Copy the fixed header information oldHeader = image.getHead() oldHeader.data_type = data_type imageInverted.setHead(oldHeader) # Set ISMRMRD Meta Attributes meta = ismrmrd.Meta({ 'DataRole': 'Image', 'ImageProcessingHistory': ['FIRE', 'PYTHON'], 'WindowCenter': '16384', 'WindowWidth': '32768' }) xml = meta.serialize() logging.debug("Image MetaAttributes: %s", xml) logging.debug("Image data has %d elements", image.data.size) imageInverted.attribute_string = xml return imageInverted
def process_raw(group, connection, config, metadata): # Start timer tic = perf_counter() # 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)) logging.debug("Raw data is size %s" % (data.shape, )) np.save(debugFolder + "/" + "raw.npy", data) # Remove readout oversampling data = fft.ifft(data, axis=2) data = np.delete( data, np.arange(int(data.shape[2] * 1 / 4), int(data.shape[2] * 3 / 4)), 2) data = fft.fft(data, axis=2) logging.debug("Raw data is size after readout oversampling removal %s" % (data.shape, )) np.save(debugFolder + "/" + "rawNoOS.npy", data) # Fourier Transform data = fft.fftshift(data, axes=(1, 2)) data = fft.ifft2(data, axes=(1, 2)) data = fft.ifftshift(data, axes=(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) # Normalize and convert to int16 data *= 32767 / 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) # Measure processing time toc = perf_counter() strProcessTime = "Total processing time: %.2f ms" % ((toc - tic) * 1000.0) logging.info(strProcessTime) # Send this as a text message back to the client connection.send_logging(constants.MRD_LOGGING_INFO, strProcessTime) # Format as ISMRMRD image data imagesOut = [] for phs in range(data.shape[2]): # Create new MRD instance for the processed image # NOTE: from_array() takes input data as [x y z coil], which is # different than the internal representation in the "data" field as # [coil z y x], so we need to transpose tmpImg = ismrmrd.Image.from_array(data[..., phs].transpose()) # 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'] = ['FIRE', 'PYTHON'] tmpMeta['WindowCenter'] = '16384' tmpMeta['WindowWidth'] = '32768' tmpMeta['Keep_image_geometry'] = 1 xml = tmpMeta.serialize() logging.debug("Image MetaAttributes: %s", xml) tmpImg.attribute_string = xml imagesOut.append(tmpImg) # Call process_image() to invert image contrast imagesOut = process_image(imagesOut, connection, config, metadata) return imagesOut
def main(args): dsetsAll = [] for entryPath in GetDicomFiles(args.folder): dsetsAll.append(pydicom.dcmread(entryPath)) # Group by series number uSeriesNum = np.unique([dset.SeriesNumber for dset in dsetsAll]) print("Found %d unique series from %d files in folder %s" % (len(uSeriesNum), len(dsetsAll), args.folder)) print("Creating MRD XML header from file %s" % dsetsAll[0].filename) mrdHead = CreateMrdHeader(dsetsAll[0]) print(mrdHead.toXML()) imgAll = [None] * len(uSeriesNum) for iSer in range(len(uSeriesNum)): dsets = [ dset for dset in dsetsAll if dset.SeriesNumber == uSeriesNum[iSer] ] imgAll[iSer] = [None] * len(dsets) # Sort images by instance number, as they may be read out of order def get_instance_number(item): return item.InstanceNumber dsets = sorted(dsets, key=get_instance_number) # Build a list of unique SliceLocation and TriggerTimes, as the MRD # slice and phase counters index into these uSliceLoc = np.unique([dset.SliceLocation for dset in dsets]) if dsets[0].SliceLocation != uSliceLoc[0]: uSliceLoc = uSliceLoc[::-1] try: # This field may not exist for non-gated sequences uTrigTime = np.unique([dset.TriggerTime for dset in dsets]) if dsets[0].TriggerTime != uTrigTime[0]: uTrigTime = uTrigTime[::-1] except: uTrigTime = np.zeros_like(uSliceLoc) print("Series %d has %d images with %d slices and %d phases" % (uSeriesNum[iSer], len(dsets), len(uSliceLoc), len(uTrigTime))) for iImg in range(len(dsets)): tmpDset = dsets[iImg] # Create new MRD image instance. # NOTE: from_array() takes input data as [x y z coil], but the # pixel_array() output returns data as [row col], so need to transpose. # This will also set the data_type and matrix_size fields. tmpMrdImg = ismrmrd.Image.from_array( tmpDset.pixel_array.transpose()) tmpMeta = ismrmrd.Meta() try: tmpMrdImg.image_type = imtype_map[tmpDset.ImageType[2]] except: print( "Unsupported ImageType %s -- defaulting to IMTYPE_MAGNITUDE" % tmpDset.ImageType[2]) tmpMrdImg.image_type = ismrmrd.IMTYPE_MAGNITUDE tmpMrdImg.field_of_view = (tmpDset.PixelSpacing[0] * tmpDset.Rows, tmpDset.PixelSpacing[1] * tmpDset.Columns, tmpDset.SliceThickness) tmpMrdImg.position = tuple(np.stack(tmpDset.ImagePositionPatient)) tmpMrdImg.read_dir = tuple( np.stack(tmpDset.ImageOrientationPatient[0:3])) tmpMrdImg.phase_dir = tuple( np.stack(tmpDset.ImageOrientationPatient[3:7])) tmpMrdImg.slice_dir = tuple( np.cross(np.stack(tmpDset.ImageOrientationPatient[0:3]), np.stack(tmpDset.ImageOrientationPatient[3:7]))) tmpMrdImg.acquisition_time_stamp = round( (int(tmpDset.AcquisitionTime[0:2]) * 3600 + int(tmpDset.AcquisitionTime[2:4]) * 60 + int(tmpDset.AcquisitionTime[4:6]) + float(tmpDset.AcquisitionTime[6:])) * 1000 / 2.5) try: tmpMrdImg.physiology_time_stamp[0] = round( int(tmpDset.TriggerTime / 2.5)) except: pass try: ImaAbsTablePosition = tmpDset.get_private_item( 0x0019, 0x13, 'SIEMENS MR HEADER').value tmpMrdImg.patient_table_position = ( ctypes.c_float(ImaAbsTablePosition[0]), ctypes.c_float(ImaAbsTablePosition[1]), ctypes.c_float(ImaAbsTablePosition[2])) except: pass tmpMrdImg.image_series_index = uSeriesNum.tolist().index( tmpDset.SeriesNumber) tmpMrdImg.image_index = tmpDset.get('InstanceNumber', 0) tmpMrdImg.slice = uSliceLoc.tolist().index(tmpDset.SliceLocation) try: tmpMrdImg.phase = uTrigTime.tolist().index(tmpDset.TriggerTime) except: pass try: res = re.search(r'(?<=_v).*$', tmpDset.SequenceName) venc = re.search(r'^\d+', res.group(0)) dir = re.search(r'(?<=\d)[^\d]*$', res.group(0)) tmpMeta['FlowVelocity'] = float(venc.group(0)) tmpMeta['FlowDirDisplay'] = venc_dir_map[dir.group(0)] except: pass tmpMeta['SequenceDescription'] = tmpDset.SeriesDescription # Remove pixel data from pydicom class del tmpDset['PixelData'] # Store the complete base64, json-formatted DICOM header so that non-MRD fields can be # recapitulated when generating DICOMs from MRD images tmpMeta['DicomJson'] = base64.b64encode( tmpDset.to_json().encode('utf-8')).decode('utf-8') tmpMrdImg.attribute_string = tmpMeta.serialize() imgAll[iSer][iImg] = tmpMrdImg # Create an MRD file print("Creating MRD file %s with group %s" % (args.outFile, args.outGroup)) mrdDset = ismrmrd.Dataset(args.outFile, args.outGroup) mrdDset._file.require_group(args.outGroup) # Write MRD Header mrdDset.write_xml_header(bytes(mrdHead.toXML(), 'utf-8')) # Write all images for iSer in range(len(imgAll)): for iImg in range(len(imgAll[iSer])): mrdDset.append_image( "images_%d" % imgAll[iSer][iImg].image_series_index, imgAll[iSer][iImg]) mrdDset.close()
def process_image(images, config, metadata): # Create folder, if necessary if not os.path.exists(debugFolder): os.makedirs(debugFolder) logging.debug("Created folder " + debugFolder + " for debug output files") logging.debug("Incoming image data of type %s", ismrmrd.get_dtype_from_data_type(images[0].data_type)) # Display MetaAttributes for first image meta = ismrmrd.Meta.deserialize(images[0].attribute_string) logging.debug("MetaAttributes: %s", ismrmrd.Meta.serialize(meta)) # Optional serialization of ICE MiniHeader if 'IceMiniHead' in meta: logging.debug("IceMiniHead: %s", base64.b64decode(meta['IceMiniHead']).decode('utf-8')) # Extract image data into a 5D array of size [img cha z y x] data = np.stack([img.data for img in images]) head = [img.getHead() for img in images] logging.debug("Original image data is size %s" % (data.shape, )) np.save(debugFolder + "/" + "imgOrig.npy", data) # Normalize and convert to int16 data = data.astype(np.float64) data *= 32767 / data.max() data = np.around(data) data = data.astype(np.int16) # Invert image contrast data = 32767 - data data = np.abs(data) data = data.astype(np.int16) np.save(debugFolder + "/" + "imgInverted.npy", data) # Re-slice back into 2D images imagesOut = [None] * data.shape[0] for iImg in range(data.shape[0]): # Create new MRD instance for the inverted image imagesOut[iImg] = ismrmrd.Image.from_array(data[iImg, ...].transpose()) data_type = imagesOut[iImg].data_type # Copy the fixed header information oldHeader = head[iImg] oldHeader.data_type = data_type imagesOut[iImg].setHead(oldHeader) # Set ISMRMRD Meta Attributes meta = ismrmrd.Meta({ 'DataRole': 'Image', 'ImageProcessingHistory': ['FIRE', 'PYTHON'], 'WindowCenter': '16384', 'WindowWidth': '32768' }) xml = meta.serialize() logging.debug("Image MetaAttributes: %s", xml) logging.debug("Image data has %d elements", imagesOut[iImg].data.size) imagesOut[iImg].attribute_string = xml return imagesOut
def process_raw(group, config, metadata): # 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) # Normalize and convert to int16 data *= 32767 / 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 # NOTE: from_array() takes input data as [x y z coil], which is # different than the internal representation in the "data" field as # [coil z y x], so we need to transpose tmpImg = ismrmrd.Image.from_array(data[..., phs].transpose()) # 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'] = '16384' tmpMeta['WindowWidth'] = '32768' 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
def process_group(group, config, metadata): # 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 RO PE] array data = [acquisition.data for acquisition in group] data = np.stack(data, axis=-1) logging.debug("Raw data is size %s" % (data.shape, )) np.save(debugFolder + "/" + "raw.npy", data) # Remove readout oversampling data = fft.ifft(data, axis=1) data = np.delete( data, np.arange(int(data.shape[1] * 1 / 4), int(data.shape[1] * 3 / 4)), 1) data = fft.fft(data, axis=1) logging.debug("Raw data is size after readout oversampling removal %s" % (data.shape, )) np.save(debugFolder + "/" + "rawNoOS.npy", data) # Fourier Transform data = fft.fftshift(data, axes=(1, 2)) data = fft.ifft2(data, axes=(1, 2)) data = fft.ifftshift(data, axes=(1, 2)) # Sum of squares coil combination 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) # Normalize and convert to int16 data *= 32767 / data.max() data = np.around(data) data = data.astype(np.int16) # Remove readout oversampling offset = int( (data.shape[0] - metadata.encoding[0].reconSpace.matrixSize.x) / 2) data = data[offset:offset + metadata.encoding[0].reconSpace.matrixSize.x, :] # Remove phase oversampling offset = int( (data.shape[1] - 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 image = ismrmrd.Image.from_array(data, acquisition=group[0]) image.image_index = 1 # Set field of view image.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)) # Set ISMRMRD Meta Attributes meta = ismrmrd.Meta({ 'DataRole': 'Image', 'ImageProcessingHistory': ['FIRE', 'PYTHON'], 'WindowCenter': '16384', 'WindowWidth': '32768' }) # Add image orientation directions to MetaAttributes if not already present if meta.get('ImageRowDir') is None: meta['ImageRowDir'] = [ "{:.18f}".format(image.getHead().read_dir[0]), "{:.18f}".format(image.getHead().read_dir[1]), "{:.18f}".format(image.getHead().read_dir[2]) ] if meta.get('ImageColumnDir') is None: meta['ImageColumnDir'] = [ "{:.18f}".format(image.getHead().phase_dir[0]), "{:.18f}".format(image.getHead().phase_dir[1]), "{:.18f}".format(image.getHead().phase_dir[2]) ] xml = meta.serialize() logging.debug("Image MetaAttributes: %s", xml) logging.debug("Image data has %d elements", image.data.size) image.attribute_string = xml return image