Пример #1
0
def parse_private_csa_header(dcm_data,
                             public_attr,
                             private_attr,
                             default=None):
    """
    Parses CSA header in cases where value is not defined publicly

    Parameters
    ----------
    dcm_data : pydicom Dataset object
        DICOM metadata
    public_attr : string
        non-private DICOM attribute
    private_attr : string
        private DICOM attribute
    default (optional)
        default value if private_attr not found

    Returns
    -------
    val (default: empty string)
        private attribute value or default
    """
    # TODO: provide mapping to private_attr from public_attr
    from nibabel.nicom import csareader
    import dcmstack.extract as dsextract
    try:
        # TODO: test with attr besides ProtocolName
        for protocolStr in ['MrPhoenixProtocol', 'MrProtocol']:
            if protocolStr in csareader.get_csa_header(dcm_data,
                                                       'series')['tags']:
                break
        csastr = csareader.get_csa_header(
            dcm_data, 'series')['tags'][protocolStr]['items'][0]
        csastr = csastr.replace("### ASCCONV BEGIN", "### ASCCONV BEGIN ### ")
        parsedhdr = dsextract.parse_phoenix_prot(protocolStr, csastr)

        val = parsedhdr[private_attr]
        # if val is a string, it will remove spaces.
        val = val.replace(' ', '')

    except AttributeError:
        # it's a number; just return it:
        return val

    except Exception as e:
        lgr.debug("Failed to parse CSA header: %s", str(e))
        val = default or ""
    return val
Пример #2
0
def get_slice_direction(dcm_file):
    ret_dirs = [("i","i-"),("j","j-"),("k-","k")] 
    dcm_dat = dicom.read_file(dcm_file)
    csa = csareader.get_csa_header(dcm_dat)
    norm = csareader.get_slice_normal(csa)
    max_index = np.argmax(np.abs(norm))
    return ret_dirs[max_index][norm[max_index] > 0]
Пример #3
0
def get_ascii_header(ds):
    """
	Expects a pydicom dataset.
	Uses nibabel's "nicom" module to extract CSA data
	"""
    csa = csar.get_csa_header(ds, 'series')

    return csa['tags']['MrPhoenixProtocol']['items'][0]
Пример #4
0
def identify_integrated_references(img, inst_num):
    '''Heuristics for identifying integrated reference scans in known sequences.
    Sequences handled: CMRR svs_slaserVOI_dkd

    :param img: nibable dicom image

    :return: Ref scan index 0 = not refderence scan, higher integer splits into groups.
    :return: name suffix
    '''

    fullcsa = csar.get_csa_header(img.dcm_data, csa_type='series')
    xprot = parse_buffer(fullcsa['tags']['MrPhoenixProtocol']['items'][0])

    # Handle CMRR DKD sequence
    # https://www.cmrr.umn.edu/spectro/
    # SEMI-LASER (MRM 2011, NMB 2019) Release 2016-12
    if xprot[('tSequenceFileName',)].strip('"').lower() == '%customerseq%\\svs_slaservoi_dkd'\
            and xprot[('sSpecPara', 'lAutoRefScanMode')] == 8.0:
        num_ref = int(xprot[('sSpecPara', 'lAutoRefScanNo')])
        num_dyn = int(xprot[('lAverages', )])
        total_dyn = num_dyn + (num_ref * 4)
        if inst_num <= num_ref:
            # First ecc calibration references
            return 1, '_ecc'
        elif inst_num > num_ref\
                and inst_num <= (num_ref * 2):
            # First quantitation calibration references
            return 2, '_quant'
        elif inst_num <= (total_dyn - num_ref)\
                and inst_num > (total_dyn - (2 * num_ref)):
            # Second ecc calibration references
            return 1, '_ecc'
        elif inst_num <= total_dyn\
                and inst_num > (total_dyn - num_ref):
            # Second quantitation calibration references
            return 2, '_quant'
        else:
            return 0, ''
    else:
        return 0, ''
Пример #5
0
                        action="store_true",
                        default=False)

    inArgs = parser.parse_args()

    if inArgs.debug:
        print "inArgs.display  = " + str(inArgs.display)
        print "inArgs.debug    = " + str(inArgs.debug)
        print "inArgs.verbose  = " + str(inArgs.verbose)

    ds = dicom.read_file(inArgs.in_dcm)
    print ds
    print dir(ds)
    print ds.PatientsName

    csa = csar.get_csa_header(ds, 'b_matrix')
    print csa
#     ascii_header = csa['tags']['MrPhoenixProtocol']['items'][0]
"""
Patient's Name                      PN: 'CENC_WL_WFU_111315'
Patient ID                          LO: 'CENC_WL_WFU_111315'
Issuer of Patient ID                LO: ''
Patient's Birth Date                DA: '19720101'
Patient's Sex                       CS: 'F'
Patient's Age                       AS: '043Y'
Patient's Size                      DS: '1.5748031516667'
Patient's Weight                    DS: '58.967015601'
Body Part Examined                  CS: 'HEAD'
Scanning Sequence                   CS: ['GR', 'IR']
Sequence Variant                    CS: ['SK', 'SP', 'MP']
Scan Options                        CS: 'IR'
Пример #6
0
 def get_csa_header(dcm_file):
     import pydicom
     from nibabel.nicom import csareader as csar
     dcm_data = pydicom.read_file(dcm_file)
     return csar.get_csa_header(dcm_data, 'series')
Пример #7
0
def parse_private_data(dicom_dataset):

    private_data = {
        'is_mosaic': False,
        'data': None
    }

    try:

        manufacturer = dicom_dataset[(0x0008, 0x0070)].value

        if "siemens" in manufacturer.lower():

            try:
                siemens_header = get_csa_header(dicom_dataset)
            except:
                siemens_header = None

            if siemens_header:

                private_data['is_mosaic'] = is_mosaic(siemens_header)

                # Remove an unused key that is composed of random bytes and seems to be used for padding
                # (can't be converted to json easily)
                siemens_header.pop('unused0', None)

                # # For the remember of the entries, check that the values in the 'items' key of the 'tags'
                # # field in the header are of the proper type according to the specified VR for that tag.
                # # Additionally, ignore tags that are of VR SQ, PN, or any of the binary types.
                if siemens_header.get('tags', None):

                    for tag, val in siemens_header['tags'].items():

                        vr = val['vr']

                        if vr in ('OB', 'OD', 'OF', 'OL', 'OW', 'UN', 'SQ', 'PN'):
                            continue

                        sanitized_items = []

                        for item in val['items']:
                            sanitized_items.append(_vr_encoding(vr)(item))

                        siemens_header['tags'][tag]['items'] = sanitized_items

                # Make sure the remaining csa headers are json serializable
                try:

                    _ = json.dumps(siemens_header)

                    private_data['data'] = siemens_header

                except TypeError:

                    pass

        elif ("ge" in manufacturer.lower()) or ("general electric" in manufacturer.lower()):

            ge_dat = dicom_dataset[(0x0025, 0x101B)].value

            ge_priv_data = decode_ge_private_data(ge_dat)

            if ge_priv_data:
                private_data['data'] = ge_priv_data

    except KeyError:

        pass

    except (CSAError, CSAReadError):

        pass

    return private_data
Пример #8
0
    def __init__(self, path):
        """Initialize a Siemens object from a DICOM file or directory.
        If a directory is given, read the first DICOM file in the directory.


        Args:
            path (str): The path to a Siemens SVS DICOM file or directory of
                DICOM files.

        """
        self.path = os.path.realpath(path)
        dcmfile = None
        if os.path.isdir(self.path):
            # find the first dicom file to read meta data from
            files = os.listdir(self.path)
            # exclude any non dicoms
            files = [
                f for f in files
                if (os.path.isfile(os.path.join(self.path, f)) and f.endswith((
                    '.DCM', '.dcm', '.ima', '.IMA')) and not f.startswith('.'))
            ]
            if len(files) == 0:
                raise FileNotFoundError('No DICOM files found')

            self.files = sorted(files)

            dcmfile = os.path.join(self.path, files[0])

            # a single file, change the path
            if len(files) == 1:
                path = os.path.join(self.path, files[0])
                self.path = path

            if not (dcmfile):
                raise FileNotFoundError('No DICOM files found')

        else:
            dcmfile = path

        dcm = dcmwrapper.wrapper_from_file(dcmfile)
        self.csa = csareader.get_csa_header(dcm.dcm_data)

        scalar_fields = [
            'MagneticFieldStrength', 'ImagingFrequency', 'MixingTime',
            'EchoTime', 'RepetitionTime', 'ImaCoilString', 'SequenceName',
            'VoiReadoutFoV', 'VoiPhaseFoV', 'VoiThickness',
            'VoiInPlaneRotation', 'DataPointColumns', 'RealDwellTime',
            'PixelBandwidth', 'ImagedNucleus'
        ]

        for k in scalar_fields:
            self.meta[k] = csareader.get_scalar(self.csa, k)

        self.meta['ImagePositionPatient'] = \
            csareader.get_vector(self.csa, 'ImagePositionPatient', 3)
        self.meta['VoiPosition'] = csareader.get_vector(
            self.csa, 'VoiPosition', 3)
        self.meta['VoiOrientation'] = csareader.get_vector(
            self.csa, 'VoiOrientation', 3)
        self.meta['ImageOrientationPatient'] = \
            csareader.get_vector(self.csa, 'ImageOrientationPatient', 6)
Пример #9
0
    def read_data(self, conj=True):
        """Read the associated fids.
        If the instance was initialized with a directory, the directory is assumed
        to contain a single series in order.

        Dimensions are channel x rep x mega x isis x t
        """

        print('Reading data...')
        if os.path.isfile(self.path):
            dcm = dcmwrapper.wrapper_from_file(self.path)
            data = np.array(_read_fid(dcm), ndmin=5)
            self.dcm_data = dcm.dcm_data

        elif os.path.isdir(self.path):
            # read a directory of DICOMS, each containing one fid
            # the directory must contain more than one file, as this is checked
            # in the class constructor

            channels = []

            # find the instance number of the last dicom to calculate data size
            # this assumes different interleaved acquisitions have the same
            # (0020, 0012) Acquisition Number
            # true for eja sequences
            lastdcmfile = os.path.join(self.path, self.files[-1])
            lastdcm = dcmwrapper.wrapper_from_file(lastdcmfile)
            self.dcm_data = lastdcm.dcm_data
            lastinstance = int(lastdcm.get((0x0020, 0x0013)).value)

            # figure out which channels are on
            csa_series = csareader.get_csa_header(lastdcm.dcm_data, 'series')
            csa_image = csareader.get_csa_header(lastdcm.dcm_data, 'image')
            siemens_hdr = csa_series['tags']['MrPhoenixProtocol']['items'][0]
            m = re.findall(
                r"""sCoilSelectMeas.aRxCoilSelectData\[0\].asList\[(?P<coilnum>\d+)\].sCoilElementID.tElement\t = \t""(?P<coilname>[HENC]+\d+)"""
                "", siemens_hdr)
            channels = dict(m)
            channels = dict(zip(channels.values(), channels.keys()))
            n_channels = len(channels)
            self.channels = channels

            # is the data combined over channels?
            # mri_probedicom reports ucUncombImages, but where is this in the CSA?

            # the first two instances of uncombined eja sequences are single channels
            # TODO: figure out what they are
            # Assume the first match the last two, which are missing the same channel
            n_reps = lastinstance - 2

            is_combined = False
            # TODO: handle channel combined data
            if len(self.files) != (n_reps * (n_channels + 1)):
                # not enough files for uncombined data
                if len(self.files) == lastinstance:
                    warnings.warn('Assuming channels are combined')
                    n_reps = lastinstance
                    n_channels = 1
                    is_combined = True

                else:
                    raise Exception(
                        'Expected n_reps[%d] * (n_channels[%d] + 1 files' %
                        (n_reps, n_channels))

            data = np.zeros(
                (n_channels, n_reps, 1, 1,
                 int(csareader.get_scalar(csa_image, 'DataPointColumns'))),
                dtype=complex)

            for fi in range(len(self.files)):
                dcmfile = os.path.join(self.path, self.files[fi])
                dcm = dcmwrapper.wrapper_from_file(dcmfile)
                csa = csareader.get_csa_header(dcm.dcm_data)
                fid = np.array(_read_fid(dcm), ndmin=5)
                channel = csareader.get_scalar(csa, 'ImaCoilString')
                inst = int(dcm.get((0x0020, 0x0013)).value)

                if not is_combined:
                    if inst <= 2:
                        ri = n_reps - inst
                    else:
                        ri = inst - 2 - 1
                else:
                    ri = inst - 1

                # there are combined coils (HC1-7) in the dicoms?
                # make sure this channel is one that is turned on
                if not is_combined:
                    if channel in channels.keys():
                        ci = int(channels[channel])
                        data[ci, ri, 0, 0, :] = fid
                else:
                    data[0, ri, 0, 0, :] = fid

            print('Read %d acquisitions from %d channels' %
                  (n_reps, n_channels))
            print(channels.keys())
        # take the complex conjugate, which Tarquin seems to expect
        if conj:
            data = np.conj(data)

        # permute data for SPECIAL and MEGA

        if self.get_parameter('SequenceName') in [
                'eja_svs_mpress', 'eja_svs_mslaser'
        ]:
            data_on = data[:, 0:int(n_reps / 2.), 0, ::]
            data_off = data[:, int(n_reps / 2.):n_reps, 0, ::]
            data = np.stack((data_off, data_on), 2)
            #data = np.reshape(data, (n_channels, int(n_reps/2.), 2, 1, self.get_parameter('DataPointColumns')))

        self.data = data
        return (data)
Пример #10
0
 def get_csa_header(dcm_file):
     import pydicom
     from nibabel.nicom import csareader as csar
     dcm_data = pydicom.read_file(dcm_file)
     return csar.get_csa_header(dcm_data, 'series')
Пример #11
0
def dicom_readout_msec(ds, mode='CVN'):
    '''
    dicom_readout_msec(ds, mode='CVN'):

    output the <epireadouttime> in milliseconds. This function is based on Keith's
    matlab utility function dicom_readout_msec.m. The key variable is 'BandwidthPerPixelPhaseEncode'
    This variable is in csaheader. We use nibabel.nicom.csareader.get_csa_header()
    function to read this field.

    Input:
        <ds>: can be
            1. ds structure created by pydicom.dcmread()
            2. a file path or wildcard for a dicom file, e.g, 'MR008-0032.dcm'. When wildcard
                case, make sure only one matched file.
        <mode>: FSL and SPM expect different calculations of <epireadouttime>
            can be
            'FSL':
            'SPM','CVN' (default):
    Output:
        <epireadouttime> in millisecs, is an important variable in KK's func
            preprocessing pipeline to use fieldmap to correct
        <bpppe>: BandwidthPerPixelPhaseEncode in millisecs

    Note
        1. partial fourier does not effect distortion
        2. iPAT is "included" in echo spacing when computed directly from the
            dicom header bpppe, so don't separately account for that
        3. Chris said the regular expression approach might fail in some dataset.
            consider to use other one

    To do:
        * more rigourous testing??
    '''
    from pydicom import dcmread
    from pydicom.dataset import FileDataset
    from nibabel.nicom.csareader import get_csa_header
    from RZutilpy.rzio import matchfiles
    import re

    if isinstance(ds, FileDataset):
        pass
    elif isinstance(ds, str):
        try:
            ds = matchfiles(ds)
            ds = dcmread(ds)
        except:
            print('dicom read failed, double check this file')

    csa = get_csa_header(ds)
    bpppe = csa['tags']['BandwidthPerPixelPhaseEncode']['items'][0]

    # figure out inplane matrix, not that we assume

    # use regular expression might be wrong for some data structure.. be careful
    p = re.compile(r'^(\d{1,4})p\*(\d{1,4})s$')

    matchgroup = p.match(ds[int('0051', 16), int('100b', 16)].value)
    npe = int(matchgroup.group(1))  # step in phase encoding direction

    es = 1 / (bpppe * npe)

    if mode.upper() in ['SPM', 'CVN']:
        epireadouttime = 1000 * npe * es
    elif mode.upper() in ['FSL']:
        epireadouttime = 1000 * (npe - 1) * es

    return epireadouttime, bpppe