Skip to content

Commit

Permalink
Add general madym_T1 wrapper (set to IR_E by default)
Browse files Browse the repository at this point in the history
  • Loading branch information
martinherrerias committed Nov 8, 2023
1 parent 2d65fcf commit 5098ddc
Show file tree
Hide file tree
Showing 5 changed files with 263 additions and 5 deletions.
16 changes: 16 additions & 0 deletions config/madym_T1.config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# madym_T1
# version = v4.23.0

method: IR_E

T1_dir: IR
T1_vols: [OE_400, OE_800, OE_1000, OE_1500, OE_2000, OE_2500]
B1_name: null

output_dir: T1_IR
log_file: logs/madym_T1_IR.log
config_out: logs/madym_T1_IR.conf

T1_noise: 0.1
B1_scaling: 1000
B1_values: []
8 changes: 3 additions & 5 deletions workflow/Snakefile
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ include: "rules/common.smk"
# include: "rules/OE_deltaR1.smk"

include: "rules/DCE_deltaCt.smk"

include: "rules/madym_T1.smk"
# include: "rules/DCE_ETM.smk"
# # include: "rules/OE_DCE_hypoxia_mapping.smk"

Expand All @@ -26,7 +26,5 @@ include: "rules/DCE_deltaCt.smk"

rule all:
input:
expand("{maps_dir}/{key}{ext}",
maps_dir = data_path("maps_dir"),
key = ['C_t', 'delta_C', 'C_baseline', 'C_enhancing', 'C_p_vals', 'S_p_vals'],
ext = ".nii.gz"),
rules.DCE_deltaCt.output,
rules.madym_T1.output
36 changes: 36 additions & 0 deletions workflow/rules/madym_T1.smk
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
'''
Wrapper for madym_T1
'''
import os
from snakemake.utils import validate

configfile: workflow.source_path("../../config/madym_T1.config.yaml")
validate(config, workflow.source_path("../schemas/madym_T1.schema.yaml"))

envvars:
"MADYM_ROOT"

rule madym_T1:
container:
"docker://registry.gitlab.com/manchester_qbi/manchester_qbi_public/madym_cxx/madym_release_no_gui:u22.04"
# "preclinicalmri_depends_no_gui:latest"
input:
# TODO: adjust for other formats, see img_fmt_r in madym_T1.schema.yaml
expand("{T1_dir}/{vols}{ext}",
T1_dir = config["T1_dir"],
vols = config["T1_vols"],
ext = [".nii.gz", ".json"])
output:
# TODO: adjust for other formats, see img_fmt_w in madym_T1.schema.yaml
expand("{output_dir}/{key}{ext}",
output_dir = config["output_dir"],
key = ["T1", "M0","efficiency"],
ext = [".xtr", ".nii.gz"]),
os.path.join(config["output_dir"], "error_tracker.nii.gz")
# touch(os.path.join(config["output_dir"], "madym_T1.done"))
log:
log = config["log_file"],
cfg = config["config_out"],
audit = os.path.join(config["audit_dir"], config["audit_name"])
script:
"../scripts/madym_T1.py"
121 changes: 121 additions & 0 deletions workflow/schemas/madym_T1.schema.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
$schema: "https://json-schema.org/draft/2020-12/schema"


# The following options are overriden by snakemake's functionality:
# working_directory: set to workdir
# config_file: disabled (replaced by rule's configfile)

# roi_name

properties:

# Inputs
T1_vols:
type: [array, "null"]
default: null
description: Variable flip angle file names, comma separated (no spaces).
T1_dir:
type: [string, "null"]
default: null
description: Folder containing T1 input volumes, can be left empty if already included in option --T1_vols
img_fmt_r:
type: string
default: NIFTI_GZ
# enum: [ANALYZE, ANALYZE_SPARSE, NIFTI, NIFTI_GZ, DICOM] # TODO: adjust madym_T1.smk inputs for other formats
const: NIFTI_GZ
description: Image format for reading input.
error_name:
type: [string, "null"]
default: null
description: Error codes image file name.

# Outputs
output_dir:
type: [string, "null"]
default: null
description: Output path, will use a temporary directory if empty.
img_fmt_w:
type: string
default: NIFTI_GZ
# enum: [ANALYZE, ANALYZE_SPARSE, NIFTI, NIFTI_GZ, DICOM] # TODO: adjust madym_T1.smk outputs for other formats
const: NIFTI_GZ
description: Image format for writing output.
log_file:
type: string
default: logs/madym_T1.log
description: Folder in which audit output is saved.
config_out:
type: [string, "null"]
default: logs/madym_T1.conf
description: Filename of the output config file.
no_log:
type: boolean
default: false
description: Switch off program logging.
no_audit:
type: boolean
default: true
description: Switch off audit logging.
audit_dir:
type: string
default: audit_logs
description: Folder in which audit output is saved.
audit_name:
type: string
default: madym_T1.audit
description: Audit file name.
quiet:
type: boolean
default: false
description: Do not display logging messages in cout.

# Params
method:
type: ["null","string"]
default: null
enum: [VFA, VFA_B1, IR, IR_E]
description: T1 method to use to fit, Variable Flip-Angle [B1 corrected], Inversion Recovery [with efficiency weighting]
B1_name:
type: [string, "null"]
default: null
description: Path to the B1 correction map.
B1_scaling:
type: ["number", "null"]
default: null
description: Value applied to scaled values in the B1 correction map.
B1_values:
type: [array, "null"]
default: null
description: B1 correction values, 1D array of length n_samples.
T1_noise:
type: ["number", "null"]
default: null
description: PD noise threshold.

# Other
nifti_scaling:
type: boolean
default: false
description: If set, applies intensity scaling and offset when reading/writing NIFTI images.
nifti_4D:
type: boolean
default: false
description: If set, reads NIFTI 4D images for T1 mapping and dynamic inputs.
use_BIDS:
type: boolean
default: false
description: If set, writes images using BIDS JSON meta info.
voxel_size_warn_only:
type: boolean
default: false
description: Warn if voxel sizes don't match for subsequent images.
overwrite:
type: boolean
default: true
description: Set overwrite existing analysis in the output directory ON.

required:
- method
- T1_vols

# additionalProperties: false
87 changes: 87 additions & 0 deletions workflow/scripts/madym_T1.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import os
import re

from QbiMadym import madym_T1
from QbiMadym.utils import local_madym_root

import subprocess
command = 'madym_T1.exe'
try:
command = os.path.join(local_madym_root(),'madym_T1')
subprocess.run([command, "--version"], stdout = subprocess.PIPE, stderr = subprocess.PIPE, check=True)
except subprocess.CalledProcessError:
raise NameError(f"{command} is not available.")

madym_T1.run(
#
working_directory = os.getcwd(),
config_file = None,
# cmd_exe = command,
#
output_dir = snakemake.config["output_dir"],
img_fmt_w = "NIFTI_GZ",

# Input
# T1_dir = snakemake.config["T1_dir"],
T1_vols = [os.path.join(snakemake.config["T1_dir"], vol) for vol in snakemake.config["T1_vols"]],
img_fmt_r = "NIFTI_GZ",
roi_name = snakemake.config["roi_path"],
error_name = snakemake.config["error_name"],

# Params
method = snakemake.config["method"],
B1_name = snakemake.config["B1_name"],
B1_scaling = snakemake.config["B1_scaling"],
noise_thresh = snakemake.config["T1_noise"],
nifti_scaling = snakemake.config["nifti_scaling"],
nifti_4D = snakemake.config["nifti_4D"],
use_BIDS = snakemake.config["use_BIDS"],
voxel_size_warn_only = snakemake.config["voxel_size_warn_only"],
#
no_log = False,
quiet = False,
overwrite = True,
return_maps = False,
dummy_run = False,

no_audit = snakemake.config["no_audit"],
audit_dir = snakemake.config["audit_dir"],

program_log_name = "smk.log", # see below (*)
config_out = "smk.cfg",
audit_name = "smk.audit"

# madym_T1_lite options (not exposed)
# ScannerParams = None,
# signals = None,
# TR = None,
# B1_values = None,
# output_name = 'madym_analysis.dat',
)

# (*) Logs are time-stamped and auto-renamed by madym:
#
# {output_dir}/madym_T1_{date}_{time}_smk.log
# {output_dir}/madym_T1_{date}_{time}_smk.cfg
# {output_dir}/madym_T1_{date}_{time}_override_smk.cfg
# {audit_dir}/madym_T1_{date}_{time}_smk.audit
#
# Rename to consistent snakemake.log entries
for item in snakemake.log.keys():

if item == "log" or item == "cfg":
dir = snakemake.config["output_dir"]
elif item == "audit":
dir = snakemake.config["audit_dir"]
else:
raise NameError("Unexpected log key: " + item)

for filename in os.listdir(dir):
match = re.match(r'madym_T1_(\d+)_(\d+)(_\w)?_smk\.' + item, filename)
if match:
if match.group(3) is None:
logname = snakemake.log[item]
else:
# Modify log.ext to e.g. log_override.ext
logname = re.sub(r'(\.\w+)$', match.group(3) + r'\1', snakemake.log[item])
os.rename(os.path.join(dir, filename), logname)

0 comments on commit 5098ddc

Please sign in to comment.