def read_sdat_spar_pair(sdat_file, spar_file, tag=None): spar_params = read_spar(spar_file) data = read_sdat(sdat_file, spar_params['samples'], spar_params['rows']) if data.ndim < 4: data = data.reshape((1, 1, 1) + data.shape) # Move to right handed frame data = data.conj() # Dwelltime dwelltime = 1.0 / float(spar_params["sample_frequency"]) # Meta meta = spar_to_nmrs_hdrext(spar_params) meta.set_standard_def('OriginalFile', [sdat_file.name]) if tag is not None: meta.set_dim_info(0, tag) # Orientation if spar_params["volume_selection_enable"] == "yes": affine = _philips_orientation(spar_params) else: # Use default affine affine = np.diag(np.array([10000, 10000, 10000, 1])) orientation = NIFTIOrient(affine) return [ nifti_mrs.NIfTI_MRS(data, orientation.Q44, dwelltime, meta), ]
def read_data_list_pair(data_file, list_file, spar_file): df, num_dict, coord_dict, os_dict = _read_list(list_file) sorted_data_dict = _read_data(data_file, df) spar_params = read_spar(spar_file) # Dwelltime dwelltime = 1.0 / float(spar_params["sample_frequency"]) # Orientation affine = _philips_orientation(spar_params) orientation = NIFTIOrient(affine) data_out = [] name_out = [] for data_type in sorted_data_dict: data = sorted_data_dict[data_type] name = data_type # Meta meta = spar_to_nmrs_hdrext(spar_params) meta.set_standard_def('OriginalFile', [data_file.name, list_file.name, spar_file.name]) kept_ind = [] for ii, sha in zip(indicies, data.shape[1:]): if sha > 1: kept_ind.append(ii) out_data = data.squeeze() if len(kept_ind) > 3: raise TooManyDimError( 'Number of additional dimensions > 3.' f' Dimensions are {kept_ind} with shape {out_data.shape[1:]}.' ' NIFTI-MRS can only handle three dynamic dimensions. Unsure how to proceed.' ) unknown_counter = 0 for idx, ki in enumerate(kept_ind): if ki in defaults: meta.set_dim_info(idx, defaults[ki], info=f'data/list dim {ki}') else: meta.set_dim_info(idx, f'DIM_USER_{unknown_counter}', info=f'data/list dim {ki}') unknown_counter += 1 # Insert spatial dimensions out_data = out_data.reshape((1, 1, 1) + out_data.shape) # Apply conjugate out_data = out_data.conj() data_out.append( nifti_mrs.NIfTI_MRS(out_data, orientation.Q44, dwelltime, meta)) name_out.append(name) return data_out, name_out
def _process_mrsi_pfile(pfile): '''Handle MRSI data :param Pfile pfile: Pfile object :return: List of NIFTI MRS data objects :return: List of file name suffixes ''' psd = pfile.hdr.rhi_psdname.decode('utf-8').lower() if psd != 'probe-p': raise UnsupportedPulseSequenceError('Unrecognised sequence, psdname must be "probe-p".') warn('The interpretation of pfile CSI data is poorly tested; rotations or transpositions of the' ' CSI grid could be present. Spec2nii currently lacks a complete set of CSI test data.' ' Please get in touch to help solve this issue!') data = np.transpose(pfile.map.raw_data, [0, 1, 2, 5, 4, 3]) if data.shape[5] == 1: data = data.squeeze(axis=5) # Perform fft def fft_and_shift(x, axis): return np.fft.fftshift(np.fft.fft(x, axis=axis), axes=axis) data = fft_and_shift(data, 0) data = fft_and_shift(data, 1) data = fft_and_shift(data, 2) dwelltime = 1 / pfile.hdr.rhr_spectral_width meta = _populate_metadata(pfile) orientation = NIFTIOrient(_calculate_affine_mrsi(pfile)) return [nifti_mrs.NIfTI_MRS(data, orientation.Q44, dwelltime, meta), ], ['', ]
def _process_svs_pfile(pfile): '''Handle SVS data :param Pfile pfile: Pfile object :return: List of NIFTI MRS data objects :return: List of file name suffixes ''' psd = pfile.hdr.rhi_psdname.decode('utf-8').lower() if psd == 'probe-p': data, meta, dwelltime, fname_suffix = _process_probe_p(pfile) elif psd == 'oslaser': data, meta, dwelltime, fname_suffix = _process_oslaser(pfile) else: raise UnsupportedPulseSequenceError( 'Unrecognised sequence, psdname must be "prob-p" or "oslaser".') orientation = NIFTIOrient(_calculate_affine(pfile)) out_nmrs = [] for dd, mm in zip(data, meta): out_nmrs.append(nifti_mrs.NIfTI_MRS(dd, orientation.Q44, dwelltime, mm)) return out_nmrs, fname_suffix
def read_bruker(args): """ :param args: :return list imageOut: :return list fileoutNames: """ imageOut = [] fileoutNames = [] # for all Bruker datasets compliant all queries for data, properties in yield_bruker(args): orientation = NIFTIOrient(np.reshape(np.array(properties['affine']), (4,4))) imageOut.append( nifti_mrs.NIfTI_MRS(data, orientation.Q44, properties['dwell_s'], nifti_mrs.hdr_ext( properties['SpectrometerFrequency'], properties['ResonantNucleus'] )) ) fileoutNames.append(properties['id']) return imageOut, fileoutNames
def _proc_dataset(d, args): """ Yield data and properties of a single dataset """ # merge 2dseq complex frame group if present if d.is_complex and d.type == '2dseq': d = FrameGroupMerger().merge(d, 'FG_COMPLEX') # prepare the data array if d.is_svs: data = _prep_data_svs(d) elif d.is_mrsi: data = _prep_data_mrsi(d) else: data = d.data # get properties properties = d.to_dict() # Orientation information if d.type == 'fid': orientation = NIFTIOrient(_fid_affine_from_params(d)) else: orientation = NIFTIOrient(np.reshape(np.array(properties['affine']), (4, 4))) # Meta data if d.type == 'fid': meta = _fid_meta(d, dump=args.dump_headers) else: meta = _2dseq_meta(d, dump=args.dump_headers) # Dwelltime - to do resolve this factor of 2 issue if d.type == 'fid': dwelltime = d.dwell_s * 2 else: dwelltime = d.dwell_s * 2 if args.fileout: name = args.fileout + '_' + d.id.rstrip('_') else: name = d.id.rstrip('_') yield data, orientation, dwelltime, meta, name
def process_uih_csi(img, verbose): """Process UIH DICOM MRSI data""" specData = np.frombuffer(img.dcm_data[('5600', '0020')].value, dtype=np.single) specDataCmplx = specData[0::2] + 1j * specData[1::2] warn('The orientation of UIH MRSI is relatively untested.' ' Please contribute data to help fix this!') shape = (img.dcm_data.Columns, img.dcm_data.Rows, int(img.dcm_data.NumberOfFrames), img.dcm_data.SpectroscopyAcquisitionDataColumns) specDataCmplx = specDataCmplx.reshape(shape) # WTC 05/01/21 From the limited test data I have I think there is this transposition. specDataCmplx = np.swapaxes(specDataCmplx, 0, 1) # Extract dicom parameters imageOrientationPatient = img.image_orient_patient.T imagePositionPatient = img.image_position xyzMM = np.asarray(img.voxel_sizes) currNiftiOrientation = dcm_to_nifti_orientation(imageOrientationPatient, imagePositionPatient, xyzMM, specDataCmplx.shape[:3], half_shift=True, verbose=verbose) # UIH specific tweaks Q44 = currNiftiOrientation.Q44 # 1) negate 3rd direction if ('0065', 'ff06') 3rd element is negative if float(img.dcm_data[('0065', 'ff06')].value[2]) < 0.0: Q44[:3, :3] = Q44[:3, :3] @ np.array([[1, 0, 0], [0, 1, 0], [0, 0, -1]]) # 2) A half voxel shift is needed in the third dimension v = np.asarray([0, 0, 0.5]) @ Q44[:3, :3].T Q44[:3, 3] += v currNiftiOrientation = NIFTIOrient(Q44) dwelltime = 1.0 / img.dcm_data.SpectralWidth meta = extractDicomMetadata(img) return specDataCmplx, currNiftiOrientation, dwelltime, meta
def jmrui_mrui(args): '''Process .mrui format files.''' data, header, str_info = read_mrui(args.file) newshape = (1, 1, 1) + data.shape data = data.reshape(newshape) # meta-data dwelltime = header['sampling_interval'] * 1E-3 meta = jmrui_hdr_to_obj_mrui(header, str_info) meta.set_standard_def('OriginalFile', [ args.file.name, ]) if data.ndim > 4: meta.set_dim_info(0, 'DIM_USER_0', info='jMRUI frames') # Read optional affine file if args.affine: affine = np.loadtxt(args.affine) else: tmp = np.array([10000, 10000, 10000, 1]) affine = np.diag(tmp) nifti_orientation = NIFTIOrient(affine) img_out = [ nifti_mrs.NIfTI_MRS(data, nifti_orientation.Q44, dwelltime, meta), ] # File names if args.fileout: fname_out = [ args.fileout, ] else: fname_out = [ args.file.stem, ] # Place in data output format return img_out, fname_out
def dcm_to_nifti_orientation(imageOrientationPatient, imagePositionPatient, xyzMM, data_shape, half_shift=False, verbose=False): """Convert DCM orientation parameters to nifti orientations. :arg numpy.ndarray imageOrientationPatient: DICOM imageOrientationPatient tag. Shape = 2,3. :arg numpy.ndarray imagePositionPatient: DICOM imagePositionPatient tag. :arg numpy.ndarray xyzMM: Array containing the voxel sizes as contained in the nibabel.nicom.dicomwrappers.Wrapper voxel_sizes property. :arg data_shape: Shape of the spatial dimensions (dimensions 0-2). :arg bool half_shift: Apply half voxel shift to the x and y dimension. :arg bool verbose: Print debugging output. :return: NIFTIOrient object. :rtype: NIFTIOrient """ # in style of dcm2niix # 1) calculate Q44 Q44 = nifti_dicom2mat(imageOrientationPatient, imagePositionPatient, xyzMM, verbose=verbose) # 2) calculate nifti quaternion parameters # From github.com/rordenlab/dcm2niix/blob/ # 081c6300d0cf47088f0873cd586c9745498f637a/console/nii_dicom.cpp#L604 _, Q44 = verify_slice_dir(Q44, data_shape, imagePositionPatient, verbose=verbose) Q44[:2, :] *= -1 # 3) If required apply the half-voxel shift in the first two dimensions if half_shift: Q44 = apply_half_voxel_shift(Q44) if verbose: print(f'Final Q44:\n {Q44}') # 4) place in data class for nifti orientation parameters return NIFTIOrient(Q44)
def lcm_raw(args): '''Processing for LCModel .RAW (and .H2O) files. Currently only handles one FID per file. ''' # Read data from file data, header = readLCModelRaw(args.file, conjugate=True) newshape = (1, 1, 1) + data.shape data = data.reshape(newshape) # meta dwelltime = header['dwelltime'] meta = nifti_mrs.hdr_ext(header['centralFrequency'], args.nucleus) meta.set_standard_def('ConversionMethod', f'spec2nii v{spec2nii_ver}') conversion_time = datetime.now().isoformat(sep='T', timespec='milliseconds') meta.set_standard_def('ConversionTime', conversion_time) meta.set_standard_def('OriginalFile', [basename(args.file), ]) # Read optional affine file if args.affine: affine = np.loadtxt(args.affine) else: tmp = np.array([10000, 10000, 10000, 1]) affine = np.diag(tmp) nifti_orientation = NIFTIOrient(affine) img_out = [nifti_mrs.NIfTI_MRS(data, nifti_orientation.Q44, dwelltime, meta), ] # File names if args.fileout: fname_out = [args.fileout, ] else: fname_out = [splitext(basename(args.file))[0], ] # Place in data output format return img_out, fname_out
def text(args): '''Processing for simple ascii formatted columns of data.''' # Read text from file data = np.loadtxt(args.file) data = data[:, 0] + 1j * data[:, 1] newshape = (1, 1, 1) + data.shape data = data.reshape(newshape) # Interpret required arguments (frequency and bandwidth) dwelltime = 1.0 / args.bandwidth meta = nifti_mrs.hdr_ext(args.imagingfreq, args.nucleus) meta.set_standard_def('ConversionMethod', 'spec2nii') conversion_time = datetime.now().isoformat(sep='T', timespec='milliseconds') meta.set_standard_def('ConversionTime', conversion_time) meta.set_standard_def('OriginalFile', [basename(args.file), ]) # Read optional affine file if args.affine: affine = np.loadtxt(args.affine) else: tmp = np.array([10000, 10000, 10000, 1]) affine = np.diag(tmp) nifti_orientation = NIFTIOrient(affine) img_out = [nifti_mrs.NIfTI_MRS(data, nifti_orientation.Q44, dwelltime, meta), ] # File names if args.fileout: fname_out = [args.fileout, ] else: fname_out = [splitext(basename(args.file))[0], ] # Place in data output format return img_out, fname_out
def _process_philips_fid(img, verbose): """Process Philips DICOM FID data""" specData = np.frombuffer(img.dcm_data[('5600', '0020')].value, dtype=np.single) specDataCmplx = specData[0::2] + 1j * specData[1::2] # In the one piece of data I have been provided the data is twice as long as indicated (1 avg) # and the second half is a water reference. spec_points = img.dcm_data.SpectroscopyAcquisitionDataColumns spec_data_main = specDataCmplx[:spec_points] spec_data_ref = specDataCmplx[spec_points:] # 1) Extract dicom parameters defaultaffine = np.diag(np.array([10000, 10000, 10000, 1])) currNiftiOrientation = NIFTIOrient(defaultaffine) dwelltime = 1.0 / img.dcm_data.SpectralWidth meta = _extractDicomMetadata(img) meta_r = _extractDicomMetadata(img, water_suppressed=False) return spec_data_main, spec_data_ref, currNiftiOrientation, dwelltime, meta, meta_r
def read_varian(args): """read_varian -- load a varian fid. Note that this format is very flexible and some rather large assumptions are made. At the moment, this assumes little. :param file: path to the varian .fid directory, containing fid and procpar files. returns img_out, fname_out """ dic, data = v.read(args.file) # extract number of coils number_of_coils = 0 for i in dic['procpar']['rcvrs']['values'][0]: if re.search(i, 'y'): number_of_coils += 1 # number of time points -- probably number_of_time_points = float( dic['procpar']['arraydim']['values'][0]) / number_of_coils if (not number_of_time_points.is_integer()): raise ValueError('Coil reshaping failed') number_of_time_points = int(number_of_time_points) # spectral number of points number_of_spectral_points = int(int(dic['np']) / 2) # reshape newshape = (1, 1, 1, number_of_spectral_points, number_of_coils, number_of_time_points) data = data.transpose() data = np.resize(data, newshape) # extract additional spectral metadata dwelltime = 1.0 / float(dic['procpar']['sw']['values'][0]) imagingfreq = float(dic['procpar']['sfrq']['values'][0]) nucleus = dic['procpar']['tn']['values'][0] # reshape to be in the form '13C' rather than 'C13' nucleus = nucleus[1:] + nucleus[0] try: repitition_time = float(dic['procpar']['tr']['values'][0]) except KeyError: pass try: echotime = float( dic['procpar']['te']['values'][0]) # In ms if 'tis there except KeyError: pass try: echotime = float(dic['procpar']['pw']['values'][0]) + float( dic['procpar']['alfa']['values'][0]) except KeyError: pass try: echotime = float(dic['procpar']['p1']['values'][0]) + float( dic['procpar']['alfa']['values'][0]) except KeyError: pass # Parse 3D localisation sequence_name = dic['procpar']['seqfil']['values'][0] if (sequence_name.count('press') or sequence_name.count('steam')): affine = _varian_orientation_3d(dic) else: affine = np.diag(np.array([10000, 10000, 10000, 1])) # 10 m slab for now.... # TODO: Jack should implement the affine matrix correctly for all sequences orientation = NIFTIOrient(affine) # create object meta = nifti_mrs.hdr_ext(imagingfreq, nucleus) meta.set_standard_def('ConversionMethod', f'spec2nii v{spec2nii_ver}') meta.set_standard_def('EchoTime', echotime) meta.set_standard_def('RepetitionTime', repitition_time) meta.set_standard_def('Manufacturer', 'Varian') meta.set_standard_def('ProtocolName', dic['procpar']['seqfil']['values'][0]) meta.set_standard_def('PatientName', dic['procpar']['comment']['values'][0]) # stuff that is nice to have: try: meta.set_standard_def('SoftwareVersions', dic['procpar']['parver']['values'][0]) meta.set_standard_def('TxCoil', dic['procpar']['rfcoil']['values'][0]) meta.set_standard_def('RxCoil', dic['procpar']['rfcoil']['values'][0]) except KeyError: warnings.warn('Expected standard metadata keying failed') try: meta.set_standard_def('InversionTime', dic['procpar']['ti']['values'][0]) except KeyError: pass try: meta.set_standard_def('ExcitationFlipAngle', dic['procpar']['flip1']['values'][0]) except KeyError: pass conversion_time = datetime.now().isoformat(sep='T', timespec='milliseconds') meta.set_standard_def('ConversionTime', conversion_time) meta.set_standard_def('OriginalFile', [basename(args.file)]) # k-space meta.set_standard_def('kSpace', [False, False, False]) # set tag dimensions meta.set_dim_info(0, "DIM_COIL") meta.set_dim_info(1, args.tag6) # Stuff full headers into user fields if args.dump_headers: meta.set_user_def(key='VarianProcpar', doc='Varian procpar metadata.', value=dic['procpar']) # File names if args.fileout: fname_out = [ args.fileout, ] else: fname_out = [ splitext(basename(args.file))[0], ] return [ nifti_mrs.NIfTI_MRS(data, orientation.Q44, dwelltime, meta), ], fname_out