Skip to content

Module dcm2bids

dcm2bids


Reorganising NIfTI files from dcm2niix into the Brain Imaging Data Structure

View Source
# -*- coding: utf-8 -*-

"""

dcm2bids

--------

Reorganising NIfTI files from dcm2niix into the Brain Imaging Data Structure

"""

from .dcm2bids import Dcm2bids

from .scaffold import scaffold

from .version import __version__

__all__ = ["__version__", "Dcm2bids", "scaffold"]

Sub-modules

Variables

__version__

Functions

scaffold

def scaffold(
    output_dir_override: Optional[str] = None
)

scaffold entry point

View Source
def scaffold(output_dir_override: Optional[str] = None):

    """scaffold entry point"""

    args = _get_arguments()

    output_dir_ = output_dir_override if output_dir_override is not None else args.output_dir

    for _ in ["code", "derivatives", "sourcedata"]:

        os.makedirs(os.path.join(output_dir_, _), exist_ok=True)

    for _ in [

        "dataset_description.json",

        "participants.json",

        "participants.tsv",

        "README",

    ]:

        dest = os.path.join(output_dir_, _)

        with resources.path(__name__, _) as src:

            shutil.copyfile(src, dest)

    with resources.path(__name__, "CHANGES") as changes_template:

        with open(changes_template) as _:

            data = _.read().format(datetime.date.today().strftime("%Y-%m-%d"))

        write_txt(

            os.path.join(output_dir_, "CHANGES"),

            data.split("\n")[:-1],

    )

Classes

Dcm2bids

class Dcm2bids(
    dicom_dir,
    participant,
    config,
    output_dir=PosixPath('/home/sam/Projects/Dcm2Bids'),
    session='',
    clobber=False,
    forceDcm2niix=False,
    log_level='WARNING',
    **_
)

Object to handle dcm2bids execution steps

Attributes

Name Type Description Default
dicom_dir str or list A list of folder with dicoms to convert None
participant str Label of your participant None
config path Path to a dcm2bids configuration file None
output_dir path Path to the BIDS base folder None
session str Optional label of a session None
clobber boolean Overwrite file if already in BIDS folder None
forceDcm2niix boolean Forces a cleaning of a previous execution of
dcm2niix
None
log_level str logging level None
View Source
class Dcm2bids(object):

    """ Object to handle dcm2bids execution steps

    Args:

        dicom_dir (str or list): A list of folder with dicoms to convert

        participant (str): Label of your participant

        config (path): Path to a dcm2bids configuration file

        output_dir (path): Path to the BIDS base folder

        session (str): Optional label of a session

        clobber (boolean): Overwrite file if already in BIDS folder

        forceDcm2niix (boolean): Forces a cleaning of a previous execution of

                                 dcm2niix

        log_level (str): logging level

    """

    def __init__(

        self,

        dicom_dir,

        participant,

        config,

        output_dir=DEFAULT.outputDir,

        session=DEFAULT.session,

        clobber=DEFAULT.clobber,

        forceDcm2niix=DEFAULT.forceDcm2niix,

        log_level=DEFAULT.logLevel,

        **_

    ):

        self._dicomDirs = []

        self.dicomDirs = dicom_dir

        self.bidsDir = valid_path(output_dir, type="folder")

        self.config = load_json(valid_path(config, type="file"))

        self.participant = Participant(participant, session)

        self.clobber = clobber

        self.forceDcm2niix = forceDcm2niix

        self.logLevel = log_level

        # logging setup

        self.set_logger()

        self.logger.info("--- dcm2bids start ---")

        self.logger.info("OS:version: %s", platform.platform())

        self.logger.info("python:version: %s", sys.version.replace("\n", ""))

        self.logger.info("dcm2bids:version: %s", __version__)

        self.logger.info("dcm2niix:version: %s", dcm2niix_version())

        self.logger.info("participant: %s", self.participant.name)

        self.logger.info("session: %s", self.participant.session)

        self.logger.info("config: %s", os.path.realpath(config))

        self.logger.info("BIDS directory: %s", os.path.realpath(output_dir))

    @property

    def dicomDirs(self):

        """List of DICOMs directories"""

        return self._dicomDirs

    @dicomDirs.setter

    def dicomDirs(self, value):

        dicom_dirs = value if isinstance(value, list) else [value]

        valid_dirs = [valid_path(_dir, "folder") for _dir in dicom_dirs]

        self._dicomDirs = valid_dirs

    def set_logger(self):

        """ Set a basic logger"""

        logDir = self.bidsDir / DEFAULT.tmpDirName / "log"

        logFile = logDir / f"{self.participant.prefix}_{datetime.now().isoformat().replace(':', '')}.log"

        logDir.mkdir(parents=True, exist_ok=True)

        setup_logging(self.logLevel, logFile)

        self.logger = logging.getLogger(__name__)

    def run(self):

        """Run dcm2bids"""

        dcm2niix = Dcm2niix(

            self.dicomDirs,

            self.bidsDir,

            self.participant,

            self.config.get("dcm2niixOptions", DEFAULT.dcm2niixOptions),

        )

        check_latest()

        check_latest("dcm2niix")

        dcm2niix.run(self.forceDcm2niix)

        sidecars = []

        for filename in dcm2niix.sidecarFiles:

            sidecars.append(

                Sidecar(filename, self.config.get("compKeys", DEFAULT.compKeys))

            )

        sidecars = sorted(sidecars)

        parser = SidecarPairing(

            sidecars,

            self.config["descriptions"],

            self.config.get("searchMethod", DEFAULT.searchMethod),

            self.config.get("caseSensitive", DEFAULT.caseSensitive)

        )

        parser.build_graph()

        parser.build_acquisitions(self.participant)

        parser.find_runs()

        self.logger.info("moving acquisitions into BIDS folder")

        intendedForList = [[] for i in range(len(parser.descriptions))]

        for acq in parser.acquisitions:

            acq.setDstFile()

            intendedForList = self.move(acq, intendedForList)

    def move(self, acquisition, intendedForList):

        """Move an acquisition to BIDS format"""

        for srcFile in glob(acquisition.srcRoot + ".*"):

            ext = Path(srcFile).suffixes

            ext = [curr_ext for curr_ext in ext if curr_ext in ['.nii','.gz',

                                                                '.json',

                                                                '.bval','.bvec']]

            dstFile = (self.bidsDir / acquisition.dstRoot).with_suffix("".join(ext))

            dstFile.parent.mkdir(parents = True, exist_ok = True)

            # checking if destination file exists

            if dstFile.exists():

                self.logger.info("'%s' already exists", dstFile)

                if self.clobber:

                    self.logger.info("Overwriting because of --clobber option")

                else:

                    self.logger.info("Use --clobber option to overwrite")

                    continue

            # it's an anat nifti file and the user using a deface script

            if (

                self.config.get("defaceTpl")

                and acquisition.dataType == "func"

                and ".nii" in ext

                ):

                try:

                    os.remove(dstFile)

                except FileNotFoundError:

                    pass

                defaceTpl = self.config.get("defaceTpl")

                cmd = [w.replace('srcFile', srcFile) for w in defaceTpl]

                cmd = [w.replace('dstFile', dstFile) for w in defaceTpl]

                run_shell_command(cmd)

                intendedForList[acquisition.indexSidecar].append(acquisition.dstIntendedFor + "".join(ext))

            elif ".json" in ext:

                data = acquisition.dstSidecarData(self.config["descriptions"],

                                                  intendedForList)

                save_json(dstFile, data)

                os.remove(srcFile)

            # just move

            else:

                os.rename(srcFile, dstFile)

            intendedFile = acquisition.dstIntendedFor + ".nii.gz"

            if intendedFile not in intendedForList[acquisition.indexSidecar]:

                intendedForList[acquisition.indexSidecar].append(intendedFile)

        return intendedForList

Instance variables

dicomDirs

List of DICOMs directories

Methods

move

def move(
    self,
    acquisition,
    intendedForList
)

Move an acquisition to BIDS format

View Source
    def move(self, acquisition, intendedForList):

        """Move an acquisition to BIDS format"""

        for srcFile in glob(acquisition.srcRoot + ".*"):

            ext = Path(srcFile).suffixes

            ext = [curr_ext for curr_ext in ext if curr_ext in ['.nii','.gz',

                                                                '.json',

                                                                '.bval','.bvec']]

            dstFile = (self.bidsDir / acquisition.dstRoot).with_suffix("".join(ext))

            dstFile.parent.mkdir(parents = True, exist_ok = True)

            # checking if destination file exists

            if dstFile.exists():

                self.logger.info("'%s' already exists", dstFile)

                if self.clobber:

                    self.logger.info("Overwriting because of --clobber option")

                else:

                    self.logger.info("Use --clobber option to overwrite")

                    continue

            # it's an anat nifti file and the user using a deface script

            if (

                self.config.get("defaceTpl")

                and acquisition.dataType == "func"

                and ".nii" in ext

                ):

                try:

                    os.remove(dstFile)

                except FileNotFoundError:

                    pass

                defaceTpl = self.config.get("defaceTpl")

                cmd = [w.replace('srcFile', srcFile) for w in defaceTpl]

                cmd = [w.replace('dstFile', dstFile) for w in defaceTpl]

                run_shell_command(cmd)

                intendedForList[acquisition.indexSidecar].append(acquisition.dstIntendedFor + "".join(ext))

            elif ".json" in ext:

                data = acquisition.dstSidecarData(self.config["descriptions"],

                                                  intendedForList)

                save_json(dstFile, data)

                os.remove(srcFile)

            # just move

            else:

                os.rename(srcFile, dstFile)

            intendedFile = acquisition.dstIntendedFor + ".nii.gz"

            if intendedFile not in intendedForList[acquisition.indexSidecar]:

                intendedForList[acquisition.indexSidecar].append(intendedFile)

        return intendedForList

run

def run(
    self
)

Run dcm2bids

View Source
    def run(self):

        """Run dcm2bids"""

        dcm2niix = Dcm2niix(

            self.dicomDirs,

            self.bidsDir,

            self.participant,

            self.config.get("dcm2niixOptions", DEFAULT.dcm2niixOptions),

        )

        check_latest()

        check_latest("dcm2niix")

        dcm2niix.run(self.forceDcm2niix)

        sidecars = []

        for filename in dcm2niix.sidecarFiles:

            sidecars.append(

                Sidecar(filename, self.config.get("compKeys", DEFAULT.compKeys))

            )

        sidecars = sorted(sidecars)

        parser = SidecarPairing(

            sidecars,

            self.config["descriptions"],

            self.config.get("searchMethod", DEFAULT.searchMethod),

            self.config.get("caseSensitive", DEFAULT.caseSensitive)

        )

        parser.build_graph()

        parser.build_acquisitions(self.participant)

        parser.find_runs()

        self.logger.info("moving acquisitions into BIDS folder")

        intendedForList = [[] for i in range(len(parser.descriptions))]

        for acq in parser.acquisitions:

            acq.setDstFile()

            intendedForList = self.move(acq, intendedForList)

set_logger

def set_logger(
    self
)

Set a basic logger

View Source
    def set_logger(self):

        """ Set a basic logger"""

        logDir = self.bidsDir / DEFAULT.tmpDirName / "log"

        logFile = logDir / f"{self.participant.prefix}_{datetime.now().isoformat().replace(':', '')}.log"

        logDir.mkdir(parents=True, exist_ok=True)

        setup_logging(self.logLevel, logFile)

        self.logger = logging.getLogger(__name__)

Last update: 2023-07-13
Created: 2023-07-13