Esempio n. 1
0
def built_dicommap(dicomfile: Path, bidsmap: dict, template: dict) -> dict:
    """
    All the logic to map dicomfields onto bids labels go into this function

    :param dicomfile:   The full-path name of the source dicom-file
    :param bidsmap:     The bidsmap as we had it
    :param template:    Full BIDS heuristics data structure, with all options, BIDS labels and attributes, etc
    :return:            The bidsmap with new entries in it
    """

    # Get the bidsmodality and dirname (= bidslabel) from the pathname (samples/bidsmodality/[dirname/]dicomfile)
    suffix = dicomfile.parts[-2]
    if suffix in bids.bidsmodalities + (bids.unknownmodality,
                                        bids.ignoremodality):
        modality = suffix
    else:
        modality = dicomfile.parts[-3]

    # Input checks
    if not bids.is_dicomfile(dicomfile) or not template[
            'DICOM'] or not template['DICOM'][modality]:
        return bidsmap
    if modality not in bids.bidsmodalities + (bids.unknownmodality,
                                              bids.ignoremodality):
        raise ValueError(
            "Don't know what to do with this bidsmodality directory name: {}\n{}"
            .format(modality, dicomfile))

    # Get bids-labels from the matching run in the template
    run = bids.get_run(
        template, 'DICOM', modality, suffix,
        dicomfile)  # TODO: check if the dicomfile argument is not broken
    if not run:
        raise ValueError(
            f"Oops, this should not happen! BIDS modality '{modality}' or one of the bidslabels is not accounted for in the code\n{dicomfile}"
        )

    # Copy the filled-in run over to the bidsmap
    if not bids.exist_run(bidsmap, 'DICOM', modality, run):
        bidsmap = bids.append_run(bidsmap, 'DICOM', modality, run)

    return bidsmap
Esempio n. 2
0
def bidsmapper_plugin(session: Path, bidsmap_new: dict, bidsmap_old: dict,
                      template: dict, store: dict) -> None:
    """
    All the logic to map the DICOM/PAR source fields onto bids labels go into this function

    :param session:     The full-path name of the subject/session raw data source folder
    :param bidsmap_new: The new study bidsmap that we are building
    :param bidsmap_old: The previous study bidsmap that has precedence over the template bidsmap
    :param template:    The template bidsmap with the default heuristics
    :param store:       The paths of the source- and target-folder
    :return:
    """

    # Get started
    plugin = {
        'dcm2niix2bids': bidsmap_new['Options']['plugins']['dcm2niix2bids']
    }
    datasource = bids.get_datasource(session, plugin)
    dataformat = datasource.dataformat
    if not dataformat:
        return

    # Collect the different DICOM/PAR source files for all runs in the session
    sourcefiles = []
    if dataformat == 'DICOM':
        for sourcedir in bidscoin.lsdirs(session):
            for n in range(
                    1
            ):  # Option: Use range(2) to scan two files and catch e.g. magnitude1/2 fieldmap files that are stored in one Series folder (but bidscoiner sees only the first file anyhow and it makes bidsmapper 2x slower :-()
                sourcefile = bids.get_dicomfile(sourcedir, n)
                if sourcefile.name:
                    sourcefiles.append(sourcefile)
    elif dataformat == 'PAR':
        sourcefiles = bids.get_parfiles(session)
    else:
        LOGGER.exception(f"Unsupported dataformat '{dataformat}'")

    # Update the bidsmap with the info from the source files
    for sourcefile in sourcefiles:

        # Input checks
        if not sourcefile.name or (not template[dataformat]
                                   and not bidsmap_old[dataformat]):
            LOGGER.error(
                f"No {dataformat} source information found in the bidsmap and template for: {sourcefile}"
            )
            return

        # See if we can find a matching run in the old bidsmap
        datasource = bids.DataSource(sourcefile, plugin, dataformat)
        run, match = bids.get_matching_run(datasource, bidsmap_old)

        # If not, see if we can find a matching run in the template
        if not match:
            run, _ = bids.get_matching_run(datasource, template)

        # See if we have collected the run somewhere in our new bidsmap
        if not bids.exist_run(bidsmap_new, '', run):

            # Communicate with the user if the run was not present in bidsmap_old or in template, i.e. that we found a new sample
            if not match:
                LOGGER.info(
                    f"Discovered '{datasource.datatype}' {dataformat} sample: {sourcefile}"
                )

            # Now work from the provenance store
            if store:
                targetfile = store['target'] / sourcefile.relative_to(
                    store['source'])
                targetfile.parent.mkdir(parents=True, exist_ok=True)
                run['provenance'] = str(shutil.copy2(sourcefile, targetfile))

            # Copy the filled-in run over to the new bidsmap
            bids.append_run(bidsmap_new, run)

        else:
            # Communicate with the user if the run was already present in bidsmap_old or in template
            LOGGER.debug(
                f"Known '{datasource.datatype}' {dataformat} sample: {sourcefile}"
            )
def bidsmapper_plugin(session: Path, bidsmap_new: dict, bidsmap_old: dict,
                      template: dict, store: dict) -> None:
    """
    All the heuristics spec2nii2bids attributes and properties onto bids labels and meta-data go into this plugin function.
    The function is expected to update / append new runs to the bidsmap_new data structure. The bidsmap options for this plugin
    are stored in:

    bidsmap_new['Options']['plugins']['spec2nii2bids']

    :param session:     The full-path name of the subject/session raw data source folder
    :param bidsmap_new: The new study bidsmap that we are building
    :param bidsmap_old: The previous study bidsmap that has precedence over the template bidsmap
    :param template:    The template bidsmap with the default heuristics
    :param store:       The paths of the source- and target-folder
    :return:
    """

    # Get the plugin settings
    plugin = {
        'spec2nii2bids': bidsmap_new['Options']['plugins']['spec2nii2bids']
    }

    # Update the bidsmap with the info from the source files
    for sourcefile in [
            file for file in session.rglob('*') if is_sourcefile(file)
    ]:

        datasource = bids.DataSource(sourcefile, plugin)
        dataformat = datasource.dataformat

        # Input checks
        if not template[dataformat] and not bidsmap_old[dataformat]:
            LOGGER.error(
                f"No {dataformat} source information found in the bidsmap and template for: {sourcefile}"
            )
            return
        if not template.get(dataformat) and not bidsmap_old.get(dataformat):
            LOGGER.error(
                f"No {dataformat} source information found in the bidsmap and template for: {sourcefile}"
            )
            return

        # See if we can find a matching run in the old bidsmap
        run, match = bids.get_matching_run(datasource, bidsmap_old)

        # If not, see if we can find a matching run in the template
        if not match:
            run, _ = bids.get_matching_run(datasource, template)

        # See if we have collected the run somewhere in our new bidsmap
        if not bids.exist_run(bidsmap_new, '', run):

            # Communicate with the user if the run was not present in bidsmap_old or in template, i.e. that we found a new sample
            if not match:
                LOGGER.info(
                    f"Discovered '{datasource.datatype}' {dataformat} sample: {sourcefile}"
                )

            # Now work from the provenance store
            if store:
                targetfile = store['target'] / sourcefile.relative_to(
                    store['source'])
                targetfile.parent.mkdir(parents=True, exist_ok=True)
                run['provenance'] = str(shutil.copy2(sourcefile, targetfile))

            # Copy the filled-in run over to the new bidsmap
            bids.append_run(bidsmap_new, run)

        else:
            # Communicate with the user if the run was already present in bidsmap_old or in template
            LOGGER.debug(
                f"Known '{datasource.datatype}' {dataformat} sample: {sourcefile}"
            )
Esempio n. 4
0
def build_dicommap(runfolder: Path, bidsmap_new: dict, bidsmap_old: dict,
                   template: dict, gui: object) -> dict:
    """
    All the logic to map dicom-attributes (fields/tags) onto bids-labels go into this function

    :param runfolder:   The full-path name of the series-folder containing source dicom-files
    :param bidsmap_new: The bidsmap that we are building
    :param bidsmap_old: Full BIDS heuristics data structure, with all options, BIDS labels and attributes, etc
    :param template:    The bidsmap template with the default heuristics
    :param gui:         If not None, the user will not be asked for help if an unknown run is encountered
    :return:            The bidsmap with new entries in it
    """

    # Input checks
    dicomfile = bids.get_dicomfile(runfolder)
    if not dicomfile.name or (not template['DICOM']
                              and not bidsmap_old['DICOM']):
        LOGGER.info('No DICOM information found in the bidsmap and template')
        return bidsmap_new

    # See if we can find a matching run in the old bidsmap
    run, modality, index = bids.get_matching_run(dicomfile, bidsmap_old)

    # If not, see if we can find a matching run in the template
    if index is None:
        run, modality, _ = bids.get_matching_run(dicomfile, template)

    # See if we have collected the run in our new bidsmap
    if not bids.exist_run(bidsmap_new, 'DICOM', '', run):

        # Copy the filled-in run over to the new bidsmap
        bidsmap_new = bids.append_run(bidsmap_new, 'DICOM', modality, run)

        # Communicate with the user if the run was not present in bidsmap_old or in template
        LOGGER.info(f"New '{modality}' sample found: {dicomfile}")

        # Launch a GUI to ask the user for help if the new run comes from the template (i.e. was not yet in the old bidsmap)
        if gui and gui.interactive == 2 and index is None:
            # Open the interactive edit window to get the new mapping
            dialog_edit = bidseditor.EditDialog(dicomfile, modality,
                                                bidsmap_new, template,
                                                gui.subprefix, gui.sesprefix)
            dialog_edit.exec()

            # Get the result
            if dialog_edit.result() == 1:  # The user has finished the edit
                bidsmap_new = dialog_edit.target_bidsmap
            elif dialog_edit.result() in [
                    0, 2
            ]:  # The user has canceled / aborted the edit
                answer = QMessageBox.question(
                    None, 'BIDSmapper',
                    'Do you want to abort and quit the bidsmapper?',
                    QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
                if answer == QMessageBox.No:
                    pass
                if answer == QMessageBox.Yes:
                    LOGGER.info('User has quit the bidsmapper')
                    sys.exit()

            else:
                LOGGER.debug(
                    f'Unexpected result {dialog_edit.result()} from the edit dialog'
                )

    return bidsmap_new
Esempio n. 5
0
def build_bidsmap(dataformat: str, sourcefile: Path, bidsmap_new: dict,
                  bidsmap_old: dict, template: dict, store: dict,
                  gui: object) -> dict:
    """
    All the logic to map the Philips PAR/XML fields onto bids labels go into this function

    :param dataformat:  The information source in the bidsmap that is used, e.g. 'DICOM'
    :param sourcefile:  The full-path name of the source file
    :param bidsmap_new: The bidsmap that we are building
    :param bidsmap_old: Full BIDS heuristics data structure, with all options, BIDS labels and attributes, etc
    :param template:    The bidsmap template with the default heuristics
    :param store:       The paths of the source- and target-folder
    :param gui:         If not None, the user will not be asked for help if an unknown run is encountered
    :return:            The bidsmap with new entries in it
    """

    # Input checks
    if not sourcefile.name or (not template[dataformat]
                               and not bidsmap_old[dataformat]):
        LOGGER.info(
            f"No {dataformat} source information found in the bidsmap and template"
        )
        return bidsmap_new

    # See if we can find a matching run in the old bidsmap
    run, modality, index = bids.get_matching_run(sourcefile, bidsmap_old,
                                                 dataformat)

    # If not, see if we can find a matching run in the template
    if index is None:
        run, modality, _ = bids.get_matching_run(sourcefile, template,
                                                 dataformat)

    # See if we have collected the run in our new bidsmap
    if not bids.exist_run(bidsmap_new, dataformat, '', run):

        # Now work from the provenance store
        if store:
            targetfile = store['target'] / sourcefile.relative_to(
                store['source'])
            targetfile.parent.mkdir(parents=True, exist_ok=True)
            sourcefile = Path(shutil.copy2(sourcefile, targetfile))
            run['provenance'] = str(sourcefile.resolve())

        # Communicate with the user if the run was not present in bidsmap_old or in template, i.e. that we found a new sample
        LOGGER.info(f"Found '{modality}' {dataformat} sample: {sourcefile}")

        # Copy the filled-in run over to the new bidsmap
        bidsmap_new = bids.append_run(bidsmap_new, dataformat, modality, run)

        # Launch a GUI to ask the user for help if the new run comes from the template (i.e. was not yet in the old bidsmap)
        if gui and gui.interactive == 2 and index is None:

            # Open the interactive edit window to get the new mapping
            dialog_edit = bidseditor.EditDialog(dataformat, sourcefile,
                                                modality, bidsmap_new,
                                                template, gui.subprefix,
                                                gui.sesprefix)
            dialog_edit.exec()

            # Get the result
            if dialog_edit.result() == 1:  # The user has finished the edit
                bidsmap_new = dialog_edit.target_bidsmap
            elif dialog_edit.result() in [
                    0, 2
            ]:  # The user has canceled / aborted the edit
                answer = QMessageBox.question(
                    None, 'BIDSmapper',
                    'Do you want to abort and quit the bidsmapper?',
                    QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
                if answer == QMessageBox.No:
                    pass
                if answer == QMessageBox.Yes:
                    LOGGER.info('User has quit the bidsmapper')
                    sys.exit()

            else:
                LOGGER.debug(
                    f'Unexpected result {dialog_edit.result()} from the edit dialog'
                )

    return bidsmap_new
Esempio n. 6
0
def bidsmapper_plugin(session: Path, bidsmap_new: dict, bidsmap_old: dict,
                      template: dict, store: dict) -> None:
    """
    All the logic to map the Nibabel header fields onto bids labels go into this function

    :param session:     The full-path name of the subject/session raw data source folder
    :param bidsmap_new: The new study bidsmap that we are building
    :param bidsmap_old: The previous study bidsmap that has precedence over the template bidsmap
    :param template:    The template bidsmap with the default heuristics
    :param store:       The paths of the source- and target-folder
    :return:
    """

    # Get started
    plugin = {
        'nibabel2bids': bidsmap_new['Options']['plugins']['nibabel2bids']
    }
    datasource = bids.get_datasource(session, plugin, recurse=2)
    if not datasource.dataformat:
        return
    if not (template[datasource.dataformat]
            or bidsmap_old[datasource.dataformat]):
        LOGGER.error(
            f"No {datasource.dataformat} source information found in the bidsmap and template"
        )
        return

    # Collect the different DICOM/PAR source files for all runs in the session
    for sourcefile in [
            file for file in session.rglob('*') if is_sourcefile(file)
    ]:

        # See if we can find a matching run in the old bidsmap
        datasource = bids.DataSource(sourcefile, plugin, datasource.dataformat)
        run, match = bids.get_matching_run(datasource, bidsmap_old)

        # If not, see if we can find a matching run in the template
        if not match:
            run, _ = bids.get_matching_run(datasource, template)

        # See if we have collected the run somewhere in our new bidsmap
        if not bids.exist_run(bidsmap_new, '', run):

            # Communicate with the user if the run was not present in bidsmap_old or in template, i.e. that we found a new sample
            if not match:
                LOGGER.info(
                    f"Discovered '{datasource.datatype}' {datasource.dataformat} sample: {sourcefile}"
                )

            # Now work from the provenance store
            if store:
                targetfile = store['target'] / sourcefile.relative_to(
                    store['source'])
                targetfile.parent.mkdir(parents=True, exist_ok=True)
                run['provenance'] = str(shutil.copy2(sourcefile, targetfile))

            # Copy the filled-in run over to the new bidsmap
            bids.append_run(bidsmap_new, run)

        else:
            # Communicate with the user if the run was already present in bidsmap_old or in template
            LOGGER.debug(
                f"Known '{datasource.datatype}' {datasource.dataformat} sample: {sourcefile}"
            )