def parse_one(self): """ Composer function, parses all metadata that can be parsed from a single dicom. Called by NIMSDicom init, if dicom manufacturer is Siemens. """ mr.parse_standard_mr_tags(self) self.psd_name = self.getelem( self._hdr, 'CsaSeries.MrPhoenixProtocol.tSequenceFileName', str, '').lower().replace('%', '', 1) self.psd_iname = self.getelem(self._hdr, 'SeriesDescription') self.fov_x = self.getelem( self._hdr, 'CsaSeries.MrPhoenixProtocol.sSliceArray.asSlice[0].dPhaseFOV', float) # XXX fov-x = PhaseFOV? self.fov_y = self.getelem( self._hdr, 'CsaSeries.MrPhoenixProtocol.sSliceArray.asSlice[0].dReadoutFOV', float) # XXX fov-y = ReadoutFOV? self.receive_coil_name = self.getelem(self._hdr, 'CsaImage.ImaCoilString') slice_duration = self.getelem(self._hdr, 'CsaImage.SliceMeasurementDuration', float, 0.) self.slice_duration = slice_duration / 1e6 if slice_duration else None self.prescribed_duration = self.getelem( self._hdr, 'CsaSeries.MrPhoenixProtocol.lScanTimeSec') # FIXME self.duration = self.getelem( self._hdr, 'CsaSeries.MrPhoenixProtocol.lTotalScanTimeSec' ) # FIXME: not guaranteed self.acq_no = None # siemens acq # indicates the brain volume instance. varies within one scan. # this is not an ideal solution, as it assumes that EPI timeserise are always stored in mosaic # for mosaics, lReps = prescribed timepoints. non-mosaics and non-timeseries will not have this tag lRep = self.getelem(self._hdr, 'CsaSeries.MrPhoenixProtocol.lRepetitions', int) self.num_timepoints = (lRep + 1) if lRep else None self.dwi_dirs = self.getelem( self._hdr, 'CsaSeries.MrPhoenixProtocol.sDiffusion.lDiffDirections', int, None) if (self.dwi_dirs or 0) > 1: self.is_dwi = True self.num_timepoints = 1 # some siemens MR dicoms are not reconstructable if 'CSAPARALLEL' in self.image_type: self.is_non_image = True if 'POSDISP' in self.image_type: self.is_non_image = True if self.image_type == SIEMENS_TYPE_DIS2D: # had 2 image orientations, and other metdata that varied between dicoms, and is not a localizer. AFNI cannot reconstsruct. # this is a specific hack fix. A more general fix is to check each dicom to see if the orientation matches the first dicom # but checking EVERY dicom isn't very ideal solution (some datasets might have an ENORMOUS number of dicoms) self.is_non_image = True infer_psd_type(self) mr.adjust_fov_acqmat(self) mr.infer_scan_type(self)
def parse_all(self): """ Composer function, parses all metadata that requires all dicoms. Called by NIMSDicom load_data, if dicom manufacturer is Siemens. """ if 'MOSAIC' not in self.image_type: log.debug('SIEMENS SINGLE SLICE DICOM') self.total_num_slices = len(self._dcm_list) # num slices from CSA self.num_slices = self.getelem(self._hdr, 'CsaSeries.MrPhoenixProtocol.sSliceArray.lSize', int, self.getelem(self._hdr, 'CsaSeries.MrPhoenixProtocol.sGroupArray.asGroup[0].nSize', int, None) ) self.num_timepoints = self.total_num_slices / self.num_slices if self.num_slices else None elif 'MOSAIC' in self.image_type: # explicit for readability log.debug('SIEMENS MOSAIC') self.num_slices = self.getelem(self._hdr, 'CsaImage.NumberOfImagesInMosaic', int, self.getelem(self._hdr, 'NumberOfImagesInMosaic', int)) self.num_timepoints = len(self._dcm_list) mosaic_dim = int(self.num_slices ** 0.5) if mosaic_dim ** 2 < self.num_slices: mosaic_dim += 1 self.size = [x / mosaic_dim for x in self.size] self.fov = [x / mosaic_dim for x in self.fov] self.total_num_slices = self.num_slices * self.num_timepoints log.debug('num slices / vol: %s' % str(self.num_slices)) # stringify to be able to log NoneType self.duration = self.num_timepoints * (self.num_averages or 1) * self.tr if self.num_timepoints and self.tr else None # CsaSeries.MrPhoenixProtocol.sSliceArray.ucMode indicates siemens slice order: # Siemens; 1 Ascending, 2 Descending, and 4 Interleaved Ascending. # NIFTI; 1 Ascending, 2 Descending, and 4 Interleaving Descending # Siemens slice order 4 could be nifti slice order 3 or 5 depending on num_slices # - nifti slice order 3: interleave_asc, odd first, odd num slices, interleave INC # - nifti slice order 5: interleave_asc, even first, even num slices, interleave INC 2 self.slice_order = self.getelem(self._hdr, 'CsaSeries.MrPhoenixProtocol.sSliceArray.ucMode', None, 0) if self.slice_order == 4: # don't try to guess if num_slices can't be determined if self.num_slices % 2 != 0: self.slice_order = 3 # interleaved ascending, odd first else: self.slice_order = 5 # interleaved ascending, even first self.num_receivers = len([self._hdr[key] for key in self._hdr if key.endswith('sCoilElementID.tCoilID')]) if self.total_num_slices < MAX_LOC_DCMS: slice_norms = [np.cross(np.matrix(d.get('ImageOrientationPatient')[0:3]), np.matrix(d.get('ImageOrientationPatient')[3:6]))[0] for d in self._dcm_list] norm_diff = [np.abs(np.dot(slice_norms[0], n)).round(2) for n in slice_norms] self.is_localizer = bool(len(set(norm_diff)) > 1) if self.is_dwi: self.bvals = np.array([MetaExtractor(d).get(TAG_BVALUE, 0.) for d in self._dcm_list[0:self.num_slices]]) self.bvecs = np.array([MetaExtractor(d).get(TAG_BVEC, [0., 0., 0.]) for d in self._dcm_list[0:self.num_slices]]).transpose() mr.infer_scan_type(self) # infer scan type again after determining all info
def parse_one(self): """ Composer function, parses all metadata that can be parsed from a single dicom. Called by NIMSDicom init, if dicom manufacturer is Siemens. """ mr.parse_standard_mr_tags(self) self.psd_name = self.getelem(self._hdr, 'CsaSeries.MrPhoenixProtocol.tSequenceFileName', str, '').lower().replace('%', '', 1) self.psd_iname = self.getelem(self._hdr, 'SeriesDescription') self.fov_x = self.getelem(self._hdr, 'CsaSeries.MrPhoenixProtocol.sSliceArray.asSlice[0].dPhaseFOV', float) # XXX fov-x = PhaseFOV? self.fov_y = self.getelem(self._hdr, 'CsaSeries.MrPhoenixProtocol.sSliceArray.asSlice[0].dReadoutFOV', float) # XXX fov-y = ReadoutFOV? self.receive_coil_name = self.getelem(self._hdr, 'CsaImage.ImaCoilString') slice_duration = self.getelem(self._hdr, 'CsaImage.SliceMeasurementDuration', float, 0.) self.slice_duration = slice_duration / 1e6 if slice_duration else None self.prescribed_duration = self.getelem(self._hdr, 'CsaSeries.MrPhoenixProtocol.lScanTimeSec') # FIXME self.duration = self.getelem(self._hdr, 'CsaSeries.MrPhoenixProtocol.lTotalScanTimeSec') # FIXME: not guaranteed self.acq_no = None # siemens acq # indicates the brain volume instance. varies within one scan. # this is not an ideal solution, as it assumes that EPI timeserise are always stored in mosaic # for mosaics, lReps = prescribed timepoints. non-mosaics and non-timeseries will not have this tag lRep = self.getelem(self._hdr, 'CsaSeries.MrPhoenixProtocol.lRepetitions', int) self.num_timepoints = (lRep + 1) if lRep else None self.dwi_dirs = self.getelem(self._hdr, 'CsaSeries.MrPhoenixProtocol.sDiffusion.lDiffDirections', int, None) if (self.dwi_dirs or 0) > 1: self.is_dwi = True self.num_timepoints = 1 # some siemens MR dicoms are not reconstructable if 'CSAPARALLEL' in self.image_type: self.is_non_image = True if 'POSDISP' in self.image_type: self.is_non_image = True if self.image_type == SIEMENS_TYPE_DIS2D: # had 2 image orientations, and other metdata that varied between dicoms, and is not a localizer. AFNI cannot reconstsruct. # this is a specific hack fix. A more general fix is to check each dicom to see if the orientation matches the first dicom # but checking EVERY dicom isn't very ideal solution (some datasets might have an ENORMOUS number of dicoms) self.is_non_image = True infer_psd_type(self) mr.adjust_fov_acqmat(self) mr.infer_scan_type(self)
def parse_one(self): """ Composer function, parses all metadata that can be parsed from a single dicom. Called by NIMSData init, if dicom manufacturer is GE Medical Sytems. """ mr.parse_standard_mr_tags(self) self.psd_name = self.getelem(self._hdr, 'PulseSequenceName', str, '').lower() self.psd_iname = self.getelem(self._hdr, 'InternalPulseSequenceName') self.fov_x, self.fov_y = 2 * [ self.getelem(self._hdr, 'ReconstructionDiameter', float) ] self.receive_coil_name = self.getelem(self._hdr, 'ReceiveCoilName') self.mt_offset_hz = self.getelem(self._hdr, 'OffsetFrequency', float) effective_echo_spacing = self.getelem(self._hdr, 'EffectiveEchoSpacing', float) self.effective_echo_spacing = effective_echo_spacing / 1e6 if effective_echo_spacing else None asset_r = self.getelem(self._hdr, 'AssetRFactors', None, [None, None]) if isinstance( asset_r, unicode ) and '\\' in asset_r: # GE Signa HDxt stores asset as string '1\1' asset_r = map( int, asset_r.split('\\')) # reformat to [1, 1] for consistency elif isinstance(asset_r, float): # asset_r can be single item float asset_r = [None, None] self.phase_encode_undersample, self.slice_encode_undersample = asset_r # some very old Ge systems will output dicoms that don't define Locations in Acquition, or define it in a way # that is weird. It may incorrectly label the value type as OB, but not be able to translate the value, resulting # in the MetaExtractor excluding it from the it's output metadata. self.num_slices = self.getelem(self._hdr, 'LocationsInAcquisition', int) self.total_num_slices = self.getelem(self._hdr, 'ImagesInAcquisition', int) self.num_timepoints = self.getelem(self._hdr, 'NumberOfTemporalPositions', int) # slice check could end up wrong, if both total_num_slices and num_slices are None # could force num_slices and total_num_slices into different ORs, to prevent matching if both are None # thus only when they are both defined, AND not equal, can this test pass if (self.total_num_slices or 1) == (self.num_slices or 0): self.total_num_slices = (self.num_slices or 1) * (self.num_timepoints or 1) log.debug('adjusted total_num_slices from %3d to %3d' % (self.num_slices, self.total_num_slices)) # num_slices == 'old' total_num # some localizer don't have header field to indicate the number of slices # per acquisition. If the total_number of slices is set, and the num_timepoints is 1 # then the number of slices should be equal to total number of slices if not self.num_slices and (self.num_timepoints or 1) == 1: self.num_slices = self.total_num_slices prescribed_duration = (self.tr or 0) * (self.num_timepoints or 0) * ( self.num_averages or 1) # FIXME: only works for fMRI, not anatomical if prescribed_duration != 0: self.prescribed_duration = prescribed_duration self.duration = prescribed_duration else: self.prescribed_duration = None self.duration = None dwi_dirs = self.getelem(self._hdr, 'UserData24{#DTIDiffusionDir.,Release10.0&Above}', float) self.dwi_dirs = int(dwi_dirs) if dwi_dirs else None if self.image_type == GEMS_TYPE_ORIG and (self.dwi_dirs or 0) >= 6: self.is_dwi = True self.num_timepoints = 1 if self.image_type == GEMS_TYPE_DERIVED_RFMT: self.is_non_image = True infer_psd_type(self) mr.adjust_fov_acqmat(self) mr.infer_scan_type(self)
def parse_all(self): """ Composer function, parses all metadata that requires all dicoms. Called by NIMSDicom load_data, if dicom manufacturer is Siemens. """ if 'MOSAIC' not in self.image_type: log.debug('SIEMENS SINGLE SLICE DICOM') self.total_num_slices = len(self._dcm_list) # num slices from CSA self.num_slices = self.getelem( self._hdr, 'CsaSeries.MrPhoenixProtocol.sSliceArray.lSize', int, self.getelem( self._hdr, 'CsaSeries.MrPhoenixProtocol.sGroupArray.asGroup[0].nSize', int, None)) self.num_timepoints = self.total_num_slices / self.num_slices if self.num_slices else None elif 'MOSAIC' in self.image_type: # explicit for readability log.debug('SIEMENS MOSAIC') self.num_slices = self.getelem( self._hdr, 'CsaImage.NumberOfImagesInMosaic', int, self.getelem(self._hdr, 'NumberOfImagesInMosaic', int)) self.num_timepoints = len(self._dcm_list) mosaic_dim = int(self.num_slices**0.5) if mosaic_dim**2 < self.num_slices: mosaic_dim += 1 self.size = [x / mosaic_dim for x in self.size] self.fov = [x / mosaic_dim for x in self.fov] self.total_num_slices = self.num_slices * self.num_timepoints log.debug('num slices / vol: %s' % str(self.num_slices)) # stringify to be able to log NoneType self.duration = self.num_timepoints * ( self.num_averages or 1) * self.tr if self.num_timepoints and self.tr else None # CsaSeries.MrPhoenixProtocol.sSliceArray.ucMode indicates siemens slice order: # Siemens; 1 Ascending, 2 Descending, and 4 Interleaved Ascending. # NIFTI; 1 Ascending, 2 Descending, and 4 Interleaving Descending # Siemens slice order 4 could be nifti slice order 3 or 5 depending on num_slices # - nifti slice order 3: interleave_asc, odd first, odd num slices, interleave INC # - nifti slice order 5: interleave_asc, even first, even num slices, interleave INC 2 self.slice_order = self.getelem( self._hdr, 'CsaSeries.MrPhoenixProtocol.sSliceArray.ucMode', None, 0) if self.slice_order == 4: # don't try to guess if num_slices can't be determined if self.num_slices % 2 != 0: self.slice_order = 3 # interleaved ascending, odd first else: self.slice_order = 5 # interleaved ascending, even first self.num_receivers = len([ self._hdr[key] for key in self._hdr if key.endswith('sCoilElementID.tCoilID') ]) if self.total_num_slices < MAX_LOC_DCMS: slice_norms = [ np.cross(np.matrix(d.get('ImageOrientationPatient')[0:3]), np.matrix(d.get('ImageOrientationPatient')[3:6]))[0] for d in self._dcm_list ] norm_diff = [ np.abs(np.dot(slice_norms[0], n)).round(2) for n in slice_norms ] self.is_localizer = bool(len(set(norm_diff)) > 1) if self.is_dwi: self.bvals = np.array( [MetaExtractor(d).get(TAG_BVALUE, 0.) for d in self._dcm_list[:]]) self.bvecs = np.array([ MetaExtractor(d).get(TAG_BVEC, [0., 0., 0.]) for d in self._dcm_list[:] ]).transpose() mr.infer_scan_type( self) # infer scan type again after determining all info
def parse_one(self): """ Composer function, parses all metadata that can be parsed from a single dicom. Called by NIMSData init, if dicom manufacturer is GE Medical Sytems. """ mr.parse_standard_mr_tags(self) self.psd_name = self.getelem(self._hdr, 'PulseSequenceName', str, '').lower() self.psd_iname = self.getelem(self._hdr, 'InternalPulseSequenceName') self.fov_x, self.fov_y = 2 * [self.getelem(self._hdr, 'ReconstructionDiameter', float)] self.receive_coil_name = self.getelem(self._hdr, 'ReceiveCoilName') self.mt_offset_hz = self.getelem(self._hdr, 'OffsetFrequency', float) effective_echo_spacing = self.getelem(self._hdr, 'EffectiveEchoSpacing', float) self.effective_echo_spacing = effective_echo_spacing / 1e6 if effective_echo_spacing else None asset_r = self.getelem(self._hdr, 'AssetRFactors', None, [None, None]) if isinstance(asset_r, unicode) and '\\' in asset_r: # GE Signa HDxt stores asset as string '1\1' asset_r = map(int, asset_r.split('\\')) # reformat to [1, 1] for consistency elif isinstance(asset_r, float): # asset_r can be single item float asset_r = [None, None] self.phase_encode_undersample, self.slice_encode_undersample = asset_r # some very old Ge systems will output dicoms that don't define Locations in Acquition, or define it in a way # that is weird. It may incorrectly label the value type as OB, but not be able to translate the value, resulting # in the MetaExtractor excluding it from the it's output metadata. self.num_slices = self.getelem(self._hdr, 'LocationsInAcquisition', int) self.total_num_slices = self.getelem(self._hdr, 'ImagesInAcquisition', int) self.num_timepoints = self.getelem(self._hdr, 'NumberOfTemporalPositions', int) # slice check could end up wrong, if both total_num_slices and num_slices are None # could force num_slices and total_num_slices into different ORs, to prevent matching if both are None # thus only when they are both defined, AND not equal, can this test pass if (self.total_num_slices or 1) == (self.num_slices or 0): self.total_num_slices = (self.num_slices or 1) * (self.num_timepoints or 1) log.debug('adjusted total_num_slices from %3d to %3d' % (self.num_slices, self.total_num_slices)) # num_slices == 'old' total_num # some localizer don't have header field to indicate the number of slices # per acquisition. If the total_number of slices is set, and the num_timepoints is 1 # then the number of slices should be equal to total number of slices if not self.num_slices and (self.num_timepoints or 1) == 1: self.num_slices = self.total_num_slices prescribed_duration = (self.tr or 0) * (self.num_timepoints or 0) * (self.num_averages or 1) # FIXME: only works for fMRI, not anatomical if prescribed_duration != 0: self.prescribed_duration = prescribed_duration self.duration = prescribed_duration else: self.prescribed_duration = None self.duration = None dwi_dirs = self.getelem(self._hdr, 'UserData24{#DTIDiffusionDir.,Release10.0&Above}', float) self.dwi_dirs = int(dwi_dirs) if dwi_dirs else None if self.image_type == GEMS_TYPE_ORIG and (self.dwi_dirs or 0) >= 6: self.is_dwi = True self.num_timepoints = 1 if self.image_type == GEMS_TYPE_DERIVED_RFMT: self.is_non_image = True infer_psd_type(self) mr.adjust_fov_acqmat(self) mr.infer_scan_type(self)