Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix windows paths #158

Merged
merged 15 commits into from
May 26, 2022
217 changes: 79 additions & 138 deletions dcm2bids/dcm2bids.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,25 @@
# -*- coding: utf-8 -*-

"""dcm2bids module"""
"""
Reorganising NIfTI files from dcm2niix into the Brain Imaging Data Structure
"""

import argparse
import logging
import os
from pathlib import Path
import platform
import sys
from datetime import datetime
from glob import glob
from .dcm2niix import Dcm2niix
from .logger import setup_logging
from .sidecar import Sidecar, SidecarPairing
from .structure import Participant
from .utils import DEFAULT, load_json, save_json, run_shell_command, splitext_
from .version import __version__, check_latest, dcm2niix_version

from dcm2bids.dcm2niix import Dcm2niix
from dcm2bids.logger import setup_logging
from dcm2bids.sidecar import Sidecar, SidecarPairing
from dcm2bids.structure import Participant
from dcm2bids.utils import (DEFAULT, load_json, save_json,
splitext_, run_shell_command, valid_path)
from dcm2bids.version import __version__, check_latest, dcm2niix_version


class Dcm2bids(object):
Expand Down Expand Up @@ -47,8 +52,8 @@ def __init__(
self._dicomDirs = []

self.dicomDirs = dicom_dir
self.bidsDir = output_dir
self.config = load_json(config)
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
Expand All @@ -74,37 +79,18 @@ def dicomDirs(self):

@dicomDirs.setter
def dicomDirs(self, value):
if isinstance(value, list):
dicom_dirs = value
else:
dicom_dirs = [value]

dir_not_found = []
for _dir in dicom_dirs:
if os.path.isdir(_dir):
pass
else:
dir_not_found.append(_dir)

if dir_not_found:
raise FileNotFoundError(dir_not_found)
dicom_dirs = value if isinstance(value, list) else [value]

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

self._dicomDirs = valid_dirs

def set_logger(self):
""" Set a basic logger"""
logDir = os.path.join(self.bidsDir, DEFAULT.tmpDirName, "log")
logFile = os.path.join(
logDir,
"{}_{}.log".format(
self.participant.prefix, datetime.now().isoformat().replace(":", "")
),
)

# os.makedirs(logdir, exist_ok=True)
# python2 compatibility
if not os.path.exists(logDir):
os.makedirs(logDir)
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__)
Expand Down Expand Up @@ -148,40 +134,41 @@ def move(self, acquisition, intendedForList):
"""Move an acquisition to BIDS format"""
for srcFile in glob(acquisition.srcRoot + ".*"):

_, ext = splitext_(srcFile)
dstFile = os.path.join(self.bidsDir, acquisition.dstRoot + ext)
ext = Path(srcFile).suffixes
dstFile = (self.bidsDir / acquisition.dstRoot).with_suffix("".join(ext))

if not os.path.exists(os.path.dirname(dstFile)):
os.makedirs(os.path.dirname(dstFile))
dstFile.parent.mkdir(parents = True, exist_ok = True)

# checking if destination file exists
if os.path.isfile(dstFile):
if dstFile.exists():
self.logger.info("'%s' already exists", dstFile)

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

else:
self.logger.info("Use clobber option to overwrite")
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 == "anat"
and acquisition.dataType == "func"
and ".nii" in ext
):
):
try:
os.remove(dstFile)
except FileNotFoundError:
pass
defaceTpl = self.config.get("defaceTpl")
cmd = defaceTpl.format(srcFile=srcFile, dstFile=dstFile)

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 + ext)

elif ext == ".json":
intendedForList[acquisition.indexSidecar].append(acquisition.dstIntendedFor + "".join(ext))

elif ".json" in ext:
data = acquisition.dstSidecarData(self.config["descriptions"],
intendedForList)
save_json(dstFile, data)
Expand All @@ -198,102 +185,56 @@ def move(self, acquisition, intendedForList):
return intendedForList


def get_arguments():
"""Load arguments for main"""
parser = argparse.ArgumentParser(
formatter_class=argparse.RawDescriptionHelpFormatter,
description="""
Reorganising NIfTI files from dcm2niix into the Brain Imaging Data Structure
dcm2bids {}""".format(
__version__
),
epilog="""
Documentation at https://github.com/unfmontreal/Dcm2Bids
""",
)

parser.add_argument(
"-d", "--dicom_dir", required=True, nargs="+", help="DICOM directory(ies)"
)

parser.add_argument("-p", "--participant", required=True, help="Participant ID")

parser.add_argument(
"-s", "--session", required=False, default=DEFAULT.cliSession, help="Session ID"
)

parser.add_argument(
"-c",
"--config",
required=True,
help="JSON configuration file (see example/config.json)",
)

parser.add_argument(
"-o",
"--output_dir",
required=False,
default=DEFAULT.cliOutputDir,
help="Output BIDS directory, Default: current directory ({})".format(
DEFAULT.cliOutputDir
),
)

parser.add_argument(
"--forceDcm2niix",
required=False,
action="store_true",
help="Overwrite previous temporary dcm2niix output if it exists",
)

parser.add_argument(
"--clobber",
required=False,
action="store_true",
help="Overwrite output if it exists",
)

parser.add_argument(
"-l",
"--log_level",
required=False,
default=DEFAULT.cliLogLevel,
choices=["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"],
help="Set logging level",
)

parser.add_argument(
"-a",
"--anonymizer",
required=False,
action="store_true",
help="""
This option no longer exists from the script in this release.
See:https://github.com/unfmontreal/Dcm2Bids/blob/master/README.md#defaceTpl""",
)
def _build_arg_parser():
p = argparse.ArgumentParser(description=__doc__, epilog=DEFAULT.EPILOG,
formatter_class=argparse.RawTextHelpFormatter)

args = parser.parse_args()
return args
p.add_argument("-d", "--dicom_dir",
type=Path, required=True, nargs="+",
help="DICOM directory(ies).")

p.add_argument("-p", "--participant",
required=True,
help="Participant ID.")

p.add_argument("-s", "--session",
required=False,
default="",
help="Session ID.")

p.add_argument("-c", "--config",
type=Path,
required=True,
help="JSON configuration file (see example/config.json).")

p.add_argument("-o", "--output_dir",
required=False,
type=Path,
default=Path.cwd(),
help="Output BIDS directory. (Default: %(default)s)")

p.add_argument("--forceDcm2niix",
action="store_true",
help="Overwrite previous temporary dcm2niix "
"output if it exists.")

p.add_argument("--clobber",
action="store_true",
help="Overwrite output if it exists.")

p.add_argument("-l", "--log_level",
required=False,
default=DEFAULT.cliLogLevel,
choices=["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"],
help="Set logging level. [%(default)s]")

return p


def main():
"""Let's go"""
args = get_arguments()

if args.anonymizer:
print(
"""
The anonymizer option no longer exists from the script in this release
It is still possible to deface the anatomical nifti images
Please add "defaceTpl" key in the congifuration file

For example, if you use the last version of pydeface, add:
"defaceTpl": "pydeface --outfile {dstFile} {srcFile}"
It is a template string and dcm2bids will replace {srcFile} and {dstFile}
by the source file (input) and the destination file (output)
"""
)
return 1
parser = _build_arg_parser()
args = parser.parse_args()

check_latest()
check_latest("dcm2niix")
Expand Down
14 changes: 7 additions & 7 deletions dcm2bids/dcm2niix.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

import logging
import os
from pathlib import Path
import shlex
import shutil
from glob import glob
from .utils import DEFAULT, run_shell_command
Expand Down Expand Up @@ -40,11 +42,9 @@ def outputDir(self):
Returns:
A directory to save all the output files of dcm2niix
"""
if self.participant:
tmpDir = self.participant.prefix
else:
tmpDir = DEFAULT.helperDir
return os.path.join(self.bidsDir, DEFAULT.tmpDirName, tmpDir)
tmpDir = self.participant.prefix if self.participant else DEFAULT.helperDir

return self.bidsDir / DEFAULT.tmpDirName / tmpDir

def run(self, force=False):
""" Run dcm2niix if necessary
Expand Down Expand Up @@ -95,8 +95,8 @@ def execute(self):
""" Execute dcm2niix for each directory in dicomDirs
"""
for dicomDir in self.dicomDirs:
commandTpl = "dcm2niix {} -o {} {}"
cmd = commandTpl.format(self.options, self.outputDir, dicomDir)
cmd = ['dcm2niix', *shlex.split(self.options),
'-o', self.outputDir, dicomDir]
output = run_shell_command(cmd)

try:
Expand Down
23 changes: 11 additions & 12 deletions dcm2bids/helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,27 +4,27 @@

import argparse
import os
from pathlib import Path
import sys
from .dcm2niix import Dcm2niix
from .utils import DEFAULT, assert_dirs_empty

EPILOG = """
Documentation at https://github.com/unfmontreal/Dcm2Bids
"""
from dcm2bids.dcm2niix import Dcm2niix
from dcm2bids.utils import DEFAULT, assert_dirs_empty


def _build_arg_parser():
p = argparse.ArgumentParser(description=__doc__, epilog=EPILOG,
p = argparse.ArgumentParser(description=__doc__, epilog=DEFAULT.EPILOG,
formatter_class=argparse.RawTextHelpFormatter)

p.add_argument("-d", "--dicom_dir",
type=Path,
required=True, nargs="+",
help="DICOM files directory.")

p.add_argument("-o", "--output_dir",
required=False, default=DEFAULT.cliOutputDir,
help="Output BIDS directory."
" (Default: %(default)s)")
required=False, default=Path.cwd(),
type=Path,
help="Output BIDS directory. "
"(Default: %(default)s)")

p.add_argument('--force',
dest='overwrite', action='store_true',
Expand All @@ -37,12 +37,11 @@ def main():
"""Let's go"""
parser = _build_arg_parser()
args = parser.parse_args()
out_folder = os.path.join(args.output_dir, 'tmp_dcm2bids', 'helper')
out_folder = args.output_dir / DEFAULT.tmpDirName / DEFAULT.helperDir
assert_dirs_empty(parser, args, out_folder)
app = Dcm2niix(dicomDirs=args.dicom_dir, bidsDir=args.output_dir)
rsl = app.run()
print("Example in:")
print(os.path.join(args.output_dir, DEFAULT.tmpDirName, DEFAULT.helperDir))
print(f"Example in: {out_folder}")
return rsl


Expand Down
4 changes: 2 additions & 2 deletions dcm2bids/sidecar.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ class SidecarPairing(object):
"""
Args:
sidecars (list): List of Sidecar objects
descriptions (list): List of dictionnaries describing acquisitions
descriptions (list): List of dictionaries describing acquisitions
"""

def __init__(self, sidecars, descriptions, searchMethod=DEFAULT.searchMethod,
Expand Down Expand Up @@ -146,7 +146,7 @@ def caseSensitive(self, value):
def build_graph(self):
"""
Test all the possible links between the list of sidecars and the
description dictionnaries and build a graph from it
description dictionaries and build a graph from it
The graph is in a OrderedDict object. The keys are the Sidecars and
the values are a list of possible descriptions

Expand Down
Loading