예제 #1
0
    def read_recon(self, scan, recon):
        """
        Reads the metadata from a specified recon and returns a dictionary.

        Reads the 'reco' and 'visu_pars' files from within a specific
        reconstruction sub-directory and compiles a dictionary. At the top
        level the dictionary has keys corresponding to the two files and then
        the sub-dictionaries are keyed according to variables in the relevant
        file (minus any ##/$/PVM_ decorators).
        """
        recon_data = {}
        data_path = os.path.join(self.pfo_input, scan)
        recon_data["reco"] = utils.bruker_read_files("reco", data_path, recon)
        recon_data["visu_pars"] = utils.bruker_read_files("visu_pars",
          data_path, recon)
        return recon_data
예제 #2
0
def get_subject_name(pfo_study):
    """
    :param pfo_study: path to study folder.
    :return: name of the subject in the study. See get_subject_id.
    """
    # (1) 'subject' at the study level is present
    if os.path.exists(os.path.join(pfo_study, "subject")):
        subject = bruker_read_files("subject", pfo_study)
        return subject["SUBJECT_id"]
    # (2) 'subject' at the study level is not present, we use 'VisuSubjectId' from visu_pars of the first scan.
    # Longer solution as at the end 'visu_pars' will be unavoidably scanned twice.
    else:
        list_scans = get_list_scans(pfo_study)
        visu_pars = bruker_read_files("visu_pars",
                                      pfo_study,
                                      sub_scan_num=list_scans[0])
        return visu_pars["VisuSubjectId"]
예제 #3
0
 def test_read_recon(self):
     selected_scan = "3"
     selected_recon = "1"
     data_path = os.path.join(banana_data, selected_scan)
     ex_reco = bruker_read_files("reco", data_path, selected_recon)
     ex_visu_pars = bruker_read_files("visu_pars", data_path,
                                      selected_recon)
     expected_contents = {"reco": ex_reco, "visu_pars": ex_visu_pars}
     with mock.patch(
             "bruker2nifti._utils.bruker_read_files") as mock_function:
         mock_function.configure_mock(side_effect=bruker_read_files)
         m = BrukerMetadata(banana_data)
         recon = m.read_recon(selected_scan, selected_recon)
         assert recon.keys() == expected_contents.keys()
         assert recon["reco"].keys() == ex_reco.keys()
         assert recon["visu_pars"].keys() == ex_visu_pars.keys()
         mock_function.assert_called()
예제 #4
0
    def read_scan(self, scan):
        """
        Reads the metadata for a specified scan and returns a dictionary.

        Reads 'acqp', 'method' and all 'reco' and 'visu_pars' files for a given
        scan and its reconstructions. A dictionary of these values is returned.
        The top level keys are 'acqp', 'method' and 'recons' where 'recons' is
        sub-keyed with individual recon numbers, each of which is in turn keyed
        with 'reco' and 'visu_pars'. Beneath the appropriate key is a
        sub-dictionary keyed with variables from the source file (minus and
        ##/$/PVM_ decorators).
        """
        scan_data = {}
        data_path = os.path.join(self.pfo_input, scan)
        scan_data["acqp"] = utils.bruker_read_files("acqp", data_path)
        scan_data["method"] = utils.bruker_read_files("method", data_path)
        scan_data["recons"] = self.read_recons(scan)
        return scan_data
예제 #5
0
    def read_subject(self):
        """
        Reads metadata from the 'subject' file and returns a dictionary.

        Reads the 'subject' metadata file within the root study folder. It
        populates a dictionary with the data where keys correspond to variables
        within the source file (minus any ##/$/PVM_ decorators).
        """
        return utils.bruker_read_files("subject", self.pfo_input)
예제 #6
0
 def test_read_subject(self):
     expected_contents = bruker_read_files("subject", banana_data)
     with mock.patch(
             "bruker2nifti._utils.bruker_read_files") as mock_function:
         mock_function.configure_mock(side_effect=bruker_read_files)
         m = BrukerMetadata(banana_data)
         subject = m.read_subject()
         assert subject == expected_contents
         mock_function.assert_called_once_with("subject", banana_data)
예제 #7
0
 def test_read_scan(self):
     selected_scan = "1"
     data_path = os.path.join(banana_data, selected_scan)
     expected_keys = ["acqp", "method", "recons"]
     ex_acqp = bruker_read_files("acqp", data_path)
     ex_method = bruker_read_files("method", data_path)
     with mock.patch.object(BrukerMetadata,
                            "read_recons",
                            return_value={
                                "1": None,
                                "2": None
                            }) as mock_read_recons:
         m = BrukerMetadata(banana_data)
         scan = m.read_scan(selected_scan)
         assert set(scan.keys()) == set(expected_keys)
         assert scan["acqp"].keys() == ex_acqp.keys()
         assert scan["method"].keys() == ex_method.keys()
         assert set(scan["recons"].keys()) == {"1", "2"}
         mock_read_recons.assert_called_once_with(selected_scan)
예제 #8
0
    def show_study_structure(self):
        """
        Print to console the structure of the selected study.
        :return: [None] only print to console information.
        """
        if not os.path.isdir(self.pfo_study_bruker_input):
            raise IOError("Input folder does not exist.")

        print("Study folder structure: ")
        scans_list = get_list_scans(self.pfo_study_bruker_input)
        print("\n")
        print("List of scans: {}".format(scans_list))
        pfi_first_scan = os.path.join(self.pfo_study_bruker_input, scans_list[0])
        acqp = bruker_read_files("acqp", pfi_first_scan)
        print("Version: {}".format(acqp["ACQ_sw_version"][0]))
예제 #9
0
    def show_study_structure(self):
        """
        Print to console the structure of the selected study.
        :return: [None] only print to console information.
        """
        if not os.path.isdir(self.pfo_study_bruker_input):
            raise IOError('Input folder does not exist.')

        print('Study folder structure: ')
        scans_list = get_list_scans(self.pfo_study_bruker_input)
        print('\n')
        print('List of scans: {}'.format(scans_list))
        pfi_first_scan = os.path.join(self.pfo_study_bruker_input,
                                      scans_list[0])
        acqp = bruker_read_files('acqp', pfi_first_scan)
        print('Version: {}'.format(acqp['ACQ_sw_version'][0]))
예제 #10
0
def scan2struct(
    pfo_scan,
    correct_slope=True,
    correct_offset=True,
    sample_upside_down=False,
    nifti_version=1,
    qform_code=1,
    sform_code=2,
    get_acqp=False,
    get_method=False,
    get_reco=False,
    frame_body_as_frame_head=False,
    keep_same_det=True,
    consider_subject_position=False,
):
    """
    The core method of the converter has 2 parts.
    1) parsing the Bruker scan folder structure into an internal dictionary called struct.
    2) writing the information parsed in struct into folders.
    ----
    scan2struct is the first part of the bridge. Info required to fill nifti header are in the visu_pars file.
    The user may want to parse as well acqp, method (must when EpiDti) and reco parameter files.
    Data are parsed in the intermediate dictionary struct containing the final scan(s) converted in nibabel
    image, with additional infos.
    :param pfo_scan: path to folder containing the scan
    :param correct_slope: [True] if you want to correct the slope of the values.
    :param correct_offset: [True] if you want to correct the offset of the values.
    :param sample_upside_down: [False] if you want to have the sample rotated 180 around the Anterior-Posterior axis.
    :param nifti_version: [1] output nifti version can be version 1 or version 2 (see nibabel documentation)
    :param qform_code: [1] qform of the final nifti image
    :param sform_code: [2] sform of the final nifti image
    :param get_acqp: [False] if you want to parse the information in the acqp parameter file of the bruker raw data
    :param get_method: [False] if you want to parse the information in the method file. Forced to True when
    dealing with diffusion weighted images.
    :param get_reco: [False] if you want to parse the information in the reco parameter file.
    :param frame_body_as_frame_head: e.g. true if monkey, false if rat.
    :param keep_same_det: impose to have in the nifti affine matrix, the same determinat as in the bruker parameter.
    :param consider_subject_position : visu_pars SubjPosition can be 'Head_Prone' or 'Head_Supine'. While it may
    make sense in most cases to take this value into account, in some other it may not, as it is
    tuned to switch from radiological to neurological coordinate systems in a work-around.
    If the subject is Prone and the technician wants to have the coordinates
    in neurological he/she can consciously set the variable vc_subject_position to 'Head_Supine'.
    :return: output_data data structure containing the nibabel image(s) {nib_list, visu_pars_list, acqp, method, reco}
    """

    if not os.path.isdir(pfo_scan):
        raise IOError("Input folder does not exists.")

    # Get system endian_nes
    system_endian_nes = sys.byteorder

    # Get sub-scans series in the same experiment.
    list_sub_scans = get_list_scans(jph(pfo_scan, "pdata"))

    if not list_sub_scans:
        warn_msg = (
            "\nNo sub scan in the folder structure: \n{}. \nAre you sure the input folder contains a "
            "proper Bruker scan?\n".format(jph(pfo_scan, "pdata")))
        warnings.warn(warn_msg)
        return None

    nib_scans_list = []
    visu_pars_list = []

    for id_sub_scan in list_sub_scans:

        visu_pars = bruker_read_files("visu_pars",
                                      pfo_scan,
                                      sub_scan_num=id_sub_scan)

        if visu_pars == {}:
            warn_msg = (
                "\nNo 'visu_pars' data found here: \n{}. \nAre you sure the input folder contains a "
                "proper Bruker scan?\n".format(
                    jph(pfo_scan, "pdata", id_sub_scan)))
            warnings.warn(warn_msg)
            return None

        # In some cases we cannot deal with, VisuPars['VisuCoreSize'] can be a float. No conversion in this case.
        if not (isinstance(visu_pars["VisuCoreSize"], np.ndarray)
                or isinstance(visu_pars["VisuCoreSize"], list)):
            warn_msg = (
                "\nWarning, VisuCoreSize in VisuPars parameter file {} \n"
                "is not a list or a vector in. The study cannot be converted."
                " \n".format(jph(pfo_scan, "pdata", id_sub_scan)))
            warnings.warn(warn_msg)
            return None

        # Get data endian_nes - default big!!
        if visu_pars["VisuCoreByteOrder"] == "littleEndian":
            data_endian_ness = "little"
        elif visu_pars["VisuCoreByteOrder"] == "bigEndian":
            data_endian_ness = "big"
        else:
            data_endian_ness = "big"

        # Get datatype
        if visu_pars["VisuCoreWordType"] == "_32BIT_SGN_INT":
            dt = np.int32
        elif visu_pars["VisuCoreWordType"] == "_16BIT_SGN_INT":
            dt = np.int16
        elif visu_pars["VisuCoreWordType"] == "_8BIT_UNSGN_INT":
            dt = np.uint8
        elif visu_pars["VisuCoreWordType"] == "_32BIT_FLOAT":
            dt = np.float32
        else:
            raise IOError("Unknown data type for VisuPars VisuCoreWordType")

        # GET IMAGE VOLUME
        if os.path.exists(jph(pfo_scan, "pdata", id_sub_scan, "2dseq")):
            img_data_vol = np.copy(
                np.fromfile(jph(pfo_scan, "pdata", id_sub_scan, "2dseq"),
                            dtype=dt))
        else:
            warn_msg = (
                "\nNo '2dseq' data found here: \n{}. \nAre you sure the input folder contains a "
                "proper Bruker scan?\n".format(
                    jph(pfo_scan, "pdata", id_sub_scan)))
            warnings.warn(warn_msg)
            return None

        if not data_endian_ness == system_endian_nes:
            img_data_vol.byteswap(True)

        if "VisuAcqSequenceName" in visu_pars.keys():
            visu_pars_acq_sequence_name = visu_pars["VisuAcqSequenceName"]
        else:
            visu_pars_acq_sequence_name = ""

        is_dwi = "dtiepi" in visu_pars_acq_sequence_name.lower()

        if is_dwi:
            # Force to not correcting the slope, if true. Diffusion weighted images must be slope corrected before the
            # DTI analysis. They will be to heavy otherwise.
            correct_slope = False
            correct_offset = False
            # Force method to be parsed. Useful infos in this file to process the DWI.
            get_method = True

        # ------------------------------------------------------ #
        # ------ Generate the nifti image using visu_pars. ----- #
        # ------------------------------------------------------ #

        nib_im = nifti_getter(
            img_data_vol,
            visu_pars,
            correct_slope,
            correct_offset,
            sample_upside_down,
            nifti_version,
            qform_code,
            sform_code,
            frame_body_as_frame_head=frame_body_as_frame_head,
            keep_same_det=keep_same_det,
            consider_subject_position=consider_subject_position,
        )
        # ------------------------------------------------------ #
        # ------------------------------------------------------ #

        nib_scans_list.append(nib_im)
        visu_pars_list.append(visu_pars)

    # -- Get additional data

    # Get information from method, if it exists. Parse Method parameter and erase the dictionary if unwanted
    method = bruker_read_files("method", pfo_scan)

    if method == {}:
        print("Warning: No 'method' file to parse.")
    if "Method" in method.keys():
        acquisition_method = (method["Method"].replace("<", "").replace(
            ">", "").split(":")[-1])
    else:
        acquisition_method = ""

    if not get_method:
        method = {}

    # Get information from acqp, reco, if they exist.
    acqp = {}
    reco = {}

    if get_acqp:
        acqp = bruker_read_files("acqp", pfo_scan)
        if acqp == {}:
            print("Warning: No 'acqp' file to parse.")

    if get_reco:
        reco = bruker_read_files("reco", pfo_scan)
        if reco == {}:
            print("Warning: No 'method' file to parse.")

    # -- Return data structure
    struct_scan = {
        "nib_scans_list": nib_scans_list,
        "visu_pars_list": visu_pars_list,
        "acqp": acqp,
        "reco": reco,
        "method": method,
        "acquisition_method": acquisition_method,
    }

    return struct_scan