コード例 #1
0
ファイル: test_fitting.py プロジェクト: MalteRoehl/suspect
def test_dependencies(fixed_fid_sum):

    data = MRSData(fixed_fid_sum, 5e-4, 123)

    model = {
        "phase0": 0.0,
        "phase1": 0.0,
        "pcr": {
            "amplitude": 1.0,
            "fwhm": {
                "value": 45.0,
                "min": 42.0,
                "max": 55.0,
            },
            "phase": "0",
            "frequency": 0
        },
        "pcr2": {
            "amplitude": 1.0,
            "fwhm": {
                "value": 45.0,
                "min": 42.0,
                "max": 55.0,
            },
            "phase": "0",
            "frequency": "pcr_frequency+200"
        }
    }

    fitting_result = singlet.fit(data, model)

    numpy.testing.assert_allclose(fitting_result["model"]["pcr"]["fwhm"],
                                  50.0,
                                  rtol=1e-2)
    numpy.testing.assert_allclose(fitting_result["model"]["pcr"]["amplitude"],
                                  1.0,
                                  rtol=2e-2)
    numpy.testing.assert_allclose(fitting_result["model"]["pcr"]["frequency"],
                                  0.0,
                                  atol=1e-1)

    numpy.testing.assert_allclose(fitting_result["fit"],
                                  fixed_fid_sum,
                                  atol=0.001)
コード例 #2
0
def load_sdat(sdat_filename, spar_filename=None, spar_encoding=None):
    # if the spar filename is not supplied, assume it is in the same folder as
    # the sdat and only differs in the extension
    if spar_filename is None:
        path, ext = os.path.splitext(sdat_filename)
        # match the capitalisation of the sdat extension
        if ext == ".SDAT":
            spar_filename = path + ".SPAR"
        elif ext == ".sdat":
            spar_filename = path + ".spar"

    with open(spar_filename, 'r', encoding=spar_encoding) as fin:
        parameter_dict = {}
        for line in fin:
            # ignore empty lines and comments starting with !
            if line != "\n" and not line.startswith("!"):
                key, value = map(str.strip, line.split(":", 1))
                if key in spar_types["floats"]:
                    parameter_dict[key] = float(value)
                elif key in spar_types["integers"]:
                    parameter_dict[key] = int(value)
                elif key in spar_types["strings"]:
                    parameter_dict[key] = value
                else:
                    pass
                    #print("{} : {}".format(key, value))

    dt = 1 / parameter_dict["sample_frequency"]

    with open(sdat_filename, 'rb') as fin:
        raw_bytes = fin.read()

    floats = _vax_to_ieee_single_float(raw_bytes)
    data_iter = iter(floats)
    complex_iter = (complex(r, -i) for r, i in zip(data_iter, data_iter))
    raw_data = numpy.fromiter(complex_iter, "complex64")
    raw_data = numpy.reshape(
        raw_data,
        (parameter_dict["rows"], parameter_dict["samples"])).squeeze()
    return MRSData(raw_data,
                   dt,
                   parameter_dict["synthesizer_frequency"] * 1e-6,
                   te=parameter_dict["echo_time"])
コード例 #3
0
ファイル: test_fitting.py プロジェクト: MalteRoehl/suspect
def test_missing_param(fixed_fid):

    data = MRSData(fixed_fid, 5e-4, 123)

    # No width value passed in, to test whether KeyError is raised
    model = {
        "phase0": 0,
        "phase1": 0,
        "pcr": {
            "amplitude": 1,
            "fwhm": {
                # "value": 45,
                "min": 42,
                "max": 55,
            },
            "phase": "0",
            "frequency": 0
        }
    }
    with pytest.raises(KeyError):
        fitting_result = singlet.fit(data, model)
コード例 #4
0
ファイル: test_fitting.py プロジェクト: MalteRoehl/suspect
def test_missing_global_phase(fixed_fid):

    data = MRSData(fixed_fid, 5e-4, 123)

    # None value supplied for phase0 and phase1, to test whether TypeError is raised
    model = {
        "phase0": None,
        "phase1": None,
        "pcr": {
            "amplitude": 1.0,
            "fwhm": {
                "value": 45.0,
                "min": 42.0,
                "max": 55.0,
            },
            "phase": "0",
            "frequency": 0.0
        }
    }
    with pytest.raises(TypeError):
        fitting_result = singlet.fit(data, model)
コード例 #5
0
ファイル: test_fitting.py プロジェクト: MalteRoehl/suspect
def test_bad_param(fixed_fid):

    data = MRSData(fixed_fid, 5e-4, 123)

    # invalid key added to width dict, to test whether KeyError is raised
    model = {
        "phase0": 0.0,
        "phase1": 0.0,
        "pcr": {
            "amplitude": 1.0,
            "fwhm": {
                "value": 45.0,
                "min": 42.0,
                "max": 55.0,
                "avg": 47  # this is the bad key
            },
            "phase": "0",
            "frequency": 0.0
        }
    }
    with pytest.raises(KeyError):
        fitting_result = singlet.fit(data, model)
コード例 #6
0
def prepare_pfile_advanced(pfile):
    """
    Transform a P-file containing advanced MRS data into an MRSData object.

    Despite the name, this is the simplest P-file preparation option as it does
    nothing to the data apart from unpacking it into an ndarray and adding the
    necessary Suspect metadata.

    Parameters
    ----------
    pfile
        P-file loaded with GE's Orchestra for Python library.

    Returns
    -------
    MRSData
        The raw FID data loaded into a Suspect MRSData object.
    """
    header = pfile.Header()
    metadata = pfile.MetaData()

    num_echoes = int(header["rdb_hdr_rec"]["rdb_hdr_nechoes"])

    # when we load the data from the pfile, we do it echo by echo
    # the order of the other axes is initially what is returned from pfile
    # we will reshape and reorder it afterwards
    raw_data = numpy.zeros((num_echoes, metadata["acquiredXRes"],
                            metadata["acquiredYRes"], metadata["channels"]),
                           dtype=numpy.complex)

    for i in range(num_echoes):
        raw_data[i] = pfile.KSpace(0, i)

    # reorganise the data to put channels and ADC after the averages/phase encodes
    # then squeeze to remove any size 1 axes (e.g. echoes or channels)
    raw_data = raw_data.transpose((0, 2, 3, 1)).squeeze()

    return MRSData(raw_data, **extract_header_parameters(header))
コード例 #7
0
ファイル: dicom.py プロジェクト: saikm/suspect
def load_dicom(filename):
    """
    Load a file in the DICOM Magnetic Resonance Spectroscopy format
    (SOP 1.2.840.10008.5.1.4.1.1.4.2)

    Parameters
    ----------
    filename : str
        The name of the file to load

    Returns
    -------
    MRSData
        The loaded data from the file
    """
    dataset = pydicom.dicomio.read_file(filename)

    sw = dataset[0x0018, 0x9052].value
    dt = 1.0 / sw

    f0 = dataset[0x0018, 0x9098].value

    ppm0 = dataset[0x0018, 0x9053].value

    rows = dataset[0x0028, 0x0010].value
    cols = dataset[0x0028, 0x0011].value
    frames = dataset[0x0028, 0x0008].value
    num_second_spectral = dataset[0x0028, 0x9001].value
    num_points = dataset[0x0028, 0x9002].value

    data_shape = [frames, rows, cols, num_second_spectral, num_points]

    # turn the data into a numpy array
    data_iter = iter(dataset[0x5600, 0x0020])
    data = complex_array_from_iter(data_iter, shape=data_shape, chirality=-1)

    return MRSData(data, dt, f0, ppm0=ppm0)
コード例 #8
0
def load_siemens_dicom(filename):
    """Imports a file in the Siemens .IMA format.

    Parameters
    ----------
    filename : str
        The name of the file to import

    """
    # the .IMA format is a DICOM standard, unfortunately most of the information is contained inside a private and very
    # complicated header with its own data storage format, we have to get that information out along with the data
    # start by reading in the DICOM file completely
    dataset = pydicom.dicomio.read_file(filename)
    # now look through the tags (0029, 00xx) to work out which xx refers to the csa header
    # xx seems to start at 10 for Siemens
    xx = 0x0010
    header_index = 0
    while (0x0029, xx) in dataset:
        if dataset[0x0029, xx].value == "SIEMENS CSA HEADER":
            header_index = xx
        xx += 1
    # check that we have found the header
    if header_index == 0:
        raise KeyError("Could not find header index")
    # now we know which tag contains the CSA image header info: (0029, xx10)
    csa_header_bytes = dataset[0x0029, 0x0100 * header_index + 0x0010].value
    csa_header = read_csa_header(csa_header_bytes)
    # for key, value in csa_header.items():
    #    print("%s : %s" % (str(key), str(value)))
    # we can also get the series header info: (0029, xx20), but this seems to be mostly pretty boring

    # now we can work out the shape of the data (slices, rows, columns, fid_points)
    data_shape = (
        csa_header["SpectroscopyAcquisitionOut-of-planePhaseSteps"],
        csa_header["Rows"],
        csa_header["Columns"],
        csa_header["DataPointColumns"],
    )

    # now look through the tags (0029, 00xx) to work out which xx refers to the csa header
    # xx seems to start at 10 for Siemens
    xx = 0x0010
    data_index = 0
    while (0x7fe1, xx) in dataset:
        if dataset[0x7fe1, xx].value == "SIEMENS CSA NON-IMAGE":
            data_index = xx
        xx += 1
    # check that we have found the data
    if data_index == 0:
        raise KeyError("Could not find data index")
    # extract the actual data bytes
    csa_data_bytes = dataset[0x7fe1, 0x0100 * data_index + 0x0010].value
    # the data is stored as a list of 4 byte floats in (real, imaginary) pairs
    data_floats = struct.unpack("<%df" % (len(csa_data_bytes) / 4),
                                csa_data_bytes)
    complex_data = complex_array_from_iter(iter(data_floats),
                                           length=len(data_floats) // 2,
                                           shape=data_shape)

    in_plane_rot = csa_header["VoiInPlaneRotation"]
    x_vector = numpy.array([-1, 0, 0])
    normal_vector = numpy.array(csa_header["VoiOrientation"])
    orthogonal_x = x_vector - numpy.dot(x_vector,
                                        normal_vector) * normal_vector
    orthonormal_x = orthogonal_x / numpy.linalg.norm(orthogonal_x)
    rot_matrix = rotation_matrix(in_plane_rot, normal_vector)
    row_vector = numpy.dot(rot_matrix, orthonormal_x)
    column_vector = numpy.cross(row_vector, normal_vector)
    voxel_size = (*csa_header["PixelSpacing"], csa_header["SliceThickness"])
    transform = transformation_matrix(row_vector, column_vector,
                                      csa_header["VoiPosition"], voxel_size)

    voi_size = [
        csa_header["VoiReadoutFoV"], csa_header["VoiPhaseFoV"],
        csa_header["VoiThickness"]
    ]

    metadata = {"voi_size": voi_size}

    return MRSData(complex_data,
                   csa_header["RealDwellTime"] * 1e-9,
                   csa_header["ImagingFrequency"],
                   te=csa_header["EchoTime"],
                   tr=csa_header["RepetitionTime"],
                   transform=transform,
                   metadata=metadata)
コード例 #9
0
ファイル: philips.py プロジェクト: oscarjalnefjord/suspect
def load_sdat(sdat_filename, spar_filename=None, spar_encoding=None):
    # if the spar filename is not supplied, assume it is in the same folder as
    # the sdat and only differs in the extension
    if spar_filename is None:
        path, ext = os.path.splitext(sdat_filename)
        # match the capitalisation of the sdat extension
        if ext == ".SDAT":
            spar_filename = path + ".SPAR"
        elif ext == ".sdat":
            spar_filename = path + ".spar"

    with open(spar_filename, 'r', encoding=spar_encoding) as fin:
        parameter_dict = {}
        for line in fin:
            # ignore empty lines and comments starting with !
            if line != "\n" and not line.startswith("!"):
                key, value = map(str.strip, line.split(":", 1))
                if key in spar_types["floats"]:
                    parameter_dict[key] = float(value)
                elif key in spar_types["integers"]:
                    parameter_dict[key] = int(value)
                elif key in spar_types["strings"]:
                    parameter_dict[key] = value
                else:
                    pass
                    #print("{} : {}".format(key, value))

    dt = 1 / parameter_dict["sample_frequency"]

    with open(sdat_filename, 'rb') as fin:
        raw_bytes = fin.read()

    floats = _vax_to_ieee_single_float(raw_bytes)
    data_iter = iter(floats)
    complex_iter = (complex(r, -i) for r, i in zip(data_iter, data_iter))
    raw_data = numpy.fromiter(complex_iter, "complex64")
    if parameter_dict["rows"] > 1:
        raw_data = numpy.reshape(
            raw_data, (parameter_dict["rows"] //
                       parameter_dict["nr_of_phase_encoding_profiles_ky"],
                       parameter_dict["nr_of_phase_encoding_profiles_ky"],
                       parameter_dict["nr_of_slices_for_multislice"],
                       parameter_dict["samples"])).squeeze()
    else:
        raw_data = numpy.reshape(
            raw_data,
            (parameter_dict["rows"], parameter_dict["samples"])).squeeze()

    # calculate transformation matrix
    voxel_size = numpy.array([
        parameter_dict["lr_size"], parameter_dict["ap_size"],
        parameter_dict["cc_size"]
    ])
    position_vector = numpy.array([
        parameter_dict["lr_off_center"], parameter_dict["ap_off_center"],
        parameter_dict["cc_off_center"]
    ])

    A = numpy.eye(3)
    for a, ang in enumerate(
        ["lr_angulation", "ap_angulation", "cc_angulation"]):
        axis = numpy.zeros(3)
        axis[a] = 1
        A = A @ rotation_matrix(parameter_dict[ang] / 180 * numpy.pi, axis)
    e1 = A[:, 0]
    e1 = e1 / numpy.linalg.norm(e1)
    e2 = A[:, 1]
    e2 = e2 / numpy.linalg.norm(e2)

    transform = transformation_matrix(e1, e2, position_vector, voxel_size)

    return MRSData(raw_data,
                   dt,
                   parameter_dict["synthesizer_frequency"] * 1e-6,
                   te=parameter_dict["echo_time"],
                   tr=parameter_dict["repetition_time"],
                   transform=transform)
コード例 #10
0
ファイル: rda.py プロジェクト: oscarjalnefjord/suspect
def load_rda(filename):
    header_dict = {}
    with open(filename, 'rb') as fin:
        header_line = fin.readline().strip()
        if header_line != b">>> Begin of header <<<":
            raise Exception("Error reading file {} as a .rda".format(filename))
        header_line = fin.readline().strip().decode('windows-1252')
        while header_line != ">>> End of header <<<":
            key, value = map(str.strip, header_line.split(":", 1))
            if key in rda_types["strings"]:
                header_dict[key] = value
            elif key in rda_types["integers"]:
                header_dict[key] = int(value)
            elif key in rda_types["floats"]:
                header_dict[key] = float(value)
            elif "[" in key and "]" in key:
                # could be a dict or a list
                key, index = re.split(r"\]|\[", key)[0:2]
                if key in rda_types["dictionaries"]:
                    if key not in header_dict:
                        header_dict[key] = {}
                    header_dict[key][index] = value
                else:
                    # not a dictionary, must be a list
                    if key in rda_types["float_arrays"]:
                        value = float(value)
                    elif key in rda_types["integer_arrays"]:
                        value = int(value)
                    index = int(index)
                    # make sure there is a list in the header_dict, with enough entries
                    if not key in header_dict:
                        header_dict[key] = []
                    while len(header_dict[key]) <= index:
                        header_dict[key].append(0)
                    header_dict[key][index] = value
            header_line = fin.readline().strip().decode('windows-1252')
        # now we can read the data
        data = fin.read()

    # the shape of the data in slice, column, row, time format
    data_shape = header_dict["CSIMatrixSize"][::-1]
    data_shape.append(header_dict["VectorSize"])
    data_shape = numpy.array(data_shape)
    data_size = numpy.prod(
        data_shape) * 16  # each data point is a complex double, 16 bytes
    if data_size != len(data):
        raise ValueError(
            "Error reading file {}: expected {} bytes of data, got {}".format(
                filename, data_size, len(data)))

    # unpack the data into complex numbers
    data_as_floats = struct.unpack("<{}d".format(numpy.prod(data_shape) * 2),
                                   data)
    float_iter = iter(data_as_floats)
    complex_iter = (complex(r, i) for r, i in zip(float_iter, float_iter))
    complex_data = numpy.fromiter(complex_iter, "complex64",
                                  int(numpy.prod(data_shape)))
    complex_data = numpy.reshape(complex_data, data_shape).squeeze()

    # some .rda files have a misnamed field, correct this here
    if "VOIReadoutFOV" not in header_dict:
        if "VOIReadoutVOV" in header_dict:
            header_dict["VOIReadoutFOV"] = header_dict.pop("VOIReadoutVOV")

    # combine positional elements in the header
    voi_size = (header_dict["VOIReadoutFOV"], header_dict["VOIPhaseFOV"],
                header_dict["VOIThickness"])
    voi_center = (header_dict["VOIPositionSag"], header_dict["VOIPositionCor"],
                  header_dict["VOIPositionTra"])
    voxel_size = (header_dict["PixelSpacingCol"],
                  header_dict["PixelSpacingRow"],
                  header_dict["PixelSpacing3D"])

    x_vector = numpy.array(header_dict["RowVector"])
    y_vector = numpy.array(header_dict["ColumnVector"])

    to_scanner = transformation_matrix(x_vector, y_vector,
                                       numpy.array(voi_center), voxel_size)

    # put useful components from the header in the metadata
    metadata = {
        "voi_size": voi_size,
        "position": voi_center,
        "voxel_size": voxel_size,
        "protocol": header_dict["ProtocolName"],
        "to_scanner": to_scanner,
        "from_scanner": numpy.linalg.inv(to_scanner)
    }

    return MRSData(complex_data,
                   header_dict["DwellTime"] * 1e-6,
                   header_dict["MRFrequency"],
                   te=header_dict["TE"],
                   tr=header_dict["TR"],
                   transform=to_scanner,
                   metadata=metadata)
コード例 #11
0
def prepare_pfile_svs(pfile):
    """
    Transform a P-file containing SVS MRS data into an MRSData object.

    Single voxel spectroscopy on GE is the most complicated case to process.

    Parameters
    ----------
    pfile
        P-file loaded with GE's Orchestra for Python library.

    Returns
    -------
        The raw FID data loaded into a Suspect MRSData object.
    """
    header = pfile.Header()
    metadata = pfile.MetaData()

    num_echoes = int(header["rdb_hdr_rec"]["rdb_hdr_nechoes"])
    num_frames = int(header["rdb_hdr_rec"]["rdb_hdr_nframes"])
    num_averages = int(header["rdb_hdr_rec"]["rdb_hdr_navs"])

    # read the number of (water-suppressed) spectra acquired
    data_frames = int(header["rdb_hdr_rec"]["rdb_hdr_user4"])
    # in some cases GE automatically combines blocks of num_averages on the
    # scanner depending on the value of the no_add parameter
    # I think that this is stored in the lsb of the below header parameter
    no_add = header["rdb_hdr_image"]["user24"] % 2 == 1
    if no_add is not True:
        data_frames //= num_averages
    # the header parameter with the number of reference frames is not reliable:
    # sometimes it ignores the value of no_add so we just assume that all non
    # data frames are ref frames
    ref_frames = num_frames - data_frames

    # when we load the data from the pfile, we do it echo by echo
    # the order of the other axes is initially what is returned from pfile
    # we will reshape and reorder it afterwards
    raw_data = numpy.zeros((num_echoes, metadata["acquiredXRes"],
                            metadata["acquiredYRes"], metadata["channels"]),
                           dtype=numpy.complex)

    for i in range(num_echoes):
        raw_data[i] = pfile.KSpace(0, i)

    # if no_add was set, every other average will have opposite sign and has to
    # be flipped
    if no_add:
        # TODO we assume that the flipping happens within a set of num_averages
        # TODO and not outside, should check this is the case
        flip_array = numpy.array(-1)**numpy.arange(num_averages)
        flip_array = numpy.tile(flip_array, int(num_frames / num_averages))
        flip_array = flip_array[numpy.newaxis, numpy.newaxis, :, numpy.newaxis]
        raw_data *= flip_array

    # reorganise the data to be echoes, averages, channels and ADC
    raw_data = raw_data.transpose((0, 2, 3, 1))

    # we have the acquired data, now put together the desired metadata
    header_params = extract_header_parameters(header)

    mrs_data = MRSData(raw_data, **header_params)

    #print(ref_frames)
    #print(metadata)
    #print(mrs_data.shape)

    wref = mrs_data[:, :ref_frames].reshape(
        -1, metadata["channels"], metadata["acquiredXRes"]).squeeze()
    data = mrs_data[:, ref_frames:].squeeze()

    return data, wref
コード例 #12
0
ファイル: bruker.py プロジェクト: saikm/suspect
def load_svs_bruker(fid_filename, acqp_filename=None, method_filename=None):
    """
    Load SVS data in the Bruker format

    Parameters
    ----------
    fid_filename: str
        The location of the file containing the fid data to load
    acqp_filename: str, optional
        The location of the acquisition parameters file. If not provided
        a file named acqp in the same directory as fid_filename will be
        used or an error raised if no such file exists.
    method_filename: str, optional
        The location of the method file. If not provided a file named
        method in the same directory as fid_filename will be used, or an
        error raised if no such file exists.

    Returns
    -------
    data: MRSData
        Data read from the file
    """

    if acqp_filename is None:
        path, fid_file = os.path.split(fid_filename)
        acqp_filename = os.path.join(path, "acqp")
    if not os.path.isfile(acqp_filename):
        raise FileNotFoundError(
            "No acqp file found at {0}".format(acqp_filename))

    if method_filename is None:
        path, fid_file = os.path.split(fid_filename)
        method_filename = os.path.join(path, "method")
    if not os.path.isfile(method_filename):
        raise FileNotFoundError("No method file found")

    # read the data out of the method file
    with open(method_filename) as fin:
        method_string = fin.read()
    dwell_time_string = re.search(r"\$PVM_DigDw=\d+\.?\d*",
                                  method_string).group()
    # dwell time is in ms, convert to s
    dt = float(dwell_time_string.split("=")[1]) * 1e-3
    digitiser_delay_string = re.search(r"\$PVM_DigShift=\d+",
                                       method_string).group()
    digitiser_delay = int(digitiser_delay_string.split("=")[1])

    # read the data out of the acqp file
    with open(acqp_filename) as fin:
        acqp_string = fin.read()
    f0_string = re.search(r"\$BF1=\d+\.\d*", acqp_string).group()
    f0 = float(f0_string.split("=")[1])

    # read the fid data
    with open(fid_filename, 'rb') as fin:
        fid_bytes = fin.read()
    # bruker data is stored as real/imaginary int 32 pairs
    num_ints = len(fid_bytes) // 4
    fid_ints = struct.unpack("{0}i".format(num_ints), fid_bytes)
    data = complex_array_from_iter(iter(fid_ints), num_ints // 2, chirality=-1)

    return MRSData(data[digitiser_delay:], dt, f0)
コード例 #13
0
ファイル: siemens.py プロジェクト: oscarjalnefjord/suspect
def load_siemens_dicom(filename):
    """Imports a file in the Siemens .IMA format.

    Parameters
    ----------
    filename : str
        The name of the file to import

    """
    # the .IMA format is a DICOM standard, unfortunately most of the information is contained inside a private and very
    # complicated header with its own data storage format, we have to get that information out along with the data
    # start by reading in the DICOM file completely
    dataset = pydicom.dicomio.read_file(filename)
    # now look through the tags (0029, 00xx) to work out which xx refers to the csa header
    # xx seems to start at 10 for Siemens
    xx = 0x0010
    header_index = 0
    while (0x0029, xx) in dataset:
        if dataset[0x0029, xx].value == "SIEMENS CSA HEADER":
            header_index = xx
        xx += 1
    # check that we have found the header
    if header_index == 0:
        raise KeyError("Could not find header index")
    # now we know which tag contains the CSA image header info: (0029, xx10)
    csa_header_bytes = dataset[0x0029, 0x0100 * header_index + 0x0010].value
    csa_header = read_csa_header(csa_header_bytes)
    # for key, value in csa_header.items():
    #    print("%s : %s" % (str(key), str(value)))
    # we can also get the series header info: (0029, xx20), but this seems to be mostly pretty boring

    # now we can work out the shape of the data (slices, rows, columns, fid_points)
    data_shape = (
        csa_header["SpectroscopyAcquisitionOut-of-planePhaseSteps"],
        csa_header["Rows"],
        csa_header["Columns"],
        csa_header["DataPointColumns"],
    )

    # now look through the tags (0029, 00xx) to work out which xx refers to the csa header
    # xx seems to start at 10 for Siemens
    xx = 0x0010
    data_index = 0
    while (0x7fe1, xx) in dataset:
        if dataset[0x7fe1, xx].value == "SIEMENS CSA NON-IMAGE":
            data_index = xx
        xx += 1
    # check that we have found the data
    if data_index == 0:
        raise KeyError("Could not find data index")
    # extract the actual data bytes
    csa_data_bytes = dataset[0x7fe1, 0x0100 * data_index + 0x0010].value
    # the data is stored as a list of 4 byte floats in (real, imaginary) pairs
    data_floats = struct.unpack("<%df" % (len(csa_data_bytes) / 4),
                                csa_data_bytes)

    # a bug report (#143) has been submitted that for at least one .IMA dataset
    # created with an old Siemens VB17 WIP, the data_shape worked out above
    # does not match the actual size of the data because the
    # Out-of-planePhaseSteps value is not the number of slices. Assuming this
    # is a rare situation that is unlikely to happen often, the simple solution
    # is simply to check the size matches here, and if not then use the size
    # of data available as the shape
    available_points = len(data_floats) // 2
    if numpy.prod(data_shape) != available_points:
        data_shape = (available_points, )
        warnings.warn("The calculated data shape for this file {} does not "
                      "match the size of data contained in the file {}. "
                      "Therefore the returned data shape from this function "
                      "will simply be ({},), any reshaping must be done by "
                      "the user. If you need help with this or believe this "
                      "has occured in error, please raise an issue at"
                      "https://github.com/openmrslab/suspect/issues.")

    complex_data = complex_array_from_iter(iter(data_floats),
                                           length=len(data_floats) // 2,
                                           shape=data_shape)

    in_plane_rot = csa_header["VoiInPlaneRotation"]
    x_vector = numpy.array([-1, 0, 0])
    normal_vector = numpy.array(csa_header["VoiOrientation"])
    orthogonal_x = x_vector - numpy.dot(x_vector,
                                        normal_vector) * normal_vector
    orthonormal_x = orthogonal_x / numpy.linalg.norm(orthogonal_x)
    rot_matrix = rotation_matrix(in_plane_rot, normal_vector)
    row_vector = numpy.dot(rot_matrix, orthonormal_x)
    column_vector = numpy.cross(row_vector, normal_vector)
    voxel_size = (*csa_header["PixelSpacing"], csa_header["SliceThickness"])
    transform = transformation_matrix(row_vector, column_vector,
                                      csa_header["VoiPosition"], voxel_size)

    voi_size = [
        csa_header["VoiReadoutFoV"], csa_header["VoiPhaseFoV"],
        csa_header["VoiThickness"]
    ]

    metadata = {"voi_size": voi_size}

    return MRSData(complex_data,
                   csa_header["RealDwellTime"] * 1e-9,
                   csa_header["ImagingFrequency"],
                   te=csa_header["EchoTime"],
                   tr=csa_header["RepetitionTime"],
                   transform=transform,
                   metadata=metadata)
コード例 #14
0
def load_siemens_dicom(filename):
    """
    Imports a file in the Siemens .IMA format.
    :param filename: The filename of the file to import
    """
    # the .IMA format is a DICOM standard, unfortunately most of the information is contained inside a private and very
    # complicated header with its own data storage format, we have to get that information out along with the data
    # start by reading in the DICOM file completely
    dataset = pydicom.dicomio.read_file(filename)
    # now look through the tags (0029, 00xx) to work out which xx refers to the csa header
    # xx seems to start at 10 for Siemens
    xx = 0x0010
    header_index = 0
    while (0x0029, xx) in dataset:
        if dataset[0x0029, xx].value == "SIEMENS CSA HEADER":
            header_index = xx
        xx += 1
    # check that we have found the header
    if header_index == 0:
        raise KeyError("Could not find header index")
    # now we know which tag contains the CSA image header info: (0029, xx10)
    csa_header_bytes = dataset[0x0029, 0x0100 * header_index + 0x0010].value
    csa_header = read_csa_header(csa_header_bytes)
    #for key, value in csa_header.items():
    #    print("%s : %s" % (str(key), str(value)))
    # we can also get the series header info: (0029, xx20), but this seems to be mostly pretty boring

    # now we can work out the shape of the data (slices, rows, columns, fid_points)
    data_shape = (
        csa_header["SpectroscopyAcquisitionOut-of-planePhaseSteps"],
        csa_header["Rows"],
        csa_header["Columns"],
        csa_header["DataPointColumns"],
    )

    # now look through the tags (0029, 00xx) to work out which xx refers to the csa header
    # xx seems to start at 10 for Siemens
    xx = 0x0010
    data_index = 0
    while (0x7fe1, xx) in dataset:
        if dataset[0x7fe1, xx].value == "SIEMENS CSA NON-IMAGE":
            data_index = xx
        xx += 1
    # check that we have found the data
    if data_index == 0:
        raise KeyError("Could not find data index")
    # extract the actual data bytes
    csa_data_bytes = dataset[0x7fe1, 0x0100 * data_index + 0x0010].value
    # the data is stored as a list of 4 byte floats in (real, imaginary) pairs
    data_floats = struct.unpack("<%df" % (len(csa_data_bytes) / 4),
                                csa_data_bytes)
    # form an iterator which will provide the numbers one at a time, then an iterator which calls that iterator twice
    # each cycle to give a complex pair
    data_iter = iter(data_floats)
    complex_iter = (complex(r, i) for r, i in zip(data_iter, data_iter))
    # give this iterator to numpy to build the data array
    complex_data = numpy.fromiter(complex_iter, "complex128",
                                  int(len(csa_data_bytes) / 8))
    # reshape the array to structure of rows, columns and slices
    complex_data = numpy.reshape(complex_data, data_shape).squeeze()

    return MRSData(complex_data,
                   csa_header["RealDwellTime"] * 1e-9,
                   csa_header["ImagingFrequency"],
                   te=csa_header["EchoTime"])