From f01e074cbd76e4f28b7d7fef0c745ab298dd047d Mon Sep 17 00:00:00 2001 From: Deepro Pasha Date: Thu, 7 Jul 2022 18:52:29 -0700 Subject: [PATCH 01/33] created copy of eval_net script for motion corruption, added arguments to eval_net_motion_corruption.py for 2D motion corruption --- tools/eval_net_motion_corruption.py | 554 ++++++++++++++++++++++++++++ 1 file changed, 554 insertions(+) create mode 100644 tools/eval_net_motion_corruption.py diff --git a/tools/eval_net_motion_corruption.py b/tools/eval_net_motion_corruption.py new file mode 100644 index 00000000..fb04230a --- /dev/null +++ b/tools/eval_net_motion_corruption.py @@ -0,0 +1,554 @@ +"""Run inference on test set scans. + +This consists of comparing both zero-filled recon and DL-recon to fully-sampled +scans. All comparisons are done per volume (not per slice). + +Example: + python eval_net.py --config-file my/experiment/folder/config.yaml --metric val_psnr_scan +""" +import itertools +import os +from copy import deepcopy +from typing import Any, Dict, Sequence + +import pandas as pd +import torch +from tabulate import tabulate + +import meddlr.ops.complex as cplx +from meddlr.checkpoint import Checkpointer +from meddlr.config import get_cfg +from meddlr.data.build import build_recon_val_loader +from meddlr.engine import DefaultTrainer, default_argument_parser, default_setup +from meddlr.evaluation import DatasetEvaluators, ReconEvaluator, inference_on_dataset +from meddlr.evaluation.testing import check_consistency, find_weights +from meddlr.modeling.meta_arch import CSModel +from meddlr.utils.logger import setup_logger + +_FILE_NAME = os.path.splitext(os.path.basename(__file__))[0] +_LOGGER_NAME = "{}.{}".format(_FILE_NAME, __name__) +# logger = logging.getLogger(_LOGGER_NAME) +logger = None # initialize in setup() + +# Default values for parameters that may not have been initially added. +_DEFAULT_VALS = {"rescaled": True} + + +class ZFReconEvaluator(ReconEvaluator): + """Zero-filled recon evaluator.""" + + def process(self, inputs, outputs): + zf_out = {k: outputs[k] for k in ("target",)} + zf_image = outputs["zf_image"] + if cplx.is_complex_as_real(zf_image): + zf_image = torch.view_as_complex(zf_image) + zf_out["pred"] = zf_image + return super().process(inputs, zf_out) + + +def setup(args): + """ + Create configs and perform basic setups. + We do not save the config. + """ + cfg = get_cfg() + cfg.merge_from_file(args.config_file) + opts = args.opts + if opts and opts[0] == "--": + opts = opts[1:] + cfg.merge_from_list(opts) + cfg.freeze() + default_setup(cfg, args, save_cfg=False) + + # Setup logger for test results + global logger + dirname = "test_results" + logger = setup_logger(os.path.join(cfg.OUTPUT_DIR, dirname), name=_FILE_NAME) + + logger.info(f"Command Line Args: {args}") + return cfg + + +def add_default_params(metrics: pd.DataFrame, ignore_case=True): + """Adds default config parameters (if missing). + + Args: + metrics (pd.DataFrame): Will be filtered based on column values. + ignore_case (bool, optional): If `True`, ignores the column casing. + Raises `ValueError` if two columns have the same lower case + form. + """ + + df = deepcopy(metrics) + if ignore_case: + column_map = {x: x.lower() for x in df.columns} + defaults_keys_map = {k.lower(): k for k in _DEFAULT_VALS.keys()} + df = df.rename(columns=column_map) + else: + defaults_keys_map = {k: k for k in _DEFAULT_VALS.keys()} + + for fmt_key, real_key in defaults_keys_map.items(): + if fmt_key not in df.columns: + df[real_key] = _DEFAULT_VALS[real_key] + + if ignore_case: + df = df.rename(columns={v: k for k, v in column_map.items()}) + + return df + + +def find_metrics( + metrics: pd.DataFrame, params: Dict[str, Any], ignore_missing=False, ignore_case=True +): + """Find subset of metrics dictionary that matches parameter configuration. + + Note: + Values that are not available will be filled in by _DEFAULT_VALS. + + Args: + metrics (pd.DataFrame): Will be filtered based on column values. + params (Dict[str, Any]): Parameter values to filter by. + Keys should correspond to column names in `metrics`. + ignore_missing (bool, optional): If `True`, ignores filtering by + columns that are missing. + ignore_case (bool, optional): If `True`, ignores the column casing. + Raises `ValueError` if two columns have the same lower case + form. + + Returns: + df (pd.DataFrame): The remaining dataframe after filtering. + """ + + df = deepcopy(metrics) + if ignore_case: + column_map = {x: x.lower() for x in df.columns} + df = df.rename(columns=column_map) + params = {k.lower(): v for k, v in params.items()} + + # Fill in with default values if missing. + # Note these will always be lower case, so we match based on case. + # Fill in default values when the columns are not available. + default_keys = {x.lower() for x in params} & {x.lower() for x in _DEFAULT_VALS.keys()} + lowercase_cols = [x.lower() for x in df.columns] + for k in default_keys: + if k not in lowercase_cols: + df[k] = _DEFAULT_VALS[k] + + for k, v in params.items(): + if k not in df.columns: + if ignore_missing: + continue + else: + raise KeyError(f"No column {k} in `metrics`") + df = df[df[k] == v] + + # Undo matching by lower case. + if ignore_case: + df = df.rename(columns={v: k for k, v in column_map.items()}) + + return df + + +def update_metrics(metrics_new: pd.DataFrame, metrics_old: pd.DataFrame, on: Sequence[str]): + """Update a previous metrics version with the new one. + + Metrics that were previously computed, may not be recomputed. + To avoid overwriting them when writing to a csv, we want to + port over any old metrics that we did not recompute. + """ + # We currently do not support missing columns. + missing_cols = [k not in metrics_old.columns for k in on] + if any(missing_cols): + raise KeyError(f"Column(s) {missing_cols} not found in `metrics_old`") + missing_cols = [k not in metrics_new.columns for k in on] + if any(missing_cols): + raise KeyError(f"Column(s) {missing_cols} not found in `metrics_new`") + + # Find combination of columns to select on that is not + # available in the new metrics, but is available in the + # old metrics. + old_metrics_combos = list(itertools.product(*[metrics_old[k].unique().tolist() for k in on])) + new_metrics_combos = list(itertools.product(*[metrics_new[k].unique().tolist() for k in on])) + + to_prepend = [] + for combo in old_metrics_combos: + if combo not in new_metrics_combos: + combo_as_dict = {k: v for k, v in zip(on, combo)} + to_prepend.append(find_metrics(metrics_old, combo_as_dict)) + + if len(to_prepend) > 0: + to_prepend = pd.concat(to_prepend, ignore_index=True) + metrics = pd.concat([to_prepend, metrics_new], ignore_index=True) + else: + metrics = metrics_new + return metrics + + +@torch.no_grad() +def eval(cfg, args, model, weights_basename, criterion, best_value): + zero_filled = args.zero_filled + noise_arg = args.noise.lower() + motion_arg = args.motion.lower() + include_noise = noise_arg != "false" + include_motion = motion_arg != "false" + noise_sweep_vals = args.sweep_vals + motion_sweep_vals = args.sweep_vals_motion + skip_rescale = args.skip_rescale + overwrite = args.overwrite + save_scans = args.save_scans or "save_scans" in args.ops + compute_metrics = "metrics" in args.ops + # TODO: Set up W&B configuration. + # use_wandb = args.use_wandb + # if use_wandb: + # run = init_wandb_run(cfg, resume=True, job_type="eval", use_api=True) + + device = cfg.MODEL.DEVICE + model = model.to(device) + model = model.eval() + + # Get and load metrics file + output_dir = os.path.join(cfg.OUTPUT_DIR, "test_results") + metrics_file = os.path.join(output_dir, args.metrics_file) + if not overwrite and os.path.isfile(metrics_file): + metrics = pd.read_csv(metrics_file, index_col=0) + # Add default parameters to metrics. + metrics = add_default_params(metrics) + else: + metrics = None + + # Returns average or each scan + group_by_scan = True + + # Find range of noise values to search + if include_noise: + noise_vals = noise_sweep_vals if noise_arg == "sweep" else [0] + # noise_vals += list(cfg.MODEL.CONSISTENCY.AUG.NOISE.STD_DEV) + noise_vals = sorted(set(noise_vals)) + else: + noise_vals = [0] + + if include_motion: + motion_vals = [0] + motion_sweep_vals if motion_arg == "sweep" else [0] + motion_vals = sorted(set(motion_vals)) + else: + motion_vals = [0] + + values = itertools.product( + cfg.DATASETS.TEST, cfg.AUG_TEST.UNDERSAMPLE.ACCELERATIONS, noise_vals, motion_vals + ) + values = list(values) + all_results = [] + + default_metrics = ReconEvaluator.default_metrics() + if args.extra_metrics: + if not compute_metrics: + raise ValueError( + "Extra metrics were specified, but `--ops` did not " + "indicate eval should perform metric computation" + ) + default_metrics.extend(args.extra_metrics) + + for exp_idx, (dataset_name, acc, noise_level, motion_level) in enumerate(values): + # Check if the current configuration already has metrics computed + # If so, dont recompute + params = { + "Acceleration": acc, + "dataset": dataset_name, + "Noise Level": noise_level, + "Motion Level": motion_level, + "weights": weights_basename, + "rescaled": not skip_rescale, + } + eval_metrics = default_metrics + + logger.info("==" * 30) + logger.info("Experiment ({}/{})".format(exp_idx + 1, len(values))) + logger.info(", ".join([f"{k}: {v}" for k, v in params.items()])) + logger.info("==" * 30) + + existing_metrics = None + if metrics is not None and compute_metrics: + try: + existing_metrics = find_metrics(metrics, params) + except KeyError: + existing_metrics = None + if existing_metrics is not None and len(existing_metrics) > 0: + eval_metrics = list(set(eval_metrics) - set(existing_metrics.columns)) + if len(eval_metrics) == 0: + logger.info( + "Metrics for ({}) exist:\n{}".format( + ", ".join([f"{k}: {v}" for k, v in params.items()]), + tabulate(existing_metrics, headers=existing_metrics.columns), + ) + ) + all_results.append(existing_metrics) + continue + + # Add criterion and value after to avoid searching by it. + params.update({"Criterion Name": criterion, "Criterion Val": best_value}) + + # Assign the current acceleration + s_cfg = cfg.clone() + s_cfg.defrost() + s_cfg.AUG_TRAIN.UNDERSAMPLE.ACCELERATIONS = (acc,) + s_cfg.MODEL.CONSISTENCY.AUG.MOTION.RANGE = motion_level + s_cfg.MODEL.CONSISTENCY.AUG.NOISE.STD_DEV = (noise_level,) + s_cfg.freeze() + + # Build a recon val loader + dataloader = build_recon_val_loader( + s_cfg, + dataset_name, + as_test=True, + add_noise=noise_level > 0, + add_motion=motion_level > 0, + ) + + # Build evaluators. Only save reconstructions for last scan. + params_str = "-".join(f"{k}={v}" for k, v in params.items() if k != "dataset") + exp_output_dir = os.path.join(output_dir, dataset_name, params_str) + evaluators = [ + ReconEvaluator( + dataset_name, + s_cfg, + group_by_scan=group_by_scan, + skip_rescale=skip_rescale, + save_scans=save_scans, + output_dir=exp_output_dir, + metrics=eval_metrics if compute_metrics else False, + prefix=None, + ) + ] + # TODO: add support for multiple evaluators. + if zero_filled: + zf_output_dir = os.path.join(output_dir, dataset_name, "ZeroFilled-" + params_str) + + evaluators.append( + ZFReconEvaluator( + dataset_name, + s_cfg, + group_by_scan=group_by_scan, + skip_rescale=skip_rescale, + save_scans=save_scans, + output_dir=zf_output_dir, + metrics=eval_metrics if compute_metrics else False, + prefix=None, + ) + ) + evaluators = DatasetEvaluators(evaluators, as_list=True) + + results = inference_on_dataset(model, dataloader, evaluators) + results = [ + pd.DataFrame(x).T.reset_index().rename(columns={"index": "scan_name"}) for x in results + ] + + results[0]["Method"] = s_cfg.MODEL.META_ARCHITECTURE + if zero_filled: + results[1]["Method"] = "Zero-Filled" + scan_results = pd.concat(results, ignore_index=True) + + if existing_metrics is not None and len(existing_metrics) > 0: + scan_results = existing_metrics.merge( + scan_results, on=["scan_name", "Method"], suffixes=("", "_y") + ) + scan_results = scan_results.drop( + scan_results.filter(regex="_y$").columns.tolist(), axis=1 + ) + else: + for k, v in params.items(): + scan_results[k] = v + logger.info("\n" + tabulate(scan_results, headers=scan_results.columns)) + + all_results.append(scan_results) + del evaluators + del dataloader + # Currently don't support writing data because it takes too long + # logger.info("Saving data...") + # file_path = os.path.join(output_dir, dataset_name, "{}.h5".format(scan_name)) + # os.makedirs(os.path.dirname(file_path), exist_ok=True) + + if len(all_results) > 0: + all_results = pd.concat(all_results, ignore_index=True) + logger.info("Summary:\n{}".format(tabulate(all_results, headers=all_results.columns))) + else: + logger.info("No evaluation metrics were computed or available in this run") + + # Try to copy over old metrics information. + # TODO: If fails, it automatically saves the old file in a versioned + # form and prints logging message. + if compute_metrics: + if metrics is not None: + try: + running_results = update_metrics( + all_results, + metrics, + on=[ + "Acceleration", + "dataset", + "Noise Level", + "Motion Level", + "weights", + "Method", + "rescaled", + ], + ) + except KeyError as e: + logger.error(e) + logger.error("Failed to load old metrics information") + # raise e + running_results = all_results + else: + running_results = all_results + running_results.to_csv(metrics_file, mode="w") + + +def main(args): + cfg = setup(args) + model = DefaultTrainer.build_model(cfg) + if isinstance(model, CSModel): + weights, criterion, best_value = None, None, 0 + else: + metric = args.metric if args.metric else f"val_{cfg.MODEL.RECON_LOSS.NAME}" + weights, criterion, best_value = ( + (cfg.MODEL.WEIGHTS, None, None) + if cfg.MODEL.WEIGHTS + else find_weights(cfg, metric, iter_limit=args.iter_limit) + ) + model = model.to(cfg.MODEL.DEVICE) + Checkpointer(model, save_dir=cfg.OUTPUT_DIR).resume_or_load(weights, resume=args.resume) + + # See https://github.com/pytorch/pytorch/issues/42300 + logger.info("Checking weights were properly loaded...") + check_consistency(torch.load(weights)["model"], model) + + logger.info("\n\n==============================") + logger.info("Loading weights from {}".format(weights)) + + # Do not limit number of scans to evaluate during testing. + cfg.defrost() + cfg.DATALOADER.SUBSAMPLE_TRAIN.NUM_VAL = -1 + cfg.freeze() + + eval(cfg, args, model, os.path.basename(weights) if weights else None, criterion, best_value) + + +if __name__ == "__main__": + parser = default_argument_parser() + # parser.add_argument( + # "--dir", type=str, default=None, + # help="Process all completed experiment directories under this directory" + # ) + parser.add_argument( + "--metric", + "--criterion", + type=str, + default="", + help=( + "Val metric used to select weights. " + "Defaults to recon loss. " + "Ignored if `MODEL.WEIGHTS` specified" + ), + ) + parser.add_argument( + "--zero-filled", action="store_true", help="Calculate metrics for zero-filled images" + ) + parser.add_argument( + "--noise", + default="false", + choices=("false", "standard", "sweep"), + help="Type of noise evaluation", + ) + +## Arguments for 2D Motion Corruption of the Dataset ## + + parser.add_argument( + "--rotation", + default=0, + type=float, + help=( + "How much rotation should be used for motion corruption " + "of the dataset" + ) + ) + parser.add_argument( + "--translation", + default=0, + type=float, + help=( + "How much translation should be used for motion " + "corruption of the dataset" + ) + ) + parser.add_argument( + "--nshots", + default=0, + type=int, + help=( + "How many shots should be used for motion corruption " + "of the dataset." + ) + ) + parser.add_argument( + "--interleaved_or_blocked", + default="blocked", + choice=("interleaved", "blocked"), + help=( + "Chooses between interleaved or blocked shots for motion " + "corruption of the dataset" + ) + ) + parser.add_argument( + "--motion", + default="false", + choices=("false", "standard", "sweep"), + help="Type of motion evaluation", + ) + parser.add_argument( + "--sweep-vals", + default=[0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0], + nargs="*", + type=float, + help="args to sweep for noise", + ) + parser.add_argument("--extra-metrics", nargs="*", help="Extra metrics for testing") + parser.add_argument( + "--sweep-vals-motion", + default=[0, 0.2, 0.4], + nargs="*", + type=float, + help="args to sweep for motion", + ) + parser.add_argument( + "--iter-limit", + default=None, + type=int, + help=( + "Time limit. If negative, interpreted as epoch. " + "Chooses weights at or before this time point." + ), + ) + parser.add_argument("--overwrite", action="store_true", help="Overwrite existing metrics file") + parser.add_argument( + "--skip-rescale", action="store_true", help="Skip rescaling when evaluating" + ) + parser.add_argument("--save-scans", action="store_true", help="Save reconstruction outputs") + parser.add_argument("--metrics-file", type=str, default="metrics.csv", help="Metrics file") + # parser.add_argument( + # "--wandb", action="store_true", help="Log to W&B during evaluation" + # ) + parser.add_argument( + "--ops", + type=str, + nargs="*", + default=["metrics"], + choices=["metrics", "save_scans"], + help="Operations to run. 'metrics': Compute metrics. 'save_scans': Save Scans", + ) + + args = parser.parse_args() + args.ops = set(args.ops) + if args.save_scans: + args.ops |= {"save_scans"} + + print("Command Line Args:", args) + main(args) From c525ad68c9c9a54c53324fb91dc4c3db4c694f91 Mon Sep 17 00:00:00 2001 From: Deepro Pasha Date: Thu, 7 Jul 2022 19:08:27 -0700 Subject: [PATCH 02/33] made copy of build_recon_val_loader, adjusted parameters of eval_net_motion_corruption.py --- meddlr/data/build.py | 65 +++++++++++++++++++++++++++++ tools/eval_net_motion_corruption.py | 4 +- 2 files changed, 67 insertions(+), 2 deletions(-) diff --git a/meddlr/data/build.py b/meddlr/data/build.py index 884fea21..4f5bb131 100644 --- a/meddlr/data/build.py +++ b/meddlr/data/build.py @@ -243,6 +243,71 @@ def build_recon_train_loader(cfg, dataset_type=None): ) return train_loader +def motion_corrupted_build_recon_val_loader( + cfg, + dataset_name, + as_test: bool = False, + angle: float = 0, + translation_amount : float = 0, + n_shot_amount: int = 0, + interleaved: bool = False, + blocked: bool = False, + dataset_type=None, +): + if ( + cfg.DATALOADER.SUBSAMPLE_TRAIN.NUM_VAL > 0 + and cfg.DATALOADER.SUBSAMPLE_TRAIN.NUM_VAL_BY_GROUP + ): + raise ValueError( + "`DATALOADER.SUBSAMPLE_TRAIN.NUM_VAL` and " + "`DATALOADER.SUBSAMPLE_TRAIN.NUM_VAL_BY_GROUP` are mutually exclusive." + ) + num_scans_total = ( + cfg.DATALOADER.SUBSAMPLE_TRAIN.NUM_VAL_BY_GROUP + if cfg.DATALOADER.SUBSAMPLE_TRAIN.NUM_VAL_BY_GROUP + else cfg.DATALOADER.SUBSAMPLE_TRAIN.NUM_VAL + ) + + dataset_dicts = get_recon_dataset_dicts( + dataset_names=[dataset_name], + filter_by=cfg.DATALOADER.FILTER.BY, + num_scans_total=num_scans_total, + seed=cfg.DATALOADER.SUBSAMPLE_TRAIN.SEED, + ) + if dataset_type is None: + dataset_type = _get_default_dataset_type(dataset_name) + + mask_func = build_mask_func(cfg.AUG_TRAIN) + data_transform = T.DataTransform( + cfg, mask_func, is_test=as_test, add_noise=add_noise, add_motion=add_motion + ) + + val_data = _build_dataset( + cfg, dataset_dicts, data_transform, is_eval=True, dataset_type=dataset_type + ) + + # Build sampler. + sampler, is_batch_sampler = build_val_sampler(cfg, val_data) + if is_batch_sampler: + dl_kwargs = {"batch_sampler": sampler} + else: + dl_kwargs = { + "sampler": sampler, + "batch_size": cfg.SOLVER.TEST_BATCH_SIZE, + "shuffle": False, + "drop_last": False, + } + + val_loader = DataLoader( + dataset=val_data, + num_workers=cfg.DATALOADER.NUM_WORKERS, + pin_memory=True, + collate_fn=default_collate, + prefetch_factor=cfg.DATALOADER.PREFETCH_FACTOR, + **dl_kwargs, + ) + return val_loader + def build_recon_val_loader( cfg, diff --git a/tools/eval_net_motion_corruption.py b/tools/eval_net_motion_corruption.py index fb04230a..cf9521dd 100644 --- a/tools/eval_net_motion_corruption.py +++ b/tools/eval_net_motion_corruption.py @@ -462,11 +462,11 @@ def main(args): ## Arguments for 2D Motion Corruption of the Dataset ## parser.add_argument( - "--rotation", + "--angle", default=0, type=float, help=( - "How much rotation should be used for motion corruption " + "How much rotation angle should be used for motion corruption " "of the dataset" ) ) From 2676e8927d38b13dbc20b067585b89fee1f0e289 Mon Sep 17 00:00:00 2001 From: Deepro Pasha Date: Fri, 8 Jul 2022 10:45:46 -0700 Subject: [PATCH 03/33] renamed eval_net_motion_corruption.py to eval_net_motion.py --- tools/eval_net_motion.py | 554 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 554 insertions(+) create mode 100644 tools/eval_net_motion.py diff --git a/tools/eval_net_motion.py b/tools/eval_net_motion.py new file mode 100644 index 00000000..cf9521dd --- /dev/null +++ b/tools/eval_net_motion.py @@ -0,0 +1,554 @@ +"""Run inference on test set scans. + +This consists of comparing both zero-filled recon and DL-recon to fully-sampled +scans. All comparisons are done per volume (not per slice). + +Example: + python eval_net.py --config-file my/experiment/folder/config.yaml --metric val_psnr_scan +""" +import itertools +import os +from copy import deepcopy +from typing import Any, Dict, Sequence + +import pandas as pd +import torch +from tabulate import tabulate + +import meddlr.ops.complex as cplx +from meddlr.checkpoint import Checkpointer +from meddlr.config import get_cfg +from meddlr.data.build import build_recon_val_loader +from meddlr.engine import DefaultTrainer, default_argument_parser, default_setup +from meddlr.evaluation import DatasetEvaluators, ReconEvaluator, inference_on_dataset +from meddlr.evaluation.testing import check_consistency, find_weights +from meddlr.modeling.meta_arch import CSModel +from meddlr.utils.logger import setup_logger + +_FILE_NAME = os.path.splitext(os.path.basename(__file__))[0] +_LOGGER_NAME = "{}.{}".format(_FILE_NAME, __name__) +# logger = logging.getLogger(_LOGGER_NAME) +logger = None # initialize in setup() + +# Default values for parameters that may not have been initially added. +_DEFAULT_VALS = {"rescaled": True} + + +class ZFReconEvaluator(ReconEvaluator): + """Zero-filled recon evaluator.""" + + def process(self, inputs, outputs): + zf_out = {k: outputs[k] for k in ("target",)} + zf_image = outputs["zf_image"] + if cplx.is_complex_as_real(zf_image): + zf_image = torch.view_as_complex(zf_image) + zf_out["pred"] = zf_image + return super().process(inputs, zf_out) + + +def setup(args): + """ + Create configs and perform basic setups. + We do not save the config. + """ + cfg = get_cfg() + cfg.merge_from_file(args.config_file) + opts = args.opts + if opts and opts[0] == "--": + opts = opts[1:] + cfg.merge_from_list(opts) + cfg.freeze() + default_setup(cfg, args, save_cfg=False) + + # Setup logger for test results + global logger + dirname = "test_results" + logger = setup_logger(os.path.join(cfg.OUTPUT_DIR, dirname), name=_FILE_NAME) + + logger.info(f"Command Line Args: {args}") + return cfg + + +def add_default_params(metrics: pd.DataFrame, ignore_case=True): + """Adds default config parameters (if missing). + + Args: + metrics (pd.DataFrame): Will be filtered based on column values. + ignore_case (bool, optional): If `True`, ignores the column casing. + Raises `ValueError` if two columns have the same lower case + form. + """ + + df = deepcopy(metrics) + if ignore_case: + column_map = {x: x.lower() for x in df.columns} + defaults_keys_map = {k.lower(): k for k in _DEFAULT_VALS.keys()} + df = df.rename(columns=column_map) + else: + defaults_keys_map = {k: k for k in _DEFAULT_VALS.keys()} + + for fmt_key, real_key in defaults_keys_map.items(): + if fmt_key not in df.columns: + df[real_key] = _DEFAULT_VALS[real_key] + + if ignore_case: + df = df.rename(columns={v: k for k, v in column_map.items()}) + + return df + + +def find_metrics( + metrics: pd.DataFrame, params: Dict[str, Any], ignore_missing=False, ignore_case=True +): + """Find subset of metrics dictionary that matches parameter configuration. + + Note: + Values that are not available will be filled in by _DEFAULT_VALS. + + Args: + metrics (pd.DataFrame): Will be filtered based on column values. + params (Dict[str, Any]): Parameter values to filter by. + Keys should correspond to column names in `metrics`. + ignore_missing (bool, optional): If `True`, ignores filtering by + columns that are missing. + ignore_case (bool, optional): If `True`, ignores the column casing. + Raises `ValueError` if two columns have the same lower case + form. + + Returns: + df (pd.DataFrame): The remaining dataframe after filtering. + """ + + df = deepcopy(metrics) + if ignore_case: + column_map = {x: x.lower() for x in df.columns} + df = df.rename(columns=column_map) + params = {k.lower(): v for k, v in params.items()} + + # Fill in with default values if missing. + # Note these will always be lower case, so we match based on case. + # Fill in default values when the columns are not available. + default_keys = {x.lower() for x in params} & {x.lower() for x in _DEFAULT_VALS.keys()} + lowercase_cols = [x.lower() for x in df.columns] + for k in default_keys: + if k not in lowercase_cols: + df[k] = _DEFAULT_VALS[k] + + for k, v in params.items(): + if k not in df.columns: + if ignore_missing: + continue + else: + raise KeyError(f"No column {k} in `metrics`") + df = df[df[k] == v] + + # Undo matching by lower case. + if ignore_case: + df = df.rename(columns={v: k for k, v in column_map.items()}) + + return df + + +def update_metrics(metrics_new: pd.DataFrame, metrics_old: pd.DataFrame, on: Sequence[str]): + """Update a previous metrics version with the new one. + + Metrics that were previously computed, may not be recomputed. + To avoid overwriting them when writing to a csv, we want to + port over any old metrics that we did not recompute. + """ + # We currently do not support missing columns. + missing_cols = [k not in metrics_old.columns for k in on] + if any(missing_cols): + raise KeyError(f"Column(s) {missing_cols} not found in `metrics_old`") + missing_cols = [k not in metrics_new.columns for k in on] + if any(missing_cols): + raise KeyError(f"Column(s) {missing_cols} not found in `metrics_new`") + + # Find combination of columns to select on that is not + # available in the new metrics, but is available in the + # old metrics. + old_metrics_combos = list(itertools.product(*[metrics_old[k].unique().tolist() for k in on])) + new_metrics_combos = list(itertools.product(*[metrics_new[k].unique().tolist() for k in on])) + + to_prepend = [] + for combo in old_metrics_combos: + if combo not in new_metrics_combos: + combo_as_dict = {k: v for k, v in zip(on, combo)} + to_prepend.append(find_metrics(metrics_old, combo_as_dict)) + + if len(to_prepend) > 0: + to_prepend = pd.concat(to_prepend, ignore_index=True) + metrics = pd.concat([to_prepend, metrics_new], ignore_index=True) + else: + metrics = metrics_new + return metrics + + +@torch.no_grad() +def eval(cfg, args, model, weights_basename, criterion, best_value): + zero_filled = args.zero_filled + noise_arg = args.noise.lower() + motion_arg = args.motion.lower() + include_noise = noise_arg != "false" + include_motion = motion_arg != "false" + noise_sweep_vals = args.sweep_vals + motion_sweep_vals = args.sweep_vals_motion + skip_rescale = args.skip_rescale + overwrite = args.overwrite + save_scans = args.save_scans or "save_scans" in args.ops + compute_metrics = "metrics" in args.ops + # TODO: Set up W&B configuration. + # use_wandb = args.use_wandb + # if use_wandb: + # run = init_wandb_run(cfg, resume=True, job_type="eval", use_api=True) + + device = cfg.MODEL.DEVICE + model = model.to(device) + model = model.eval() + + # Get and load metrics file + output_dir = os.path.join(cfg.OUTPUT_DIR, "test_results") + metrics_file = os.path.join(output_dir, args.metrics_file) + if not overwrite and os.path.isfile(metrics_file): + metrics = pd.read_csv(metrics_file, index_col=0) + # Add default parameters to metrics. + metrics = add_default_params(metrics) + else: + metrics = None + + # Returns average or each scan + group_by_scan = True + + # Find range of noise values to search + if include_noise: + noise_vals = noise_sweep_vals if noise_arg == "sweep" else [0] + # noise_vals += list(cfg.MODEL.CONSISTENCY.AUG.NOISE.STD_DEV) + noise_vals = sorted(set(noise_vals)) + else: + noise_vals = [0] + + if include_motion: + motion_vals = [0] + motion_sweep_vals if motion_arg == "sweep" else [0] + motion_vals = sorted(set(motion_vals)) + else: + motion_vals = [0] + + values = itertools.product( + cfg.DATASETS.TEST, cfg.AUG_TEST.UNDERSAMPLE.ACCELERATIONS, noise_vals, motion_vals + ) + values = list(values) + all_results = [] + + default_metrics = ReconEvaluator.default_metrics() + if args.extra_metrics: + if not compute_metrics: + raise ValueError( + "Extra metrics were specified, but `--ops` did not " + "indicate eval should perform metric computation" + ) + default_metrics.extend(args.extra_metrics) + + for exp_idx, (dataset_name, acc, noise_level, motion_level) in enumerate(values): + # Check if the current configuration already has metrics computed + # If so, dont recompute + params = { + "Acceleration": acc, + "dataset": dataset_name, + "Noise Level": noise_level, + "Motion Level": motion_level, + "weights": weights_basename, + "rescaled": not skip_rescale, + } + eval_metrics = default_metrics + + logger.info("==" * 30) + logger.info("Experiment ({}/{})".format(exp_idx + 1, len(values))) + logger.info(", ".join([f"{k}: {v}" for k, v in params.items()])) + logger.info("==" * 30) + + existing_metrics = None + if metrics is not None and compute_metrics: + try: + existing_metrics = find_metrics(metrics, params) + except KeyError: + existing_metrics = None + if existing_metrics is not None and len(existing_metrics) > 0: + eval_metrics = list(set(eval_metrics) - set(existing_metrics.columns)) + if len(eval_metrics) == 0: + logger.info( + "Metrics for ({}) exist:\n{}".format( + ", ".join([f"{k}: {v}" for k, v in params.items()]), + tabulate(existing_metrics, headers=existing_metrics.columns), + ) + ) + all_results.append(existing_metrics) + continue + + # Add criterion and value after to avoid searching by it. + params.update({"Criterion Name": criterion, "Criterion Val": best_value}) + + # Assign the current acceleration + s_cfg = cfg.clone() + s_cfg.defrost() + s_cfg.AUG_TRAIN.UNDERSAMPLE.ACCELERATIONS = (acc,) + s_cfg.MODEL.CONSISTENCY.AUG.MOTION.RANGE = motion_level + s_cfg.MODEL.CONSISTENCY.AUG.NOISE.STD_DEV = (noise_level,) + s_cfg.freeze() + + # Build a recon val loader + dataloader = build_recon_val_loader( + s_cfg, + dataset_name, + as_test=True, + add_noise=noise_level > 0, + add_motion=motion_level > 0, + ) + + # Build evaluators. Only save reconstructions for last scan. + params_str = "-".join(f"{k}={v}" for k, v in params.items() if k != "dataset") + exp_output_dir = os.path.join(output_dir, dataset_name, params_str) + evaluators = [ + ReconEvaluator( + dataset_name, + s_cfg, + group_by_scan=group_by_scan, + skip_rescale=skip_rescale, + save_scans=save_scans, + output_dir=exp_output_dir, + metrics=eval_metrics if compute_metrics else False, + prefix=None, + ) + ] + # TODO: add support for multiple evaluators. + if zero_filled: + zf_output_dir = os.path.join(output_dir, dataset_name, "ZeroFilled-" + params_str) + + evaluators.append( + ZFReconEvaluator( + dataset_name, + s_cfg, + group_by_scan=group_by_scan, + skip_rescale=skip_rescale, + save_scans=save_scans, + output_dir=zf_output_dir, + metrics=eval_metrics if compute_metrics else False, + prefix=None, + ) + ) + evaluators = DatasetEvaluators(evaluators, as_list=True) + + results = inference_on_dataset(model, dataloader, evaluators) + results = [ + pd.DataFrame(x).T.reset_index().rename(columns={"index": "scan_name"}) for x in results + ] + + results[0]["Method"] = s_cfg.MODEL.META_ARCHITECTURE + if zero_filled: + results[1]["Method"] = "Zero-Filled" + scan_results = pd.concat(results, ignore_index=True) + + if existing_metrics is not None and len(existing_metrics) > 0: + scan_results = existing_metrics.merge( + scan_results, on=["scan_name", "Method"], suffixes=("", "_y") + ) + scan_results = scan_results.drop( + scan_results.filter(regex="_y$").columns.tolist(), axis=1 + ) + else: + for k, v in params.items(): + scan_results[k] = v + logger.info("\n" + tabulate(scan_results, headers=scan_results.columns)) + + all_results.append(scan_results) + del evaluators + del dataloader + # Currently don't support writing data because it takes too long + # logger.info("Saving data...") + # file_path = os.path.join(output_dir, dataset_name, "{}.h5".format(scan_name)) + # os.makedirs(os.path.dirname(file_path), exist_ok=True) + + if len(all_results) > 0: + all_results = pd.concat(all_results, ignore_index=True) + logger.info("Summary:\n{}".format(tabulate(all_results, headers=all_results.columns))) + else: + logger.info("No evaluation metrics were computed or available in this run") + + # Try to copy over old metrics information. + # TODO: If fails, it automatically saves the old file in a versioned + # form and prints logging message. + if compute_metrics: + if metrics is not None: + try: + running_results = update_metrics( + all_results, + metrics, + on=[ + "Acceleration", + "dataset", + "Noise Level", + "Motion Level", + "weights", + "Method", + "rescaled", + ], + ) + except KeyError as e: + logger.error(e) + logger.error("Failed to load old metrics information") + # raise e + running_results = all_results + else: + running_results = all_results + running_results.to_csv(metrics_file, mode="w") + + +def main(args): + cfg = setup(args) + model = DefaultTrainer.build_model(cfg) + if isinstance(model, CSModel): + weights, criterion, best_value = None, None, 0 + else: + metric = args.metric if args.metric else f"val_{cfg.MODEL.RECON_LOSS.NAME}" + weights, criterion, best_value = ( + (cfg.MODEL.WEIGHTS, None, None) + if cfg.MODEL.WEIGHTS + else find_weights(cfg, metric, iter_limit=args.iter_limit) + ) + model = model.to(cfg.MODEL.DEVICE) + Checkpointer(model, save_dir=cfg.OUTPUT_DIR).resume_or_load(weights, resume=args.resume) + + # See https://github.com/pytorch/pytorch/issues/42300 + logger.info("Checking weights were properly loaded...") + check_consistency(torch.load(weights)["model"], model) + + logger.info("\n\n==============================") + logger.info("Loading weights from {}".format(weights)) + + # Do not limit number of scans to evaluate during testing. + cfg.defrost() + cfg.DATALOADER.SUBSAMPLE_TRAIN.NUM_VAL = -1 + cfg.freeze() + + eval(cfg, args, model, os.path.basename(weights) if weights else None, criterion, best_value) + + +if __name__ == "__main__": + parser = default_argument_parser() + # parser.add_argument( + # "--dir", type=str, default=None, + # help="Process all completed experiment directories under this directory" + # ) + parser.add_argument( + "--metric", + "--criterion", + type=str, + default="", + help=( + "Val metric used to select weights. " + "Defaults to recon loss. " + "Ignored if `MODEL.WEIGHTS` specified" + ), + ) + parser.add_argument( + "--zero-filled", action="store_true", help="Calculate metrics for zero-filled images" + ) + parser.add_argument( + "--noise", + default="false", + choices=("false", "standard", "sweep"), + help="Type of noise evaluation", + ) + +## Arguments for 2D Motion Corruption of the Dataset ## + + parser.add_argument( + "--angle", + default=0, + type=float, + help=( + "How much rotation angle should be used for motion corruption " + "of the dataset" + ) + ) + parser.add_argument( + "--translation", + default=0, + type=float, + help=( + "How much translation should be used for motion " + "corruption of the dataset" + ) + ) + parser.add_argument( + "--nshots", + default=0, + type=int, + help=( + "How many shots should be used for motion corruption " + "of the dataset." + ) + ) + parser.add_argument( + "--interleaved_or_blocked", + default="blocked", + choice=("interleaved", "blocked"), + help=( + "Chooses between interleaved or blocked shots for motion " + "corruption of the dataset" + ) + ) + parser.add_argument( + "--motion", + default="false", + choices=("false", "standard", "sweep"), + help="Type of motion evaluation", + ) + parser.add_argument( + "--sweep-vals", + default=[0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0], + nargs="*", + type=float, + help="args to sweep for noise", + ) + parser.add_argument("--extra-metrics", nargs="*", help="Extra metrics for testing") + parser.add_argument( + "--sweep-vals-motion", + default=[0, 0.2, 0.4], + nargs="*", + type=float, + help="args to sweep for motion", + ) + parser.add_argument( + "--iter-limit", + default=None, + type=int, + help=( + "Time limit. If negative, interpreted as epoch. " + "Chooses weights at or before this time point." + ), + ) + parser.add_argument("--overwrite", action="store_true", help="Overwrite existing metrics file") + parser.add_argument( + "--skip-rescale", action="store_true", help="Skip rescaling when evaluating" + ) + parser.add_argument("--save-scans", action="store_true", help="Save reconstruction outputs") + parser.add_argument("--metrics-file", type=str, default="metrics.csv", help="Metrics file") + # parser.add_argument( + # "--wandb", action="store_true", help="Log to W&B during evaluation" + # ) + parser.add_argument( + "--ops", + type=str, + nargs="*", + default=["metrics"], + choices=["metrics", "save_scans"], + help="Operations to run. 'metrics': Compute metrics. 'save_scans': Save Scans", + ) + + args = parser.parse_args() + args.ops = set(args.ops) + if args.save_scans: + args.ops |= {"save_scans"} + + print("Command Line Args:", args) + main(args) From 75da90fc9f313162db592450d59468b79955dd43 Mon Sep 17 00:00:00 2001 From: Deepro Pasha Date: Fri, 8 Jul 2022 11:14:29 -0700 Subject: [PATCH 04/33] removed the motion corrupted version of build_recon_val_loader, will just be modifying build_recon_val_laoder --- meddlr/data/build.py | 66 -------------------------------------------- 1 file changed, 66 deletions(-) diff --git a/meddlr/data/build.py b/meddlr/data/build.py index 4f5bb131..f72090de 100644 --- a/meddlr/data/build.py +++ b/meddlr/data/build.py @@ -243,72 +243,6 @@ def build_recon_train_loader(cfg, dataset_type=None): ) return train_loader -def motion_corrupted_build_recon_val_loader( - cfg, - dataset_name, - as_test: bool = False, - angle: float = 0, - translation_amount : float = 0, - n_shot_amount: int = 0, - interleaved: bool = False, - blocked: bool = False, - dataset_type=None, -): - if ( - cfg.DATALOADER.SUBSAMPLE_TRAIN.NUM_VAL > 0 - and cfg.DATALOADER.SUBSAMPLE_TRAIN.NUM_VAL_BY_GROUP - ): - raise ValueError( - "`DATALOADER.SUBSAMPLE_TRAIN.NUM_VAL` and " - "`DATALOADER.SUBSAMPLE_TRAIN.NUM_VAL_BY_GROUP` are mutually exclusive." - ) - num_scans_total = ( - cfg.DATALOADER.SUBSAMPLE_TRAIN.NUM_VAL_BY_GROUP - if cfg.DATALOADER.SUBSAMPLE_TRAIN.NUM_VAL_BY_GROUP - else cfg.DATALOADER.SUBSAMPLE_TRAIN.NUM_VAL - ) - - dataset_dicts = get_recon_dataset_dicts( - dataset_names=[dataset_name], - filter_by=cfg.DATALOADER.FILTER.BY, - num_scans_total=num_scans_total, - seed=cfg.DATALOADER.SUBSAMPLE_TRAIN.SEED, - ) - if dataset_type is None: - dataset_type = _get_default_dataset_type(dataset_name) - - mask_func = build_mask_func(cfg.AUG_TRAIN) - data_transform = T.DataTransform( - cfg, mask_func, is_test=as_test, add_noise=add_noise, add_motion=add_motion - ) - - val_data = _build_dataset( - cfg, dataset_dicts, data_transform, is_eval=True, dataset_type=dataset_type - ) - - # Build sampler. - sampler, is_batch_sampler = build_val_sampler(cfg, val_data) - if is_batch_sampler: - dl_kwargs = {"batch_sampler": sampler} - else: - dl_kwargs = { - "sampler": sampler, - "batch_size": cfg.SOLVER.TEST_BATCH_SIZE, - "shuffle": False, - "drop_last": False, - } - - val_loader = DataLoader( - dataset=val_data, - num_workers=cfg.DATALOADER.NUM_WORKERS, - pin_memory=True, - collate_fn=default_collate, - prefetch_factor=cfg.DATALOADER.PREFETCH_FACTOR, - **dl_kwargs, - ) - return val_loader - - def build_recon_val_loader( cfg, dataset_name, From e53c04c7e396e89e4c0fd304341dbf819b8721b1 Mon Sep 17 00:00:00 2001 From: Deepro Pasha Date: Fri, 8 Jul 2022 12:28:03 -0700 Subject: [PATCH 05/33] Added MotionDataTransform --- meddlr/data/transforms/transform.py | 203 ++++++++++++++++++++++++++++ 1 file changed, 203 insertions(+) diff --git a/meddlr/data/transforms/transform.py b/meddlr/data/transforms/transform.py index 6c992988..9a644850 100644 --- a/meddlr/data/transforms/transform.py +++ b/meddlr/data/transforms/transform.py @@ -364,3 +364,206 @@ def __call__(self, kspace, maps, target, fname, slice_id, is_fixed, acceleration target = target.squeeze(0) return masked_kspace, maps, target, mean, std, norm + + +class MotionDataTransform: + """ + Data Transformer for training unrolled reconstruction models. + + This is for emulating 2D roto-translational motion corrupted MR scans. + """ + + def __init__( + self, + cfg, + mask_func, + is_test: bool = False, + add_noise: bool = False, + add_motion: bool = False, + angle: float = 0, + translation: float = 0, + nshots: int = 0, + blocked: bool = True, + interleaved: bool = False + ): + """ + Args: + mask_func (utils.subsample.MaskFunc): A function that can create a + mask of appropriate shape. + is_test (bool): If `True`, this class behaves with test-time + functionality. In particular, it computes a pseudo random number + generator seed from the filename. This ensures that the same + mask is used for all the slices of a given volume every time. + """ + from meddlr.transforms.builtin.mri import MRIReconAugmentor + + self._cfg = cfg + self.mask_func = mask_func + self._is_test = is_test + + # Build subsampler. + # mask_func = build_mask_func(cfg) + self._subsampler = Subsampler(self.mask_func) + self.add_noise = add_noise + self.add_motion = add_motion + + # These will be used for the motion corruption. + self.angle = angle + self.translation = translation + self.nshots = nshots + self.blocked = blocked + self.interleaved = interleaved + + seed = cfg.SEED if cfg.SEED > -1 else None + self.rng = np.random.RandomState(seed) + + if is_test: + # When we test we dont want to initialize with certain parameters (e.g. scheduler). + self.noiser = NoiseModel(cfg.MODEL.CONSISTENCY.AUG.NOISE.STD_DEV, seed=seed) + self.motion_simulator = MotionModel(cfg.MODEL.CONSISTENCY.AUG.MOTION.RANGE, seed=seed) + else: + self.noiser = NoiseModel.from_cfg(cfg, seed=seed) + self.motion_simulator = MotionModel.from_cfg(cfg, seed=seed) + + self.p_noise = cfg.AUG_TRAIN.NOISE_P + self.p_motion = cfg.AUG_TRAIN.MOTION_P + self._normalizer = build_normalizer(cfg) + + # Build augmentation pipeline. + self.augmentor = None + if not is_test and cfg.AUG_TRAIN.MRI_RECON.TRANSFORMS: + self.augmentor = MRIReconAugmentor.from_cfg(cfg, aug_kind="aug_train", seed=seed) + + def _call_augmentor( + self, kspace, maps, target, fname, slice_id, is_fixed, acceleration: int = None + ): + assert not self._is_test, "Augmentor is not supported with testing yet" + + # Convert everything from numpy arrays to tensors + kspace = cplx.to_tensor(kspace).unsqueeze(0) + maps = cplx.to_tensor(maps).unsqueeze(0) + target_init = cplx.to_tensor(target).unsqueeze(0) + target = ( + torch.complex(target_init, torch.zeros_like(target_init)).unsqueeze(-1) + if not torch.is_complex(target_init) + else target_init + ) # handle rss vs. sensitivity-integrated + norm = torch.sqrt(torch.mean(cplx.abs(target) ** 2)) + + seed = sum(tuple(map(ord, fname))) if self._is_test or is_fixed else None # noqa + mask_gen = partial( + self._subsampler.__call__, mode="2D", seed=seed, acceleration=acceleration + ) + + out, _, _ = self.augmentor( + kspace, + maps=maps, + target=target, + normalizer=self._normalizer, + mask_gen=mask_gen, + skip_tfm=is_fixed, # Skip augmentations for unsupervised scans. + ) + masked_kspace = out["kspace"] + maps = out["maps"] + target = out["target"] + mean = out["mean"] + std = out["std"] + + # Get rid of batch dimension... + masked_kspace = masked_kspace.squeeze(0) + maps = maps.squeeze(0) + target = target.squeeze(0) + + return masked_kspace, maps, target, mean, std, norm + + def __call__(self, kspace, maps, target, fname, slice_id, is_fixed, acceleration: int = None): + """ + Args: + kspace (numpy.array): Input k-space of shape + (num_coils, rows, cols, 2) for multi-coil + data or (rows, cols, 2) for single coil data. + target (numpy.array): Target image + attrs (dict): Acquisition related information stored in the HDF5 + object. + fname (str): File name + slice (int): Serial number of the slice. + is_fixed (bool, optional): If `True`, transform the example + to have a fixed mask and acceleration factor. + acceleration (int): Acceleration factor. Must be provided if + `is_undersampled=True`. + Returns: + (tuple): tuple containing: + image (torch.Tensor): Zero-filled input image. + target (torch.Tensor): Target image converted to a torch Tensor. + mean (float): Mean value used for normalization. + std (float): Standard deviation value used for normalization. + norm (float): L2 norm of the entire volume. + """ + if is_fixed and not acceleration: + raise ValueError("Accelerations must be specified for undersampled scans") + + # If augmentor is defined, use it to do computation. + if self.augmentor is not None: + return self._call_augmentor( + kspace, maps, target, fname, slice_id, is_fixed, acceleration + ) + + # Convert everything from numpy arrays to tensors + kspace = cplx.to_tensor(kspace).unsqueeze(0) + maps = cplx.to_tensor(maps).unsqueeze(0) + target_init = cplx.to_tensor(target).unsqueeze(0) + target = ( + torch.complex(target_init, torch.zeros_like(target_init)).unsqueeze(-1) + if not torch.is_complex(target_init) + else target_init + ) # handle rss vs. sensitivity-integrated + norm = torch.sqrt(torch.mean(cplx.abs(target) ** 2)) + + # TODO: Add other transforms here. + + # Apply mask in k-space + seed = sum(tuple(map(ord, fname))) if self._is_test or is_fixed else None # noqa + masked_kspace, mask = self._subsampler( + kspace, mode="2D", seed=seed, acceleration=acceleration + ) + + # Zero-filled Sense Recon. + if torch.is_complex(target_init): + A = SenseModel(maps, weights=mask) + image = A(masked_kspace, adjoint=True) + # Zero-filled RSS Recon. + else: + image = T.ifft2(masked_kspace) + image_rss = torch.sqrt(torch.sum(cplx.abs(image) ** 2, axis=-1)) + image = torch.complex(image_rss, torch.zeros_like(image_rss)).unsqueeze(-1) + + # Normalize + normalized = self._normalizer.normalize( + **{"masked_kspace": masked_kspace, "image": image, "target": target, "mask": mask} + ) + masked_kspace = normalized["masked_kspace"] + target = normalized["target"] + mean = normalized["mean"] + std = normalized["std"] + + add_noise = self.add_noise and ( + self._is_test or (not is_fixed and self.rng.uniform() < self.p_noise) + ) + add_motion = self.add_motion and ( + self._is_test or (not is_fixed and self.rng.uniform() < self.p_motion) + ) + if add_noise: + # Seed should be different for each slice of a scan. + noise_seed = seed + slice_id if seed is not None else None + masked_kspace = self.noiser(masked_kspace, mask=mask, seed=noise_seed) + if add_motion: + # Motion seed should not be different for each slice for now. + # TODO: Change this for 2D acquisitions. + masked_kspace = self.motion_simulator(masked_kspace, seed=seed) + # Get rid of batch dimension... + masked_kspace = masked_kspace.squeeze(0) + maps = maps.squeeze(0) + target = target.squeeze(0) + + return masked_kspace, maps, target, mean, std, norm + From e046249cc486c3a02e974af56ecda1c2fef952c6 Mon Sep 17 00:00:00 2001 From: Deepro Pasha Date: Sat, 9 Jul 2022 14:59:33 -0700 Subject: [PATCH 06/33] Finished writing 2DMotionModel and MotionDataTransform for 2D roto-translational motion corruption --- meddlr/data/transforms/2D_motion.py | 80 +++++++++++++++++++++++++++++ meddlr/data/transforms/transform.py | 26 ++++++---- 2 files changed, 96 insertions(+), 10 deletions(-) create mode 100644 meddlr/data/transforms/2D_motion.py diff --git a/meddlr/data/transforms/2D_motion.py b/meddlr/data/transforms/2D_motion.py new file mode 100644 index 00000000..d321f6a0 --- /dev/null +++ b/meddlr/data/transforms/2D_motion.py @@ -0,0 +1,80 @@ +from typing import Optional, Tuple, Sequence, Union + +import math +import numpy as np +import torch +import meddlr.ops as F +from meddlr.transforms import RandomAffine, RandomTranslation, TransformList +from meddlr.forward.mri import SenseModel + +from meddlr.utils.events import get_event_storage + + +class 2DMotionModel: + """A model that corrupts kspace inputs with motion. + + Motion is a common artifact experienced during the MR imaging forward problem. + When a patient moves, the recorded (expected) location of the kspace sample is + different than the actual location where the kspace sample that was acquired. + This module is responsible for simulating different motion artifacts. + """ + + def __init__(self, nshots, angle, translate, trajectory): + """ + Args: + nshots (int) : The number of shots in the image. + This should be equivalent to ceil(phase_encode_dim / + echo_train_length). + angle : The (min, max) angle for rotation. Values should be in + degrees and should be >=-180, <=180. Use 'None' to + ignore rotation. + translate: The fraction of (height, width) to translate. + e.g. 0.1 => 10% of the corresponding dimension. + So (0.1, 0.2) => 10% of height, 20% of width. + Use 'None' to ignore translation. + trajectory: One of 'interleaved' or 'consecutive'. + """ + self.nshots = nshots + self.angle = angle + self.translate = translate + self.trajectory = trajectory + + def __call__(self, *args, **kwargs): + return self.forward(*args, **kwargs) + + def forward(self, image) -> torch.Tensor: + """ + Simulate 2D motion for multi-shot Cartesian MRI. + + This function supports two trajectories: + - 'blocked' : Where each shot corresponds to a consecutive block of + kspace. (e.g. 1 1 2 2 3 3) + - 'interleaved' : Where shots are interleaved (e.g. 1 2 3 1 2 3) + + We assume the phase encode direction is left to right (i.e. along + width dimesion). + + TODO: Add support for sensitivity maps. + + Args: + image : The complex-valued iamge. Shape [..., height, width]. + + Returns: + The motion corrupted kspace. + """ + tfm_gen = RandomAffine(p = 1.0, translate=translate, angle=angle) + kspace = torch.zeros_like(image) + offset = int(math.ceil(kspace.shape[-1] / nshots)) + + for shot in range(nshots): + motion_image = tfm_gen.get_transform(image).apply_image(image) + motion_kspace = F.fft2c(motion_image) + if trajectory == "blocked": + kspace[..., shot*offset:(shot+1)*offset] = motion_kspace[..., shot*offset:(shot+1)*offset] + elif trajectory == "interleaved": + kspace[..., shot::nshots] = motion_kspace[..., shot::nshots] + else: + raise ValueError(f"trajectory '{trajectory}' not supported.") + + return kspace + diff --git a/meddlr/data/transforms/transform.py b/meddlr/data/transforms/transform.py index 9a644850..2668dd30 100644 --- a/meddlr/data/transforms/transform.py +++ b/meddlr/data/transforms/transform.py @@ -11,6 +11,7 @@ from meddlr.utils import transforms as T from .motion import MotionModel +from .motion import 2DMotionModel from .noise import NoiseModel NORMALIZER_REGISTRY = Registry("NORMALIZER") @@ -383,8 +384,7 @@ def __init__( angle: float = 0, translation: float = 0, nshots: int = 0, - blocked: bool = True, - interleaved: bool = False + trajectory: str = "blocked" ): """ Args: @@ -411,8 +411,7 @@ def __init__( self.angle = angle self.translation = translation self.nshots = nshots - self.blocked = blocked - self.interleaved = interleaved + self.trajectory = trajectory seed = cfg.SEED if cfg.SEED > -1 else None self.rng = np.random.RandomState(seed) @@ -420,10 +419,14 @@ def __init__( if is_test: # When we test we dont want to initialize with certain parameters (e.g. scheduler). self.noiser = NoiseModel(cfg.MODEL.CONSISTENCY.AUG.NOISE.STD_DEV, seed=seed) - self.motion_simulator = MotionModel(cfg.MODEL.CONSISTENCY.AUG.MOTION.RANGE, seed=seed) + self.motion_simulator = 2DMotionModel(self.nshots, self.angle, + self.translation, + self.trajectory) else: self.noiser = NoiseModel.from_cfg(cfg, seed=seed) - self.motion_simulator = MotionModel.from_cfg(cfg, seed=seed) + self.motion_simulator = 2DMotionModel(self.nshots, self.angle, + self.translation, + self.trajectory) self.p_noise = cfg.AUG_TRAIN.NOISE_P self.p_motion = cfg.AUG_TRAIN.MOTION_P @@ -552,14 +555,17 @@ def __call__(self, kspace, maps, target, fname, slice_id, is_fixed, acceleration add_motion = self.add_motion and ( self._is_test or (not is_fixed and self.rng.uniform() < self.p_motion) ) + if add_motion: + # Motion seed should not be different for each slice for now. + # TODO: Change this for 2D acquisitions. + # masked_kspace = self.motion_simulator(masked_kspace, seed=seed) + masked_kspace = self.motion_simulator(image) + if add_noise: # Seed should be different for each slice of a scan. noise_seed = seed + slice_id if seed is not None else None masked_kspace = self.noiser(masked_kspace, mask=mask, seed=noise_seed) - if add_motion: - # Motion seed should not be different for each slice for now. - # TODO: Change this for 2D acquisitions. - masked_kspace = self.motion_simulator(masked_kspace, seed=seed) + # Get rid of batch dimension... masked_kspace = masked_kspace.squeeze(0) maps = maps.squeeze(0) From 955e00c81fc417de2f63a78c43d57c16cf5235b0 Mon Sep 17 00:00:00 2001 From: Deepro Pasha Date: Sat, 9 Jul 2022 15:12:05 -0700 Subject: [PATCH 07/33] updated build_recon_val_loader to use MotionDataTransform, takes in angle, translation, nshots, and trajectory as paramters --- meddlr/data/build.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/meddlr/data/build.py b/meddlr/data/build.py index f72090de..9c7dadbf 100644 --- a/meddlr/data/build.py +++ b/meddlr/data/build.py @@ -249,6 +249,10 @@ def build_recon_val_loader( as_test: bool = False, add_noise: bool = False, add_motion: bool = False, + angle: float = 0, + translation: float = 0, + nshots: int = 0, + trajectory: str = "blocked", dataset_type=None, ): if ( @@ -275,8 +279,10 @@ def build_recon_val_loader( dataset_type = _get_default_dataset_type(dataset_name) mask_func = build_mask_func(cfg.AUG_TRAIN) - data_transform = T.DataTransform( - cfg, mask_func, is_test=as_test, add_noise=add_noise, add_motion=add_motion + data_transform = T.MotionDataTransform( + cfg, mask_func, is_test=as_test, add_noise=add_noise, + add_motion=add_motion, angle=angle, translation=translation, + nshots=nshots, trajectory=trajectory ) val_data = _build_dataset( From 951230c67cc57aadd09f06415d08c2bc8136043c Mon Sep 17 00:00:00 2001 From: Deepro Pasha Date: Sat, 9 Jul 2022 16:42:47 -0700 Subject: [PATCH 08/33] added changes to eval_net_motion.py to take 2D Motion corruption parameters --- tools/eval_net_motion.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/tools/eval_net_motion.py b/tools/eval_net_motion.py index cf9521dd..abe49424 100644 --- a/tools/eval_net_motion.py +++ b/tools/eval_net_motion.py @@ -187,8 +187,14 @@ def update_metrics(metrics_new: pd.DataFrame, metrics_old: pd.DataFrame, on: Seq @torch.no_grad() def eval(cfg, args, model, weights_basename, criterion, best_value): zero_filled = args.zero_filled + angle = args.angle + translation = args.translation + nshots = args.nshots + trajectory = args.trajectory.lower() + noise_arg = args.noise.lower() motion_arg = args.motion.lower() + include_noise = noise_arg != "false" include_motion = motion_arg != "false" noise_sweep_vals = args.sweep_vals @@ -302,6 +308,10 @@ def eval(cfg, args, model, weights_basename, criterion, best_value): as_test=True, add_noise=noise_level > 0, add_motion=motion_level > 0, + angle=angle, + translation=translation, + nshots=nshots, + trajectory=trajectory, ) # Build evaluators. Only save reconstructions for last scan. @@ -489,7 +499,7 @@ def main(args): ) ) parser.add_argument( - "--interleaved_or_blocked", + "--trajectory", default="blocked", choice=("interleaved", "blocked"), help=( From 12b6e14666c6dcfc4f183091f886dcec1157ec5a Mon Sep 17 00:00:00 2001 From: Deepro Pasha Date: Sun, 10 Jul 2022 12:54:10 -0700 Subject: [PATCH 09/33] Changed name of 2DMotionModel to MotionModel2D --- meddlr/data/transforms/.2D_motion.py.swp | Bin 0 -> 12288 bytes meddlr/data/transforms/2D_motion.py | 2 +- meddlr/data/transforms/transform.py | 6 +++--- 3 files changed, 4 insertions(+), 4 deletions(-) create mode 100644 meddlr/data/transforms/.2D_motion.py.swp diff --git a/meddlr/data/transforms/.2D_motion.py.swp b/meddlr/data/transforms/.2D_motion.py.swp new file mode 100644 index 0000000000000000000000000000000000000000..0ab19826ee62c0fc052539105acdcf4f111b7b7f GIT binary patch literal 12288 zcmeHNON<;x87>0>l8^_nxWMISBD6DRre|k-u_mk~ySMBsQ{i01|mPY!?etoSBw<5KFCv@hQK*k>-QVsyuj9^I6w;l*ln z=jMe;<19Dhcsz{`S+lGbtMS$7=@1#@nY@8aXzV%ym*!nI2E~=UHJ? zkw@kE1@Ya3j)9JW$1zZwWOVHradkS`wL@O)eUdJ}aQAVeyXPGP9RnQ$9RnQ$9RnQ$ z9RnQ$9RvS|44Cqg_!|6rU-0ctIA40?9B#W)$3VwG$3VwG$3VwG$3VwG$3VwG$3VwG z$3Vxx{~!Z07UFIE@&EVt!+8Avzxn(Bo#%vj2>cBA5%4zf7Vr}AQQ&#t_sbvrfCd=& z1n_a-N#GA36yi6)TfiK65$FN`d_sty0$&H-0RH>|$OAMWfy=-Pz(2sFp%G{Ua?xP%#hAhY*zOo zVI|n(Mzw4UKEAzwdqy|nxTzR&Vc=XVqfV7Mr%aX_wh1>IKTaUUDr1dJaD$fFuQN%l z>x{|3&2lACvYao9m@&LUtztYWO(`b^L7>W7NPQK-beR_Ua;;;1F`}JT_3Xg=*H>Jz z%M(o!SFP97=V%$xgr;=W|F$C0)OfTWWyjf(S2qm;>Oxg)RIyyFd|uj!u`Y`|*4BK9 z5ayZ}HHczz^QcHRQ9NG3*QCz9-=QI4=2@|65Q^KuI8s?{^NObXO_tbb;Gt0?+Yaca zL{HO7=5%oQ@+PQFn#G*h_?^ir?rTnyp--9tjjo5RKSpGWs+C!x@!HW*Gxdr@k__p} zmHUvi)Se$Jdp=*tG@Z|b@JGkB#5#HHKDhDud>!3pkgq0NnN4MFSaf4tS>e4uZ_oFI zj+g#o{j%YN!m%-CMz?deq1w(ZFul`?=uRuM8yR?GmkanZ#fcCD_WcO26cT=BJf9tAh6VPV1OK41QO|-SO$wZYeT`i(spP|P7!UBwC5Gzg73ud@@b zY=CXOYP*)%fVtk?V%B%zMpl!mzQk0n%(}$9im<%2mL7K*zJHnpvJvAP%Zj*8p;(je zSz4Xo)^e$Rm+_gCBxt8_*{|W~s758o6YWzeR?T~}LeH_D;AJgS!ncFV1v+#6*uDwt ztCn_Y!dPkOOd=;1AhyDXn4=EuJ4fd%G1;pKzG-wQihLd08X6XUePsb&*)4SpCRbb6}@V_t6kVQWY8bE#(oGid08@3fGYunf*c~ z`t1 literal 0 HcmV?d00001 diff --git a/meddlr/data/transforms/2D_motion.py b/meddlr/data/transforms/2D_motion.py index d321f6a0..61238c79 100644 --- a/meddlr/data/transforms/2D_motion.py +++ b/meddlr/data/transforms/2D_motion.py @@ -10,7 +10,7 @@ from meddlr.utils.events import get_event_storage -class 2DMotionModel: +class MotionModel2D: """A model that corrupts kspace inputs with motion. Motion is a common artifact experienced during the MR imaging forward problem. diff --git a/meddlr/data/transforms/transform.py b/meddlr/data/transforms/transform.py index 2668dd30..7a39a6af 100644 --- a/meddlr/data/transforms/transform.py +++ b/meddlr/data/transforms/transform.py @@ -11,7 +11,7 @@ from meddlr.utils import transforms as T from .motion import MotionModel -from .motion import 2DMotionModel +from .motion import MotionModel2D from .noise import NoiseModel NORMALIZER_REGISTRY = Registry("NORMALIZER") @@ -419,12 +419,12 @@ def __init__( if is_test: # When we test we dont want to initialize with certain parameters (e.g. scheduler). self.noiser = NoiseModel(cfg.MODEL.CONSISTENCY.AUG.NOISE.STD_DEV, seed=seed) - self.motion_simulator = 2DMotionModel(self.nshots, self.angle, + self.motion_simulator = MotionModel2D(self.nshots, self.angle, self.translation, self.trajectory) else: self.noiser = NoiseModel.from_cfg(cfg, seed=seed) - self.motion_simulator = 2DMotionModel(self.nshots, self.angle, + self.motion_simulator = MotionModel2D(self.nshots, self.angle, self.translation, self.trajectory) From f7f503ce8f276d6287fae2885f4368c3e1f04490 Mon Sep 17 00:00:00 2001 From: Deepro Pasha Date: Sun, 10 Jul 2022 15:58:00 -0700 Subject: [PATCH 10/33] added seed parameter to MotionModel2D --- meddlr/data/transforms/.2D_motion.py.swp | Bin 12288 -> 0 bytes meddlr/data/transforms/2D_motion.py | 11 +++++++++-- 2 files changed, 9 insertions(+), 2 deletions(-) delete mode 100644 meddlr/data/transforms/.2D_motion.py.swp diff --git a/meddlr/data/transforms/.2D_motion.py.swp b/meddlr/data/transforms/.2D_motion.py.swp deleted file mode 100644 index 0ab19826ee62c0fc052539105acdcf4f111b7b7f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12288 zcmeHNON<;x87>0>l8^_nxWMISBD6DRre|k-u_mk~ySMBsQ{i01|mPY!?etoSBw<5KFCv@hQK*k>-QVsyuj9^I6w;l*ln z=jMe;<19Dhcsz{`S+lGbtMS$7=@1#@nY@8aXzV%ym*!nI2E~=UHJ? zkw@kE1@Ya3j)9JW$1zZwWOVHradkS`wL@O)eUdJ}aQAVeyXPGP9RnQ$9RnQ$9RnQ$ z9RnQ$9RvS|44Cqg_!|6rU-0ctIA40?9B#W)$3VwG$3VwG$3VwG$3VwG$3VwG$3VwG z$3Vxx{~!Z07UFIE@&EVt!+8Avzxn(Bo#%vj2>cBA5%4zf7Vr}AQQ&#t_sbvrfCd=& z1n_a-N#GA36yi6)TfiK65$FN`d_sty0$&H-0RH>|$OAMWfy=-Pz(2sFp%G{Ua?xP%#hAhY*zOo zVI|n(Mzw4UKEAzwdqy|nxTzR&Vc=XVqfV7Mr%aX_wh1>IKTaUUDr1dJaD$fFuQN%l z>x{|3&2lACvYao9m@&LUtztYWO(`b^L7>W7NPQK-beR_Ua;;;1F`}JT_3Xg=*H>Jz z%M(o!SFP97=V%$xgr;=W|F$C0)OfTWWyjf(S2qm;>Oxg)RIyyFd|uj!u`Y`|*4BK9 z5ayZ}HHczz^QcHRQ9NG3*QCz9-=QI4=2@|65Q^KuI8s?{^NObXO_tbb;Gt0?+Yaca zL{HO7=5%oQ@+PQFn#G*h_?^ir?rTnyp--9tjjo5RKSpGWs+C!x@!HW*Gxdr@k__p} zmHUvi)Se$Jdp=*tG@Z|b@JGkB#5#HHKDhDud>!3pkgq0NnN4MFSaf4tS>e4uZ_oFI zj+g#o{j%YN!m%-CMz?deq1w(ZFul`?=uRuM8yR?GmkanZ#fcCD_WcO26cT=BJf9tAh6VPV1OK41QO|-SO$wZYeT`i(spP|P7!UBwC5Gzg73ud@@b zY=CXOYP*)%fVtk?V%B%zMpl!mzQk0n%(}$9im<%2mL7K*zJHnpvJvAP%Zj*8p;(je zSz4Xo)^e$Rm+_gCBxt8_*{|W~s758o6YWzeR?T~}LeH_D;AJgS!ncFV1v+#6*uDwt ztCn_Y!dPkOOd=;1AhyDXn4=EuJ4fd%G1;pKzG-wQihLd08X6XUePsb&*)4SpCRbb6}@V_t6kVQWY8bE#(oGid08@3fGYunf*c~ z`t1 diff --git a/meddlr/data/transforms/2D_motion.py b/meddlr/data/transforms/2D_motion.py index 61238c79..54844bd7 100644 --- a/meddlr/data/transforms/2D_motion.py +++ b/meddlr/data/transforms/2D_motion.py @@ -19,7 +19,7 @@ class MotionModel2D: This module is responsible for simulating different motion artifacts. """ - def __init__(self, nshots, angle, translate, trajectory): + def __init__(self, nshots, angle, translate, trajectory, seed=None): """ Args: nshots (int) : The number of shots in the image. @@ -38,6 +38,7 @@ def __init__(self, nshots, angle, translate, trajectory): self.angle = angle self.translate = translate self.trajectory = trajectory + self.seed = seed def __call__(self, *args, **kwargs): return self.forward(*args, **kwargs) @@ -62,7 +63,13 @@ def forward(self, image) -> torch.Tensor: Returns: The motion corrupted kspace. """ - tfm_gen = RandomAffine(p = 1.0, translate=translate, angle=angle) + if self.seed == None: + random_motion = RandomAffine + else: + random_motion = RandomAffine.seed(self.seed) + + + tfm_gen = random_motion(p = 1.0, translate=translate, angle=angle) kspace = torch.zeros_like(image) offset = int(math.ceil(kspace.shape[-1] / nshots)) From da220ef49d93390ba36a5414bd3c7e2a4bb6dbeb Mon Sep 17 00:00:00 2001 From: Deepro Pasha Date: Sun, 10 Jul 2022 16:03:59 -0700 Subject: [PATCH 11/33] Updated MotionDataTransform to take in seed argument --- tools/eval_net_motion_corruption.py | 554 ---------------------------- 1 file changed, 554 deletions(-) delete mode 100644 tools/eval_net_motion_corruption.py diff --git a/tools/eval_net_motion_corruption.py b/tools/eval_net_motion_corruption.py deleted file mode 100644 index cf9521dd..00000000 --- a/tools/eval_net_motion_corruption.py +++ /dev/null @@ -1,554 +0,0 @@ -"""Run inference on test set scans. - -This consists of comparing both zero-filled recon and DL-recon to fully-sampled -scans. All comparisons are done per volume (not per slice). - -Example: - python eval_net.py --config-file my/experiment/folder/config.yaml --metric val_psnr_scan -""" -import itertools -import os -from copy import deepcopy -from typing import Any, Dict, Sequence - -import pandas as pd -import torch -from tabulate import tabulate - -import meddlr.ops.complex as cplx -from meddlr.checkpoint import Checkpointer -from meddlr.config import get_cfg -from meddlr.data.build import build_recon_val_loader -from meddlr.engine import DefaultTrainer, default_argument_parser, default_setup -from meddlr.evaluation import DatasetEvaluators, ReconEvaluator, inference_on_dataset -from meddlr.evaluation.testing import check_consistency, find_weights -from meddlr.modeling.meta_arch import CSModel -from meddlr.utils.logger import setup_logger - -_FILE_NAME = os.path.splitext(os.path.basename(__file__))[0] -_LOGGER_NAME = "{}.{}".format(_FILE_NAME, __name__) -# logger = logging.getLogger(_LOGGER_NAME) -logger = None # initialize in setup() - -# Default values for parameters that may not have been initially added. -_DEFAULT_VALS = {"rescaled": True} - - -class ZFReconEvaluator(ReconEvaluator): - """Zero-filled recon evaluator.""" - - def process(self, inputs, outputs): - zf_out = {k: outputs[k] for k in ("target",)} - zf_image = outputs["zf_image"] - if cplx.is_complex_as_real(zf_image): - zf_image = torch.view_as_complex(zf_image) - zf_out["pred"] = zf_image - return super().process(inputs, zf_out) - - -def setup(args): - """ - Create configs and perform basic setups. - We do not save the config. - """ - cfg = get_cfg() - cfg.merge_from_file(args.config_file) - opts = args.opts - if opts and opts[0] == "--": - opts = opts[1:] - cfg.merge_from_list(opts) - cfg.freeze() - default_setup(cfg, args, save_cfg=False) - - # Setup logger for test results - global logger - dirname = "test_results" - logger = setup_logger(os.path.join(cfg.OUTPUT_DIR, dirname), name=_FILE_NAME) - - logger.info(f"Command Line Args: {args}") - return cfg - - -def add_default_params(metrics: pd.DataFrame, ignore_case=True): - """Adds default config parameters (if missing). - - Args: - metrics (pd.DataFrame): Will be filtered based on column values. - ignore_case (bool, optional): If `True`, ignores the column casing. - Raises `ValueError` if two columns have the same lower case - form. - """ - - df = deepcopy(metrics) - if ignore_case: - column_map = {x: x.lower() for x in df.columns} - defaults_keys_map = {k.lower(): k for k in _DEFAULT_VALS.keys()} - df = df.rename(columns=column_map) - else: - defaults_keys_map = {k: k for k in _DEFAULT_VALS.keys()} - - for fmt_key, real_key in defaults_keys_map.items(): - if fmt_key not in df.columns: - df[real_key] = _DEFAULT_VALS[real_key] - - if ignore_case: - df = df.rename(columns={v: k for k, v in column_map.items()}) - - return df - - -def find_metrics( - metrics: pd.DataFrame, params: Dict[str, Any], ignore_missing=False, ignore_case=True -): - """Find subset of metrics dictionary that matches parameter configuration. - - Note: - Values that are not available will be filled in by _DEFAULT_VALS. - - Args: - metrics (pd.DataFrame): Will be filtered based on column values. - params (Dict[str, Any]): Parameter values to filter by. - Keys should correspond to column names in `metrics`. - ignore_missing (bool, optional): If `True`, ignores filtering by - columns that are missing. - ignore_case (bool, optional): If `True`, ignores the column casing. - Raises `ValueError` if two columns have the same lower case - form. - - Returns: - df (pd.DataFrame): The remaining dataframe after filtering. - """ - - df = deepcopy(metrics) - if ignore_case: - column_map = {x: x.lower() for x in df.columns} - df = df.rename(columns=column_map) - params = {k.lower(): v for k, v in params.items()} - - # Fill in with default values if missing. - # Note these will always be lower case, so we match based on case. - # Fill in default values when the columns are not available. - default_keys = {x.lower() for x in params} & {x.lower() for x in _DEFAULT_VALS.keys()} - lowercase_cols = [x.lower() for x in df.columns] - for k in default_keys: - if k not in lowercase_cols: - df[k] = _DEFAULT_VALS[k] - - for k, v in params.items(): - if k not in df.columns: - if ignore_missing: - continue - else: - raise KeyError(f"No column {k} in `metrics`") - df = df[df[k] == v] - - # Undo matching by lower case. - if ignore_case: - df = df.rename(columns={v: k for k, v in column_map.items()}) - - return df - - -def update_metrics(metrics_new: pd.DataFrame, metrics_old: pd.DataFrame, on: Sequence[str]): - """Update a previous metrics version with the new one. - - Metrics that were previously computed, may not be recomputed. - To avoid overwriting them when writing to a csv, we want to - port over any old metrics that we did not recompute. - """ - # We currently do not support missing columns. - missing_cols = [k not in metrics_old.columns for k in on] - if any(missing_cols): - raise KeyError(f"Column(s) {missing_cols} not found in `metrics_old`") - missing_cols = [k not in metrics_new.columns for k in on] - if any(missing_cols): - raise KeyError(f"Column(s) {missing_cols} not found in `metrics_new`") - - # Find combination of columns to select on that is not - # available in the new metrics, but is available in the - # old metrics. - old_metrics_combos = list(itertools.product(*[metrics_old[k].unique().tolist() for k in on])) - new_metrics_combos = list(itertools.product(*[metrics_new[k].unique().tolist() for k in on])) - - to_prepend = [] - for combo in old_metrics_combos: - if combo not in new_metrics_combos: - combo_as_dict = {k: v for k, v in zip(on, combo)} - to_prepend.append(find_metrics(metrics_old, combo_as_dict)) - - if len(to_prepend) > 0: - to_prepend = pd.concat(to_prepend, ignore_index=True) - metrics = pd.concat([to_prepend, metrics_new], ignore_index=True) - else: - metrics = metrics_new - return metrics - - -@torch.no_grad() -def eval(cfg, args, model, weights_basename, criterion, best_value): - zero_filled = args.zero_filled - noise_arg = args.noise.lower() - motion_arg = args.motion.lower() - include_noise = noise_arg != "false" - include_motion = motion_arg != "false" - noise_sweep_vals = args.sweep_vals - motion_sweep_vals = args.sweep_vals_motion - skip_rescale = args.skip_rescale - overwrite = args.overwrite - save_scans = args.save_scans or "save_scans" in args.ops - compute_metrics = "metrics" in args.ops - # TODO: Set up W&B configuration. - # use_wandb = args.use_wandb - # if use_wandb: - # run = init_wandb_run(cfg, resume=True, job_type="eval", use_api=True) - - device = cfg.MODEL.DEVICE - model = model.to(device) - model = model.eval() - - # Get and load metrics file - output_dir = os.path.join(cfg.OUTPUT_DIR, "test_results") - metrics_file = os.path.join(output_dir, args.metrics_file) - if not overwrite and os.path.isfile(metrics_file): - metrics = pd.read_csv(metrics_file, index_col=0) - # Add default parameters to metrics. - metrics = add_default_params(metrics) - else: - metrics = None - - # Returns average or each scan - group_by_scan = True - - # Find range of noise values to search - if include_noise: - noise_vals = noise_sweep_vals if noise_arg == "sweep" else [0] - # noise_vals += list(cfg.MODEL.CONSISTENCY.AUG.NOISE.STD_DEV) - noise_vals = sorted(set(noise_vals)) - else: - noise_vals = [0] - - if include_motion: - motion_vals = [0] + motion_sweep_vals if motion_arg == "sweep" else [0] - motion_vals = sorted(set(motion_vals)) - else: - motion_vals = [0] - - values = itertools.product( - cfg.DATASETS.TEST, cfg.AUG_TEST.UNDERSAMPLE.ACCELERATIONS, noise_vals, motion_vals - ) - values = list(values) - all_results = [] - - default_metrics = ReconEvaluator.default_metrics() - if args.extra_metrics: - if not compute_metrics: - raise ValueError( - "Extra metrics were specified, but `--ops` did not " - "indicate eval should perform metric computation" - ) - default_metrics.extend(args.extra_metrics) - - for exp_idx, (dataset_name, acc, noise_level, motion_level) in enumerate(values): - # Check if the current configuration already has metrics computed - # If so, dont recompute - params = { - "Acceleration": acc, - "dataset": dataset_name, - "Noise Level": noise_level, - "Motion Level": motion_level, - "weights": weights_basename, - "rescaled": not skip_rescale, - } - eval_metrics = default_metrics - - logger.info("==" * 30) - logger.info("Experiment ({}/{})".format(exp_idx + 1, len(values))) - logger.info(", ".join([f"{k}: {v}" for k, v in params.items()])) - logger.info("==" * 30) - - existing_metrics = None - if metrics is not None and compute_metrics: - try: - existing_metrics = find_metrics(metrics, params) - except KeyError: - existing_metrics = None - if existing_metrics is not None and len(existing_metrics) > 0: - eval_metrics = list(set(eval_metrics) - set(existing_metrics.columns)) - if len(eval_metrics) == 0: - logger.info( - "Metrics for ({}) exist:\n{}".format( - ", ".join([f"{k}: {v}" for k, v in params.items()]), - tabulate(existing_metrics, headers=existing_metrics.columns), - ) - ) - all_results.append(existing_metrics) - continue - - # Add criterion and value after to avoid searching by it. - params.update({"Criterion Name": criterion, "Criterion Val": best_value}) - - # Assign the current acceleration - s_cfg = cfg.clone() - s_cfg.defrost() - s_cfg.AUG_TRAIN.UNDERSAMPLE.ACCELERATIONS = (acc,) - s_cfg.MODEL.CONSISTENCY.AUG.MOTION.RANGE = motion_level - s_cfg.MODEL.CONSISTENCY.AUG.NOISE.STD_DEV = (noise_level,) - s_cfg.freeze() - - # Build a recon val loader - dataloader = build_recon_val_loader( - s_cfg, - dataset_name, - as_test=True, - add_noise=noise_level > 0, - add_motion=motion_level > 0, - ) - - # Build evaluators. Only save reconstructions for last scan. - params_str = "-".join(f"{k}={v}" for k, v in params.items() if k != "dataset") - exp_output_dir = os.path.join(output_dir, dataset_name, params_str) - evaluators = [ - ReconEvaluator( - dataset_name, - s_cfg, - group_by_scan=group_by_scan, - skip_rescale=skip_rescale, - save_scans=save_scans, - output_dir=exp_output_dir, - metrics=eval_metrics if compute_metrics else False, - prefix=None, - ) - ] - # TODO: add support for multiple evaluators. - if zero_filled: - zf_output_dir = os.path.join(output_dir, dataset_name, "ZeroFilled-" + params_str) - - evaluators.append( - ZFReconEvaluator( - dataset_name, - s_cfg, - group_by_scan=group_by_scan, - skip_rescale=skip_rescale, - save_scans=save_scans, - output_dir=zf_output_dir, - metrics=eval_metrics if compute_metrics else False, - prefix=None, - ) - ) - evaluators = DatasetEvaluators(evaluators, as_list=True) - - results = inference_on_dataset(model, dataloader, evaluators) - results = [ - pd.DataFrame(x).T.reset_index().rename(columns={"index": "scan_name"}) for x in results - ] - - results[0]["Method"] = s_cfg.MODEL.META_ARCHITECTURE - if zero_filled: - results[1]["Method"] = "Zero-Filled" - scan_results = pd.concat(results, ignore_index=True) - - if existing_metrics is not None and len(existing_metrics) > 0: - scan_results = existing_metrics.merge( - scan_results, on=["scan_name", "Method"], suffixes=("", "_y") - ) - scan_results = scan_results.drop( - scan_results.filter(regex="_y$").columns.tolist(), axis=1 - ) - else: - for k, v in params.items(): - scan_results[k] = v - logger.info("\n" + tabulate(scan_results, headers=scan_results.columns)) - - all_results.append(scan_results) - del evaluators - del dataloader - # Currently don't support writing data because it takes too long - # logger.info("Saving data...") - # file_path = os.path.join(output_dir, dataset_name, "{}.h5".format(scan_name)) - # os.makedirs(os.path.dirname(file_path), exist_ok=True) - - if len(all_results) > 0: - all_results = pd.concat(all_results, ignore_index=True) - logger.info("Summary:\n{}".format(tabulate(all_results, headers=all_results.columns))) - else: - logger.info("No evaluation metrics were computed or available in this run") - - # Try to copy over old metrics information. - # TODO: If fails, it automatically saves the old file in a versioned - # form and prints logging message. - if compute_metrics: - if metrics is not None: - try: - running_results = update_metrics( - all_results, - metrics, - on=[ - "Acceleration", - "dataset", - "Noise Level", - "Motion Level", - "weights", - "Method", - "rescaled", - ], - ) - except KeyError as e: - logger.error(e) - logger.error("Failed to load old metrics information") - # raise e - running_results = all_results - else: - running_results = all_results - running_results.to_csv(metrics_file, mode="w") - - -def main(args): - cfg = setup(args) - model = DefaultTrainer.build_model(cfg) - if isinstance(model, CSModel): - weights, criterion, best_value = None, None, 0 - else: - metric = args.metric if args.metric else f"val_{cfg.MODEL.RECON_LOSS.NAME}" - weights, criterion, best_value = ( - (cfg.MODEL.WEIGHTS, None, None) - if cfg.MODEL.WEIGHTS - else find_weights(cfg, metric, iter_limit=args.iter_limit) - ) - model = model.to(cfg.MODEL.DEVICE) - Checkpointer(model, save_dir=cfg.OUTPUT_DIR).resume_or_load(weights, resume=args.resume) - - # See https://github.com/pytorch/pytorch/issues/42300 - logger.info("Checking weights were properly loaded...") - check_consistency(torch.load(weights)["model"], model) - - logger.info("\n\n==============================") - logger.info("Loading weights from {}".format(weights)) - - # Do not limit number of scans to evaluate during testing. - cfg.defrost() - cfg.DATALOADER.SUBSAMPLE_TRAIN.NUM_VAL = -1 - cfg.freeze() - - eval(cfg, args, model, os.path.basename(weights) if weights else None, criterion, best_value) - - -if __name__ == "__main__": - parser = default_argument_parser() - # parser.add_argument( - # "--dir", type=str, default=None, - # help="Process all completed experiment directories under this directory" - # ) - parser.add_argument( - "--metric", - "--criterion", - type=str, - default="", - help=( - "Val metric used to select weights. " - "Defaults to recon loss. " - "Ignored if `MODEL.WEIGHTS` specified" - ), - ) - parser.add_argument( - "--zero-filled", action="store_true", help="Calculate metrics for zero-filled images" - ) - parser.add_argument( - "--noise", - default="false", - choices=("false", "standard", "sweep"), - help="Type of noise evaluation", - ) - -## Arguments for 2D Motion Corruption of the Dataset ## - - parser.add_argument( - "--angle", - default=0, - type=float, - help=( - "How much rotation angle should be used for motion corruption " - "of the dataset" - ) - ) - parser.add_argument( - "--translation", - default=0, - type=float, - help=( - "How much translation should be used for motion " - "corruption of the dataset" - ) - ) - parser.add_argument( - "--nshots", - default=0, - type=int, - help=( - "How many shots should be used for motion corruption " - "of the dataset." - ) - ) - parser.add_argument( - "--interleaved_or_blocked", - default="blocked", - choice=("interleaved", "blocked"), - help=( - "Chooses between interleaved or blocked shots for motion " - "corruption of the dataset" - ) - ) - parser.add_argument( - "--motion", - default="false", - choices=("false", "standard", "sweep"), - help="Type of motion evaluation", - ) - parser.add_argument( - "--sweep-vals", - default=[0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0], - nargs="*", - type=float, - help="args to sweep for noise", - ) - parser.add_argument("--extra-metrics", nargs="*", help="Extra metrics for testing") - parser.add_argument( - "--sweep-vals-motion", - default=[0, 0.2, 0.4], - nargs="*", - type=float, - help="args to sweep for motion", - ) - parser.add_argument( - "--iter-limit", - default=None, - type=int, - help=( - "Time limit. If negative, interpreted as epoch. " - "Chooses weights at or before this time point." - ), - ) - parser.add_argument("--overwrite", action="store_true", help="Overwrite existing metrics file") - parser.add_argument( - "--skip-rescale", action="store_true", help="Skip rescaling when evaluating" - ) - parser.add_argument("--save-scans", action="store_true", help="Save reconstruction outputs") - parser.add_argument("--metrics-file", type=str, default="metrics.csv", help="Metrics file") - # parser.add_argument( - # "--wandb", action="store_true", help="Log to W&B during evaluation" - # ) - parser.add_argument( - "--ops", - type=str, - nargs="*", - default=["metrics"], - choices=["metrics", "save_scans"], - help="Operations to run. 'metrics': Compute metrics. 'save_scans': Save Scans", - ) - - args = parser.parse_args() - args.ops = set(args.ops) - if args.save_scans: - args.ops |= {"save_scans"} - - print("Command Line Args:", args) - main(args) From b96be93c49387d24231160fa7bc2a7b9746c8e59 Mon Sep 17 00:00:00 2001 From: Deepro Pasha Date: Sun, 10 Jul 2022 16:20:51 -0700 Subject: [PATCH 12/33] removed motion sweep values from eval_net_motion.py. Reminder: this code is untested/undebugged --- tools/eval_net_motion.py | 25 +++++-------------------- 1 file changed, 5 insertions(+), 20 deletions(-) diff --git a/tools/eval_net_motion.py b/tools/eval_net_motion.py index abe49424..5814d342 100644 --- a/tools/eval_net_motion.py +++ b/tools/eval_net_motion.py @@ -198,7 +198,6 @@ def eval(cfg, args, model, weights_basename, criterion, best_value): include_noise = noise_arg != "false" include_motion = motion_arg != "false" noise_sweep_vals = args.sweep_vals - motion_sweep_vals = args.sweep_vals_motion skip_rescale = args.skip_rescale overwrite = args.overwrite save_scans = args.save_scans or "save_scans" in args.ops @@ -233,12 +232,6 @@ def eval(cfg, args, model, weights_basename, criterion, best_value): else: noise_vals = [0] - if include_motion: - motion_vals = [0] + motion_sweep_vals if motion_arg == "sweep" else [0] - motion_vals = sorted(set(motion_vals)) - else: - motion_vals = [0] - values = itertools.product( cfg.DATASETS.TEST, cfg.AUG_TEST.UNDERSAMPLE.ACCELERATIONS, noise_vals, motion_vals ) @@ -254,14 +247,13 @@ def eval(cfg, args, model, weights_basename, criterion, best_value): ) default_metrics.extend(args.extra_metrics) - for exp_idx, (dataset_name, acc, noise_level, motion_level) in enumerate(values): + for exp_idx, (dataset_name, acc, noise_level) in enumerate(values): # Check if the current configuration already has metrics computed # If so, dont recompute params = { "Acceleration": acc, "dataset": dataset_name, "Noise Level": noise_level, - "Motion Level": motion_level, "weights": weights_basename, "rescaled": not skip_rescale, } @@ -297,7 +289,6 @@ def eval(cfg, args, model, weights_basename, criterion, best_value): s_cfg = cfg.clone() s_cfg.defrost() s_cfg.AUG_TRAIN.UNDERSAMPLE.ACCELERATIONS = (acc,) - s_cfg.MODEL.CONSISTENCY.AUG.MOTION.RANGE = motion_level s_cfg.MODEL.CONSISTENCY.AUG.NOISE.STD_DEV = (noise_level,) s_cfg.freeze() @@ -307,7 +298,7 @@ def eval(cfg, args, model, weights_basename, criterion, best_value): dataset_name, as_test=True, add_noise=noise_level > 0, - add_motion=motion_level > 0, + add_motion=include_motion != "false", angle=angle, translation=translation, nshots=nshots, @@ -510,8 +501,8 @@ def main(args): parser.add_argument( "--motion", default="false", - choices=("false", "standard", "sweep"), - help="Type of motion evaluation", + choices=("false", "true"), + help="Motion corruption included or not", ) parser.add_argument( "--sweep-vals", @@ -521,13 +512,7 @@ def main(args): help="args to sweep for noise", ) parser.add_argument("--extra-metrics", nargs="*", help="Extra metrics for testing") - parser.add_argument( - "--sweep-vals-motion", - default=[0, 0.2, 0.4], - nargs="*", - type=float, - help="args to sweep for motion", - ) + parser.add_argument( "--iter-limit", default=None, From 713618435c2b41cdabfa5e4a710d9ce1c1956b19 Mon Sep 17 00:00:00 2001 From: DPro Date: Wed, 13 Jul 2022 17:39:36 -0700 Subject: [PATCH 13/33] Initial changes made to address comments on PR --- meddlr/data/build.py | 16 ++- meddlr/data/transforms/2D_motion.py | 87 ------------- .../data/transforms/motion_corruption_2D.py | 68 +++++++++++ meddlr/data/transforms/transform.py | 115 +++++++----------- tools/eval_net.py | 45 ++++++- tools/eval_net_motion.py | 28 ++--- 6 files changed, 175 insertions(+), 184 deletions(-) delete mode 100644 meddlr/data/transforms/2D_motion.py create mode 100644 meddlr/data/transforms/motion_corruption_2D.py diff --git a/meddlr/data/build.py b/meddlr/data/build.py index 9c7dadbf..95df0f16 100644 --- a/meddlr/data/build.py +++ b/meddlr/data/build.py @@ -243,16 +243,18 @@ def build_recon_train_loader(cfg, dataset_type=None): ) return train_loader + def build_recon_val_loader( cfg, dataset_name, as_test: bool = False, add_noise: bool = False, add_motion: bool = False, + # data_transform: function = T.MotionDataTransform, angle: float = 0, translation: float = 0, nshots: int = 0, - trajectory: str = "blocked", + trajectory: str = "blocked", dataset_type=None, ): if ( @@ -280,9 +282,15 @@ def build_recon_val_loader( mask_func = build_mask_func(cfg.AUG_TRAIN) data_transform = T.MotionDataTransform( - cfg, mask_func, is_test=as_test, add_noise=add_noise, - add_motion=add_motion, angle=angle, translation=translation, - nshots=nshots, trajectory=trajectory + cfg, + mask_func, + is_test=as_test, + add_noise=add_noise, + add_motion=add_motion, + angle=angle, + translation=translation, + nshots=nshots, + trajectory=trajectory, ) val_data = _build_dataset( diff --git a/meddlr/data/transforms/2D_motion.py b/meddlr/data/transforms/2D_motion.py deleted file mode 100644 index 54844bd7..00000000 --- a/meddlr/data/transforms/2D_motion.py +++ /dev/null @@ -1,87 +0,0 @@ -from typing import Optional, Tuple, Sequence, Union - -import math -import numpy as np -import torch -import meddlr.ops as F -from meddlr.transforms import RandomAffine, RandomTranslation, TransformList -from meddlr.forward.mri import SenseModel - -from meddlr.utils.events import get_event_storage - - -class MotionModel2D: - """A model that corrupts kspace inputs with motion. - - Motion is a common artifact experienced during the MR imaging forward problem. - When a patient moves, the recorded (expected) location of the kspace sample is - different than the actual location where the kspace sample that was acquired. - This module is responsible for simulating different motion artifacts. - """ - - def __init__(self, nshots, angle, translate, trajectory, seed=None): - """ - Args: - nshots (int) : The number of shots in the image. - This should be equivalent to ceil(phase_encode_dim / - echo_train_length). - angle : The (min, max) angle for rotation. Values should be in - degrees and should be >=-180, <=180. Use 'None' to - ignore rotation. - translate: The fraction of (height, width) to translate. - e.g. 0.1 => 10% of the corresponding dimension. - So (0.1, 0.2) => 10% of height, 20% of width. - Use 'None' to ignore translation. - trajectory: One of 'interleaved' or 'consecutive'. - """ - self.nshots = nshots - self.angle = angle - self.translate = translate - self.trajectory = trajectory - self.seed = seed - - def __call__(self, *args, **kwargs): - return self.forward(*args, **kwargs) - - def forward(self, image) -> torch.Tensor: - """ - Simulate 2D motion for multi-shot Cartesian MRI. - - This function supports two trajectories: - - 'blocked' : Where each shot corresponds to a consecutive block of - kspace. (e.g. 1 1 2 2 3 3) - - 'interleaved' : Where shots are interleaved (e.g. 1 2 3 1 2 3) - - We assume the phase encode direction is left to right (i.e. along - width dimesion). - - TODO: Add support for sensitivity maps. - - Args: - image : The complex-valued iamge. Shape [..., height, width]. - - Returns: - The motion corrupted kspace. - """ - if self.seed == None: - random_motion = RandomAffine - else: - random_motion = RandomAffine.seed(self.seed) - - - tfm_gen = random_motion(p = 1.0, translate=translate, angle=angle) - kspace = torch.zeros_like(image) - offset = int(math.ceil(kspace.shape[-1] / nshots)) - - for shot in range(nshots): - motion_image = tfm_gen.get_transform(image).apply_image(image) - motion_kspace = F.fft2c(motion_image) - if trajectory == "blocked": - kspace[..., shot*offset:(shot+1)*offset] = motion_kspace[..., shot*offset:(shot+1)*offset] - elif trajectory == "interleaved": - kspace[..., shot::nshots] = motion_kspace[..., shot::nshots] - else: - raise ValueError(f"trajectory '{trajectory}' not supported.") - - return kspace - diff --git a/meddlr/data/transforms/motion_corruption_2D.py b/meddlr/data/transforms/motion_corruption_2D.py new file mode 100644 index 00000000..f0f57f25 --- /dev/null +++ b/meddlr/data/transforms/motion_corruption_2D.py @@ -0,0 +1,68 @@ +import math +from typing import Optional, Tuple + +import torch + +import meddlr.ops as F +from meddlr.transforms import RandomAffine + + +def add_motion_corruption( + image: torch.Tensor, + nshots: int, + angle: Optional[Tuple[float, float]] = (-5.0, 5.0), + translate: Optional[Tuple[float, float]] = (0.1, 0.1), + trajectory: str = "blocked", + seed: Optional[float] = None, +) -> torch.Tensor: + """ + Simulate 2D motion for multi-shot Cartesian MRI. + + This function supports two trajectories: + - 'blocked': Where each shot corresponds to a consecutive block of kspace. + (e.g. 1 1 2 2 3 3) + - 'interleaved': Where shots are interleaved (e.g. 1 2 3 1 2 3) + + We assume the phase encode direction is left to right + (i.e. along width dimension). + + TODO: Add support for sensitivity maps. + + Args: + image: The complex-valued image. Shape [..., height, width]. + nshots: The number of shots in the image. + This should be equivalent to ceil(phase_encode_dim / echo_train_length). + angle: The (min, max) angle for rotation. Values should be in degrees + and should be >=-180, <=180. Use `None` to ignore rotation. + translate: The fraction of (height, width) to translate. + e.g. 0.1 => 10% of the corresponding dimension. + So (0.1, 0.2) => 10% of height, 20% of width. + Use `None` to ignore translation. + trajectory: One of 'interleaved' or 'consecutive'. + + Returns: + A motion corrupted image. + """ + if seed is None: + random_motion = RandomAffine(p=1.0, translate=translate, angle=angle) + else: + random_motion = RandomAffine(p=1.0, translate=translate, angle=angle) + random_motion.seed(seed) + + tfm_gen = random_motion + kspace = torch.zeros_like(image) + offset = int(math.ceil(kspace.shape[-1] / nshots)) + + for shot in range(nshots): + motion_image = tfm_gen.get_transform(image).apply_image(image) + motion_kspace = F.fft2c(motion_image) + if trajectory == "blocked": + kspace[..., shot * offset : (shot + 1) * offset] = motion_kspace[ + ..., shot * offset : (shot + 1) * offset + ] + elif trajectory == "interleaved": + kspace[..., shot::nshots] = motion_kspace[..., shot::nshots] + else: + raise ValueError(f"trajectory '{trajectory}' not supported.") + + return kspace diff --git a/meddlr/data/transforms/transform.py b/meddlr/data/transforms/transform.py index 7a39a6af..d9fefa68 100644 --- a/meddlr/data/transforms/transform.py +++ b/meddlr/data/transforms/transform.py @@ -6,12 +6,12 @@ import torch from fvcore.common.registry import Registry +from meddlr.data.transforms.motion_corruption_2D import add_motion_corruption from meddlr.forward import SenseModel from meddlr.ops import complex as cplx from meddlr.utils import transforms as T from .motion import MotionModel -from .motion import MotionModel2D from .noise import NoiseModel NORMALIZER_REGISTRY = Registry("NORMALIZER") @@ -371,20 +371,20 @@ class MotionDataTransform: """ Data Transformer for training unrolled reconstruction models. - This is for emulating 2D roto-translational motion corrupted MR scans. + This is for emulating 2D roto-translational motion corrupted MR scans. """ def __init__( self, cfg, mask_func, + nshots: int, is_test: bool = False, add_noise: bool = False, add_motion: bool = False, - angle: float = 0, - translation: float = 0, - nshots: int = 0, - trajectory: str = "blocked" + angle: float = None, + translation: float = None, + trajectory: str = "blocked", ): """ Args: @@ -395,8 +395,6 @@ def __init__( generator seed from the filename. This ensures that the same mask is used for all the slices of a given volume every time. """ - from meddlr.transforms.builtin.mri import MRIReconAugmentor - self._cfg = cfg self.mask_func = mask_func self._is_test = is_test @@ -407,10 +405,14 @@ def __init__( self.add_noise = add_noise self.add_motion = add_motion - # These will be used for the motion corruption. + # These will be used for the motion corruption. self.angle = angle - self.translation = translation - self.nshots = nshots + self.translation = translation + + if nshots is None: + raise ValueError("The paramter nshots must be set to some integer value.") + + self.nshots = nshots self.trajectory = trajectory seed = cfg.SEED if cfg.SEED > -1 else None @@ -419,14 +421,8 @@ def __init__( if is_test: # When we test we dont want to initialize with certain parameters (e.g. scheduler). self.noiser = NoiseModel(cfg.MODEL.CONSISTENCY.AUG.NOISE.STD_DEV, seed=seed) - self.motion_simulator = MotionModel2D(self.nshots, self.angle, - self.translation, - self.trajectory) else: - self.noiser = NoiseModel.from_cfg(cfg, seed=seed) - self.motion_simulator = MotionModel2D(self.nshots, self.angle, - self.translation, - self.trajectory) + pass self.p_noise = cfg.AUG_TRAIN.NOISE_P self.p_motion = cfg.AUG_TRAIN.MOTION_P @@ -435,49 +431,7 @@ def __init__( # Build augmentation pipeline. self.augmentor = None if not is_test and cfg.AUG_TRAIN.MRI_RECON.TRANSFORMS: - self.augmentor = MRIReconAugmentor.from_cfg(cfg, aug_kind="aug_train", seed=seed) - - def _call_augmentor( - self, kspace, maps, target, fname, slice_id, is_fixed, acceleration: int = None - ): - assert not self._is_test, "Augmentor is not supported with testing yet" - - # Convert everything from numpy arrays to tensors - kspace = cplx.to_tensor(kspace).unsqueeze(0) - maps = cplx.to_tensor(maps).unsqueeze(0) - target_init = cplx.to_tensor(target).unsqueeze(0) - target = ( - torch.complex(target_init, torch.zeros_like(target_init)).unsqueeze(-1) - if not torch.is_complex(target_init) - else target_init - ) # handle rss vs. sensitivity-integrated - norm = torch.sqrt(torch.mean(cplx.abs(target) ** 2)) - - seed = sum(tuple(map(ord, fname))) if self._is_test or is_fixed else None # noqa - mask_gen = partial( - self._subsampler.__call__, mode="2D", seed=seed, acceleration=acceleration - ) - - out, _, _ = self.augmentor( - kspace, - maps=maps, - target=target, - normalizer=self._normalizer, - mask_gen=mask_gen, - skip_tfm=is_fixed, # Skip augmentations for unsupervised scans. - ) - masked_kspace = out["kspace"] - maps = out["maps"] - target = out["target"] - mean = out["mean"] - std = out["std"] - - # Get rid of batch dimension... - masked_kspace = masked_kspace.squeeze(0) - maps = maps.squeeze(0) - target = target.squeeze(0) - - return masked_kspace, maps, target, mean, std, norm + pass def __call__(self, kspace, maps, target, fname, slice_id, is_fixed, acceleration: int = None): """ @@ -524,8 +478,32 @@ def __call__(self, kspace, maps, target, fname, slice_id, is_fixed, acceleration # TODO: Add other transforms here. + seed = sum(tuple(map(ord, fname))) if self._is_test else None # noqa + + # Zero-filled Sense Recon. + if torch.is_complex(target_init): + A = SenseModel(maps) + image = A(kspace, adjoint=True) + # Zero-filled RSS Recon. + else: + image = T.ifft2(kspace) + image_rss = torch.sqrt(torch.sum(cplx.abs(image) ** 2, axis=-1)) + image = torch.complex(image_rss, torch.zeros_like(image_rss)).unsqueeze(-1) + + add_motion = self.add_motion and self._is_test + if add_motion: + # Motion seed should not be different for each slice for now. + # TODO: Change this for 2D acquisitions. + kspace = add_motion_corruption( + image=image, + nshots=self.nshots, + angle=self.angle, + translate=self.translation, + trajectory=self.trajectory, + seed=seed, + ) + # Apply mask in k-space - seed = sum(tuple(map(ord, fname))) if self._is_test or is_fixed else None # noqa masked_kspace, mask = self._subsampler( kspace, mode="2D", seed=seed, acceleration=acceleration ) @@ -549,17 +527,7 @@ def __call__(self, kspace, maps, target, fname, slice_id, is_fixed, acceleration mean = normalized["mean"] std = normalized["std"] - add_noise = self.add_noise and ( - self._is_test or (not is_fixed and self.rng.uniform() < self.p_noise) - ) - add_motion = self.add_motion and ( - self._is_test or (not is_fixed and self.rng.uniform() < self.p_motion) - ) - if add_motion: - # Motion seed should not be different for each slice for now. - # TODO: Change this for 2D acquisitions. - # masked_kspace = self.motion_simulator(masked_kspace, seed=seed) - masked_kspace = self.motion_simulator(image) + add_noise = self.add_noise and self._is_test if add_noise: # Seed should be different for each slice of a scan. @@ -572,4 +540,3 @@ def __call__(self, kspace, maps, target, fname, slice_id, is_fixed, acceleration target = target.squeeze(0) return masked_kspace, maps, target, mean, std, norm - diff --git a/tools/eval_net.py b/tools/eval_net.py index 3d95e7ff..4ab24fe6 100644 --- a/tools/eval_net.py +++ b/tools/eval_net.py @@ -187,6 +187,12 @@ def update_metrics(metrics_new: pd.DataFrame, metrics_old: pd.DataFrame, on: Seq @torch.no_grad() def eval(cfg, args, model, weights_basename, criterion, best_value): zero_filled = args.zero_filled + + angle = args.angle + translation = args.translation + nshots = args.nshots + trajectory = args.trajectory.lower() + noise_arg = args.noise.lower() motion_arg = args.motion.lower() include_noise = noise_arg != "false" @@ -301,7 +307,11 @@ def eval(cfg, args, model, weights_basename, criterion, best_value): dataset_name, as_test=True, add_noise=noise_level > 0, - add_motion=motion_level > 0, + add_motion=include_motion != "false", + angle=angle, + translation=translation, + nshots=nshots, + trajectory=trajectory, ) # Build evaluators. Only save reconstructions for last scan. @@ -458,6 +468,39 @@ def main(args): choices=("false", "standard", "sweep"), help="Type of noise evaluation", ) + + # Arguments for 2D Motion Corruption of the Dataset + + parser.add_argument( + "--angle", + default=0, + type=float, + help=("How much rotation angle should be used for motion corruption " "of the dataset"), + ) + + parser.add_argument( + "--translation", + default=0, + type=float, + help=("How much translation should be used for motion " "corruption of the dataset"), + ) + parser.add_argument( + "--nshots", + default=0, + type=int, + help=("How many shots should be used for motion corruption " "of the dataset."), + ) + parser.add_argument( + "--trajectory", + default="blocked", + choice=("interleaved", "blocked"), + help=( + "Chooses between interleaved or blocked shots for motion " "corruption of the dataset" + ), + ) + + # End of Arguments for 2D Motion Corruption of the Dataset + parser.add_argument( "--motion", default="false", diff --git a/tools/eval_net_motion.py b/tools/eval_net_motion.py index 5814d342..58a4ce24 100644 --- a/tools/eval_net_motion.py +++ b/tools/eval_net_motion.py @@ -233,7 +233,9 @@ def eval(cfg, args, model, weights_basename, criterion, best_value): noise_vals = [0] values = itertools.product( - cfg.DATASETS.TEST, cfg.AUG_TEST.UNDERSAMPLE.ACCELERATIONS, noise_vals, motion_vals + cfg.DATASETS.TEST, + cfg.AUG_TEST.UNDERSAMPLE.ACCELERATIONS, + noise_vals, ) values = list(values) all_results = [] @@ -460,48 +462,38 @@ def main(args): help="Type of noise evaluation", ) -## Arguments for 2D Motion Corruption of the Dataset ## + # Arguments for 2D Motion Corruption of the Dataset parser.add_argument( "--angle", default=0, type=float, - help=( - "How much rotation angle should be used for motion corruption " - "of the dataset" - ) + help=("How much rotation angle should be used for motion corruption " "of the dataset"), ) parser.add_argument( "--translation", default=0, type=float, - help=( - "How much translation should be used for motion " - "corruption of the dataset" - ) + help=("How much translation should be used for motion " "corruption of the dataset"), ) parser.add_argument( "--nshots", default=0, type=int, - help=( - "How many shots should be used for motion corruption " - "of the dataset." - ) + help=("How many shots should be used for motion corruption " "of the dataset."), ) parser.add_argument( "--trajectory", default="blocked", choice=("interleaved", "blocked"), help=( - "Chooses between interleaved or blocked shots for motion " - "corruption of the dataset" - ) + "Chooses between interleaved or blocked shots for motion " "corruption of the dataset" + ), ) parser.add_argument( "--motion", default="false", - choices=("false", "true"), + choices=("false", "true"), help="Motion corruption included or not", ) parser.add_argument( From 8d1adf8814ce7d1c9e306ef855c84f783ccf2fe9 Mon Sep 17 00:00:00 2001 From: DPro Date: Wed, 13 Jul 2022 20:19:57 -0700 Subject: [PATCH 14/33] Edited add_motion_corr func to take in translation as a parameter and for MotionDataTransform to form an instance and pass it through the above stated function. --- .../data/transforms/motion_corruption_2D.py | 23 ++++--------------- meddlr/data/transforms/transform.py | 14 +++++++---- 2 files changed, 14 insertions(+), 23 deletions(-) diff --git a/meddlr/data/transforms/motion_corruption_2D.py b/meddlr/data/transforms/motion_corruption_2D.py index f0f57f25..edd0a3e7 100644 --- a/meddlr/data/transforms/motion_corruption_2D.py +++ b/meddlr/data/transforms/motion_corruption_2D.py @@ -1,5 +1,5 @@ import math -from typing import Optional, Tuple +from typing import Optional import torch @@ -10,10 +10,8 @@ def add_motion_corruption( image: torch.Tensor, nshots: int, - angle: Optional[Tuple[float, float]] = (-5.0, 5.0), - translate: Optional[Tuple[float, float]] = (0.1, 0.1), + translation: Optional[RandomAffine] = None, trajectory: str = "blocked", - seed: Optional[float] = None, ) -> torch.Tensor: """ Simulate 2D motion for multi-shot Cartesian MRI. @@ -32,29 +30,18 @@ def add_motion_corruption( image: The complex-valued image. Shape [..., height, width]. nshots: The number of shots in the image. This should be equivalent to ceil(phase_encode_dim / echo_train_length). - angle: The (min, max) angle for rotation. Values should be in degrees - and should be >=-180, <=180. Use `None` to ignore rotation. - translate: The fraction of (height, width) to translate. - e.g. 0.1 => 10% of the corresponding dimension. - So (0.1, 0.2) => 10% of height, 20% of width. - Use `None` to ignore translation. + translation: This is the translation to augment images in the image + domain. This is either 'None' or 'RandomAffine' for now. trajectory: One of 'interleaved' or 'consecutive'. Returns: A motion corrupted image. """ - if seed is None: - random_motion = RandomAffine(p=1.0, translate=translate, angle=angle) - else: - random_motion = RandomAffine(p=1.0, translate=translate, angle=angle) - random_motion.seed(seed) - - tfm_gen = random_motion kspace = torch.zeros_like(image) offset = int(math.ceil(kspace.shape[-1] / nshots)) for shot in range(nshots): - motion_image = tfm_gen.get_transform(image).apply_image(image) + motion_image = translation.get_transform(image).apply_image(image) motion_kspace = F.fft2c(motion_image) if trajectory == "blocked": kspace[..., shot * offset : (shot + 1) * offset] = motion_kspace[ diff --git a/meddlr/data/transforms/transform.py b/meddlr/data/transforms/transform.py index d9fefa68..254f911a 100644 --- a/meddlr/data/transforms/transform.py +++ b/meddlr/data/transforms/transform.py @@ -1,6 +1,7 @@ """Basic Transforms. """ from functools import partial +from typing import Optional, Tuple import numpy as np import torch @@ -9,6 +10,7 @@ from meddlr.data.transforms.motion_corruption_2D import add_motion_corruption from meddlr.forward import SenseModel from meddlr.ops import complex as cplx +from meddlr.transforms.gen.spatial import RandomAffine from meddlr.utils import transforms as T from .motion import MotionModel @@ -382,8 +384,8 @@ def __init__( is_test: bool = False, add_noise: bool = False, add_motion: bool = False, - angle: float = None, - translation: float = None, + angle: Optional[Tuple[float, float]] = (-5., 5.), + translation: Optional[Tuple[float, float]] = (0.1, 0.1), trajectory: str = "blocked", ): """ @@ -494,13 +496,15 @@ def __call__(self, kspace, maps, target, fname, slice_id, is_fixed, acceleration if add_motion: # Motion seed should not be different for each slice for now. # TODO: Change this for 2D acquisitions. + + tfm_gen = RandomAffine(p=1.0, translate=self.translation, angle=self.angle) + tfm_gen.seed(seed) + kspace = add_motion_corruption( image=image, nshots=self.nshots, - angle=self.angle, - translate=self.translation, + translation=tfm_gen, trajectory=self.trajectory, - seed=seed, ) # Apply mask in k-space From eaf1099ac6b3f998814424881de459ba4a6441e7 Mon Sep 17 00:00:00 2001 From: DPro Date: Fri, 15 Jul 2022 22:30:01 -0700 Subject: [PATCH 15/33] added parameter to choose between 2D and 3D MRI (and thus seed fixedness accordingly) --- meddlr/data/build.py | 2 ++ meddlr/data/transforms/transform.py | 17 ++++++++++------- tools/eval_net.py | 9 +++++++++ 3 files changed, 21 insertions(+), 7 deletions(-) diff --git a/meddlr/data/build.py b/meddlr/data/build.py index 95df0f16..6832e162 100644 --- a/meddlr/data/build.py +++ b/meddlr/data/build.py @@ -250,6 +250,7 @@ def build_recon_val_loader( as_test: bool = False, add_noise: bool = False, add_motion: bool = False, + two_dimensional: bool = True, # data_transform: function = T.MotionDataTransform, angle: float = 0, translation: float = 0, @@ -287,6 +288,7 @@ def build_recon_val_loader( is_test=as_test, add_noise=add_noise, add_motion=add_motion, + two_dimensional=two_dimensional, angle=angle, translation=translation, nshots=nshots, diff --git a/meddlr/data/transforms/transform.py b/meddlr/data/transforms/transform.py index 254f911a..f443840f 100644 --- a/meddlr/data/transforms/transform.py +++ b/meddlr/data/transforms/transform.py @@ -384,6 +384,7 @@ def __init__( is_test: bool = False, add_noise: bool = False, add_motion: bool = False, + two_dimensional: bool = True, angle: Optional[Tuple[float, float]] = (-5., 5.), translation: Optional[Tuple[float, float]] = (0.1, 0.1), trajectory: str = "blocked", @@ -410,6 +411,7 @@ def __init__( # These will be used for the motion corruption. self.angle = angle self.translation = translation + self.two_dimensional = two_dimensional if nshots is None: raise ValueError("The paramter nshots must be set to some integer value.") @@ -461,12 +463,6 @@ def __call__(self, kspace, maps, target, fname, slice_id, is_fixed, acceleration if is_fixed and not acceleration: raise ValueError("Accelerations must be specified for undersampled scans") - # If augmentor is defined, use it to do computation. - if self.augmentor is not None: - return self._call_augmentor( - kspace, maps, target, fname, slice_id, is_fixed, acceleration - ) - # Convert everything from numpy arrays to tensors kspace = cplx.to_tensor(kspace).unsqueeze(0) maps = cplx.to_tensor(maps).unsqueeze(0) @@ -480,7 +476,14 @@ def __call__(self, kspace, maps, target, fname, slice_id, is_fixed, acceleration # TODO: Add other transforms here. - seed = sum(tuple(map(ord, fname))) if self._is_test else None # noqa + # If 2D MRI, then each slice will have different motion - seed is some + # combination of the file name and the slice id (+). + # If 3D MRI, then each slice should have the same motion - seed is + # determined only by the file name. + if self.two_dimensional: + seed = sum(tuple(map(ord, fname))) + slice if self._is_test else None # noqa + else: + seed = sum(tuple(map(ord, fname))) if self._is_test else None # noqa # Zero-filled Sense Recon. if torch.is_complex(target_init): diff --git a/tools/eval_net.py b/tools/eval_net.py index 4ab24fe6..f5f0082b 100644 --- a/tools/eval_net.py +++ b/tools/eval_net.py @@ -188,6 +188,7 @@ def update_metrics(metrics_new: pd.DataFrame, metrics_old: pd.DataFrame, on: Seq def eval(cfg, args, model, weights_basename, criterion, best_value): zero_filled = args.zero_filled + two_dimensional = args.two_dimensional != "false" angle = args.angle translation = args.translation nshots = args.nshots @@ -308,6 +309,7 @@ def eval(cfg, args, model, weights_basename, criterion, best_value): as_test=True, add_noise=noise_level > 0, add_motion=include_motion != "false", + two_dimensional=two_dimensional, angle=angle, translation=translation, nshots=nshots, @@ -499,6 +501,13 @@ def main(args): ), ) + parser.add_argument( + "--two-dimensional", + default="true", + choice=("true", "false"), + help=("If true, MRI is 2D. Otherwise, MRI is 3D.") + ) + # End of Arguments for 2D Motion Corruption of the Dataset parser.add_argument( From 4493c2afc1e48755c41c0c802e7bcb06a6f2b28f Mon Sep 17 00:00:00 2001 From: DPro Date: Thu, 21 Jul 2022 16:26:15 -0700 Subject: [PATCH 16/33] resolved comments for PR --- meddlr/data/build.py | 28 +++----- .../data/transforms/motion_corruption_2D.py | 55 -------------- meddlr/data/transforms/transform.py | 23 +++--- meddlr/transforms/functional/mri.py | 53 ++++++++++++++ tools/eval_net.py | 71 ++++++++++++++----- 5 files changed, 128 insertions(+), 102 deletions(-) delete mode 100644 meddlr/data/transforms/motion_corruption_2D.py diff --git a/meddlr/data/build.py b/meddlr/data/build.py index 6832e162..36185cd3 100644 --- a/meddlr/data/build.py +++ b/meddlr/data/build.py @@ -2,7 +2,7 @@ import logging import random from collections import defaultdict -from typing import Dict, Mapping, Sequence, Tuple, Union +from typing import Callable, Dict, Mapping, Optional, Sequence, Tuple, Union import numpy as np from torch.utils.data import DataLoader @@ -250,14 +250,16 @@ def build_recon_val_loader( as_test: bool = False, add_noise: bool = False, add_motion: bool = False, - two_dimensional: bool = True, - # data_transform: function = T.MotionDataTransform, - angle: float = 0, - translation: float = 0, - nshots: int = 0, - trajectory: str = "blocked", + data_transform: Optional[Callable] = None, dataset_type=None, ): + mask_func = build_mask_func(cfg.AUG_TRAIN) + + if data_transform is None: + data_transform = T.DataTransform( + cfg, mask_func, is_test=as_test, add_noise=add_noise, add_motion=add_motion + ) + if ( cfg.DATALOADER.SUBSAMPLE_TRAIN.NUM_VAL > 0 and cfg.DATALOADER.SUBSAMPLE_TRAIN.NUM_VAL_BY_GROUP @@ -282,18 +284,6 @@ def build_recon_val_loader( dataset_type = _get_default_dataset_type(dataset_name) mask_func = build_mask_func(cfg.AUG_TRAIN) - data_transform = T.MotionDataTransform( - cfg, - mask_func, - is_test=as_test, - add_noise=add_noise, - add_motion=add_motion, - two_dimensional=two_dimensional, - angle=angle, - translation=translation, - nshots=nshots, - trajectory=trajectory, - ) val_data = _build_dataset( cfg, dataset_dicts, data_transform, is_eval=True, dataset_type=dataset_type diff --git a/meddlr/data/transforms/motion_corruption_2D.py b/meddlr/data/transforms/motion_corruption_2D.py deleted file mode 100644 index edd0a3e7..00000000 --- a/meddlr/data/transforms/motion_corruption_2D.py +++ /dev/null @@ -1,55 +0,0 @@ -import math -from typing import Optional - -import torch - -import meddlr.ops as F -from meddlr.transforms import RandomAffine - - -def add_motion_corruption( - image: torch.Tensor, - nshots: int, - translation: Optional[RandomAffine] = None, - trajectory: str = "blocked", -) -> torch.Tensor: - """ - Simulate 2D motion for multi-shot Cartesian MRI. - - This function supports two trajectories: - - 'blocked': Where each shot corresponds to a consecutive block of kspace. - (e.g. 1 1 2 2 3 3) - - 'interleaved': Where shots are interleaved (e.g. 1 2 3 1 2 3) - - We assume the phase encode direction is left to right - (i.e. along width dimension). - - TODO: Add support for sensitivity maps. - - Args: - image: The complex-valued image. Shape [..., height, width]. - nshots: The number of shots in the image. - This should be equivalent to ceil(phase_encode_dim / echo_train_length). - translation: This is the translation to augment images in the image - domain. This is either 'None' or 'RandomAffine' for now. - trajectory: One of 'interleaved' or 'consecutive'. - - Returns: - A motion corrupted image. - """ - kspace = torch.zeros_like(image) - offset = int(math.ceil(kspace.shape[-1] / nshots)) - - for shot in range(nshots): - motion_image = translation.get_transform(image).apply_image(image) - motion_kspace = F.fft2c(motion_image) - if trajectory == "blocked": - kspace[..., shot * offset : (shot + 1) * offset] = motion_kspace[ - ..., shot * offset : (shot + 1) * offset - ] - elif trajectory == "interleaved": - kspace[..., shot::nshots] = motion_kspace[..., shot::nshots] - else: - raise ValueError(f"trajectory '{trajectory}' not supported.") - - return kspace diff --git a/meddlr/data/transforms/transform.py b/meddlr/data/transforms/transform.py index f443840f..c77dd624 100644 --- a/meddlr/data/transforms/transform.py +++ b/meddlr/data/transforms/transform.py @@ -7,9 +7,9 @@ import torch from fvcore.common.registry import Registry -from meddlr.data.transforms.motion_corruption_2D import add_motion_corruption from meddlr.forward import SenseModel from meddlr.ops import complex as cplx +from meddlr.transforms.functional import add_motion_corruption from meddlr.transforms.gen.spatial import RandomAffine from meddlr.utils import transforms as T @@ -384,8 +384,8 @@ def __init__( is_test: bool = False, add_noise: bool = False, add_motion: bool = False, - two_dimensional: bool = True, - angle: Optional[Tuple[float, float]] = (-5., 5.), + mri_dim: int = 2, + angle: Optional[Tuple[float, float]] = (-5.0, 5.0), translation: Optional[Tuple[float, float]] = (0.1, 0.1), trajectory: str = "blocked", ): @@ -398,6 +398,11 @@ def __init__( generator seed from the filename. This ensures that the same mask is used for all the slices of a given volume every time. """ + if not is_test: + raise ValueError( + "is_test must be true for this class to work - it is currently set to false" + ) + self._cfg = cfg self.mask_func = mask_func self._is_test = is_test @@ -411,7 +416,7 @@ def __init__( # These will be used for the motion corruption. self.angle = angle self.translation = translation - self.two_dimensional = two_dimensional + self.mri_dim = mri_dim if nshots is None: raise ValueError("The paramter nshots must be set to some integer value.") @@ -478,11 +483,11 @@ def __call__(self, kspace, maps, target, fname, slice_id, is_fixed, acceleration # If 2D MRI, then each slice will have different motion - seed is some # combination of the file name and the slice id (+). - # If 3D MRI, then each slice should have the same motion - seed is - # determined only by the file name. - if self.two_dimensional: - seed = sum(tuple(map(ord, fname))) + slice if self._is_test else None # noqa - else: + # If 3D MRI, then each slice should have the same motion - seed is + # determined only by the file name. + if self.mri_dim == 2: + seed = sum(tuple(map(ord, fname))) + slice_id if self._is_test else None # noqa + elif self.mri_dim == 3: seed = sum(tuple(map(ord, fname))) if self._is_test else None # noqa # Zero-filled Sense Recon. diff --git a/meddlr/transforms/functional/mri.py b/meddlr/transforms/functional/mri.py index e2805b7c..ab38ce36 100644 --- a/meddlr/transforms/functional/mri.py +++ b/meddlr/transforms/functional/mri.py @@ -1,7 +1,12 @@ +import math +from typing import Optional + import numpy as np import torch +import meddlr.ops as F import meddlr.ops.complex as cplx +from meddlr.transforms.gen.spatial import RandomAffine def add_even_odd_motion( @@ -35,3 +40,51 @@ def add_even_odd_motion( phase_matrix[:, :, line] = phase_error aug_kspace = kspace * phase_matrix return aug_kspace + + +def add_motion_corruption( + image: torch.Tensor, + nshots: int, + translation: Optional[RandomAffine] = None, + trajectory: str = "blocked", +) -> torch.Tensor: + """ + Simulate 2D motion for multi-shot Cartesian MRI. + + This function supports two trajectories: + - 'blocked': Where each shot corresponds to a consecutive block of kspace. + (e.g. 1 1 2 2 3 3) + - 'interleaved': Where shots are interleaved (e.g. 1 2 3 1 2 3) + + We assume the phase encode direction is left to right + (i.e. along width dimension). + + TODO: Add support for sensitivity maps. + + Args: + image: The complex-valued image. Shape [..., height, width]. + nshots: The number of shots in the image. + This should be equivalent to ceil(phase_encode_dim / echo_train_length). + translation: This is the translation to augment images in the image + domain. This is either 'None' or 'RandomAffine' for now. + trajectory: One of 'interleaved' or 'consecutive'. + + Returns: + A motion corrupted image. + """ + kspace = torch.zeros_like(image) + offset = int(math.ceil(kspace.shape[-1] / nshots)) + + for shot in range(nshots): + motion_image = translation.get_transform(image).apply_image(image) + motion_kspace = F.fft2c(motion_image) + if trajectory == "blocked": + kspace[..., shot * offset : (shot + 1) * offset] = motion_kspace[ + ..., shot * offset : (shot + 1) * offset + ] + elif trajectory == "interleaved": + kspace[..., shot::nshots] = motion_kspace[..., shot::nshots] + else: + raise ValueError(f"trajectory '{trajectory}' not supported.") + + return kspace diff --git a/tools/eval_net.py b/tools/eval_net.py index f5f0082b..06ad7bb4 100644 --- a/tools/eval_net.py +++ b/tools/eval_net.py @@ -9,7 +9,7 @@ import itertools import os from copy import deepcopy -from typing import Any, Dict, Sequence +from typing import Any, Dict, Optional, Sequence import pandas as pd import torch @@ -19,6 +19,8 @@ from meddlr.checkpoint import Checkpointer from meddlr.config import get_cfg from meddlr.data.build import build_recon_val_loader +from meddlr.data.transforms import transform as T +from meddlr.data.transforms.subsample import build_mask_func from meddlr.engine import DefaultTrainer, default_argument_parser, default_setup from meddlr.evaluation import DatasetEvaluators, ReconEvaluator, inference_on_dataset from meddlr.evaluation.testing import check_consistency, find_weights @@ -188,7 +190,7 @@ def update_metrics(metrics_new: pd.DataFrame, metrics_old: pd.DataFrame, on: Seq def eval(cfg, args, model, weights_basename, criterion, best_value): zero_filled = args.zero_filled - two_dimensional = args.two_dimensional != "false" + mri_dim = args.mri_dim angle = args.angle translation = args.translation nshots = args.nshots @@ -209,6 +211,44 @@ def eval(cfg, args, model, weights_basename, criterion, best_value): # if use_wandb: # run = init_wandb_run(cfg, resume=True, job_type="eval", use_api=True) + data_transform = None + + if not ( + mri_dim is None + and angle is None + and translation is None + and nshots is None + and trajectory == "None" + ): + transform_mri_dim = 2 + transform_angle = 0 + transform_translation = 0 + transform_nshots = 0 + transform_trajectory = 0 + if mri_dim is not None: + transform_mri_dim = mri_dim + if angle is not None: + transform_angle = angle + if translation is not None: + transform_translation = translation + if nshots is not None: + transform_nshots = nshots + if trajectory is not None: + transform_trajectory = trajectory + mask_func = build_mask_func(cfg.AUG_TRAIN) + data_transform = T.MotionDataTransform( + cfg, + mask_func, + nshots=transform_nshots, + is_test=True, + add_noise=include_noise, + add_motion=include_motion != "false", + mri_dim=transform_mri_dim, + angle=transform_angle, + translation=transform_translation, + trajectory=transform_trajectory, + ) + device = cfg.MODEL.DEVICE model = model.to(device) model = model.eval() @@ -309,11 +349,7 @@ def eval(cfg, args, model, weights_basename, criterion, best_value): as_test=True, add_noise=noise_level > 0, add_motion=include_motion != "false", - two_dimensional=two_dimensional, - angle=angle, - translation=translation, - nshots=nshots, - trajectory=trajectory, + data_transform=data_transform, ) # Build evaluators. Only save reconstructions for last scan. @@ -475,37 +511,34 @@ def main(args): parser.add_argument( "--angle", - default=0, - type=float, + default=None, + type=Optional[float], help=("How much rotation angle should be used for motion corruption " "of the dataset"), ) parser.add_argument( "--translation", - default=0, - type=float, + default=None, + type=Optional[float], help=("How much translation should be used for motion " "corruption of the dataset"), ) parser.add_argument( "--nshots", - default=0, - type=int, + default=None, + type=Optional[float], help=("How many shots should be used for motion corruption " "of the dataset."), ) parser.add_argument( "--trajectory", - default="blocked", - choice=("interleaved", "blocked"), + default="None", + choice=("None", "interleaved", "blocked"), help=( "Chooses between interleaved or blocked shots for motion " "corruption of the dataset" ), ) parser.add_argument( - "--two-dimensional", - default="true", - choice=("true", "false"), - help=("If true, MRI is 2D. Otherwise, MRI is 3D.") + "--mri_dim", default=None, type=Optional[float], help=("Selects dimensionality number") ) # End of Arguments for 2D Motion Corruption of the Dataset From 7c4ded4a48f0175ff79e9091516fbf89c66dcb80 Mon Sep 17 00:00:00 2001 From: DPro Date: Sat, 23 Jul 2022 10:39:31 -0700 Subject: [PATCH 17/33] moved motion_corruption in mri.py to transform.py --- meddlr/data/transforms/transform.py | 47 +++++++++++++++++++++++++++++ meddlr/transforms/functional/mri.py | 45 --------------------------- 2 files changed, 47 insertions(+), 45 deletions(-) diff --git a/meddlr/data/transforms/transform.py b/meddlr/data/transforms/transform.py index c77dd624..1f2004a8 100644 --- a/meddlr/data/transforms/transform.py +++ b/meddlr/data/transforms/transform.py @@ -21,6 +21,53 @@ Registry for normalizing images """ +def add_motion_corruption( + image: torch.Tensor, + nshots: int, + translation: Optional[RandomAffine] = None, + trajectory: str = "blocked", +) -> torch.Tensor: + """ + Simulate 2D motion for multi-shot Cartesian MRI. + + This function supports two trajectories: + - 'blocked': Where each shot corresponds to a consecutive block of kspace. + (e.g. 1 1 2 2 3 3) + - 'interleaved': Where shots are interleaved (e.g. 1 2 3 1 2 3) + + We assume the phase encode direction is left to right + (i.e. along width dimension). + + TODO: Add support for sensitivity maps. + + Args: + image: The complex-valued image. Shape [..., height, width]. + nshots: The number of shots in the image. + This should be equivalent to ceil(phase_encode_dim / echo_train_length). + translation: This is the translation to augment images in the image + domain. This is either 'None' or 'RandomAffine' for now. + trajectory: One of 'interleaved' or 'consecutive'. + + Returns: + A motion corrupted image. + """ + kspace = torch.zeros_like(image) + offset = int(math.ceil(kspace.shape[-1] / nshots)) + + for shot in range(nshots): + motion_image = translation.get_transform(image).apply_image(image) + motion_kspace = F.fft2c(motion_image) + if trajectory == "blocked": + kspace[..., shot * offset : (shot + 1) * offset] = motion_kspace[ + ..., shot * offset : (shot + 1) * offset + ] + elif trajectory == "interleaved": + kspace[..., shot::nshots] = motion_kspace[..., shot::nshots] + else: + raise ValueError(f"trajectory '{trajectory}' not supported.") + + return kspace + def build_normalizer(cfg): cfg = cfg.MODEL.NORMALIZER diff --git a/meddlr/transforms/functional/mri.py b/meddlr/transforms/functional/mri.py index ab38ce36..0f2de60f 100644 --- a/meddlr/transforms/functional/mri.py +++ b/meddlr/transforms/functional/mri.py @@ -42,49 +42,4 @@ def add_even_odd_motion( return aug_kspace -def add_motion_corruption( - image: torch.Tensor, - nshots: int, - translation: Optional[RandomAffine] = None, - trajectory: str = "blocked", -) -> torch.Tensor: - """ - Simulate 2D motion for multi-shot Cartesian MRI. - This function supports two trajectories: - - 'blocked': Where each shot corresponds to a consecutive block of kspace. - (e.g. 1 1 2 2 3 3) - - 'interleaved': Where shots are interleaved (e.g. 1 2 3 1 2 3) - - We assume the phase encode direction is left to right - (i.e. along width dimension). - - TODO: Add support for sensitivity maps. - - Args: - image: The complex-valued image. Shape [..., height, width]. - nshots: The number of shots in the image. - This should be equivalent to ceil(phase_encode_dim / echo_train_length). - translation: This is the translation to augment images in the image - domain. This is either 'None' or 'RandomAffine' for now. - trajectory: One of 'interleaved' or 'consecutive'. - - Returns: - A motion corrupted image. - """ - kspace = torch.zeros_like(image) - offset = int(math.ceil(kspace.shape[-1] / nshots)) - - for shot in range(nshots): - motion_image = translation.get_transform(image).apply_image(image) - motion_kspace = F.fft2c(motion_image) - if trajectory == "blocked": - kspace[..., shot * offset : (shot + 1) * offset] = motion_kspace[ - ..., shot * offset : (shot + 1) * offset - ] - elif trajectory == "interleaved": - kspace[..., shot::nshots] = motion_kspace[..., shot::nshots] - else: - raise ValueError(f"trajectory '{trajectory}' not supported.") - - return kspace From 176fd637bfdb8d5024ee5d54c999e7814554c605 Mon Sep 17 00:00:00 2001 From: DPro Date: Sat, 23 Jul 2022 11:47:55 -0700 Subject: [PATCH 18/33] removed import for add_motion_corruption --- meddlr/data/transforms/transform.py | 1 - 1 file changed, 1 deletion(-) diff --git a/meddlr/data/transforms/transform.py b/meddlr/data/transforms/transform.py index 1f2004a8..f3a5c596 100644 --- a/meddlr/data/transforms/transform.py +++ b/meddlr/data/transforms/transform.py @@ -9,7 +9,6 @@ from meddlr.forward import SenseModel from meddlr.ops import complex as cplx -from meddlr.transforms.functional import add_motion_corruption from meddlr.transforms.gen.spatial import RandomAffine from meddlr.utils import transforms as T From 3153746d6b78cc197da324c051907c0ed716a724 Mon Sep 17 00:00:00 2001 From: DPro Date: Sat, 23 Jul 2022 14:49:51 -0700 Subject: [PATCH 19/33] removed import RandomAffine statement from mri.py --- meddlr/transforms/functional/mri.py | 1 - 1 file changed, 1 deletion(-) diff --git a/meddlr/transforms/functional/mri.py b/meddlr/transforms/functional/mri.py index 0f2de60f..eeb7acaf 100644 --- a/meddlr/transforms/functional/mri.py +++ b/meddlr/transforms/functional/mri.py @@ -6,7 +6,6 @@ import meddlr.ops as F import meddlr.ops.complex as cplx -from meddlr.transforms.gen.spatial import RandomAffine def add_even_odd_motion( From 2cbca2d47a9fd2292f80bb934f2427707d92c1bc Mon Sep 17 00:00:00 2001 From: Deepro Pasha Date: Fri, 5 Aug 2022 21:12:10 -0700 Subject: [PATCH 20/33] succesfully working motion corruption and reconstruction --- meddlr/data/transforms/transform.py | 13 +++++++++++-- tools/eval_net.py | 28 ++++++++++++++++------------ 2 files changed, 27 insertions(+), 14 deletions(-) diff --git a/meddlr/data/transforms/transform.py b/meddlr/data/transforms/transform.py index f3a5c596..bd9cb89a 100644 --- a/meddlr/data/transforms/transform.py +++ b/meddlr/data/transforms/transform.py @@ -1,5 +1,6 @@ """Basic Transforms. """ +import math from functools import partial from typing import Optional, Tuple @@ -7,6 +8,7 @@ import torch from fvcore.common.registry import Registry +import meddlr.ops as F from meddlr.forward import SenseModel from meddlr.ops import complex as cplx from meddlr.transforms.gen.spatial import RandomAffine @@ -20,6 +22,7 @@ Registry for normalizing images """ + def add_motion_corruption( image: torch.Tensor, nshots: int, @@ -65,7 +68,7 @@ def add_motion_corruption( else: raise ValueError(f"trajectory '{trajectory}' not supported.") - return kspace + return F.ifft2c(kspace) def build_normalizer(cfg): @@ -554,13 +557,19 @@ def __call__(self, kspace, maps, target, fname, slice_id, is_fixed, acceleration tfm_gen = RandomAffine(p=1.0, translate=self.translation, angle=self.angle) tfm_gen.seed(seed) - kspace = add_motion_corruption( + image = image.permute(0, 3, 1, 2) + + motion_img = add_motion_corruption( image=image, nshots=self.nshots, translation=tfm_gen, trajectory=self.trajectory, ) + motion_img = motion_img.permute(0, 2, 3, 1) + sense = SenseModel(maps) + kspace = sense(motion_img) + # Apply mask in k-space masked_kspace, mask = self._subsampler( kspace, mode="2D", seed=seed, acceleration=acceleration diff --git a/tools/eval_net.py b/tools/eval_net.py index 06ad7bb4..a41bd816 100644 --- a/tools/eval_net.py +++ b/tools/eval_net.py @@ -9,7 +9,7 @@ import itertools import os from copy import deepcopy -from typing import Any, Dict, Optional, Sequence +from typing import Any, Dict, Sequence import pandas as pd import torch @@ -218,7 +218,7 @@ def eval(cfg, args, model, weights_basename, criterion, best_value): and angle is None and translation is None and nshots is None - and trajectory == "None" + and trajectory == "none" ): transform_mri_dim = 2 transform_angle = 0 @@ -228,9 +228,9 @@ def eval(cfg, args, model, weights_basename, criterion, best_value): if mri_dim is not None: transform_mri_dim = mri_dim if angle is not None: - transform_angle = angle + transform_angle = (-angle, angle) if translation is not None: - transform_translation = translation + transform_translation = (translation, translation) if nshots is not None: transform_nshots = nshots if trajectory is not None: @@ -303,6 +303,11 @@ def eval(cfg, args, model, weights_basename, criterion, best_value): "dataset": dataset_name, "Noise Level": noise_level, "Motion Level": motion_level, + "Angle": str(angle), + "Translation": str(translation), + "Trajectory": trajectory, + "n-shots": str(nshots), + "MRI DIM": str(mri_dim), "weights": weights_basename, "rescaled": not skip_rescale, } @@ -348,7 +353,7 @@ def eval(cfg, args, model, weights_basename, criterion, best_value): dataset_name, as_test=True, add_noise=noise_level > 0, - add_motion=include_motion != "false", + add_motion=(include_motion != "false" and data_transform is not None), data_transform=data_transform, ) @@ -512,34 +517,33 @@ def main(args): parser.add_argument( "--angle", default=None, - type=Optional[float], + type=float, help=("How much rotation angle should be used for motion corruption " "of the dataset"), ) parser.add_argument( "--translation", default=None, - type=Optional[float], + type=float, help=("How much translation should be used for motion " "corruption of the dataset"), ) parser.add_argument( "--nshots", default=None, - type=Optional[float], + type=int, help=("How many shots should be used for motion corruption " "of the dataset."), ) + parser.add_argument( "--trajectory", default="None", - choice=("None", "interleaved", "blocked"), + choices=("None", "interleaved", "blocked"), help=( "Chooses between interleaved or blocked shots for motion " "corruption of the dataset" ), ) - parser.add_argument( - "--mri_dim", default=None, type=Optional[float], help=("Selects dimensionality number") - ) + parser.add_argument("--mri_dim", default=None, type=int, help=("Selects dimensionality number")) # End of Arguments for 2D Motion Corruption of the Dataset From 52c40cd6d045e27d439a430c802acec51c0f8cfd Mon Sep 17 00:00:00 2001 From: DPro Date: Tue, 9 Aug 2022 11:45:05 -0700 Subject: [PATCH 21/33] Addressed comments in PR --- meddlr/data/transforms/transform.py | 8 ++++---- tools/eval_net.py | 16 +++++++--------- 2 files changed, 11 insertions(+), 13 deletions(-) diff --git a/meddlr/data/transforms/transform.py b/meddlr/data/transforms/transform.py index bd9cb89a..3737a74c 100644 --- a/meddlr/data/transforms/transform.py +++ b/meddlr/data/transforms/transform.py @@ -23,7 +23,7 @@ """ -def add_motion_corruption( +def affine_transform( image: torch.Tensor, nshots: int, translation: Optional[RandomAffine] = None, @@ -557,16 +557,16 @@ def __call__(self, kspace, maps, target, fname, slice_id, is_fixed, acceleration tfm_gen = RandomAffine(p=1.0, translate=self.translation, angle=self.angle) tfm_gen.seed(seed) - image = image.permute(0, 3, 1, 2) + image = image.permute(0, 3, 1, 2) # Shape: (B, 1, H, W) - motion_img = add_motion_corruption( + motion_img = affine_transform( image=image, nshots=self.nshots, translation=tfm_gen, trajectory=self.trajectory, ) - motion_img = motion_img.permute(0, 2, 3, 1) + motion_img = motion_img.permute(0, 2, 3, 1) # Shape: (B, H, W, 1) sense = SenseModel(maps) kspace = sense(motion_img) diff --git a/tools/eval_net.py b/tools/eval_net.py index a41bd816..5088329a 100644 --- a/tools/eval_net.py +++ b/tools/eval_net.py @@ -307,7 +307,7 @@ def eval(cfg, args, model, weights_basename, criterion, best_value): "Translation": str(translation), "Trajectory": trajectory, "n-shots": str(nshots), - "MRI DIM": str(mri_dim), + "mri_dim": str(mri_dim), "weights": weights_basename, "rescaled": not skip_rescale, } @@ -353,7 +353,7 @@ def eval(cfg, args, model, weights_basename, criterion, best_value): dataset_name, as_test=True, add_noise=noise_level > 0, - add_motion=(include_motion != "false" and data_transform is not None), + add_motion=(include_motion and data_transform is not None), data_transform=data_transform, ) @@ -518,32 +518,30 @@ def main(args): "--angle", default=None, type=float, - help=("How much rotation angle should be used for motion corruption " "of the dataset"), + help="How much rotation angle should be used for motion corruption of the dataset", ) parser.add_argument( "--translation", default=None, type=float, - help=("How much translation should be used for motion " "corruption of the dataset"), + help="How much translation should be used for motion corruption of the dataset", ) parser.add_argument( "--nshots", default=None, type=int, - help=("How many shots should be used for motion corruption " "of the dataset."), + help="How many shots should be used for motion corruption of the dataset.", ) parser.add_argument( "--trajectory", default="None", choices=("None", "interleaved", "blocked"), - help=( - "Chooses between interleaved or blocked shots for motion " "corruption of the dataset" - ), + help= "Chooses between interleaved or blocked shots for motion corruption of the dataset", ) - parser.add_argument("--mri_dim", default=None, type=int, help=("Selects dimensionality number")) + parser.add_argument("--mri_dim", default=None, type=int, help="Selects dimensionality number") # End of Arguments for 2D Motion Corruption of the Dataset From c0f137f6022bb2808f5fc1d72688914ca697b7f0 Mon Sep 17 00:00:00 2001 From: Deepro Pasha Date: Fri, 26 Aug 2022 15:01:36 -0700 Subject: [PATCH 22/33] added pad like command argument --- meddlr/data/transforms/transform.py | 13 +++++++++++-- tools/eval_net.py | 15 ++++++++++++++- 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/meddlr/data/transforms/transform.py b/meddlr/data/transforms/transform.py index bd9cb89a..a839db35 100644 --- a/meddlr/data/transforms/transform.py +++ b/meddlr/data/transforms/transform.py @@ -436,6 +436,7 @@ def __init__( mri_dim: int = 2, angle: Optional[Tuple[float, float]] = (-5.0, 5.0), translation: Optional[Tuple[float, float]] = (0.1, 0.1), + pad_like: str = "none", trajectory: str = "blocked", ): """ @@ -468,10 +469,11 @@ def __init__( self.mri_dim = mri_dim if nshots is None: - raise ValueError("The paramter nshots must be set to some integer value.") + raise ValueError("The parameter nshots must be set to some integer value.") self.nshots = nshots self.trajectory = trajectory + self.pad_like = pad_like seed = cfg.SEED if cfg.SEED > -1 else None self.rng = np.random.RandomState(seed) @@ -539,6 +541,11 @@ def __call__(self, kspace, maps, target, fname, slice_id, is_fixed, acceleration elif self.mri_dim == 3: seed = sum(tuple(map(ord, fname))) if self._is_test else None # noqa + if self.pad_like == "none": + pad = None + if self.pad_like == "mraugment": + pad = "MRAugment" + # Zero-filled Sense Recon. if torch.is_complex(target_init): A = SenseModel(maps) @@ -554,7 +561,9 @@ def __call__(self, kspace, maps, target, fname, slice_id, is_fixed, acceleration # Motion seed should not be different for each slice for now. # TODO: Change this for 2D acquisitions. - tfm_gen = RandomAffine(p=1.0, translate=self.translation, angle=self.angle) + tfm_gen = RandomAffine( + p=1.0, translate=self.translation, angle=self.angle, pad_like=pad + ) tfm_gen.seed(seed) image = image.permute(0, 3, 1, 2) diff --git a/tools/eval_net.py b/tools/eval_net.py index a41bd816..6d1e86f9 100644 --- a/tools/eval_net.py +++ b/tools/eval_net.py @@ -194,6 +194,7 @@ def eval(cfg, args, model, weights_basename, criterion, best_value): angle = args.angle translation = args.translation nshots = args.nshots + padlike = args.pad_like.lower() trajectory = args.trajectory.lower() noise_arg = args.noise.lower() @@ -219,12 +220,14 @@ def eval(cfg, args, model, weights_basename, criterion, best_value): and translation is None and nshots is None and trajectory == "none" + and padlike == "none" ): transform_mri_dim = 2 transform_angle = 0 transform_translation = 0 transform_nshots = 0 transform_trajectory = 0 + transform_padlike = "none" if mri_dim is not None: transform_mri_dim = mri_dim if angle is not None: @@ -233,8 +236,10 @@ def eval(cfg, args, model, weights_basename, criterion, best_value): transform_translation = (translation, translation) if nshots is not None: transform_nshots = nshots - if trajectory is not None: + if trajectory != "none": transform_trajectory = trajectory + if padlike != "none": + transform_padlike = padlike mask_func = build_mask_func(cfg.AUG_TRAIN) data_transform = T.MotionDataTransform( cfg, @@ -246,6 +251,7 @@ def eval(cfg, args, model, weights_basename, criterion, best_value): mri_dim=transform_mri_dim, angle=transform_angle, translation=transform_translation, + pad_like=transform_padlike, trajectory=transform_trajectory, ) @@ -545,6 +551,13 @@ def main(args): parser.add_argument("--mri_dim", default=None, type=int, help=("Selects dimensionality number")) + parser.add_argument( + "--pad_like", + default="None", + choices=("None", "MRAugment"), + help=("Specify pad like argument to Random Affine transformation"), + ) + # End of Arguments for 2D Motion Corruption of the Dataset parser.add_argument( From e73f2e098a04ca34b8a2ed9546b617bcfef5ab28 Mon Sep 17 00:00:00 2001 From: Deepro Pasha Date: Sun, 28 Aug 2022 13:48:52 -0700 Subject: [PATCH 23/33] fixed RandomAffine not implementing pad_like when it called AffineTransform --- meddlr/transforms/gen/spatial.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/meddlr/transforms/gen/spatial.py b/meddlr/transforms/gen/spatial.py index 791bd7fa..64cb1883 100644 --- a/meddlr/transforms/gen/spatial.py +++ b/meddlr/transforms/gen/spatial.py @@ -95,7 +95,9 @@ def get_transform(self, image): return NoOpTransform() angle, translate, scale, shear = out - return AffineTransform(angle=angle, translate=translate, scale=scale, shear=shear) + return AffineTransform( + angle=angle, translate=translate, scale=scale, shear=shear, pad_like=self.pad_like + ) @TRANSFORM_REGISTRY.register() From bda889dae46498ee43e43a2618d439a15c0d0fef Mon Sep 17 00:00:00 2001 From: Arjun Desai Date: Tue, 6 Sep 2022 08:35:49 -0700 Subject: [PATCH 24/33] implement pad fix template --- meddlr/data/transforms/transform.py | 46 +++++++++++++++++++---------- 1 file changed, 31 insertions(+), 15 deletions(-) diff --git a/meddlr/data/transforms/transform.py b/meddlr/data/transforms/transform.py index f6ef4afe..1b4c6131 100644 --- a/meddlr/data/transforms/transform.py +++ b/meddlr/data/transforms/transform.py @@ -2,7 +2,7 @@ """ import math from functools import partial -from typing import Optional, Tuple +from typing import List, Optional, Sequence, Tuple import numpy as np import torch @@ -11,7 +11,8 @@ import meddlr.ops as F from meddlr.forward import SenseModel from meddlr.ops import complex as cplx -from meddlr.transforms.gen.spatial import RandomAffine +from meddlr.transforms.gen.spatial import RandomAffine, RandomTranslation +from meddlr.transforms.transform_gen import TransformGen from meddlr.utils import transforms as T from .motion import MotionModel @@ -26,7 +27,7 @@ def affine_transform( image: torch.Tensor, nshots: int, - translation: Optional[RandomAffine] = None, + transforms: Sequence[TransformGen], trajectory: str = "blocked", ) -> torch.Tensor: """ @@ -46,9 +47,11 @@ def affine_transform( image: The complex-valued image. Shape [..., height, width]. nshots: The number of shots in the image. This should be equivalent to ceil(phase_encode_dim / echo_train_length). - translation: This is the translation to augment images in the image - domain. This is either 'None' or 'RandomAffine' for now. - trajectory: One of 'interleaved' or 'consecutive'. + transforms: A sequence of random transform generators. These transforms + will be used to augment images in the image domain. We recommend using + [RandomTranslation, RandomAffine] in that order. This matches the MRAugment + augmentation strategy. + trajectory: One of 'interleaved' or 'blocked'. Returns: A motion corrupted image. @@ -57,7 +60,11 @@ def affine_transform( offset = int(math.ceil(kspace.shape[-1] / nshots)) for shot in range(nshots): - motion_image = translation.get_transform(image).apply_image(image) + # Apply sequence of random transforms to the image. + motion_image = image + for tfm in transforms: + motion_image = tfm.get_transform(motion_image).apply_image(motion_image) + motion_kspace = F.fft2c(motion_image) if trajectory == "blocked": kspace[..., shot * offset : (shot + 1) * offset] = motion_kspace[ @@ -558,24 +565,33 @@ def __call__(self, kspace, maps, target, fname, slice_id, is_fixed, acceleration add_motion = self.add_motion and self._is_test if add_motion: + # Separate translation and affine transformations for proper padding. + translation = RandomTranslation( + p=1.0, + translate=self.translation, + pad_mode="reflect" if self.pad_like == "mraugment" else "constant", + pad_value=0.0, + ndim=2 + ) + affine = RandomAffine( + p=1.0, translate=None, angle=self.angle, pad_like=pad + ) + transforms: List[TransformGen] = [translation, affine] # Motion seed should not be different for each slice for now. # TODO: Change this for 2D acquisitions. + for tfm_gen in transforms: + tfm_gen.seed(seed) - tfm_gen = RandomAffine( - p=1.0, translate=self.translation, angle=self.angle, pad_like=pad - ) - tfm_gen.seed(seed) - - image = image.permute(0, 3, 1, 2) # Shape: (B, 1, H, W) + image = image.permute(0, 3, 1, 2) # Shape: (B, 1, H, W) motion_img = affine_transform( image=image, nshots=self.nshots, - translation=tfm_gen, + transforms=transforms, trajectory=self.trajectory, ) - motion_img = motion_img.permute(0, 2, 3, 1) # Shape: (B, H, W, 1) + motion_img = motion_img.permute(0, 2, 3, 1) # Shape: (B, H, W, 1) sense = SenseModel(maps) kspace = sense(motion_img) From 5d09e398b9cc79de92fb2f769ae008c4785c8f71 Mon Sep 17 00:00:00 2001 From: DPro <35978720+TerminalVelocityDPro@users.noreply.github.com> Date: Wed, 7 Sep 2022 20:29:21 -0700 Subject: [PATCH 25/33] Renamed affine_transform to add_affine_motion --- meddlr/data/transforms/transform.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/meddlr/data/transforms/transform.py b/meddlr/data/transforms/transform.py index 1b4c6131..2d554420 100644 --- a/meddlr/data/transforms/transform.py +++ b/meddlr/data/transforms/transform.py @@ -24,7 +24,7 @@ """ -def affine_transform( +def add_affine_motion( image: torch.Tensor, nshots: int, transforms: Sequence[TransformGen], @@ -584,7 +584,7 @@ def __call__(self, kspace, maps, target, fname, slice_id, is_fixed, acceleration image = image.permute(0, 3, 1, 2) # Shape: (B, 1, H, W) - motion_img = affine_transform( + motion_img = add_affine_motion( image=image, nshots=self.nshots, transforms=transforms, From 94efb2f8c7206079a9c3671e668df4194d787bad Mon Sep 17 00:00:00 2001 From: DPro <35978720+TerminalVelocityDPro@users.noreply.github.com> Date: Mon, 17 Jul 2023 09:14:12 -0700 Subject: [PATCH 26/33] Update transform.py --- meddlr/data/transforms/transform.py | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/meddlr/data/transforms/transform.py b/meddlr/data/transforms/transform.py index d9e63247..758d28f0 100644 --- a/meddlr/data/transforms/transform.py +++ b/meddlr/data/transforms/transform.py @@ -2,21 +2,14 @@ """ import math from functools import partial -<<<<<<< HEAD from typing import List, Optional, Sequence, Tuple -======= from typing import Any, Dict ->>>>>>> main - import numpy as np import torch from fvcore.common.registry import Registry -<<<<<<< HEAD import meddlr.ops as F -======= from meddlr.data.transforms.subsample import MaskFunc ->>>>>>> main from meddlr.forward import SenseModel from meddlr.ops import complex as cplx from meddlr.transforms.gen.spatial import RandomAffine, RandomTranslation @@ -676,4 +669,4 @@ def __call__(self, kspace, maps, target, fname, slice_id, is_fixed, acceleration maps = maps.squeeze(0) target = target.squeeze(0) - return masked_kspace, maps, target, mean, std, norm \ No newline at end of file + return masked_kspace, maps, target, mean, std, norm From 9703c8ab434b5f7f989d074ab5beadffef2acaac Mon Sep 17 00:00:00 2001 From: DPro Date: Mon, 17 Jul 2023 18:25:29 -0700 Subject: [PATCH 27/33] modified MotionDataTransform to behave like DataTransform --- meddlr/data/transforms/transform.py | 34 ++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/meddlr/data/transforms/transform.py b/meddlr/data/transforms/transform.py index 758d28f0..aa0fc2ea 100644 --- a/meddlr/data/transforms/transform.py +++ b/meddlr/data/transforms/transform.py @@ -532,11 +532,17 @@ def __init__( self.p_noise = cfg.AUG_TRAIN.NOISE_P self.p_motion = cfg.AUG_TRAIN.MOTION_P self._normalizer = build_normalizer(cfg) + self._postprocessor = cfg.TEST.POSTPROCESSOR.NAME # Build augmentation pipeline. self.augmentor = None if not is_test and cfg.AUG_TRAIN.MRI_RECON.TRANSFORMS: pass + + def _get_mask(self, masked_kspace): + # If any of the coils are non-zero at a coordinate, we assume + assert torch.is_complex(masked_kspace) + return cplx.get_mask(masked_kspace, coil_dim=-1) def __call__(self, kspace, maps, target, fname, slice_id, is_fixed, acceleration: int = None): """ @@ -575,8 +581,21 @@ def __call__(self, kspace, maps, target, fname, slice_id, is_fixed, acceleration ) # handle rss vs. sensitivity-integrated norm = torch.sqrt(torch.mean(cplx.abs(target) ** 2)) + # Apply mask in k-space + seed = sum(tuple(map(ord, fname))) if self._is_test or is_fixed else None # noqa + masked_kspace, mask = self._subsampler( + kspace, mode="2D", seed=seed, acceleration=acceleration + ) + # TODO: Add other transforms here. + edge_mask = self._subsampler.edge_mask(kspace, mode="2D") + postprocessing_mask = None + if self._is_test and self._postprocessor: + postprocessing_mask = edge_mask + if self._postprocessor == "hard_dc_all": + postprocessing_mask = (postprocessing_mask + mask).bool().type(torch.float32) + # If 2D MRI, then each slice will have different motion - seed is some # combination of the file name and the slice id (+). # If 3D MRI, then each slice should have the same motion - seed is @@ -669,4 +688,17 @@ def __call__(self, kspace, maps, target, fname, slice_id, is_fixed, acceleration maps = maps.squeeze(0) target = target.squeeze(0) - return masked_kspace, maps, target, mean, std, norm + out = { + "kspace": masked_kspace, + "maps": maps, + "target": target, + "mean": mean, + "std": std, + "norm": norm, + "edge_mask": edge_mask.squeeze(0), + "mask": self._get_mask(masked_kspace), + } + if postprocessing_mask is not None: + out["postprocessing_mask"] = postprocessing_mask.squeeze(0) + return out + From 092437da2b0ae7084bf118525880254905f9f17a Mon Sep 17 00:00:00 2001 From: Deepro Pasha Date: Thu, 10 Aug 2023 20:45:39 -0700 Subject: [PATCH 28/33] Changes made for implementing multishot transforms in training pipeline --- .../motion_eval_unet_ssdu_vortex.sh | 18 ++ .../old/SSDU_Aug_UNET_13_Scan.yaml | 199 ++++++++++++++++ .../old/SSDU_Aug_Unrolled_13_Scan.yaml | 199 ++++++++++++++++ .../mridata-3dfse-knee/old/SSDU_Template.yaml | 209 +++++++++++++++++ .../old/SSDU_UNET_13_Scan.yaml | 180 +++++++++++++++ .../old/SSDU_Unrolled_13_Scan.yaml | 180 +++++++++++++++ .../old/Supervised_Aug_UNET_14_Scan.yaml | 193 ++++++++++++++++ .../old/Supervised_Aug_UNET_1_Scan.yaml | 193 ++++++++++++++++ .../old/Supervised_Aug_Unrolled_14_Scan.yaml | 193 ++++++++++++++++ .../old/Supervised_Aug_Unrolled_1_Scan.yaml | 193 ++++++++++++++++ .../old/Supervised_Template.yaml | 216 ++++++++++++++++++ .../old/Supervised_UNET_14_Scan.yaml | 174 ++++++++++++++ .../old/Supervised_UNET_1_Scan.yaml | 174 ++++++++++++++ .../old/Supervised_Unrolled_14_Scan.yaml | 174 ++++++++++++++ .../old/Supervised_Unrolled_1_Scan.yaml | 174 ++++++++++++++ .../mridata-3dfse-knee/old/template.yaml | 36 +++ .../mridata-3dfse-knee/old/unet.yaml | 17 ++ .../mridata-3dfse-knee/old/unrolled.yaml | 23 ++ .../old/vortex_rm_template.yaml | 39 ++++ .../old/vortex_rm_unet.yaml | 41 ++++ .../old/vortex_rm_unrolled.yaml | 43 ++++ .../templates/template.yaml | 45 ++++ .../templates/template_aug.yaml | 10 + .../templates/template_multi_aug_1.yaml | 26 +++ .../templates/template_multi_aug_2.yaml | 34 +++ .../mridata-3dfse-knee/templates/unet.yaml | 14 ++ .../templates/unet_aug.yaml | 14 ++ .../templates/unrolled.yaml | 20 ++ .../templates/unrolled_aug.yaml | 20 ++ .../unet/ssdu/SSDU_Aug_UNET_13_Scan.yaml | 48 ++++ .../unet/ssdu/SSDU_UNET_13_Scan.yaml | 36 +++ .../Supervised_Aug_UNET_14_Scan.yaml | 29 +++ .../Supervised_Aug_UNET_1_Scan.yaml | 28 +++ .../supervised/Supervised_UNET_14_Scan.yaml | 29 +++ .../supervised/Supervised_UNET_1_Scan.yaml | 29 +++ .../ssdu/SSDU_Aug_Unrolled_13_Scan.yaml | 49 ++++ .../unrolled/ssdu/SSDU_Unrolled_13_Scan.yaml | 33 +++ .../Supervised_Aug_Unrolled_14_Scan.yaml | 26 +++ .../Supervised_Aug_Unrolled_1_Scan.yaml | 25 ++ .../Supervised_Unrolled_14_Scan.yaml | 26 +++ .../Supervised_Unrolled_1_Scan.yaml | 26 +++ get_available_gpu.py | 4 + meddlr/transforms/base/__init__.py | 1 + meddlr/transforms/base/motion.py | 93 ++++++++ meddlr/transforms/gen/motion.py | 46 +++- motion_eval_unet_ssdu_vortex.sh | 20 ++ motion_eval_unet_supervised_vortex.sh | 28 +++ motion_eval_unrolled_ssdu_vortex.sh | 18 ++ motion_eval_unrolled_supervised_vortex.sh | 29 +++ run_unet_norm_vortex.sh | 18 ++ run_unet_supervised_vortex.sh | 33 +++ run_unrolled_norm_vortex.sh | 16 ++ run_unrolled_ssdu_vortex.sh | 16 ++ run_unrolled_supervised_vortex.sh | 34 +++ 54 files changed, 3790 insertions(+), 1 deletion(-) create mode 100755 configs/mri-recon/mridata-3dfse-knee/motion_eval_unet_ssdu_vortex.sh create mode 100644 configs/mri-recon/mridata-3dfse-knee/old/SSDU_Aug_UNET_13_Scan.yaml create mode 100644 configs/mri-recon/mridata-3dfse-knee/old/SSDU_Aug_Unrolled_13_Scan.yaml create mode 100644 configs/mri-recon/mridata-3dfse-knee/old/SSDU_Template.yaml create mode 100644 configs/mri-recon/mridata-3dfse-knee/old/SSDU_UNET_13_Scan.yaml create mode 100644 configs/mri-recon/mridata-3dfse-knee/old/SSDU_Unrolled_13_Scan.yaml create mode 100644 configs/mri-recon/mridata-3dfse-knee/old/Supervised_Aug_UNET_14_Scan.yaml create mode 100644 configs/mri-recon/mridata-3dfse-knee/old/Supervised_Aug_UNET_1_Scan.yaml create mode 100644 configs/mri-recon/mridata-3dfse-knee/old/Supervised_Aug_Unrolled_14_Scan.yaml create mode 100644 configs/mri-recon/mridata-3dfse-knee/old/Supervised_Aug_Unrolled_1_Scan.yaml create mode 100644 configs/mri-recon/mridata-3dfse-knee/old/Supervised_Template.yaml create mode 100644 configs/mri-recon/mridata-3dfse-knee/old/Supervised_UNET_14_Scan.yaml create mode 100644 configs/mri-recon/mridata-3dfse-knee/old/Supervised_UNET_1_Scan.yaml create mode 100644 configs/mri-recon/mridata-3dfse-knee/old/Supervised_Unrolled_14_Scan.yaml create mode 100644 configs/mri-recon/mridata-3dfse-knee/old/Supervised_Unrolled_1_Scan.yaml create mode 100644 configs/mri-recon/mridata-3dfse-knee/old/template.yaml create mode 100644 configs/mri-recon/mridata-3dfse-knee/old/unet.yaml create mode 100644 configs/mri-recon/mridata-3dfse-knee/old/unrolled.yaml create mode 100644 configs/mri-recon/mridata-3dfse-knee/old/vortex_rm_template.yaml create mode 100644 configs/mri-recon/mridata-3dfse-knee/old/vortex_rm_unet.yaml create mode 100644 configs/mri-recon/mridata-3dfse-knee/old/vortex_rm_unrolled.yaml create mode 100644 configs/mri-recon/mridata-3dfse-knee/templates/template.yaml create mode 100644 configs/mri-recon/mridata-3dfse-knee/templates/template_aug.yaml create mode 100644 configs/mri-recon/mridata-3dfse-knee/templates/template_multi_aug_1.yaml create mode 100644 configs/mri-recon/mridata-3dfse-knee/templates/template_multi_aug_2.yaml create mode 100644 configs/mri-recon/mridata-3dfse-knee/templates/unet.yaml create mode 100644 configs/mri-recon/mridata-3dfse-knee/templates/unet_aug.yaml create mode 100644 configs/mri-recon/mridata-3dfse-knee/templates/unrolled.yaml create mode 100644 configs/mri-recon/mridata-3dfse-knee/templates/unrolled_aug.yaml create mode 100644 configs/mri-recon/mridata-3dfse-knee/unet/ssdu/SSDU_Aug_UNET_13_Scan.yaml create mode 100644 configs/mri-recon/mridata-3dfse-knee/unet/ssdu/SSDU_UNET_13_Scan.yaml create mode 100644 configs/mri-recon/mridata-3dfse-knee/unet/supervised/Supervised_Aug_UNET_14_Scan.yaml create mode 100644 configs/mri-recon/mridata-3dfse-knee/unet/supervised/Supervised_Aug_UNET_1_Scan.yaml create mode 100644 configs/mri-recon/mridata-3dfse-knee/unet/supervised/Supervised_UNET_14_Scan.yaml create mode 100644 configs/mri-recon/mridata-3dfse-knee/unet/supervised/Supervised_UNET_1_Scan.yaml create mode 100644 configs/mri-recon/mridata-3dfse-knee/unrolled/ssdu/SSDU_Aug_Unrolled_13_Scan.yaml create mode 100644 configs/mri-recon/mridata-3dfse-knee/unrolled/ssdu/SSDU_Unrolled_13_Scan.yaml create mode 100644 configs/mri-recon/mridata-3dfse-knee/unrolled/supervised/Supervised_Aug_Unrolled_14_Scan.yaml create mode 100644 configs/mri-recon/mridata-3dfse-knee/unrolled/supervised/Supervised_Aug_Unrolled_1_Scan.yaml create mode 100644 configs/mri-recon/mridata-3dfse-knee/unrolled/supervised/Supervised_Unrolled_14_Scan.yaml create mode 100644 configs/mri-recon/mridata-3dfse-knee/unrolled/supervised/Supervised_Unrolled_1_Scan.yaml create mode 100644 get_available_gpu.py create mode 100755 motion_eval_unet_ssdu_vortex.sh create mode 100755 motion_eval_unet_supervised_vortex.sh create mode 100755 motion_eval_unrolled_ssdu_vortex.sh create mode 100755 motion_eval_unrolled_supervised_vortex.sh create mode 100755 run_unet_norm_vortex.sh create mode 100755 run_unet_supervised_vortex.sh create mode 100755 run_unrolled_norm_vortex.sh create mode 100755 run_unrolled_ssdu_vortex.sh create mode 100755 run_unrolled_supervised_vortex.sh diff --git a/configs/mri-recon/mridata-3dfse-knee/motion_eval_unet_ssdu_vortex.sh b/configs/mri-recon/mridata-3dfse-knee/motion_eval_unet_ssdu_vortex.sh new file mode 100755 index 00000000..5cd25306 --- /dev/null +++ b/configs/mri-recon/mridata-3dfse-knee/motion_eval_unet_ssdu_vortex.sh @@ -0,0 +1,18 @@ +#!/bin/bash +export MEDDLR_DATASETS_DIR=/mnt/dense/ozt/dl-ss-recon/data +export MEDDLR_CACHE_DIR=/mnt/dense/deepro/cache +export MEDDLR_RESULTS_DIR=/mnt/dense/deepro/results/Summer_2022_2023/motion_eval/unet_official/ssdu/wandb + +gpu=$(python get_available_gpu.py) +echo "The first available gpu is $gpu" + +# Run normal unet yaml +# took off --debug to allow wb to work. +WANDB__SERVICE_WAIT=300 CUDA_VISIBLE_DEVICES=$gpu$ python tools/eval_net.py --config-file configs/mri-recon/mridata-3dfse-knee/unet/ssdu/SSDU_UNET_13_Scan.yaml --auto-version --save-scans --angle 30 --translation 0.1 --nshots 5 --trajectory interleaved --mri_dim 2 --motion standard MODEL.WEIGHTS 3Dmridata/SSDU/model.cpkt + +# gpu=$(python get_available_gpu.py) +# echo "The first available gpu for VORTEX is $gpu" + +# # Run vortex unet yaml +# # took off --debug to allow wb to work. +# WANDB__SERVICE_WAIT=300 CUDA_VISIBLE_DEVICES=$gpu$ python tools/train_net.py --config-file configs/mri-recon/mridata-3dfse-knee/unet/ssdu/SSDU_Aug_UNET_13_Scan.yaml --auto-version diff --git a/configs/mri-recon/mridata-3dfse-knee/old/SSDU_Aug_UNET_13_Scan.yaml b/configs/mri-recon/mridata-3dfse-knee/old/SSDU_Aug_UNET_13_Scan.yaml new file mode 100644 index 00000000..a86656fd --- /dev/null +++ b/configs/mri-recon/mridata-3dfse-knee/old/SSDU_Aug_UNET_13_Scan.yaml @@ -0,0 +1,199 @@ +AUG_TEST: + UNDERSAMPLE: + ACCELERATIONS: + - 16 +AUG_TRAIN: + MOTION_P: 0.2 + MRI_RECON: + AUG_SENSITIVITY_MAPS: true + SCHEDULER_P: + IGNORE: false + TRANSFORMS: [] + NOISE_P: 0.2 + UNDERSAMPLE: + ACCELERATIONS: + - 16 + CALIBRATION_SIZE: 20 + CENTER_FRACTIONS: [] + MAX_ATTEMPTS: 30 + NAME: PoissonDiskMaskFunc + USE_MOTION: false + USE_NOISE: false +CUDNN_BENCHMARK: false +# DATALOADER has 1 supervised and 13 unsupervised +DATALOADER: + ALT_SAMPLER: + PERIOD_SUPERVISED: 1 + PERIOD_UNSUPERVISED: 1 + DATA_KEYS: [] + DROP_LAST: true + FILTER: + BY: [] + GROUP_SAMPLER: + AS_BATCH_SAMPLER: false + BATCH_BY: [] + NUM_WORKERS: 8 + PREFETCH_FACTOR: 2 + SAMPLER_TRAIN: '' + SUBSAMPLE_TRAIN: + NUM_TOTAL: 14 + NUM_TOTAL_BY_GROUP: [] + NUM_UNDERSAMPLED: 13 + NUM_VAL: -1 + NUM_VAL_BY_GROUP: [] + SEED: 1000 +DATASETS: + TEST: + - mridata_knee_2019_test + TRAIN: + - mridata_knee_2019_train + VAL: + - mridata_knee_2019_val +DESCRIPTION: + BRIEF: SSDU 16x mridata - loss=k_l1, uniform/loss=k_l1/version_001 + ENTITY_NAME: '' + EXP_NAME: vortex/mridata_knee_3dfse/SSDU + PROJECT_NAME: 'vortex_rm' + TAGS: + - baseline + - ssdu + - 16x + - mridata_knee_3dfse +# For motion aug, A2R and Consistency were kept with std_dev range of 0.2 to 0.5. +# For UNET, the META_ARCHITECTURE of SSDUModel is UnetModel +MODEL: + A2R: + META_ARCHITECTURE: GeneralizedUnrolledCNN + USE_SUPERVISED_CONSISTENCY: false # TUNE + CONSISTENCY: + AUG: + MRI_RECON: + AUG_SENSITIVITY_MAPS: true + SCHEDULER_P: + IGNORE: false + TRANSFORMS: + - name: RandomMRIMotion + p: 0.2 + std_devs: + - 0.2 + - 0.5 + use_mask: true + LOSS_NAME: l1 + LOSS_WEIGHT: 0.1 + USE_CONSISTENCY: true + CS: + MAX_ITER: 200 + REGULARIZATION: 0.005 + DENOISING: + META_ARCHITECTURE: GeneralizedUnrolledCNN + NOISE: + STD_DEV: + - 1 + USE_FULLY_SAMPLED_TARGET: true + USE_FULLY_SAMPLED_TARGET_EVAL: null + DEVICE: cpu + M2R: + META_ARCHITECTURE: GeneralizedUnrolledCNN + USE_SUPERVISED_CONSISTENCY: false + META_ARCHITECTURE: SSDUModel + N2R: + META_ARCHITECTURE: GeneralizedUnrolledCNN + USE_SUPERVISED_CONSISTENCY: false + NM2R: + META_ARCHITECTURE: GeneralizedUnrolledCNN + USE_SUPERVISED_CONSISTENCY: false + NORMALIZER: + KEYWORDS: [] + NAME: TopMagnitudeNormalizer + RECON_LOSS: + NAME: k_l1 + RENORMALIZE_DATA: false + SEG: + ACTIVATION: sigmoid + CLASSES: [] + INCLUDE_BACKGROUND: false + SSDU: + MASKER: + PARAMS: + kind: uniform + p: 1.0 + per_example: true + rhos: 0.4 + std_scale: 4 + META_ARCHITECTURE: UnetModel + UNET: + BLOCK_ORDER: + - conv + - relu + - conv + - relu + - batchnorm + - dropout + CHANNELS: 32 + DROPOUT: 0.0 + IN_CHANNELS: 2 + NORMALIZE: false + NUM_POOL_LAYERS: 4 + OUT_CHANNELS: 2 + UNROLLED: + BLOCK_ARCHITECTURE: ResNet + CONV_BLOCK: + ACTIVATION: relu + NORM: none + NORM_AFFINE: false + ORDER: + - norm + - act + - drop + - conv + DROPOUT: 0.0 + FIX_STEP_SIZE: false + KERNEL_SIZE: + - 3 + NUM_EMAPS: 1 + NUM_FEATURES: 256 + NUM_RESBLOCKS: 2 + NUM_UNROLLED_STEPS: 5 + PADDING: '' + SHARE_WEIGHTS: false + WEIGHTS: '' +OUTPUT_DIR: results://vortex/mridata_knee_3dfse/SSDU +SEED: 1000 +SOLVER: + BASE_LR: 0.001 + BIAS_LR_FACTOR: 1.0 + CHECKPOINT_PERIOD: -10 + GAMMA: 0.1 + GRAD_ACCUM_ITERS: 1 + LR_SCHEDULER_NAME: '' + MAX_ITER: -200 + MOMENTUM: 0.9 + OPTIMIZER: Adam + STEPS: [] + TEST_BATCH_SIZE: 24 + TRAIN_BATCH_SIZE: 24 + WARMUP_FACTOR: 0.001 + WARMUP_ITERS: 1000 + WARMUP_METHOD: linear + WEIGHT_DECAY: 0.0001 + WEIGHT_DECAY_BIAS: 0.0001 + WEIGHT_DECAY_NORM: 0.0 +TEST: + EVAL_PERIOD: -10 + EXPECTED_RESULTS: [] + FLUSH_PERIOD: 0 + VAL_AS_TEST: true + VAL_METRICS: + RECON: + - psnr + - psnr_scan + - psnr_mag + - psnr_mag_scan + - nrmse + - nrmse_scan + - nrmse_mag + - nrmse_mag_scan + - ssim (Wang) +TIME_SCALE: iter +VERSION: 1 +VIS_PERIOD: 100 \ No newline at end of file diff --git a/configs/mri-recon/mridata-3dfse-knee/old/SSDU_Aug_Unrolled_13_Scan.yaml b/configs/mri-recon/mridata-3dfse-knee/old/SSDU_Aug_Unrolled_13_Scan.yaml new file mode 100644 index 00000000..88092b05 --- /dev/null +++ b/configs/mri-recon/mridata-3dfse-knee/old/SSDU_Aug_Unrolled_13_Scan.yaml @@ -0,0 +1,199 @@ +AUG_TEST: + UNDERSAMPLE: + ACCELERATIONS: + - 16 +AUG_TRAIN: + MOTION_P: 0.2 + MRI_RECON: + AUG_SENSITIVITY_MAPS: true + SCHEDULER_P: + IGNORE: false + TRANSFORMS: [] + NOISE_P: 0.2 + UNDERSAMPLE: + ACCELERATIONS: + - 16 + CALIBRATION_SIZE: 20 + CENTER_FRACTIONS: [] + MAX_ATTEMPTS: 30 + NAME: PoissonDiskMaskFunc + USE_MOTION: false + USE_NOISE: false +CUDNN_BENCHMARK: false +# DATALOADER has 1 supervised and 13 unsupervised +DATALOADER: + ALT_SAMPLER: + PERIOD_SUPERVISED: 1 + PERIOD_UNSUPERVISED: 1 + DATA_KEYS: [] + DROP_LAST: true + FILTER: + BY: [] + GROUP_SAMPLER: + AS_BATCH_SAMPLER: false + BATCH_BY: [] + NUM_WORKERS: 8 + PREFETCH_FACTOR: 2 + SAMPLER_TRAIN: '' + SUBSAMPLE_TRAIN: + NUM_TOTAL: 14 + NUM_TOTAL_BY_GROUP: [] + NUM_UNDERSAMPLED: 13 + NUM_VAL: -1 + NUM_VAL_BY_GROUP: [] + SEED: 1000 +DATASETS: + TEST: + - mridata_knee_2019_test + TRAIN: + - mridata_knee_2019_train + VAL: + - mridata_knee_2019_val +DESCRIPTION: + BRIEF: SSDU 16x mridata - loss=k_l1, uniform/loss=k_l1/version_001 + ENTITY_NAME: '' + EXP_NAME: vortex/mridata_knee_3dfse/SSDU + PROJECT_NAME: 'vortex_rm' + TAGS: + - baseline + - ssdu + - 16x + - mridata_knee_3dfse +# For no motion aug, A2R and Consistency were removed. +# For UNET, the META_ARCHITECTURE of SSDUModel is UnetModel +MODEL: + A2R: + META_ARCHITECTURE: GeneralizedUnrolledCNN + USE_SUPERVISED_CONSISTENCY: false # TUNE + CONSISTENCY: + AUG: + MRI_RECON: + AUG_SENSITIVITY_MAPS: true + SCHEDULER_P: + IGNORE: false + TRANSFORMS: + - name: RandomMRIMotion + p: 0.2 + std_devs: + - 0.2 + - 0.5 + use_mask: true + LOSS_NAME: l1 + LOSS_WEIGHT: 0.1 + USE_CONSISTENCY: true + CS: + MAX_ITER: 200 + REGULARIZATION: 0.005 + DENOISING: + META_ARCHITECTURE: GeneralizedUnrolledCNN + NOISE: + STD_DEV: + - 1 + USE_FULLY_SAMPLED_TARGET: true + USE_FULLY_SAMPLED_TARGET_EVAL: null + DEVICE: cpu + M2R: + META_ARCHITECTURE: GeneralizedUnrolledCNN + USE_SUPERVISED_CONSISTENCY: false + META_ARCHITECTURE: SSDUModel + N2R: + META_ARCHITECTURE: GeneralizedUnrolledCNN + USE_SUPERVISED_CONSISTENCY: false + NM2R: + META_ARCHITECTURE: GeneralizedUnrolledCNN + USE_SUPERVISED_CONSISTENCY: false + NORMALIZER: + KEYWORDS: [] + NAME: TopMagnitudeNormalizer + RECON_LOSS: + NAME: k_l1 + RENORMALIZE_DATA: false + SEG: + ACTIVATION: sigmoid + CLASSES: [] + INCLUDE_BACKGROUND: false + SSDU: + MASKER: + PARAMS: + kind: uniform + p: 1.0 + per_example: true + rhos: 0.4 + std_scale: 4 + META_ARCHITECTURE: GeneralizedUnrolledCNN + UNET: + BLOCK_ORDER: + - conv + - relu + - conv + - relu + - batchnorm + - dropout + CHANNELS: 32 + DROPOUT: 0.0 + IN_CHANNELS: 2 + NORMALIZE: false + NUM_POOL_LAYERS: 4 + OUT_CHANNELS: 2 + UNROLLED: + BLOCK_ARCHITECTURE: ResNet + CONV_BLOCK: + ACTIVATION: relu + NORM: none + NORM_AFFINE: false + ORDER: + - norm + - act + - drop + - conv + DROPOUT: 0.0 + FIX_STEP_SIZE: false + KERNEL_SIZE: + - 3 + NUM_EMAPS: 1 + NUM_FEATURES: 256 + NUM_RESBLOCKS: 2 + NUM_UNROLLED_STEPS: 5 + PADDING: '' + SHARE_WEIGHTS: false + WEIGHTS: '' +OUTPUT_DIR: results://vortex/mridata_knee_3dfse/SSDU +SEED: 1000 +SOLVER: + BASE_LR: 0.001 + BIAS_LR_FACTOR: 1.0 + CHECKPOINT_PERIOD: -10 + GAMMA: 0.1 + GRAD_ACCUM_ITERS: 1 + LR_SCHEDULER_NAME: '' + MAX_ITER: -200 + MOMENTUM: 0.9 + OPTIMIZER: Adam + STEPS: [] + TEST_BATCH_SIZE: 24 + TRAIN_BATCH_SIZE: 24 + WARMUP_FACTOR: 0.001 + WARMUP_ITERS: 1000 + WARMUP_METHOD: linear + WEIGHT_DECAY: 0.0001 + WEIGHT_DECAY_BIAS: 0.0001 + WEIGHT_DECAY_NORM: 0.0 +TEST: + EVAL_PERIOD: -10 + EXPECTED_RESULTS: [] + FLUSH_PERIOD: 0 + VAL_AS_TEST: true + VAL_METRICS: + RECON: + - psnr + - psnr_scan + - psnr_mag + - psnr_mag_scan + - nrmse + - nrmse_scan + - nrmse_mag + - nrmse_mag_scan + - ssim (Wang) +TIME_SCALE: iter +VERSION: 1 +VIS_PERIOD: 100 \ No newline at end of file diff --git a/configs/mri-recon/mridata-3dfse-knee/old/SSDU_Template.yaml b/configs/mri-recon/mridata-3dfse-knee/old/SSDU_Template.yaml new file mode 100644 index 00000000..8d3fa7a1 --- /dev/null +++ b/configs/mri-recon/mridata-3dfse-knee/old/SSDU_Template.yaml @@ -0,0 +1,209 @@ +AUG_TEST: + UNDERSAMPLE: + ACCELERATIONS: + - 16 +AUG_TRAIN: + MOTION_P: 0.2 + MRI_RECON: + AUG_SENSITIVITY_MAPS: true + SCHEDULER_P: + IGNORE: false + TRANSFORMS: [] + NOISE_P: 0.2 + UNDERSAMPLE: + ACCELERATIONS: + - 16 + CALIBRATION_SIZE: 20 + CENTER_FRACTIONS: [] + MAX_ATTEMPTS: 30 + NAME: PoissonDiskMaskFunc + USE_MOTION: false + USE_NOISE: false +CUDNN_BENCHMARK: false +DATALOADER: + ALT_SAMPLER: + PERIOD_SUPERVISED: 1 + PERIOD_UNSUPERVISED: 1 + DATA_KEYS: [] + DROP_LAST: true + FILTER: + BY: [] + GROUP_SAMPLER: + AS_BATCH_SAMPLER: false + BATCH_BY: [] + NUM_WORKERS: 8 + PREFETCH_FACTOR: 2 + SAMPLER_TRAIN: '' + SUBSAMPLE_TRAIN: + NUM_TOTAL: 6 + NUM_TOTAL_BY_GROUP: [] + NUM_UNDERSAMPLED: 5 + NUM_VAL: -1 + NUM_VAL_BY_GROUP: [] + SEED: 1000 +DATASETS: + TEST: + - mridata_knee_2019_test + TRAIN: + - mridata_knee_2019_train + VAL: + - mridata_knee_2019_val +DESCRIPTION: + BRIEF: SSDU 16x mridata - loss=k_l1, uniform/loss=k_l1/version_001 + ENTITY_NAME: '' + EXP_NAME: vortex/mridata_knee_3dfse/SSDU + PROJECT_NAME: '' + TAGS: + - baseline + - ssdu + - 16x + - mridata_knee_3dfse +MODEL: + A2R: + META_ARCHITECTURE: GeneralizedUnrolledCNN + USE_SUPERVISED_CONSISTENCY: false + CONSISTENCY: + AUG: + MOTION: + RANGE: + - 0.2 + - 0.5 + SCHEDULER: + WARMUP_ITERS: 0 + WARMUP_METHOD: '' + MRI_RECON: + AUG_SENSITIVITY_MAPS: true + SCHEDULER_P: + IGNORE: false + TRANSFORMS: [] + NOISE: + MASK: + RHO: 1.0 + SCHEDULER: + WARMUP_ITERS: 0 + WARMUP_METHOD: '' + STD_DEV: + - 1 + LATENT_LOSS_NAME: mag_l1 + LATENT_LOSS_WEIGHT: 0.1 + LOSS_NAME: l1 + LOSS_WEIGHT: 0.1 + NUM_LATENT_LAYERS: 1 + USE_CONSISTENCY: true + USE_LATENT: false + CS: + MAX_ITER: 200 + REGULARIZATION: 0.005 + DENOISING: + META_ARCHITECTURE: GeneralizedUnrolledCNN + NOISE: + STD_DEV: + - 1 + USE_FULLY_SAMPLED_TARGET: true + USE_FULLY_SAMPLED_TARGET_EVAL: null + DEVICE: cpu + M2R: + META_ARCHITECTURE: GeneralizedUnrolledCNN + USE_SUPERVISED_CONSISTENCY: false + META_ARCHITECTURE: SSDUModel + N2R: + META_ARCHITECTURE: GeneralizedUnrolledCNN + USE_SUPERVISED_CONSISTENCY: false + NM2R: + META_ARCHITECTURE: GeneralizedUnrolledCNN + USE_SUPERVISED_CONSISTENCY: false + NORMALIZER: + KEYWORDS: [] + NAME: TopMagnitudeNormalizer + RECON_LOSS: + NAME: k_l1 + RENORMALIZE_DATA: false + SEG: + ACTIVATION: sigmoid + CLASSES: [] + INCLUDE_BACKGROUND: false + SSDU: + MASKER: + PARAMS: + kind: uniform + p: 1.0 + per_example: true + rhos: 0.4 + std_scale: 4 + META_ARCHITECTURE: UnetModel + UNET: + BLOCK_ORDER: + - conv + - relu + - conv + - relu + - batchnorm + - dropout + CHANNELS: 32 + DROPOUT: 0.0 + IN_CHANNELS: 2 + NORMALIZE: false + NUM_POOL_LAYERS: 4 + OUT_CHANNELS: 2 + UNROLLED: + BLOCK_ARCHITECTURE: ResNet + CONV_BLOCK: + ACTIVATION: relu + NORM: none + NORM_AFFINE: false + ORDER: + - norm + - act + - drop + - conv + DROPOUT: 0.0 + FIX_STEP_SIZE: false + KERNEL_SIZE: + - 3 + NUM_EMAPS: 1 + NUM_FEATURES: 256 + NUM_RESBLOCKS: 2 + NUM_UNROLLED_STEPS: 5 + PADDING: '' + SHARE_WEIGHTS: false + WEIGHTS: '' +OUTPUT_DIR: results://vortex/mridata_knee_3dfse/SSDU +SEED: 1000 +SOLVER: + BASE_LR: 0.001 + BIAS_LR_FACTOR: 1.0 + CHECKPOINT_PERIOD: -10 + GAMMA: 0.1 + GRAD_ACCUM_ITERS: 1 + LR_SCHEDULER_NAME: '' + MAX_ITER: -200 + MOMENTUM: 0.9 + OPTIMIZER: Adam + STEPS: [] + TEST_BATCH_SIZE: 24 + TRAIN_BATCH_SIZE: 24 + WARMUP_FACTOR: 0.001 + WARMUP_ITERS: 1000 + WARMUP_METHOD: linear + WEIGHT_DECAY: 0.0001 + WEIGHT_DECAY_BIAS: 0.0001 + WEIGHT_DECAY_NORM: 0.0 +TEST: + EVAL_PERIOD: -10 + EXPECTED_RESULTS: [] + FLUSH_PERIOD: 0 + VAL_AS_TEST: true + VAL_METRICS: + RECON: + - psnr + - psnr_scan + - psnr_mag + - psnr_mag_scan + - nrmse + - nrmse_scan + - nrmse_mag + - nrmse_mag_scan + - ssim (Wang) +TIME_SCALE: iter +VERSION: 1 +VIS_PERIOD: 100 diff --git a/configs/mri-recon/mridata-3dfse-knee/old/SSDU_UNET_13_Scan.yaml b/configs/mri-recon/mridata-3dfse-knee/old/SSDU_UNET_13_Scan.yaml new file mode 100644 index 00000000..5d4dc406 --- /dev/null +++ b/configs/mri-recon/mridata-3dfse-knee/old/SSDU_UNET_13_Scan.yaml @@ -0,0 +1,180 @@ +AUG_TEST: + UNDERSAMPLE: + ACCELERATIONS: + - 16 +AUG_TRAIN: + MOTION_P: 0.2 + MRI_RECON: + AUG_SENSITIVITY_MAPS: true + SCHEDULER_P: + IGNORE: false + TRANSFORMS: [] + NOISE_P: 0.2 + UNDERSAMPLE: + ACCELERATIONS: + - 16 + CALIBRATION_SIZE: 20 + CENTER_FRACTIONS: [] + MAX_ATTEMPTS: 30 + NAME: PoissonDiskMaskFunc + USE_MOTION: false + USE_NOISE: false +CUDNN_BENCHMARK: false +# DATALOADER has 1 supervised and 13 unsupervised +DATALOADER: + ALT_SAMPLER: + PERIOD_SUPERVISED: 1 + PERIOD_UNSUPERVISED: 1 + DATA_KEYS: [] + DROP_LAST: true + FILTER: + BY: [] + GROUP_SAMPLER: + AS_BATCH_SAMPLER: false + BATCH_BY: [] + NUM_WORKERS: 8 + PREFETCH_FACTOR: 2 + SAMPLER_TRAIN: '' + SUBSAMPLE_TRAIN: + NUM_TOTAL: 14 + NUM_TOTAL_BY_GROUP: [] + NUM_UNDERSAMPLED: 13 + NUM_VAL: -1 + NUM_VAL_BY_GROUP: [] + SEED: 1000 +DATASETS: + TEST: + - mridata_knee_2019_test + TRAIN: + - mridata_knee_2019_train + VAL: + - mridata_knee_2019_val +DESCRIPTION: + BRIEF: SSDU 16x mridata - loss=k_l1, uniform/loss=k_l1/version_001 + ENTITY_NAME: '' + EXP_NAME: vortex/mridata_knee_3dfse/SSDU + PROJECT_NAME: 'vortex_rm' + TAGS: + - baseline + - ssdu + - 16x + - mridata_knee_3dfse +# For no motion aug, A2R and Consistency were removed. +# For UNET, the META_ARCHITECTURE of SSDUModel is UnetModel +MODEL: + CS: + MAX_ITER: 200 + REGULARIZATION: 0.005 + DENOISING: + META_ARCHITECTURE: GeneralizedUnrolledCNN + NOISE: + STD_DEV: + - 1 + USE_FULLY_SAMPLED_TARGET: true + USE_FULLY_SAMPLED_TARGET_EVAL: null + DEVICE: cpu + M2R: + META_ARCHITECTURE: GeneralizedUnrolledCNN + USE_SUPERVISED_CONSISTENCY: false + META_ARCHITECTURE: SSDUModel + N2R: + META_ARCHITECTURE: GeneralizedUnrolledCNN + USE_SUPERVISED_CONSISTENCY: false + NM2R: + META_ARCHITECTURE: GeneralizedUnrolledCNN + USE_SUPERVISED_CONSISTENCY: false + NORMALIZER: + KEYWORDS: [] + NAME: TopMagnitudeNormalizer + RECON_LOSS: + NAME: k_l1 + RENORMALIZE_DATA: false + SEG: + ACTIVATION: sigmoid + CLASSES: [] + INCLUDE_BACKGROUND: false + SSDU: + MASKER: + PARAMS: + kind: uniform + p: 1.0 + per_example: true + rhos: 0.4 + std_scale: 4 + META_ARCHITECTURE: UnetModel + UNET: + BLOCK_ORDER: + - conv + - relu + - conv + - relu + - batchnorm + - dropout + CHANNELS: 32 + DROPOUT: 0.0 + IN_CHANNELS: 2 + NORMALIZE: false + NUM_POOL_LAYERS: 4 + OUT_CHANNELS: 2 + UNROLLED: + BLOCK_ARCHITECTURE: ResNet + CONV_BLOCK: + ACTIVATION: relu + NORM: none + NORM_AFFINE: false + ORDER: + - norm + - act + - drop + - conv + DROPOUT: 0.0 + FIX_STEP_SIZE: false + KERNEL_SIZE: + - 3 + NUM_EMAPS: 1 + NUM_FEATURES: 256 + NUM_RESBLOCKS: 2 + NUM_UNROLLED_STEPS: 5 + PADDING: '' + SHARE_WEIGHTS: false + WEIGHTS: '' +OUTPUT_DIR: results://vortex/mridata_knee_3dfse/SSDU +SEED: 1000 +SOLVER: + BASE_LR: 0.001 + BIAS_LR_FACTOR: 1.0 + CHECKPOINT_PERIOD: -10 + GAMMA: 0.1 + GRAD_ACCUM_ITERS: 1 + LR_SCHEDULER_NAME: '' + MAX_ITER: -200 + MOMENTUM: 0.9 + OPTIMIZER: Adam + STEPS: [] + TEST_BATCH_SIZE: 24 + TRAIN_BATCH_SIZE: 24 + WARMUP_FACTOR: 0.001 + WARMUP_ITERS: 1000 + WARMUP_METHOD: linear + WEIGHT_DECAY: 0.0001 + WEIGHT_DECAY_BIAS: 0.0001 + WEIGHT_DECAY_NORM: 0.0 +TEST: + EVAL_PERIOD: -10 + EXPECTED_RESULTS: [] + FLUSH_PERIOD: 0 + VAL_AS_TEST: true + VAL_METRICS: + RECON: + - psnr + - psnr_scan + - psnr_mag + - psnr_mag_scan + - nrmse + - nrmse_scan + - nrmse_mag + - nrmse_mag_scan + - ssim (Wang) +TIME_SCALE: iter +VERSION: 1 +VIS_PERIOD: 100 \ No newline at end of file diff --git a/configs/mri-recon/mridata-3dfse-knee/old/SSDU_Unrolled_13_Scan.yaml b/configs/mri-recon/mridata-3dfse-knee/old/SSDU_Unrolled_13_Scan.yaml new file mode 100644 index 00000000..abf612fe --- /dev/null +++ b/configs/mri-recon/mridata-3dfse-knee/old/SSDU_Unrolled_13_Scan.yaml @@ -0,0 +1,180 @@ +AUG_TEST: + UNDERSAMPLE: + ACCELERATIONS: + - 16 +AUG_TRAIN: + MOTION_P: 0.2 + MRI_RECON: + AUG_SENSITIVITY_MAPS: true + SCHEDULER_P: + IGNORE: false + TRANSFORMS: [] + NOISE_P: 0.2 + UNDERSAMPLE: + ACCELERATIONS: + - 16 + CALIBRATION_SIZE: 20 + CENTER_FRACTIONS: [] + MAX_ATTEMPTS: 30 + NAME: PoissonDiskMaskFunc + USE_MOTION: false + USE_NOISE: false +CUDNN_BENCHMARK: false +# DATALOADER has 1 supervised and 13 unsupervised +DATALOADER: + ALT_SAMPLER: + PERIOD_SUPERVISED: 1 + PERIOD_UNSUPERVISED: 1 + DATA_KEYS: [] + DROP_LAST: true + FILTER: + BY: [] + GROUP_SAMPLER: + AS_BATCH_SAMPLER: false + BATCH_BY: [] + NUM_WORKERS: 8 + PREFETCH_FACTOR: 2 + SAMPLER_TRAIN: '' + SUBSAMPLE_TRAIN: + NUM_TOTAL: 14 + NUM_TOTAL_BY_GROUP: [] + NUM_UNDERSAMPLED: 13 + NUM_VAL: -1 + NUM_VAL_BY_GROUP: [] + SEED: 1000 +DATASETS: + TEST: + - mridata_knee_2019_test + TRAIN: + - mridata_knee_2019_train + VAL: + - mridata_knee_2019_val +DESCRIPTION: + BRIEF: SSDU 16x mridata - loss=k_l1, uniform/loss=k_l1/version_001 + ENTITY_NAME: '' + EXP_NAME: vortex/mridata_knee_3dfse/SSDU + PROJECT_NAME: 'vortex_rm' + TAGS: + - baseline + - ssdu + - 16x + - mridata_knee_3dfse +# For no motion aug, A2R and Consistency were removed. +# For UNET, the META_ARCHITECTURE of SSDUModel is UnetModel +MODEL: + CS: + MAX_ITER: 200 + REGULARIZATION: 0.005 + DENOISING: + META_ARCHITECTURE: GeneralizedUnrolledCNN + NOISE: + STD_DEV: + - 1 + USE_FULLY_SAMPLED_TARGET: true + USE_FULLY_SAMPLED_TARGET_EVAL: null + DEVICE: cpu + M2R: + META_ARCHITECTURE: GeneralizedUnrolledCNN + USE_SUPERVISED_CONSISTENCY: false + META_ARCHITECTURE: SSDUModel + N2R: + META_ARCHITECTURE: GeneralizedUnrolledCNN + USE_SUPERVISED_CONSISTENCY: false + NM2R: + META_ARCHITECTURE: GeneralizedUnrolledCNN + USE_SUPERVISED_CONSISTENCY: false + NORMALIZER: + KEYWORDS: [] + NAME: TopMagnitudeNormalizer + RECON_LOSS: + NAME: k_l1 + RENORMALIZE_DATA: false + SEG: + ACTIVATION: sigmoid + CLASSES: [] + INCLUDE_BACKGROUND: false + SSDU: + MASKER: + PARAMS: + kind: uniform + p: 1.0 + per_example: true + rhos: 0.4 + std_scale: 4 + META_ARCHITECTURE: GeneralizedUnrolledCNN + UNET: + BLOCK_ORDER: + - conv + - relu + - conv + - relu + - batchnorm + - dropout + CHANNELS: 32 + DROPOUT: 0.0 + IN_CHANNELS: 2 + NORMALIZE: false + NUM_POOL_LAYERS: 4 + OUT_CHANNELS: 2 + UNROLLED: + BLOCK_ARCHITECTURE: ResNet + CONV_BLOCK: + ACTIVATION: relu + NORM: none + NORM_AFFINE: false + ORDER: + - norm + - act + - drop + - conv + DROPOUT: 0.0 + FIX_STEP_SIZE: false + KERNEL_SIZE: + - 3 + NUM_EMAPS: 1 + NUM_FEATURES: 256 + NUM_RESBLOCKS: 2 + NUM_UNROLLED_STEPS: 5 + PADDING: '' + SHARE_WEIGHTS: false + WEIGHTS: '' +OUTPUT_DIR: results://vortex/mridata_knee_3dfse/SSDU +SEED: 1000 +SOLVER: + BASE_LR: 0.001 + BIAS_LR_FACTOR: 1.0 + CHECKPOINT_PERIOD: -10 + GAMMA: 0.1 + GRAD_ACCUM_ITERS: 1 + LR_SCHEDULER_NAME: '' + MAX_ITER: -200 + MOMENTUM: 0.9 + OPTIMIZER: Adam + STEPS: [] + TEST_BATCH_SIZE: 24 + TRAIN_BATCH_SIZE: 24 + WARMUP_FACTOR: 0.001 + WARMUP_ITERS: 1000 + WARMUP_METHOD: linear + WEIGHT_DECAY: 0.0001 + WEIGHT_DECAY_BIAS: 0.0001 + WEIGHT_DECAY_NORM: 0.0 +TEST: + EVAL_PERIOD: -10 + EXPECTED_RESULTS: [] + FLUSH_PERIOD: 0 + VAL_AS_TEST: true + VAL_METRICS: + RECON: + - psnr + - psnr_scan + - psnr_mag + - psnr_mag_scan + - nrmse + - nrmse_scan + - nrmse_mag + - nrmse_mag_scan + - ssim (Wang) +TIME_SCALE: iter +VERSION: 1 +VIS_PERIOD: 100 \ No newline at end of file diff --git a/configs/mri-recon/mridata-3dfse-knee/old/Supervised_Aug_UNET_14_Scan.yaml b/configs/mri-recon/mridata-3dfse-knee/old/Supervised_Aug_UNET_14_Scan.yaml new file mode 100644 index 00000000..d9d69b98 --- /dev/null +++ b/configs/mri-recon/mridata-3dfse-knee/old/Supervised_Aug_UNET_14_Scan.yaml @@ -0,0 +1,193 @@ +AUG_TEST: + UNDERSAMPLE: + ACCELERATIONS: + - 16 +AUG_TRAIN: + MOTION_P: 0.2 + MRI_RECON: + AUG_SENSITIVITY_MAPS: true + SCHEDULER_P: + IGNORE: false + TRANSFORMS: [] + NOISE_P: 0.2 + UNDERSAMPLE: + ACCELERATIONS: + - 16 + CALIBRATION_SIZE: 20 + CENTER_FRACTIONS: [] + MAX_ATTEMPTS: 30 + NAME: PoissonDiskMaskFunc + USE_MOTION: false + USE_NOISE: false +CUDNN_BENCHMARK: false +# DATALOADER has 14 supervised scan +DATALOADER: + ALT_SAMPLER: + PERIOD_SUPERVISED: 1 + PERIOD_UNSUPERVISED: 1 + DATA_KEYS: [] + DROP_LAST: true + FILTER: + BY: [] + GROUP_SAMPLER: + AS_BATCH_SAMPLER: false + BATCH_BY: [] + NUM_WORKERS: 8 + PREFETCH_FACTOR: 2 + SAMPLER_TRAIN: '' + SUBSAMPLE_TRAIN: + NUM_TOTAL: 14 + NUM_TOTAL_BY_GROUP: [] + NUM_UNDERSAMPLED: 0 + NUM_VAL: -1 + NUM_VAL_BY_GROUP: [] + SEED: 1000 +DATASETS: + TEST: + - mridata_knee_2019_test + TRAIN: + - mridata_knee_2019_train + VAL: + - mridata_knee_2019_val +DESCRIPTION: + BRIEF: Aug2Recon 16x mridata baseline + ENTITY_NAME: '' + EXP_NAME: vortex/mridata_knee_3dfse/Supervised + PROJECT_NAME: 'vortex_rm' + TAGS: + - baseline + - supervised + - 16x + - mridata_knee_3dfse +# For motion aug, A2R and Consistency was implemented. +# For UNET, kept META_ARCHITECTURE is UnetModel +MODEL: + A2R: + META_ARCHITECTURE: GeneralizedUnrolledCNN + USE_SUPERVISED_CONSISTENCY: false # TUNE + CONSISTENCY: + AUG: + MRI_RECON: + AUG_SENSITIVITY_MAPS: true + SCHEDULER_P: + IGNORE: false + TRANSFORMS: + - name: RandomMRIMotion + p: 0.2 + std_devs: + - 0.2 + - 0.5 + use_mask: true + LOSS_NAME: l1 + LOSS_WEIGHT: 0.1 + USE_CONSISTENCY: true + CS: + MAX_ITER: 200 + REGULARIZATION: 0.005 + DENOISING: + META_ARCHITECTURE: GeneralizedUnrolledCNN + NOISE: + STD_DEV: *id001 + USE_FULLY_SAMPLED_TARGET: true + USE_FULLY_SAMPLED_TARGET_EVAL: null + DEVICE: cpu + M2R: + META_ARCHITECTURE: GeneralizedUnrolledCNN + USE_SUPERVISED_CONSISTENCY: false + META_ARCHITECTURE: UnetModel + N2R: + META_ARCHITECTURE: GeneralizedUnrolledCNN + USE_SUPERVISED_CONSISTENCY: false + NM2R: + META_ARCHITECTURE: GeneralizedUnrolledCNN + USE_SUPERVISED_CONSISTENCY: false + NORMALIZER: + KEYWORDS: [] + NAME: TopMagnitudeNormalizer + RECON_LOSS: + NAME: l1 + RENORMALIZE_DATA: false + SEG: + ACTIVATION: sigmoid + CLASSES: [] + INCLUDE_BACKGROUND: false + SSDU: + MASKER: + PARAMS: {} + META_ARCHITECTURE: GeneralizedUnrolledCNN + UNET: + BLOCK_ORDER: + - conv + - relu + - conv + - relu + - batchnorm + - dropout + CHANNELS: 32 + DROPOUT: 0.0 + IN_CHANNELS: 2 + NORMALIZE: false + NUM_POOL_LAYERS: 4 + OUT_CHANNELS: 2 + UNROLLED: + BLOCK_ARCHITECTURE: ResNet + CONV_BLOCK: + ACTIVATION: relu + NORM: none + NORM_AFFINE: false + ORDER: + - norm + - act + - drop + - conv + DROPOUT: 0.0 + FIX_STEP_SIZE: false + KERNEL_SIZE: + - 3 + NUM_EMAPS: 1 + NUM_FEATURES: 256 + NUM_RESBLOCKS: 2 + NUM_UNROLLED_STEPS: 5 + PADDING: '' + SHARE_WEIGHTS: false + WEIGHTS: '' +OUTPUT_DIR: results://vortex/mridata_knee_3dfse/Supervised +SEED: 1000 +SOLVER: + BASE_LR: 0.001 + BIAS_LR_FACTOR: 1.0 + CHECKPOINT_PERIOD: -10 + GAMMA: 0.1 + GRAD_ACCUM_ITERS: 1 + LR_SCHEDULER_NAME: '' + MAX_ITER: -200 + MOMENTUM: 0.9 + OPTIMIZER: Adam + STEPS: [] + TEST_BATCH_SIZE: 24 + TRAIN_BATCH_SIZE: 24 + WARMUP_FACTOR: 0.001 + WARMUP_ITERS: 1000 + WARMUP_METHOD: linear + WEIGHT_DECAY: 0.0001 + WEIGHT_DECAY_BIAS: 0.0001 + WEIGHT_DECAY_NORM: 0.0 +TEST: + EVAL_PERIOD: -10 + EXPECTED_RESULTS: [] + FLUSH_PERIOD: 0 + VAL_AS_TEST: true + VAL_METRICS: + RECON: + - psnr + - psnr_scan + - psnr_mag + - psnr_mag_scan + - nrmse + - nrmse_scan + - nrmse_mag + - nrmse_mag_scan + - ssim (Wang) +TIME_SCALE: iter +VERSION: 1 +VIS_PERIOD: 400 \ No newline at end of file diff --git a/configs/mri-recon/mridata-3dfse-knee/old/Supervised_Aug_UNET_1_Scan.yaml b/configs/mri-recon/mridata-3dfse-knee/old/Supervised_Aug_UNET_1_Scan.yaml new file mode 100644 index 00000000..f5128c0d --- /dev/null +++ b/configs/mri-recon/mridata-3dfse-knee/old/Supervised_Aug_UNET_1_Scan.yaml @@ -0,0 +1,193 @@ +AUG_TEST: + UNDERSAMPLE: + ACCELERATIONS: + - 16 +AUG_TRAIN: + MOTION_P: 0.2 + MRI_RECON: + AUG_SENSITIVITY_MAPS: true + SCHEDULER_P: + IGNORE: false + TRANSFORMS: [] + NOISE_P: 0.2 + UNDERSAMPLE: + ACCELERATIONS: + - 16 + CALIBRATION_SIZE: 20 + CENTER_FRACTIONS: [] + MAX_ATTEMPTS: 30 + NAME: PoissonDiskMaskFunc + USE_MOTION: false + USE_NOISE: false +CUDNN_BENCHMARK: false +# DATALOADER has only 1 supervised scan +DATALOADER: + ALT_SAMPLER: + PERIOD_SUPERVISED: 1 + PERIOD_UNSUPERVISED: 1 + DATA_KEYS: [] + DROP_LAST: true + FILTER: + BY: [] + GROUP_SAMPLER: + AS_BATCH_SAMPLER: false + BATCH_BY: [] + NUM_WORKERS: 8 + PREFETCH_FACTOR: 2 + SAMPLER_TRAIN: '' + SUBSAMPLE_TRAIN: + NUM_TOTAL: 1 + NUM_TOTAL_BY_GROUP: [] + NUM_UNDERSAMPLED: 0 + NUM_VAL: -1 + NUM_VAL_BY_GROUP: [] + SEED: 1000 +DATASETS: + TEST: + - mridata_knee_2019_test + TRAIN: + - mridata_knee_2019_train + VAL: + - mridata_knee_2019_val +DESCRIPTION: + BRIEF: Aug2Recon 16x mridata baseline + ENTITY_NAME: '' + EXP_NAME: vortex/mridata_knee_3dfse/Supervised + PROJECT_NAME: 'vortex_rm' + TAGS: + - baseline + - supervised + - 16x + - mridata_knee_3dfse +# For motion aug, A2R and Consistency was implemented. +# For UNET, kept META_ARCHITECTURE as UnetModel +MODEL: + A2R: + META_ARCHITECTURE: GeneralizedUnrolledCNN + USE_SUPERVISED_CONSISTENCY: false # TUNE + CONSISTENCY: + AUG: + MRI_RECON: + AUG_SENSITIVITY_MAPS: true + SCHEDULER_P: + IGNORE: false + TRANSFORMS: + - name: RandomMRIMotion + p: 0.2 + std_devs: + - 0.2 + - 0.5 + use_mask: true + LOSS_NAME: l1 + LOSS_WEIGHT: 0.1 + USE_CONSISTENCY: true + CS: + MAX_ITER: 200 + REGULARIZATION: 0.005 + DENOISING: + META_ARCHITECTURE: GeneralizedUnrolledCNN + NOISE: + STD_DEV: *id001 + USE_FULLY_SAMPLED_TARGET: true + USE_FULLY_SAMPLED_TARGET_EVAL: null + DEVICE: cpu + M2R: + META_ARCHITECTURE: GeneralizedUnrolledCNN + USE_SUPERVISED_CONSISTENCY: false + META_ARCHITECTURE: UnetModel + N2R: + META_ARCHITECTURE: GeneralizedUnrolledCNN + USE_SUPERVISED_CONSISTENCY: false + NM2R: + META_ARCHITECTURE: GeneralizedUnrolledCNN + USE_SUPERVISED_CONSISTENCY: false + NORMALIZER: + KEYWORDS: [] + NAME: TopMagnitudeNormalizer + RECON_LOSS: + NAME: l1 + RENORMALIZE_DATA: false + SEG: + ACTIVATION: sigmoid + CLASSES: [] + INCLUDE_BACKGROUND: false + SSDU: + MASKER: + PARAMS: {} + META_ARCHITECTURE: GeneralizedUnrolledCNN + UNET: + BLOCK_ORDER: + - conv + - relu + - conv + - relu + - batchnorm + - dropout + CHANNELS: 32 + DROPOUT: 0.0 + IN_CHANNELS: 2 + NORMALIZE: false + NUM_POOL_LAYERS: 4 + OUT_CHANNELS: 2 + UNROLLED: + BLOCK_ARCHITECTURE: ResNet + CONV_BLOCK: + ACTIVATION: relu + NORM: none + NORM_AFFINE: false + ORDER: + - norm + - act + - drop + - conv + DROPOUT: 0.0 + FIX_STEP_SIZE: false + KERNEL_SIZE: + - 3 + NUM_EMAPS: 1 + NUM_FEATURES: 256 + NUM_RESBLOCKS: 2 + NUM_UNROLLED_STEPS: 5 + PADDING: '' + SHARE_WEIGHTS: false + WEIGHTS: '' +OUTPUT_DIR: results://vortex/mridata_knee_3dfse/Supervised +SEED: 1000 +SOLVER: + BASE_LR: 0.001 + BIAS_LR_FACTOR: 1.0 + CHECKPOINT_PERIOD: -10 + GAMMA: 0.1 + GRAD_ACCUM_ITERS: 1 + LR_SCHEDULER_NAME: '' + MAX_ITER: -200 + MOMENTUM: 0.9 + OPTIMIZER: Adam + STEPS: [] + TEST_BATCH_SIZE: 24 + TRAIN_BATCH_SIZE: 24 + WARMUP_FACTOR: 0.001 + WARMUP_ITERS: 1000 + WARMUP_METHOD: linear + WEIGHT_DECAY: 0.0001 + WEIGHT_DECAY_BIAS: 0.0001 + WEIGHT_DECAY_NORM: 0.0 +TEST: + EVAL_PERIOD: -10 + EXPECTED_RESULTS: [] + FLUSH_PERIOD: 0 + VAL_AS_TEST: true + VAL_METRICS: + RECON: + - psnr + - psnr_scan + - psnr_mag + - psnr_mag_scan + - nrmse + - nrmse_scan + - nrmse_mag + - nrmse_mag_scan + - ssim (Wang) +TIME_SCALE: iter +VERSION: 1 +VIS_PERIOD: 400 \ No newline at end of file diff --git a/configs/mri-recon/mridata-3dfse-knee/old/Supervised_Aug_Unrolled_14_Scan.yaml b/configs/mri-recon/mridata-3dfse-knee/old/Supervised_Aug_Unrolled_14_Scan.yaml new file mode 100644 index 00000000..5ddbe2fe --- /dev/null +++ b/configs/mri-recon/mridata-3dfse-knee/old/Supervised_Aug_Unrolled_14_Scan.yaml @@ -0,0 +1,193 @@ +AUG_TEST: + UNDERSAMPLE: + ACCELERATIONS: + - 16 +AUG_TRAIN: + MOTION_P: 0.2 + MRI_RECON: + AUG_SENSITIVITY_MAPS: true + SCHEDULER_P: + IGNORE: false + TRANSFORMS: [] + NOISE_P: 0.2 + UNDERSAMPLE: + ACCELERATIONS: + - 16 + CALIBRATION_SIZE: 20 + CENTER_FRACTIONS: [] + MAX_ATTEMPTS: 30 + NAME: PoissonDiskMaskFunc + USE_MOTION: false + USE_NOISE: false +CUDNN_BENCHMARK: false +# DATALOADER has only 1 supervised scan +DATALOADER: + ALT_SAMPLER: + PERIOD_SUPERVISED: 1 + PERIOD_UNSUPERVISED: 1 + DATA_KEYS: [] + DROP_LAST: true + FILTER: + BY: [] + GROUP_SAMPLER: + AS_BATCH_SAMPLER: false + BATCH_BY: [] + NUM_WORKERS: 8 + PREFETCH_FACTOR: 2 + SAMPLER_TRAIN: '' + SUBSAMPLE_TRAIN: + NUM_TOTAL: 14 + NUM_TOTAL_BY_GROUP: [] + NUM_UNDERSAMPLED: 0 + NUM_VAL: -1 + NUM_VAL_BY_GROUP: [] + SEED: 1000 +DATASETS: + TEST: + - mridata_knee_2019_test + TRAIN: + - mridata_knee_2019_train + VAL: + - mridata_knee_2019_val +DESCRIPTION: + BRIEF: Aug2Recon 16x mridata baseline + ENTITY_NAME: '' + EXP_NAME: vortex/mridata_knee_3dfse/Supervised + PROJECT_NAME: 'vortex_rm' + TAGS: + - baseline + - supervised + - 16x + - mridata_knee_3dfse +# For motion aug, A2R and Consistency was implemented. +# For Unrolled, made META_ARCHITECTURE GeneralizedUnrolledCNN +MODEL: + A2R: + META_ARCHITECTURE: GeneralizedUnrolledCNN + USE_SUPERVISED_CONSISTENCY: false # TUNE + CONSISTENCY: + AUG: + MRI_RECON: + AUG_SENSITIVITY_MAPS: true + SCHEDULER_P: + IGNORE: false + TRANSFORMS: + - name: RandomMRIMotion + p: 0.2 + std_devs: + - 0.2 + - 0.5 + use_mask: true + LOSS_NAME: l1 + LOSS_WEIGHT: 0.1 + USE_CONSISTENCY: true + CS: + MAX_ITER: 200 + REGULARIZATION: 0.005 + DENOISING: + META_ARCHITECTURE: GeneralizedUnrolledCNN + NOISE: + STD_DEV: *id001 + USE_FULLY_SAMPLED_TARGET: true + USE_FULLY_SAMPLED_TARGET_EVAL: null + DEVICE: cpu + M2R: + META_ARCHITECTURE: GeneralizedUnrolledCNN + USE_SUPERVISED_CONSISTENCY: false + META_ARCHITECTURE: GeneralizedUnrolledCNN + N2R: + META_ARCHITECTURE: GeneralizedUnrolledCNN + USE_SUPERVISED_CONSISTENCY: false + NM2R: + META_ARCHITECTURE: GeneralizedUnrolledCNN + USE_SUPERVISED_CONSISTENCY: false + NORMALIZER: + KEYWORDS: [] + NAME: TopMagnitudeNormalizer + RECON_LOSS: + NAME: l1 + RENORMALIZE_DATA: false + SEG: + ACTIVATION: sigmoid + CLASSES: [] + INCLUDE_BACKGROUND: false + SSDU: + MASKER: + PARAMS: {} + META_ARCHITECTURE: GeneralizedUnrolledCNN + UNET: + BLOCK_ORDER: + - conv + - relu + - conv + - relu + - batchnorm + - dropout + CHANNELS: 32 + DROPOUT: 0.0 + IN_CHANNELS: 2 + NORMALIZE: false + NUM_POOL_LAYERS: 4 + OUT_CHANNELS: 2 + UNROLLED: + BLOCK_ARCHITECTURE: ResNet + CONV_BLOCK: + ACTIVATION: relu + NORM: none + NORM_AFFINE: false + ORDER: + - norm + - act + - drop + - conv + DROPOUT: 0.0 + FIX_STEP_SIZE: false + KERNEL_SIZE: + - 3 + NUM_EMAPS: 1 + NUM_FEATURES: 256 + NUM_RESBLOCKS: 2 + NUM_UNROLLED_STEPS: 5 + PADDING: '' + SHARE_WEIGHTS: false + WEIGHTS: '' +OUTPUT_DIR: results://vortex/mridata_knee_3dfse/Supervised +SEED: 1000 +SOLVER: + BASE_LR: 0.001 + BIAS_LR_FACTOR: 1.0 + CHECKPOINT_PERIOD: -10 + GAMMA: 0.1 + GRAD_ACCUM_ITERS: 1 + LR_SCHEDULER_NAME: '' + MAX_ITER: -200 + MOMENTUM: 0.9 + OPTIMIZER: Adam + STEPS: [] + TEST_BATCH_SIZE: 24 + TRAIN_BATCH_SIZE: 24 + WARMUP_FACTOR: 0.001 + WARMUP_ITERS: 1000 + WARMUP_METHOD: linear + WEIGHT_DECAY: 0.0001 + WEIGHT_DECAY_BIAS: 0.0001 + WEIGHT_DECAY_NORM: 0.0 +TEST: + EVAL_PERIOD: -10 + EXPECTED_RESULTS: [] + FLUSH_PERIOD: 0 + VAL_AS_TEST: true + VAL_METRICS: + RECON: + - psnr + - psnr_scan + - psnr_mag + - psnr_mag_scan + - nrmse + - nrmse_scan + - nrmse_mag + - nrmse_mag_scan + - ssim (Wang) +TIME_SCALE: iter +VERSION: 1 +VIS_PERIOD: 400 \ No newline at end of file diff --git a/configs/mri-recon/mridata-3dfse-knee/old/Supervised_Aug_Unrolled_1_Scan.yaml b/configs/mri-recon/mridata-3dfse-knee/old/Supervised_Aug_Unrolled_1_Scan.yaml new file mode 100644 index 00000000..62348e30 --- /dev/null +++ b/configs/mri-recon/mridata-3dfse-knee/old/Supervised_Aug_Unrolled_1_Scan.yaml @@ -0,0 +1,193 @@ +AUG_TEST: + UNDERSAMPLE: + ACCELERATIONS: + - 16 +AUG_TRAIN: + MOTION_P: 0.2 + MRI_RECON: + AUG_SENSITIVITY_MAPS: true + SCHEDULER_P: + IGNORE: false + TRANSFORMS: [] + NOISE_P: 0.2 + UNDERSAMPLE: + ACCELERATIONS: + - 16 + CALIBRATION_SIZE: 20 + CENTER_FRACTIONS: [] + MAX_ATTEMPTS: 30 + NAME: PoissonDiskMaskFunc + USE_MOTION: false + USE_NOISE: false +CUDNN_BENCHMARK: false +# DATALOADER has only 1 supervised scan +DATALOADER: + ALT_SAMPLER: + PERIOD_SUPERVISED: 1 + PERIOD_UNSUPERVISED: 1 + DATA_KEYS: [] + DROP_LAST: true + FILTER: + BY: [] + GROUP_SAMPLER: + AS_BATCH_SAMPLER: false + BATCH_BY: [] + NUM_WORKERS: 8 + PREFETCH_FACTOR: 2 + SAMPLER_TRAIN: '' + SUBSAMPLE_TRAIN: + NUM_TOTAL: 1 + NUM_TOTAL_BY_GROUP: [] + NUM_UNDERSAMPLED: 0 + NUM_VAL: -1 + NUM_VAL_BY_GROUP: [] + SEED: 1000 +DATASETS: + TEST: + - mridata_knee_2019_test + TRAIN: + - mridata_knee_2019_train + VAL: + - mridata_knee_2019_val +DESCRIPTION: + BRIEF: Aug2Recon 16x mridata baseline + ENTITY_NAME: '' + EXP_NAME: vortex/mridata_knee_3dfse/Supervised + PROJECT_NAME: 'vortex_rm' + TAGS: + - baseline + - supervised + - 16x + - mridata_knee_3dfse +# For motion aug, A2R and Consistency was implemented. +# For Unrolled, made META_ARCHITECTURE GeneralizedUnrolledCNN +MODEL: + A2R: + META_ARCHITECTURE: GeneralizedUnrolledCNN + USE_SUPERVISED_CONSISTENCY: false # TUNE + CONSISTENCY: + AUG: + MRI_RECON: + AUG_SENSITIVITY_MAPS: true + SCHEDULER_P: + IGNORE: false + TRANSFORMS: + - name: RandomMRIMotion + p: 0.2 + std_devs: + - 0.2 + - 0.5 + use_mask: true + LOSS_NAME: l1 + LOSS_WEIGHT: 0.1 + USE_CONSISTENCY: true + CS: + MAX_ITER: 200 + REGULARIZATION: 0.005 + DENOISING: + META_ARCHITECTURE: GeneralizedUnrolledCNN + NOISE: + STD_DEV: *id001 + USE_FULLY_SAMPLED_TARGET: true + USE_FULLY_SAMPLED_TARGET_EVAL: null + DEVICE: cpu + M2R: + META_ARCHITECTURE: GeneralizedUnrolledCNN + USE_SUPERVISED_CONSISTENCY: false + META_ARCHITECTURE: GeneralizedUnrolledCNN + N2R: + META_ARCHITECTURE: GeneralizedUnrolledCNN + USE_SUPERVISED_CONSISTENCY: false + NM2R: + META_ARCHITECTURE: GeneralizedUnrolledCNN + USE_SUPERVISED_CONSISTENCY: false + NORMALIZER: + KEYWORDS: [] + NAME: TopMagnitudeNormalizer + RECON_LOSS: + NAME: l1 + RENORMALIZE_DATA: false + SEG: + ACTIVATION: sigmoid + CLASSES: [] + INCLUDE_BACKGROUND: false + SSDU: + MASKER: + PARAMS: {} + META_ARCHITECTURE: GeneralizedUnrolledCNN + UNET: + BLOCK_ORDER: + - conv + - relu + - conv + - relu + - batchnorm + - dropout + CHANNELS: 32 + DROPOUT: 0.0 + IN_CHANNELS: 2 + NORMALIZE: false + NUM_POOL_LAYERS: 4 + OUT_CHANNELS: 2 + UNROLLED: + BLOCK_ARCHITECTURE: ResNet + CONV_BLOCK: + ACTIVATION: relu + NORM: none + NORM_AFFINE: false + ORDER: + - norm + - act + - drop + - conv + DROPOUT: 0.0 + FIX_STEP_SIZE: false + KERNEL_SIZE: + - 3 + NUM_EMAPS: 1 + NUM_FEATURES: 256 + NUM_RESBLOCKS: 2 + NUM_UNROLLED_STEPS: 5 + PADDING: '' + SHARE_WEIGHTS: false + WEIGHTS: '' +OUTPUT_DIR: results://vortex/mridata_knee_3dfse/Supervised +SEED: 1000 +SOLVER: + BASE_LR: 0.001 + BIAS_LR_FACTOR: 1.0 + CHECKPOINT_PERIOD: -10 + GAMMA: 0.1 + GRAD_ACCUM_ITERS: 1 + LR_SCHEDULER_NAME: '' + MAX_ITER: -200 + MOMENTUM: 0.9 + OPTIMIZER: Adam + STEPS: [] + TEST_BATCH_SIZE: 24 + TRAIN_BATCH_SIZE: 24 + WARMUP_FACTOR: 0.001 + WARMUP_ITERS: 1000 + WARMUP_METHOD: linear + WEIGHT_DECAY: 0.0001 + WEIGHT_DECAY_BIAS: 0.0001 + WEIGHT_DECAY_NORM: 0.0 +TEST: + EVAL_PERIOD: -10 + EXPECTED_RESULTS: [] + FLUSH_PERIOD: 0 + VAL_AS_TEST: true + VAL_METRICS: + RECON: + - psnr + - psnr_scan + - psnr_mag + - psnr_mag_scan + - nrmse + - nrmse_scan + - nrmse_mag + - nrmse_mag_scan + - ssim (Wang) +TIME_SCALE: iter +VERSION: 1 +VIS_PERIOD: 400 \ No newline at end of file diff --git a/configs/mri-recon/mridata-3dfse-knee/old/Supervised_Template.yaml b/configs/mri-recon/mridata-3dfse-knee/old/Supervised_Template.yaml new file mode 100644 index 00000000..599c0f2d --- /dev/null +++ b/configs/mri-recon/mridata-3dfse-knee/old/Supervised_Template.yaml @@ -0,0 +1,216 @@ +# These are included in the template.yaml +# make new AUG_TEST and AUG_TRAIN like template.yaml +AUG_TEST: + UNDERSAMPLE: + ACCELERATIONS: + - 16 +AUG_TRAIN: + MOTION_P: 0.2 + MRI_RECON: + AUG_SENSITIVITY_MAPS: true + SCHEDULER_P: + IGNORE: false + TRANSFORMS: [] + NOISE_P: 0.2 + UNDERSAMPLE: + ACCELERATIONS: + - 16 + CALIBRATION_SIZE: 20 + CENTER_FRACTIONS: [] + MAX_ATTEMPTS: 30 + NAME: PoissonDiskMaskFunc + USE_MOTION: false + USE_NOISE: false +CUDNN_BENCHMARK: false + +# Make new template.yaml that includes this stuff for the dataloader +DATALOADER: + ALT_SAMPLER: + PERIOD_SUPERVISED: 1 + PERIOD_UNSUPERVISED: 1 + DATA_KEYS: [] + DROP_LAST: true + FILTER: + BY: [] + GROUP_SAMPLER: + AS_BATCH_SAMPLER: false + BATCH_BY: [] + NUM_WORKERS: 8 + PREFETCH_FACTOR: 2 + SAMPLER_TRAIN: '' + SUBSAMPLE_TRAIN: + NUM_TOTAL: 1 + NUM_TOTAL_BY_GROUP: [] + NUM_UNDERSAMPLED: 0 + NUM_VAL: -1 + NUM_VAL_BY_GROUP: [] + SEED: 1000 +# keep this +DATASETS: + TEST: + - mridata_knee_2019_test + TRAIN: + - mridata_knee_2019_train + VAL: + - mridata_knee_2019_val +# keep this +DESCRIPTION: + BRIEF: Aug2Recon 16x mridata baseline + ENTITY_NAME: '' + EXP_NAME: vortex/mridata_knee_3dfse/Supervised + PROJECT_NAME: '' + TAGS: + - baseline + - supervised + - 16x + - mridata_knee_3dfse + +# Use UNET at first for the meta architecture +# But remove motion +MODEL: + # For no motion, remove A2R and Consistency, since this is for + # motion implementation. + A2R: + META_ARCHITECTURE: GeneralizedUnrolledCNN + USE_SUPERVISED_CONSISTENCY: false + # Doesn't need this. + CONSISTENCY: + AUG: + MOTION: + RANGE: + - 0.2 + - 0.5 + SCHEDULER: + WARMUP_ITERS: 0 + WARMUP_METHOD: '' + MRI_RECON: + AUG_SENSITIVITY_MAPS: true + SCHEDULER_P: + IGNORE: false + TRANSFORMS: [] + NOISE: + MASK: + RHO: 1.0 + SCHEDULER: + WARMUP_ITERS: 0 + WARMUP_METHOD: '' + STD_DEV: &id001 + - 1 + LATENT_LOSS_NAME: mag_l1 + LATENT_LOSS_WEIGHT: 0.1 + LOSS_NAME: l1 + LOSS_WEIGHT: 0.1 + NUM_LATENT_LAYERS: 1 + USE_CONSISTENCY: true + USE_LATENT: false + CS: + MAX_ITER: 200 + REGULARIZATION: 0.005 + DENOISING: + META_ARCHITECTURE: GeneralizedUnrolledCNN + NOISE: + STD_DEV: *id001 + USE_FULLY_SAMPLED_TARGET: true + USE_FULLY_SAMPLED_TARGET_EVAL: null + DEVICE: cpu + M2R: + META_ARCHITECTURE: GeneralizedUnrolledCNN + USE_SUPERVISED_CONSISTENCY: false + # just modify META_ARCHITECTURE HERE. + META_ARCHITECTURE: UnetModel + N2R: + META_ARCHITECTURE: GeneralizedUnrolledCNN + USE_SUPERVISED_CONSISTENCY: false + NM2R: + META_ARCHITECTURE: GeneralizedUnrolledCNN + USE_SUPERVISED_CONSISTENCY: false + NORMALIZER: + KEYWORDS: [] + NAME: TopMagnitudeNormalizer + RECON_LOSS: + NAME: l1 + RENORMALIZE_DATA: false + SEG: + ACTIVATION: sigmoid + CLASSES: [] + INCLUDE_BACKGROUND: false + SSDU: + MASKER: + PARAMS: {} + META_ARCHITECTURE: GeneralizedUnrolledCNN + UNET: + BLOCK_ORDER: + - conv + - relu + - conv + - relu + - batchnorm + - dropout + CHANNELS: 32 + DROPOUT: 0.0 + IN_CHANNELS: 2 + NORMALIZE: false + NUM_POOL_LAYERS: 4 + OUT_CHANNELS: 2 + UNROLLED: + BLOCK_ARCHITECTURE: ResNet + CONV_BLOCK: + ACTIVATION: relu + NORM: none + NORM_AFFINE: false + ORDER: + - norm + - act + - drop + - conv + DROPOUT: 0.0 + FIX_STEP_SIZE: false + KERNEL_SIZE: + - 3 + NUM_EMAPS: 1 + NUM_FEATURES: 256 + NUM_RESBLOCKS: 2 + NUM_UNROLLED_STEPS: 5 + PADDING: '' + SHARE_WEIGHTS: false + WEIGHTS: '' +OUTPUT_DIR: results://vortex/mridata_knee_3dfse/Supervised +SEED: 1000 +SOLVER: + BASE_LR: 0.001 + BIAS_LR_FACTOR: 1.0 + CHECKPOINT_PERIOD: -10 + GAMMA: 0.1 + GRAD_ACCUM_ITERS: 1 + LR_SCHEDULER_NAME: '' + MAX_ITER: -200 + MOMENTUM: 0.9 + OPTIMIZER: Adam + STEPS: [] + TEST_BATCH_SIZE: 24 + TRAIN_BATCH_SIZE: 24 + WARMUP_FACTOR: 0.001 + WARMUP_ITERS: 1000 + WARMUP_METHOD: linear + WEIGHT_DECAY: 0.0001 + WEIGHT_DECAY_BIAS: 0.0001 + WEIGHT_DECAY_NORM: 0.0 +TEST: + EVAL_PERIOD: -10 + EXPECTED_RESULTS: [] + FLUSH_PERIOD: 0 + VAL_AS_TEST: true + VAL_METRICS: + RECON: + - psnr + - psnr_scan + - psnr_mag + - psnr_mag_scan + - nrmse + - nrmse_scan + - nrmse_mag + - nrmse_mag_scan + - ssim (Wang) +TIME_SCALE: iter +VERSION: 1 +VIS_PERIOD: 400 diff --git a/configs/mri-recon/mridata-3dfse-knee/old/Supervised_UNET_14_Scan.yaml b/configs/mri-recon/mridata-3dfse-knee/old/Supervised_UNET_14_Scan.yaml new file mode 100644 index 00000000..2d41cb08 --- /dev/null +++ b/configs/mri-recon/mridata-3dfse-knee/old/Supervised_UNET_14_Scan.yaml @@ -0,0 +1,174 @@ +AUG_TEST: + UNDERSAMPLE: + ACCELERATIONS: + - 16 +AUG_TRAIN: + MOTION_P: 0.2 + MRI_RECON: + AUG_SENSITIVITY_MAPS: true + SCHEDULER_P: + IGNORE: false + TRANSFORMS: [] + NOISE_P: 0.2 + UNDERSAMPLE: + ACCELERATIONS: + - 16 + CALIBRATION_SIZE: 20 + CENTER_FRACTIONS: [] + MAX_ATTEMPTS: 30 + NAME: PoissonDiskMaskFunc + USE_MOTION: false + USE_NOISE: false +CUDNN_BENCHMARK: false +# DATALOADER has 14 supervised scan +DATALOADER: + ALT_SAMPLER: + PERIOD_SUPERVISED: 1 + PERIOD_UNSUPERVISED: 1 + DATA_KEYS: [] + DROP_LAST: true + FILTER: + BY: [] + GROUP_SAMPLER: + AS_BATCH_SAMPLER: false + BATCH_BY: [] + NUM_WORKERS: 8 + PREFETCH_FACTOR: 2 + SAMPLER_TRAIN: '' + SUBSAMPLE_TRAIN: + NUM_TOTAL: 14 + NUM_TOTAL_BY_GROUP: [] + NUM_UNDERSAMPLED: 0 + NUM_VAL: -1 + NUM_VAL_BY_GROUP: [] + SEED: 1000 +DATASETS: + TEST: + - mridata_knee_2019_test + TRAIN: + - mridata_knee_2019_train + VAL: + - mridata_knee_2019_val +DESCRIPTION: + BRIEF: Aug2Recon 16x mridata baseline + ENTITY_NAME: '' + EXP_NAME: vortex/mridata_knee_3dfse/Supervised + PROJECT_NAME: 'vortex_rm' + TAGS: + - baseline + - supervised + - 16x + - mridata_knee_3dfse +# For no motion aug, A2R and Consistency was removed. +# For UNET, kept META_ARCHITECTURE is UnetModel +MODEL: + CS: + MAX_ITER: 200 + REGULARIZATION: 0.005 + DENOISING: + META_ARCHITECTURE: GeneralizedUnrolledCNN + NOISE: + STD_DEV: *id001 + USE_FULLY_SAMPLED_TARGET: true + USE_FULLY_SAMPLED_TARGET_EVAL: null + DEVICE: cpu + M2R: + META_ARCHITECTURE: GeneralizedUnrolledCNN + USE_SUPERVISED_CONSISTENCY: false + META_ARCHITECTURE: UnetModel + N2R: + META_ARCHITECTURE: GeneralizedUnrolledCNN + USE_SUPERVISED_CONSISTENCY: false + NM2R: + META_ARCHITECTURE: GeneralizedUnrolledCNN + USE_SUPERVISED_CONSISTENCY: false + NORMALIZER: + KEYWORDS: [] + NAME: TopMagnitudeNormalizer + RECON_LOSS: + NAME: l1 + RENORMALIZE_DATA: false + SEG: + ACTIVATION: sigmoid + CLASSES: [] + INCLUDE_BACKGROUND: false + SSDU: + MASKER: + PARAMS: {} + META_ARCHITECTURE: GeneralizedUnrolledCNN + UNET: + BLOCK_ORDER: + - conv + - relu + - conv + - relu + - batchnorm + - dropout + CHANNELS: 32 + DROPOUT: 0.0 + IN_CHANNELS: 2 + NORMALIZE: false + NUM_POOL_LAYERS: 4 + OUT_CHANNELS: 2 + UNROLLED: + BLOCK_ARCHITECTURE: ResNet + CONV_BLOCK: + ACTIVATION: relu + NORM: none + NORM_AFFINE: false + ORDER: + - norm + - act + - drop + - conv + DROPOUT: 0.0 + FIX_STEP_SIZE: false + KERNEL_SIZE: + - 3 + NUM_EMAPS: 1 + NUM_FEATURES: 256 + NUM_RESBLOCKS: 2 + NUM_UNROLLED_STEPS: 5 + PADDING: '' + SHARE_WEIGHTS: false + WEIGHTS: '' +OUTPUT_DIR: results://vortex/mridata_knee_3dfse/Supervised +SEED: 1000 +SOLVER: + BASE_LR: 0.001 + BIAS_LR_FACTOR: 1.0 + CHECKPOINT_PERIOD: -10 + GAMMA: 0.1 + GRAD_ACCUM_ITERS: 1 + LR_SCHEDULER_NAME: '' + MAX_ITER: -200 + MOMENTUM: 0.9 + OPTIMIZER: Adam + STEPS: [] + TEST_BATCH_SIZE: 24 + TRAIN_BATCH_SIZE: 24 + WARMUP_FACTOR: 0.001 + WARMUP_ITERS: 1000 + WARMUP_METHOD: linear + WEIGHT_DECAY: 0.0001 + WEIGHT_DECAY_BIAS: 0.0001 + WEIGHT_DECAY_NORM: 0.0 +TEST: + EVAL_PERIOD: -10 + EXPECTED_RESULTS: [] + FLUSH_PERIOD: 0 + VAL_AS_TEST: true + VAL_METRICS: + RECON: + - psnr + - psnr_scan + - psnr_mag + - psnr_mag_scan + - nrmse + - nrmse_scan + - nrmse_mag + - nrmse_mag_scan + - ssim (Wang) +TIME_SCALE: iter +VERSION: 1 +VIS_PERIOD: 400 \ No newline at end of file diff --git a/configs/mri-recon/mridata-3dfse-knee/old/Supervised_UNET_1_Scan.yaml b/configs/mri-recon/mridata-3dfse-knee/old/Supervised_UNET_1_Scan.yaml new file mode 100644 index 00000000..394a66fd --- /dev/null +++ b/configs/mri-recon/mridata-3dfse-knee/old/Supervised_UNET_1_Scan.yaml @@ -0,0 +1,174 @@ +AUG_TEST: + UNDERSAMPLE: + ACCELERATIONS: + - 16 +AUG_TRAIN: + MOTION_P: 0.2 + MRI_RECON: + AUG_SENSITIVITY_MAPS: true + SCHEDULER_P: + IGNORE: false + TRANSFORMS: [] + NOISE_P: 0.2 + UNDERSAMPLE: + ACCELERATIONS: + - 16 + CALIBRATION_SIZE: 20 + CENTER_FRACTIONS: [] + MAX_ATTEMPTS: 30 + NAME: PoissonDiskMaskFunc + USE_MOTION: false + USE_NOISE: false +CUDNN_BENCHMARK: false +# DATALOADER has only 1 supervised scan +DATALOADER: + ALT_SAMPLER: + PERIOD_SUPERVISED: 1 + PERIOD_UNSUPERVISED: 1 + DATA_KEYS: [] + DROP_LAST: true + FILTER: + BY: [] + GROUP_SAMPLER: + AS_BATCH_SAMPLER: false + BATCH_BY: [] + NUM_WORKERS: 8 + PREFETCH_FACTOR: 2 + SAMPLER_TRAIN: '' + SUBSAMPLE_TRAIN: + NUM_TOTAL: 1 + NUM_TOTAL_BY_GROUP: [] + NUM_UNDERSAMPLED: 0 + NUM_VAL: -1 + NUM_VAL_BY_GROUP: [] + SEED: 1000 +DATASETS: + TEST: + - mridata_knee_2019_test + TRAIN: + - mridata_knee_2019_train + VAL: + - mridata_knee_2019_val +DESCRIPTION: + BRIEF: Aug2Recon 16x mridata baseline + ENTITY_NAME: '' + EXP_NAME: vortex/mridata_knee_3dfse/Supervised + PROJECT_NAME: 'vortex_rm' + TAGS: + - baseline + - supervised + - 16x + - mridata_knee_3dfse +# For no motion aug, A2R and Consistency was removed. +# For UNET, kept META_ARCHITECTURE as UnetModel +MODEL: + CS: + MAX_ITER: 200 + REGULARIZATION: 0.005 + DENOISING: + META_ARCHITECTURE: GeneralizedUnrolledCNN + NOISE: + STD_DEV: *id001 + USE_FULLY_SAMPLED_TARGET: true + USE_FULLY_SAMPLED_TARGET_EVAL: null + DEVICE: cpu + M2R: + META_ARCHITECTURE: GeneralizedUnrolledCNN + USE_SUPERVISED_CONSISTENCY: false + META_ARCHITECTURE: UnetModel + N2R: + META_ARCHITECTURE: GeneralizedUnrolledCNN + USE_SUPERVISED_CONSISTENCY: false + NM2R: + META_ARCHITECTURE: GeneralizedUnrolledCNN + USE_SUPERVISED_CONSISTENCY: false + NORMALIZER: + KEYWORDS: [] + NAME: TopMagnitudeNormalizer + RECON_LOSS: + NAME: l1 + RENORMALIZE_DATA: false + SEG: + ACTIVATION: sigmoid + CLASSES: [] + INCLUDE_BACKGROUND: false + SSDU: + MASKER: + PARAMS: {} + META_ARCHITECTURE: GeneralizedUnrolledCNN + UNET: + BLOCK_ORDER: + - conv + - relu + - conv + - relu + - batchnorm + - dropout + CHANNELS: 32 + DROPOUT: 0.0 + IN_CHANNELS: 2 + NORMALIZE: false + NUM_POOL_LAYERS: 4 + OUT_CHANNELS: 2 + UNROLLED: + BLOCK_ARCHITECTURE: ResNet + CONV_BLOCK: + ACTIVATION: relu + NORM: none + NORM_AFFINE: false + ORDER: + - norm + - act + - drop + - conv + DROPOUT: 0.0 + FIX_STEP_SIZE: false + KERNEL_SIZE: + - 3 + NUM_EMAPS: 1 + NUM_FEATURES: 256 + NUM_RESBLOCKS: 2 + NUM_UNROLLED_STEPS: 5 + PADDING: '' + SHARE_WEIGHTS: false + WEIGHTS: '' +OUTPUT_DIR: results://vortex/mridata_knee_3dfse/Supervised +SEED: 1000 +SOLVER: + BASE_LR: 0.001 + BIAS_LR_FACTOR: 1.0 + CHECKPOINT_PERIOD: -10 + GAMMA: 0.1 + GRAD_ACCUM_ITERS: 1 + LR_SCHEDULER_NAME: '' + MAX_ITER: -200 + MOMENTUM: 0.9 + OPTIMIZER: Adam + STEPS: [] + TEST_BATCH_SIZE: 24 + TRAIN_BATCH_SIZE: 24 + WARMUP_FACTOR: 0.001 + WARMUP_ITERS: 1000 + WARMUP_METHOD: linear + WEIGHT_DECAY: 0.0001 + WEIGHT_DECAY_BIAS: 0.0001 + WEIGHT_DECAY_NORM: 0.0 +TEST: + EVAL_PERIOD: -10 + EXPECTED_RESULTS: [] + FLUSH_PERIOD: 0 + VAL_AS_TEST: true + VAL_METRICS: + RECON: + - psnr + - psnr_scan + - psnr_mag + - psnr_mag_scan + - nrmse + - nrmse_scan + - nrmse_mag + - nrmse_mag_scan + - ssim (Wang) +TIME_SCALE: iter +VERSION: 1 +VIS_PERIOD: 400 \ No newline at end of file diff --git a/configs/mri-recon/mridata-3dfse-knee/old/Supervised_Unrolled_14_Scan.yaml b/configs/mri-recon/mridata-3dfse-knee/old/Supervised_Unrolled_14_Scan.yaml new file mode 100644 index 00000000..ac568954 --- /dev/null +++ b/configs/mri-recon/mridata-3dfse-knee/old/Supervised_Unrolled_14_Scan.yaml @@ -0,0 +1,174 @@ +AUG_TEST: + UNDERSAMPLE: + ACCELERATIONS: + - 16 +AUG_TRAIN: + MOTION_P: 0.2 + MRI_RECON: + AUG_SENSITIVITY_MAPS: true + SCHEDULER_P: + IGNORE: false + TRANSFORMS: [] + NOISE_P: 0.2 + UNDERSAMPLE: + ACCELERATIONS: + - 16 + CALIBRATION_SIZE: 20 + CENTER_FRACTIONS: [] + MAX_ATTEMPTS: 30 + NAME: PoissonDiskMaskFunc + USE_MOTION: false + USE_NOISE: false +CUDNN_BENCHMARK: false +# DATALOADER has only 1 supervised scan +DATALOADER: + ALT_SAMPLER: + PERIOD_SUPERVISED: 1 + PERIOD_UNSUPERVISED: 1 + DATA_KEYS: [] + DROP_LAST: true + FILTER: + BY: [] + GROUP_SAMPLER: + AS_BATCH_SAMPLER: false + BATCH_BY: [] + NUM_WORKERS: 8 + PREFETCH_FACTOR: 2 + SAMPLER_TRAIN: '' + SUBSAMPLE_TRAIN: + NUM_TOTAL: 14 + NUM_TOTAL_BY_GROUP: [] + NUM_UNDERSAMPLED: 0 + NUM_VAL: -1 + NUM_VAL_BY_GROUP: [] + SEED: 1000 +DATASETS: + TEST: + - mridata_knee_2019_test + TRAIN: + - mridata_knee_2019_train + VAL: + - mridata_knee_2019_val +DESCRIPTION: + BRIEF: Aug2Recon 16x mridata baseline + ENTITY_NAME: '' + EXP_NAME: vortex/mridata_knee_3dfse/Supervised + PROJECT_NAME: 'vortex_rm' + TAGS: + - baseline + - supervised + - 16x + - mridata_knee_3dfse +# For no motion aug, A2R and Consistency was removed. +# For Unrolled, made META_ARCHITECTURE GeneralizedUnrolledCNN +MODEL: + CS: + MAX_ITER: 200 + REGULARIZATION: 0.005 + DENOISING: + META_ARCHITECTURE: GeneralizedUnrolledCNN + NOISE: + STD_DEV: *id001 + USE_FULLY_SAMPLED_TARGET: true + USE_FULLY_SAMPLED_TARGET_EVAL: null + DEVICE: cpu + M2R: + META_ARCHITECTURE: GeneralizedUnrolledCNN + USE_SUPERVISED_CONSISTENCY: false + META_ARCHITECTURE: GeneralizedUnrolledCNN + N2R: + META_ARCHITECTURE: GeneralizedUnrolledCNN + USE_SUPERVISED_CONSISTENCY: false + NM2R: + META_ARCHITECTURE: GeneralizedUnrolledCNN + USE_SUPERVISED_CONSISTENCY: false + NORMALIZER: + KEYWORDS: [] + NAME: TopMagnitudeNormalizer + RECON_LOSS: + NAME: l1 + RENORMALIZE_DATA: false + SEG: + ACTIVATION: sigmoid + CLASSES: [] + INCLUDE_BACKGROUND: false + SSDU: + MASKER: + PARAMS: {} + META_ARCHITECTURE: GeneralizedUnrolledCNN + UNET: + BLOCK_ORDER: + - conv + - relu + - conv + - relu + - batchnorm + - dropout + CHANNELS: 32 + DROPOUT: 0.0 + IN_CHANNELS: 2 + NORMALIZE: false + NUM_POOL_LAYERS: 4 + OUT_CHANNELS: 2 + UNROLLED: + BLOCK_ARCHITECTURE: ResNet + CONV_BLOCK: + ACTIVATION: relu + NORM: none + NORM_AFFINE: false + ORDER: + - norm + - act + - drop + - conv + DROPOUT: 0.0 + FIX_STEP_SIZE: false + KERNEL_SIZE: + - 3 + NUM_EMAPS: 1 + NUM_FEATURES: 256 + NUM_RESBLOCKS: 2 + NUM_UNROLLED_STEPS: 5 + PADDING: '' + SHARE_WEIGHTS: false + WEIGHTS: '' +OUTPUT_DIR: results://vortex/mridata_knee_3dfse/Supervised +SEED: 1000 +SOLVER: + BASE_LR: 0.001 + BIAS_LR_FACTOR: 1.0 + CHECKPOINT_PERIOD: -10 + GAMMA: 0.1 + GRAD_ACCUM_ITERS: 1 + LR_SCHEDULER_NAME: '' + MAX_ITER: -200 + MOMENTUM: 0.9 + OPTIMIZER: Adam + STEPS: [] + TEST_BATCH_SIZE: 24 + TRAIN_BATCH_SIZE: 24 + WARMUP_FACTOR: 0.001 + WARMUP_ITERS: 1000 + WARMUP_METHOD: linear + WEIGHT_DECAY: 0.0001 + WEIGHT_DECAY_BIAS: 0.0001 + WEIGHT_DECAY_NORM: 0.0 +TEST: + EVAL_PERIOD: -10 + EXPECTED_RESULTS: [] + FLUSH_PERIOD: 0 + VAL_AS_TEST: true + VAL_METRICS: + RECON: + - psnr + - psnr_scan + - psnr_mag + - psnr_mag_scan + - nrmse + - nrmse_scan + - nrmse_mag + - nrmse_mag_scan + - ssim (Wang) +TIME_SCALE: iter +VERSION: 1 +VIS_PERIOD: 400 \ No newline at end of file diff --git a/configs/mri-recon/mridata-3dfse-knee/old/Supervised_Unrolled_1_Scan.yaml b/configs/mri-recon/mridata-3dfse-knee/old/Supervised_Unrolled_1_Scan.yaml new file mode 100644 index 00000000..966a871c --- /dev/null +++ b/configs/mri-recon/mridata-3dfse-knee/old/Supervised_Unrolled_1_Scan.yaml @@ -0,0 +1,174 @@ +AUG_TEST: + UNDERSAMPLE: + ACCELERATIONS: + - 16 +AUG_TRAIN: + MOTION_P: 0.2 + MRI_RECON: + AUG_SENSITIVITY_MAPS: true + SCHEDULER_P: + IGNORE: false + TRANSFORMS: [] + NOISE_P: 0.2 + UNDERSAMPLE: + ACCELERATIONS: + - 16 + CALIBRATION_SIZE: 20 + CENTER_FRACTIONS: [] + MAX_ATTEMPTS: 30 + NAME: PoissonDiskMaskFunc + USE_MOTION: false + USE_NOISE: false +CUDNN_BENCHMARK: false +# DATALOADER has only 1 supervised scan +DATALOADER: + ALT_SAMPLER: + PERIOD_SUPERVISED: 1 + PERIOD_UNSUPERVISED: 1 + DATA_KEYS: [] + DROP_LAST: true + FILTER: + BY: [] + GROUP_SAMPLER: + AS_BATCH_SAMPLER: false + BATCH_BY: [] + NUM_WORKERS: 8 + PREFETCH_FACTOR: 2 + SAMPLER_TRAIN: '' + SUBSAMPLE_TRAIN: + NUM_TOTAL: 1 + NUM_TOTAL_BY_GROUP: [] + NUM_UNDERSAMPLED: 0 + NUM_VAL: -1 + NUM_VAL_BY_GROUP: [] + SEED: 1000 +DATASETS: + TEST: + - mridata_knee_2019_test + TRAIN: + - mridata_knee_2019_train + VAL: + - mridata_knee_2019_val +DESCRIPTION: + BRIEF: Aug2Recon 16x mridata baseline + ENTITY_NAME: '' + EXP_NAME: vortex/mridata_knee_3dfse/Supervised + PROJECT_NAME: 'vortex_rm' + TAGS: + - baseline + - supervised + - 16x + - mridata_knee_3dfse +# For no motion aug, A2R and Consistency was removed. +# For Unrolled, made META_ARCHITECTURE GeneralizedUnrolledCNN +MODEL: + CS: + MAX_ITER: 200 + REGULARIZATION: 0.005 + DENOISING: + META_ARCHITECTURE: GeneralizedUnrolledCNN + NOISE: + STD_DEV: *id001 + USE_FULLY_SAMPLED_TARGET: true + USE_FULLY_SAMPLED_TARGET_EVAL: null + DEVICE: cpu + M2R: + META_ARCHITECTURE: GeneralizedUnrolledCNN + USE_SUPERVISED_CONSISTENCY: false + META_ARCHITECTURE: GeneralizedUnrolledCNN + N2R: + META_ARCHITECTURE: GeneralizedUnrolledCNN + USE_SUPERVISED_CONSISTENCY: false + NM2R: + META_ARCHITECTURE: GeneralizedUnrolledCNN + USE_SUPERVISED_CONSISTENCY: false + NORMALIZER: + KEYWORDS: [] + NAME: TopMagnitudeNormalizer + RECON_LOSS: + NAME: l1 + RENORMALIZE_DATA: false + SEG: + ACTIVATION: sigmoid + CLASSES: [] + INCLUDE_BACKGROUND: false + SSDU: + MASKER: + PARAMS: {} + META_ARCHITECTURE: GeneralizedUnrolledCNN + UNET: + BLOCK_ORDER: + - conv + - relu + - conv + - relu + - batchnorm + - dropout + CHANNELS: 32 + DROPOUT: 0.0 + IN_CHANNELS: 2 + NORMALIZE: false + NUM_POOL_LAYERS: 4 + OUT_CHANNELS: 2 + UNROLLED: + BLOCK_ARCHITECTURE: ResNet + CONV_BLOCK: + ACTIVATION: relu + NORM: none + NORM_AFFINE: false + ORDER: + - norm + - act + - drop + - conv + DROPOUT: 0.0 + FIX_STEP_SIZE: false + KERNEL_SIZE: + - 3 + NUM_EMAPS: 1 + NUM_FEATURES: 256 + NUM_RESBLOCKS: 2 + NUM_UNROLLED_STEPS: 5 + PADDING: '' + SHARE_WEIGHTS: false + WEIGHTS: '' +OUTPUT_DIR: results://vortex/mridata_knee_3dfse/Supervised +SEED: 1000 +SOLVER: + BASE_LR: 0.001 + BIAS_LR_FACTOR: 1.0 + CHECKPOINT_PERIOD: -10 + GAMMA: 0.1 + GRAD_ACCUM_ITERS: 1 + LR_SCHEDULER_NAME: '' + MAX_ITER: -200 + MOMENTUM: 0.9 + OPTIMIZER: Adam + STEPS: [] + TEST_BATCH_SIZE: 24 + TRAIN_BATCH_SIZE: 24 + WARMUP_FACTOR: 0.001 + WARMUP_ITERS: 1000 + WARMUP_METHOD: linear + WEIGHT_DECAY: 0.0001 + WEIGHT_DECAY_BIAS: 0.0001 + WEIGHT_DECAY_NORM: 0.0 +TEST: + EVAL_PERIOD: -10 + EXPECTED_RESULTS: [] + FLUSH_PERIOD: 0 + VAL_AS_TEST: true + VAL_METRICS: + RECON: + - psnr + - psnr_scan + - psnr_mag + - psnr_mag_scan + - nrmse + - nrmse_scan + - nrmse_mag + - nrmse_mag_scan + - ssim (Wang) +TIME_SCALE: iter +VERSION: 1 +VIS_PERIOD: 400 \ No newline at end of file diff --git a/configs/mri-recon/mridata-3dfse-knee/old/template.yaml b/configs/mri-recon/mridata-3dfse-knee/old/template.yaml new file mode 100644 index 00000000..a62c1df8 --- /dev/null +++ b/configs/mri-recon/mridata-3dfse-knee/old/template.yaml @@ -0,0 +1,36 @@ +# Shared/default properties between different configs for the 3d fse mridata knee dataset. +AUG_TEST: + UNDERSAMPLE: + ACCELERATIONS: (16,) +AUG_TRAIN: + UNDERSAMPLE: + ACCELERATIONS: (16,) + CALIBRATION_SIZE: 20 + CENTER_FRACTIONS: [] + NAME: PoissonDiskMaskFunc +MODEL: + RECON_LOSS: + NAME: "l1" + RENORMALIZE_DATA: False +DATASETS: + TRAIN: ("mridata_knee_2019_train",) + VAL: ("mridata_knee_2019_val",) + TEST: ("mridata_knee_2019_test",) +DATALOADER: + DROP_LAST: True + NUM_WORKERS: 8 +SOLVER: + OPTIMIZER: "Adam" + LR_SCHEDULER_NAME: "" + CHECKPOINT_PERIOD: -5000 # Checkpoint every 10 epochs + MAX_ITER: -8000 # Max number of epochs to train for +TEST: + EVAL_PERIOD: -5000 # Run validation every 10 epochs + VAL_METRICS: + RECON: ("psnr", "psnr_scan", "psnr_mag", "psnr_mag_scan", "nrmse", "nrmse_scan", "nrmse_mag", "nrmse_mag_scan", "ssim (Wang)") +TIME_SCALE: "epoch" +SEED: 1000 +VIS_PERIOD: -400 # save images every 400 iterations +VERSION: 1 +DESCRIPTION: + PROJECT_NAME: "vortex_rm" diff --git a/configs/mri-recon/mridata-3dfse-knee/old/unet.yaml b/configs/mri-recon/mridata-3dfse-knee/old/unet.yaml new file mode 100644 index 00000000..09d9c8b2 --- /dev/null +++ b/configs/mri-recon/mridata-3dfse-knee/old/unet.yaml @@ -0,0 +1,17 @@ +# Configuration for DL-CS Paper +_BASE_: "template.yaml" +MODEL: + META_ARCHITECTURE: "UnetModel" + UNET: + IN_CHANNELS: 2 + OUT_CHANNELS: 2 + CHANNELS: 32 + NUM_POOL_LAYERS: 4 + DROPOUT: 0. +SOLVER: + TRAIN_BATCH_SIZE: 16 + TEST_BATCH_SIZE: 16 + BASE_LR: 1e-3 +OUTPUT_DIR: "results://mri-recon/mridata-3dfse-knee/unet" +DESCRIPTION: + EXP_NAME: "UNET_NON_VORTEX" \ No newline at end of file diff --git a/configs/mri-recon/mridata-3dfse-knee/old/unrolled.yaml b/configs/mri-recon/mridata-3dfse-knee/old/unrolled.yaml new file mode 100644 index 00000000..dbfd9c0e --- /dev/null +++ b/configs/mri-recon/mridata-3dfse-knee/old/unrolled.yaml @@ -0,0 +1,23 @@ +_BASE_: "template.yaml" +MODEL: + META_ARCHITECTURE: "GeneralizedUnrolledCNN" + UNROLLED: + NUM_UNROLLED_STEPS: 8 + NUM_RESBLOCKS: 2 + NUM_FEATURES: 128 + DROPOUT: 0. + CONV_BLOCK: + ACTIVATION: "relu" + NORM: "none" + ORDER: ("act", "conv") +SOLVER: + OPTIMIZER: "Adam" + LR_SCHEDULER_NAME: "" + TRAIN_BATCH_SIZE: 4 + GRAD_ACCUM_ITERS: 4 + TEST_BATCH_SIZE: 12 + BASE_LR: 1e-4 +OUTPUT_DIR: "results://mri-recon/mridata-3dfse-knee/unrolled" +VERSION: 1 +DESCRIPTION: + EXP_NAME: "UNROLLED_NON_VORTEX" diff --git a/configs/mri-recon/mridata-3dfse-knee/old/vortex_rm_template.yaml b/configs/mri-recon/mridata-3dfse-knee/old/vortex_rm_template.yaml new file mode 100644 index 00000000..3a83e1fe --- /dev/null +++ b/configs/mri-recon/mridata-3dfse-knee/old/vortex_rm_template.yaml @@ -0,0 +1,39 @@ +# Shared/default properties between different configs for the 3d fse mridata knee dataset. +AUG_TEST: + UNDERSAMPLE: + ACCELERATIONS: (16,) +AUG_TRAIN: + UNDERSAMPLE: + ACCELERATIONS: (16,) + CALIBRATION_SIZE: 20 + CENTER_FRACTIONS: [] + NAME: PoissonDiskMaskFunc +MODEL: + RECON_LOSS: + NAME: "l1" + RENORMALIZE_DATA: False +DATASETS: + TRAIN: ("mridata_knee_2019_train",) + VAL: ("mridata_knee_2019_val",) + TEST: ("mridata_knee_2019_test",) +DATALOADER: + DROP_LAST: True + NUM_WORKERS: 8 + SUBSAMPLE_TRAIN: + NUM_UNDERSAMPLED: 13 +SOLVER: + # Set to step counts, not epoch counts + OPTIMIZER: "Adam" + LR_SCHEDULER_NAME: "" + CHECKPOINT_PERIOD: -5000 # Checkpoint every 10 epochs + MAX_ITER: -8000 # Max number of epochs to train for +TEST: + EVAL_PERIOD: -5000 # Run validation every 10 epochs + VAL_METRICS: + RECON: ("psnr", "psnr_scan", "psnr_mag", "psnr_mag_scan", "nrmse", "nrmse_scan", "nrmse_mag", "nrmse_mag_scan", "ssim (Wang)") +TIME_SCALE: "epoch" +SEED: 1000 +VIS_PERIOD: -400 # save images every 400 iterations +VERSION: 1 +DESCRIPTION: + PROJECT_NAME: "vortex_rm" \ No newline at end of file diff --git a/configs/mri-recon/mridata-3dfse-knee/old/vortex_rm_unet.yaml b/configs/mri-recon/mridata-3dfse-knee/old/vortex_rm_unet.yaml new file mode 100644 index 00000000..7969019d --- /dev/null +++ b/configs/mri-recon/mridata-3dfse-knee/old/vortex_rm_unet.yaml @@ -0,0 +1,41 @@ +# Configuration for DL-CS Paper +_BASE_: "vortex_rm_template.yaml" +MODEL: + DEVICE: cuda + META_ARCHITECTURE: VortexModel + A2R: + META_ARCHITECTURE: UnetModel + USE_SUPERVISED_CONSISTENCY: false # TUNE + UNROLLED: + NUM_UNROLLED_STEPS: 8 + NUM_RESBLOCKS: 2 + NUM_FEATURES: 128 + DROPOUT: 0. + CONV_BLOCK: + ACTIVATION: "relu" + NORM: "none" + ORDER: ("act", "conv") + CONSISTENCY: + AUG: + MRI_RECON: + AUG_SENSITIVITY_MAPS: true + SCHEDULER_P: + IGNORE: false + TRANSFORMS: + - name: RandomNoise + p: 1.0 + std_devs: + - 0.2 + - 0.5 + use_mask: true + LOSS_NAME: l1 + LOSS_WEIGHT: 0.1 + USE_CONSISTENCY: true +SOLVER: + TRAIN_BATCH_SIZE: 16 + TEST_BATCH_SIZE: 16 + BASE_LR: 1e-3 +OUTPUT_DIR: "results://mri-recon/mridata-3dfse-knee/vortex_rm_unet" +DESCRIPTION: + EXP_NAME: "UNET_VORTEX_RM" + diff --git a/configs/mri-recon/mridata-3dfse-knee/old/vortex_rm_unrolled.yaml b/configs/mri-recon/mridata-3dfse-knee/old/vortex_rm_unrolled.yaml new file mode 100644 index 00000000..32f94078 --- /dev/null +++ b/configs/mri-recon/mridata-3dfse-knee/old/vortex_rm_unrolled.yaml @@ -0,0 +1,43 @@ +_BASE_: "vortex_rm_template.yaml" +MODEL: + DEVICE: cuda + META_ARCHITECTURE: VortexModel + A2R: + META_ARCHITECTURE: GeneralizedUnrolledCNN + USE_SUPERVISED_CONSISTENCY: false # TUNE + UNROLLED: + NUM_UNROLLED_STEPS: 8 + NUM_RESBLOCKS: 2 + NUM_FEATURES: 128 + DROPOUT: 0. + CONV_BLOCK: + ACTIVATION: "relu" + NORM: "none" + ORDER: ("act", "conv") + CONSISTENCY: + AUG: + MRI_RECON: + AUG_SENSITIVITY_MAPS: true + SCHEDULER_P: + IGNORE: false + TRANSFORMS: + - name: RandomNoise + p: 1.0 + std_devs: + - 0.2 + - 0.5 + use_mask: true + LOSS_NAME: l1 + LOSS_WEIGHT: 0.1 + USE_CONSISTENCY: true +SOLVER: + OPTIMIZER: "Adam" + LR_SCHEDULER_NAME: "" + TRAIN_BATCH_SIZE: 4 + GRAD_ACCUM_ITERS: 4 + TEST_BATCH_SIZE: 12 + BASE_LR: 1e-4 +OUTPUT_DIR: "results://mri-recon/mridata-3dfse-knee/vortex_rm_unrolled/" +VERSION: 1 +DESCRIPTION: + EXP_NAME: "UNROLLED_VORTEX_RM" diff --git a/configs/mri-recon/mridata-3dfse-knee/templates/template.yaml b/configs/mri-recon/mridata-3dfse-knee/templates/template.yaml new file mode 100644 index 00000000..4e960b70 --- /dev/null +++ b/configs/mri-recon/mridata-3dfse-knee/templates/template.yaml @@ -0,0 +1,45 @@ +# Shared/default properties between different configs for the 3d fse mridata knee dataset. +DESCRIPTION: + # You want to log in the ss_recon team + ENTITY_NAME: 'ss_recon' + # You want to log in the vortex_rm project + PROJECT_NAME: 'vortex_rm' +AUG_TRAIN: + UNDERSAMPLE: + # Use 16x acceleration at train time. + ACCELERATIONS: (16,) + CALIBRATION_SIZE: 20 + CENTER_FRACTIONS: [] + NAME: PoissonDiskMaskFunc +AUG_TEST: + UNDERSAMPLE: + # Use 16x acceleration at validation & test time. + ACCELERATIONS: (16,) +MODEL: + DEVICE: cuda + RECON_LOSS: + NAME: "l1" + RENORMALIZE_DATA: False +DATASETS: + TRAIN: ("mridata_knee_2019_train",) + VAL: ("mridata_knee_2019_val",) + TEST: ("mridata_knee_2019_test",) +DATALOADER: + DROP_LAST: True + NUM_WORKERS: 8 +TIME_SCALE: "iter" # Positive values are in iterations +SOLVER: + OPTIMIZER: "Adam" + LR_SCHEDULER_NAME: "" + # Checkpointing frequency + CHECKPOINT_PERIOD: 5000 + # Number of iterations to train for. + MAX_ITER: 80000 +TEST: + # Validation frequency + EVAL_PERIOD: 5000 + VAL_METRICS: + RECON: ("psnr", "psnr_scan", "psnr_mag", "psnr_mag_scan", "nrmse", "nrmse_scan", "nrmse_mag", "nrmse_mag_scan", "ssim (Wang)") +SEED: 1000 +VIS_PERIOD: 100 # save images every 100 iterations +VERSION: 1 diff --git a/configs/mri-recon/mridata-3dfse-knee/templates/template_aug.yaml b/configs/mri-recon/mridata-3dfse-knee/templates/template_aug.yaml new file mode 100644 index 00000000..f1e6502b --- /dev/null +++ b/configs/mri-recon/mridata-3dfse-knee/templates/template_aug.yaml @@ -0,0 +1,10 @@ +_BASE_: "template.yaml" +AUG_TRAIN: + MRI_RECON: + AUG_SENSITIVITY_MAPS: true + TRANSFORMS: + - name: RandomMRIMotion + p: 0.2 + std_devs: + - 0.2 + - 0.5 \ No newline at end of file diff --git a/configs/mri-recon/mridata-3dfse-knee/templates/template_multi_aug_1.yaml b/configs/mri-recon/mridata-3dfse-knee/templates/template_multi_aug_1.yaml new file mode 100644 index 00000000..c6e25904 --- /dev/null +++ b/configs/mri-recon/mridata-3dfse-knee/templates/template_multi_aug_1.yaml @@ -0,0 +1,26 @@ +_BASE_: "template.yaml" +AUG_TRAIN: + MRI_RECON: + AUG_SENSITIVITY_MAPS: true + TRANSFORMS: + - name: RandomMRIMultiShotMotion + p: 0.2 + seq_tfm_gens: + - name: RandomAffine + p: 0.2 + angle: 10 + translate: 0.5 + scale: 0.2 + shear: 30 + - name: RandomAffine + p: 0.2 + angle: 0 + translate: 0.5 + scale: 0.2 + shear: 30 + - name: RandomAffine + p: 0.2 + angle: 10 + translate: 0 + scale: 0.2 + shear: 30 \ No newline at end of file diff --git a/configs/mri-recon/mridata-3dfse-knee/templates/template_multi_aug_2.yaml b/configs/mri-recon/mridata-3dfse-knee/templates/template_multi_aug_2.yaml new file mode 100644 index 00000000..9513c1f7 --- /dev/null +++ b/configs/mri-recon/mridata-3dfse-knee/templates/template_multi_aug_2.yaml @@ -0,0 +1,34 @@ +_BASE_: "template.yaml" +AUG_TRAIN: + MRI_RECON: + AUG_SENSITIVITY_MAPS: true + TRANSFORMS: + - name: RandomMRIMultiShotMotion + p: 0.2 + seq_tfm_gens: + - name: SequenceOne + - name: RandomAffine + p: 0.2 + angle: 10 + translate: 0.5 + scale: 0.2 + shear: 30 + - name: RandomAffine + p: 0.2 + angle: 0 + translate: 0.5 + scale: 0.2 + shear: 30 + - name: SequenceTwo + - name: RandomAffine + p: 0.2 + angle: 10 + translate: 0.5 + scale: 0.2 + shear: 30 + - name: RandomAffine + p: 0.2 + angle: 0 + translate: 0.5 + scale: 0.2 + shear: 30 diff --git a/configs/mri-recon/mridata-3dfse-knee/templates/unet.yaml b/configs/mri-recon/mridata-3dfse-knee/templates/unet.yaml new file mode 100644 index 00000000..6250bdc5 --- /dev/null +++ b/configs/mri-recon/mridata-3dfse-knee/templates/unet.yaml @@ -0,0 +1,14 @@ +# Configuration for DL-CS Paper +_BASE_: "template.yaml" +MODEL: + META_ARCHITECTURE: "UnetModel" + UNET: + IN_CHANNELS: 2 + OUT_CHANNELS: 2 + CHANNELS: 32 + NUM_POOL_LAYERS: 4 + DROPOUT: 0. +SOLVER: + TRAIN_BATCH_SIZE: 16 + TEST_BATCH_SIZE: 16 + BASE_LR: 1e-3 \ No newline at end of file diff --git a/configs/mri-recon/mridata-3dfse-knee/templates/unet_aug.yaml b/configs/mri-recon/mridata-3dfse-knee/templates/unet_aug.yaml new file mode 100644 index 00000000..73e794aa --- /dev/null +++ b/configs/mri-recon/mridata-3dfse-knee/templates/unet_aug.yaml @@ -0,0 +1,14 @@ +# Configuration for DL-CS Paper +_BASE_: "template_aug.yaml" +MODEL: + META_ARCHITECTURE: "UnetModel" + UNET: + IN_CHANNELS: 2 + OUT_CHANNELS: 2 + CHANNELS: 32 + NUM_POOL_LAYERS: 4 + DROPOUT: 0. +SOLVER: + TRAIN_BATCH_SIZE: 16 + TEST_BATCH_SIZE: 16 + BASE_LR: 1e-3 \ No newline at end of file diff --git a/configs/mri-recon/mridata-3dfse-knee/templates/unrolled.yaml b/configs/mri-recon/mridata-3dfse-knee/templates/unrolled.yaml new file mode 100644 index 00000000..c0d35540 --- /dev/null +++ b/configs/mri-recon/mridata-3dfse-knee/templates/unrolled.yaml @@ -0,0 +1,20 @@ +_BASE_: "template.yaml" +MODEL: + META_ARCHITECTURE: "GeneralizedUnrolledCNN" + UNROLLED: + NUM_UNROLLED_STEPS: 8 + NUM_RESBLOCKS: 2 + NUM_FEATURES: 128 + DROPOUT: 0. + CONV_BLOCK: + ACTIVATION: "relu" + NORM: "none" + ORDER: ("act", "conv") +SOLVER: + OPTIMIZER: "Adam" + LR_SCHEDULER_NAME: "" + TRAIN_BATCH_SIZE: 4 + GRAD_ACCUM_ITERS: 4 + TEST_BATCH_SIZE: 12 + BASE_LR: 1e-4 +VERSION: 1 \ No newline at end of file diff --git a/configs/mri-recon/mridata-3dfse-knee/templates/unrolled_aug.yaml b/configs/mri-recon/mridata-3dfse-knee/templates/unrolled_aug.yaml new file mode 100644 index 00000000..c8817576 --- /dev/null +++ b/configs/mri-recon/mridata-3dfse-knee/templates/unrolled_aug.yaml @@ -0,0 +1,20 @@ +_BASE_: "template_aug.yaml" +MODEL: + META_ARCHITECTURE: "GeneralizedUnrolledCNN" + UNROLLED: + NUM_UNROLLED_STEPS: 8 + NUM_RESBLOCKS: 2 + NUM_FEATURES: 128 + DROPOUT: 0. + CONV_BLOCK: + ACTIVATION: "relu" + NORM: "none" + ORDER: ("act", "conv") +SOLVER: + OPTIMIZER: "Adam" + LR_SCHEDULER_NAME: "" + TRAIN_BATCH_SIZE: 4 + GRAD_ACCUM_ITERS: 4 + TEST_BATCH_SIZE: 12 + BASE_LR: 1e-4 +VERSION: 1 \ No newline at end of file diff --git a/configs/mri-recon/mridata-3dfse-knee/unet/ssdu/SSDU_Aug_UNET_13_Scan.yaml b/configs/mri-recon/mridata-3dfse-knee/unet/ssdu/SSDU_Aug_UNET_13_Scan.yaml new file mode 100644 index 00000000..d56b6d9d --- /dev/null +++ b/configs/mri-recon/mridata-3dfse-knee/unet/ssdu/SSDU_Aug_UNET_13_Scan.yaml @@ -0,0 +1,48 @@ +# Inherit fields from the unet.yaml config. +_BASE_: "../../templates/unet.yaml" +DATALOADER: + SUBSAMPLE_TRAIN: + # 1 supervised and 13 unsupervised scans for training + NUM_TOTAL: 14 + NUM_UNDERSAMPLED: 13 +# Specify an output directory that is unique for this experiment. +OUTPUT_DIR: results://vortex-rm/mridata_knee_3dfse/unet/ssdu/ssdu_aug_unet_13_scan +DESCRIPTION: + # Use f-strings to specify reading values in braces ({}) the config file. + # This can only be done for string fields. + BRIEF: f"SSDU Aug U-Net {AUG_TRAIN.UNDERSAMPLE.ACCELERATIONS}x mridata - {MODEL.SSDU.MASKER.PARAMS.kind}/loss={MODEL.RECON_LOSS.NAME}" + EXP_NAME: mridata_knee_3dfse/unet/SSDU_Aug + TAGS: + - baseline + - ssdu + - 16x + - mridata_knee_3dfse + - unet +# For motion aug, A2R and Consistency were kept with std_dev range of 0.2 to 0.5. +MODEL: + META_ARCHITECTURE: SSDUModel + RECON_LOSS: + NAME: k_l1 + RENORMALIZE_DATA: false + SSDU: + # For UNET, the META_ARCHITECTURE of SSDUModel is UnetModel + META_ARCHITECTURE: UnetModel + MASKER: + # These paramters were taken from the VORTEX/Noise2Recon papers. + PARAMS: + kind: uniform + rhos: 0.4 # Default value in SSDU code + p: 1.0 + per_example: true + AUGMENTOR: + TRANSFORMS: + - name: RandomMRIMotion + p: 0.2 + std_devs: + - 0.2 + - 0.5 +AUG_TRAIN: + MRI_RECON: + TRANSFORMS: [] +VERSION: 1 + diff --git a/configs/mri-recon/mridata-3dfse-knee/unet/ssdu/SSDU_UNET_13_Scan.yaml b/configs/mri-recon/mridata-3dfse-knee/unet/ssdu/SSDU_UNET_13_Scan.yaml new file mode 100644 index 00000000..a8437e07 --- /dev/null +++ b/configs/mri-recon/mridata-3dfse-knee/unet/ssdu/SSDU_UNET_13_Scan.yaml @@ -0,0 +1,36 @@ +# Inherit fields from the unet.yaml config. +_BASE_: "../../templates/unet.yaml" +DATALOADER: + SUBSAMPLE_TRAIN: + # 1 supervised and 13 unsupervised scans for training. + NUM_TOTAL: 14 + NUM_UNDERSAMPLED: 13 +# Specify an output directory that is unique for this experiment. +OUTPUT_DIR: results://vortex-rm/mridata_knee_3dfse/unet/ssdu/ssdu_unet_13_scan +DESCRIPTION: + # Use f-strings to specify reading values in braces ({}) the config file. + # This can only be done for string fields. + BRIEF: f"SSDU U-Net {AUG_TRAIN.UNDERSAMPLE.ACCELERATIONS}x mridata - {MODEL.SSDU.MASKER.PARAMS.kind}/loss={MODEL.RECON_LOSS.NAME}" + EXP_NAME: mridata_knee_3dfse/unet/ssdu + TAGS: + - baseline + - ssdu + - 16x + - mridata_knee_3dfse + - unet +MODEL: + META_ARCHITECTURE: SSDUModel + RECON_LOSS: + NAME: k_l1 + RENORMALIZE_DATA: false + SSDU: + # For UNET, the META_ARCHITECTURE of SSDUModel is UnetModel. + META_ARCHITECTURE: UnetModel + MASKER: + # These parameters were taken from the VORTEX/Noise2Recon papers. + PARAMS: + kind: uniform + p: 1.0 + rhos: 0.4 # Default value in SSDU code + per_example: true +VERSION: 1 \ No newline at end of file diff --git a/configs/mri-recon/mridata-3dfse-knee/unet/supervised/Supervised_Aug_UNET_14_Scan.yaml b/configs/mri-recon/mridata-3dfse-knee/unet/supervised/Supervised_Aug_UNET_14_Scan.yaml new file mode 100644 index 00000000..32b6a9ac --- /dev/null +++ b/configs/mri-recon/mridata-3dfse-knee/unet/supervised/Supervised_Aug_UNET_14_Scan.yaml @@ -0,0 +1,29 @@ +# Inherit fields from the unet.yaml config. +_BASE_: "../../templates/unet_aug.yaml" +# DATALOADER has 14 supervised scans +DATALOADER: + SUBSAMPLE_TRAIN: + NUM_TOTAL: 14 + NUM_UNDERSAMPLED: 0 +# Specify an output directory that is unique for this experiment. +OUTPUT_DIR: results://vortex-rm/mridata_knee_3dfse/unet/supervised/supervised_aug_unet_14_scan +DESCRIPTION: + # Use f-strings to specify reading values in braces ({}) the config file. + # This can only be done for string fields. + BRIEF: f"Supervised Aug U-Net 14 Scan {AUG_TRAIN.UNDERSAMPLE.ACCELERATIONS}x mridata - loss={MODEL.RECON_LOSS.NAME}" + EXP_NAME: mridata_knee_3dfse/unet/supervised_aug_unet_14 + TAGS: + - baseline + - supervised + - 14_scan + - 16x + - mridata_knee_3dfse + - unet +# For no motion aug, A2R and Consistency was removed. +# For UNET, kept META_ARCHITECTURE as UnetModel +MODEL: + META_ARCHITECTURE: UnetModel + RECON_LOSS: + NAME: l1 + RENORMALIZE_DATA: false +VERSION: 1 \ No newline at end of file diff --git a/configs/mri-recon/mridata-3dfse-knee/unet/supervised/Supervised_Aug_UNET_1_Scan.yaml b/configs/mri-recon/mridata-3dfse-knee/unet/supervised/Supervised_Aug_UNET_1_Scan.yaml new file mode 100644 index 00000000..7e69288d --- /dev/null +++ b/configs/mri-recon/mridata-3dfse-knee/unet/supervised/Supervised_Aug_UNET_1_Scan.yaml @@ -0,0 +1,28 @@ +# Inherit fields from the unet.yaml config. +_BASE_: "../../templates/unet_aug.yaml" +# DATALOADER has only 1 supervised scan +DATALOADER: + SUBSAMPLE_TRAIN: + NUM_TOTAL: 1 + NUM_UNDERSAMPLED: 0 +# Specify an output directory that is unique for this experiment. +OUTPUT_DIR: results://vortex-rm/mridata_knee_3dfse/unet/supervised/supervised_aug_unet_1_scan +DESCRIPTION: + # Use f-strings to specify reading values in braces ({}) the config file. + # This can only be done for string fields. + BRIEF: f"Supervised Aug U-Net 1 Scan {AUG_TRAIN.UNDERSAMPLE.ACCELERATIONS}x mridata - loss={MODEL.RECON_LOSS.NAME}" + EXP_NAME: mridata_knee_3dfse/unet/supervised_aug_unet_1 + TAGS: + - baseline + - supervised + - one_scan + - 16x + - mridata_knee_3dfse + - unet +# For UNET, kept META_ARCHITECTURE as UnetModel +MODEL: + META_ARCHITECTURE: UnetModel + RECON_LOSS: + NAME: l1 + RENORMALIZE_DATA: false +VERSION: 1 diff --git a/configs/mri-recon/mridata-3dfse-knee/unet/supervised/Supervised_UNET_14_Scan.yaml b/configs/mri-recon/mridata-3dfse-knee/unet/supervised/Supervised_UNET_14_Scan.yaml new file mode 100644 index 00000000..2c6b9d63 --- /dev/null +++ b/configs/mri-recon/mridata-3dfse-knee/unet/supervised/Supervised_UNET_14_Scan.yaml @@ -0,0 +1,29 @@ +# Inherit fields from the unet.yaml config. +_BASE_: "../../templates/unet.yaml" +# DATALOADER has 14 supervised scans +DATALOADER: + SUBSAMPLE_TRAIN: + NUM_TOTAL: 14 + NUM_UNDERSAMPLED: 0 +# Specify an output directory that is unique for this experiment. +OUTPUT_DIR: results://vortex-rm/mridata_knee_3dfse/unet/supervised/supervised_unet_14_scan +DESCRIPTION: + # Use f-strings to specify reading values in braces ({}) the config file. + # This can only be done for string fields. + BRIEF: f"Supervised U-Net 14 Scan {AUG_TRAIN.UNDERSAMPLE.ACCELERATIONS}x mridata - loss={MODEL.RECON_LOSS.NAME}" + EXP_NAME: mridata_knee_3dfse/unet/supervised_unet_14 + TAGS: + - baseline + - supervised + - 14_scan + - 16x + - mridata_knee_3dfse + - unet +# For no motion aug, A2R and Consistency was removed. +# For UNET, kept META_ARCHITECTURE as UnetModel +MODEL: + META_ARCHITECTURE: UnetModel + RECON_LOSS: + NAME: l1 + RENORMALIZE_DATA: false +VERSION: 1 \ No newline at end of file diff --git a/configs/mri-recon/mridata-3dfse-knee/unet/supervised/Supervised_UNET_1_Scan.yaml b/configs/mri-recon/mridata-3dfse-knee/unet/supervised/Supervised_UNET_1_Scan.yaml new file mode 100644 index 00000000..4858fbbf --- /dev/null +++ b/configs/mri-recon/mridata-3dfse-knee/unet/supervised/Supervised_UNET_1_Scan.yaml @@ -0,0 +1,29 @@ +# Inherit fields from the unet.yaml config. +_BASE_: "../../templates/unet.yaml" +# DATALOADER has only 1 supervised scan +DATALOADER: + SUBSAMPLE_TRAIN: + NUM_TOTAL: 1 + NUM_UNDERSAMPLED: 0 +# Specify an output directory that is unique for this experiment. +OUTPUT_DIR: results://vortex-rm/mridata_knee_3dfse/unet/supervised/supervised_unet_1_scan +DESCRIPTION: + # Use f-strings to specify reading values in braces ({}) the config file. + # This can only be done for string fields. + BRIEF: f"Supervised U-Net 1 Scan {AUG_TRAIN.UNDERSAMPLE.ACCELERATIONS}x mridata - loss={MODEL.RECON_LOSS.NAME}" + EXP_NAME: mridata_knee_3dfse/unet/supervised_unet_1 + TAGS: + - baseline + - supervised + - one_scan + - 16x + - mridata_knee_3dfse + - unet +# For no motion aug, A2R and Consistency was removed. +# For UNET, kept META_ARCHITECTURE as UnetModel +MODEL: + META_ARCHITECTURE: UnetModel + RECON_LOSS: + NAME: l1 + RENORMALIZE_DATA: false +VERSION: 1 \ No newline at end of file diff --git a/configs/mri-recon/mridata-3dfse-knee/unrolled/ssdu/SSDU_Aug_Unrolled_13_Scan.yaml b/configs/mri-recon/mridata-3dfse-knee/unrolled/ssdu/SSDU_Aug_Unrolled_13_Scan.yaml new file mode 100644 index 00000000..a1200ee7 --- /dev/null +++ b/configs/mri-recon/mridata-3dfse-knee/unrolled/ssdu/SSDU_Aug_Unrolled_13_Scan.yaml @@ -0,0 +1,49 @@ +# Inherit fields from the unrolled.yaml config. +_BASE_: "../../templates/unrolled.yaml" +DATALOADER: + SUBSAMPLE_TRAIN: + # 1 supervised and 13 unsupervised scans for training + NUM_TOTAL: 14 + NUM_UNDERSAMPLED: 13 +# Specify an output directory that is unique for this experiment. +OUTPUT_DIR: results://vortex-rm/mridata_knee_3dfse/unrolled/ssdu/ssdu_aug_unrolled_13_scan +DESCRIPTION: + # Use f-strings to specify reading values in braces ({}) the config file. + # This can only be done for string fields. + BRIEF: f"SSDU Aug Unrolled {AUG_TRAIN.UNDERSAMPLE.ACCELERATIONS}x mridata - {MODEL.SSDU.MASKER.PARAMS.kind}/loss={MODEL.RECON_LOSS.NAME}" + EXP_NAME: mridata_knee_3dfse/unrolled/SSDU_Aug + TAGS: + - baseline + - ssdu + - 16x + - mridata_knee_3dfse + - unrolled +# For motion aug, A2R and Consistency were kept with std_dev range of 0.2 to 0.5. +# For Unrolled, the META_ARCHITECTURE of SSDUModel is GeneralizedUnrolledCNN +MODEL: + META_ARCHITECTURE: SSDUModel + RECON_LOSS: + NAME: k_l1 + RENORMALIZE_DATA: false + SSDU: + META_ARCHITECTURE: GeneralizedUnrolledCNN + MASKER: + # These parameters were taken from the VORTEX/Noise2Recon papers. + PARAMS: + kind: uniform + rhos: 0.4 # Default value in SSDU code + p: 1.0 + per_example: true + AUGMENTOR: + TRANSFORMS: + - name: RandomMRIMotion + p: 0.2 + std_devs: + - 0.2 + - 0.5 +AUG_TRAIN: + MRI_RECON: + TRANSFORMS: [] +VERSION: 1 + + diff --git a/configs/mri-recon/mridata-3dfse-knee/unrolled/ssdu/SSDU_Unrolled_13_Scan.yaml b/configs/mri-recon/mridata-3dfse-knee/unrolled/ssdu/SSDU_Unrolled_13_Scan.yaml new file mode 100644 index 00000000..18f096fa --- /dev/null +++ b/configs/mri-recon/mridata-3dfse-knee/unrolled/ssdu/SSDU_Unrolled_13_Scan.yaml @@ -0,0 +1,33 @@ +# Inherit fields from the unrolled.yaml config. +_BASE_: "../../templates/unrolled.yaml" +DATALOADER: + # DATALOADER has 1 supervised and 13 unsupervised + SUBSAMPLE_TRAIN: + NUM_TOTAL: 14 + NUM_UNDERSAMPLED: 13 +OUTPUT_DIR: results://vortex-rm/mridata_knee_3dfse/unrolled/ssdu/ssdu_unrolled_13_scan +DESCRIPTION: + BRIEF: f"SSDU Unrolled {AUG_TRAIN.UNDERSAMPLE.ACCELERATIONS}x mridata - {MODEL.SSDU.MASKER.PARAMS.kind}/loss={MODEL.RECON_LOSS.NAME}" + EXP_NAME: mridata_knee_3dfse/unrolled/SSDU + TAGS: + - baseline + - ssdu + - 16x + - mridata_knee_3dfse + - unrolled +# For no motion aug, A2R and Consistency were removed. +# For Unrolled, the META_ARCHITECTURE of SSDUModel is GeneralizedUnrolledCNN +MODEL: + META_ARCHITECTURE: SSDUModel + RECON_LOSS: + NAME: k_l1 + RENORMALIZE_DATA: false + SSDU: + META_ARCHITECTURE: GeneralizedUnrolledCNN + MASKER: + PARAMS: + kind: uniform + p: 1.0 + rhos: 0.4 # Default value in SSDU code + per_example: true +VERSION: 1 \ No newline at end of file diff --git a/configs/mri-recon/mridata-3dfse-knee/unrolled/supervised/Supervised_Aug_Unrolled_14_Scan.yaml b/configs/mri-recon/mridata-3dfse-knee/unrolled/supervised/Supervised_Aug_Unrolled_14_Scan.yaml new file mode 100644 index 00000000..c3eacb0b --- /dev/null +++ b/configs/mri-recon/mridata-3dfse-knee/unrolled/supervised/Supervised_Aug_Unrolled_14_Scan.yaml @@ -0,0 +1,26 @@ +# Inherit fields from the unrolled.yaml config. +_BASE_: "../../templates/unrolled_aug.yaml" +# DATALOADER has 14 supervised scan +DATALOADER: + SUBSAMPLE_TRAIN: + NUM_TOTAL: 14 + NUM_UNDERSAMPLED: 0 +OUTPUT_DIR: results://vortex-rm/mridata_knee_3dfse/unrolled/supervised/supervised_aug_unrolled_14_scan +DESCRIPTION: + BRIEF: f"Supervised Aug Unrolled 14 Scan {AUG_TRAIN.UNDERSAMPLE.ACCELERATIONS}x mridata - loss={MODEL.RECON_LOSS.NAME}" + EXP_NAME: mridata_knee_3dfse/unrolled/supervised_aug_14_scan + TAGS: + - baseline + - supervised + - 14_scan + - 16x + - mridata_knee_3dfse + - unrolled +# For no motion aug, A2R and Consistency was removed. +# For Unrolled, made META_ARCHITECTURE GeneralizedUnrolledCNN +MODEL: + META_ARCHITECTURE: GeneralizedUnrolledCNN + RECON_LOSS: + NAME: l1 + RENORMALIZE_DATA: false +VERSION: 1 \ No newline at end of file diff --git a/configs/mri-recon/mridata-3dfse-knee/unrolled/supervised/Supervised_Aug_Unrolled_1_Scan.yaml b/configs/mri-recon/mridata-3dfse-knee/unrolled/supervised/Supervised_Aug_Unrolled_1_Scan.yaml new file mode 100644 index 00000000..1593480b --- /dev/null +++ b/configs/mri-recon/mridata-3dfse-knee/unrolled/supervised/Supervised_Aug_Unrolled_1_Scan.yaml @@ -0,0 +1,25 @@ +# Inherit fields from the unrolled.yaml config. +_BASE_: "../../templates/unrolled_aug.yaml" +# DATALOADER has only 1 supervised scan +DATALOADER: + SUBSAMPLE_TRAIN: + NUM_TOTAL: 1 + NUM_UNDERSAMPLED: 0 +OUTPUT_DIR: results://vortex-rm/mridata_knee_3dfse/unrolled/supervised/supervised_aug_unrolled_1_scan +DESCRIPTION: + BRIEF: f"Supervised Aug Unrolled 1 Scan {AUG_TRAIN.UNDERSAMPLE.ACCELERATIONS}x mridata - loss={MODEL.RECON_LOSS.NAME}" + EXP_NAME: mridata_knee_3dfse/unrolled/supervised_aug_1_scan + TAGS: + - baseline + - supervised + - one_scan + - 16x + - mridata_knee_3dfse + - unrolled +# For Unrolled, made META_ARCHITECTURE GeneralizedUnrolledCNN +MODEL: + META_ARCHITECTURE: GeneralizedUnrolledCNN + RECON_LOSS: + NAME: l1 + RENORMALIZE_DATA: false +VERSION: 1 diff --git a/configs/mri-recon/mridata-3dfse-knee/unrolled/supervised/Supervised_Unrolled_14_Scan.yaml b/configs/mri-recon/mridata-3dfse-knee/unrolled/supervised/Supervised_Unrolled_14_Scan.yaml new file mode 100644 index 00000000..0320d7fe --- /dev/null +++ b/configs/mri-recon/mridata-3dfse-knee/unrolled/supervised/Supervised_Unrolled_14_Scan.yaml @@ -0,0 +1,26 @@ +# Inherit fields from the unrolled.yaml config. +_BASE_: "../../templates/unrolled.yaml" +# DATALOADER has 14 supervised scan +DATALOADER: + SUBSAMPLE_TRAIN: + NUM_TOTAL: 14 + NUM_UNDERSAMPLED: 0 +OUTPUT_DIR: results://vortex-rm/mridata_knee_3dfse/unrolled/supervised/supervised_unrolled_14_scan +DESCRIPTION: + BRIEF: f"Supervised Unrolled 14 Scan {AUG_TRAIN.UNDERSAMPLE.ACCELERATIONS}x mridata - loss={MODEL.RECON_LOSS.NAME}" + EXP_NAME: mridata_knee_3dfse/unrolled/supervised_14_scan + TAGS: + - baseline + - supervised + - 14_scan + - 16x + - mridata_knee_3dfse + - unrolled +# For no motion aug, A2R and Consistency was removed. +# For Unrolled, made META_ARCHITECTURE GeneralizedUnrolledCNN +MODEL: + META_ARCHITECTURE: GeneralizedUnrolledCNN + RECON_LOSS: + NAME: l1 + RENORMALIZE_DATA: false +VERSION: 1 diff --git a/configs/mri-recon/mridata-3dfse-knee/unrolled/supervised/Supervised_Unrolled_1_Scan.yaml b/configs/mri-recon/mridata-3dfse-knee/unrolled/supervised/Supervised_Unrolled_1_Scan.yaml new file mode 100644 index 00000000..581afe12 --- /dev/null +++ b/configs/mri-recon/mridata-3dfse-knee/unrolled/supervised/Supervised_Unrolled_1_Scan.yaml @@ -0,0 +1,26 @@ +# Inherit fields from the unrolled.yaml config. +_BASE_: "../../templates/unrolled.yaml" +# DATALOADER has only 1 supervised scan +DATALOADER: + SUBSAMPLE_TRAIN: + NUM_TOTAL: 1 + NUM_UNDERSAMPLED: 0 +OUTPUT_DIR: results://vortex-rm/mridata_knee_3dfse/unrolled/supervised/supervised_unrolled_1_scan +DESCRIPTION: + BRIEF: f"Supervised Unrolled 1 Scan {AUG_TRAIN.UNDERSAMPLE.ACCELERATIONS}x mridata - loss={MODEL.RECON_LOSS.NAME}" + EXP_NAME: mridata_knee_3dfse/unrolled/supervised_1_scan + TAGS: + - baseline + - supervised + - one_scan + - 16x + - mridata_knee_3dfse + - unrolled +# For no motion aug, A2R and Consistency was removed. +# For Unrolled, made META_ARCHITECTURE GeneralizedUnrolledCNN +MODEL: + META_ARCHITECTURE: GeneralizedUnrolledCNN + RECON_LOSS: + NAME: l1 + RENORMALIZE_DATA: false +VERSION: 1 \ No newline at end of file diff --git a/get_available_gpu.py b/get_available_gpu.py new file mode 100644 index 00000000..fbd5d09b --- /dev/null +++ b/get_available_gpu.py @@ -0,0 +1,4 @@ +from meddlr.utils.env import get_available_gpus + +if len(get_available_gpus()) > 0: + print(get_available_gpus()[0]) diff --git a/meddlr/transforms/base/__init__.py b/meddlr/transforms/base/__init__.py index 5c1d75ba..ddf4ab52 100644 --- a/meddlr/transforms/base/__init__.py +++ b/meddlr/transforms/base/__init__.py @@ -1,5 +1,6 @@ from meddlr.transforms.base.mask import KspaceMaskTransform # noqa from meddlr.transforms.base.motion import MRIMotionTransform # noqa +from meddlr.transforms.base.motion import MRIMultiShotMotion # noqa from meddlr.transforms.base.noise import NoiseTransform # noqa from meddlr.transforms.base.spatial import ( # noqa AffineTransform, diff --git a/meddlr/transforms/base/motion.py b/meddlr/transforms/base/motion.py index b3dff762..d134b21b 100644 --- a/meddlr/transforms/base/motion.py +++ b/meddlr/transforms/base/motion.py @@ -5,6 +5,7 @@ import meddlr.transforms.functional as stf from meddlr.transforms.build import TRANSFORM_REGISTRY from meddlr.transforms.transform import Transform +from meddlr.transforms.transform_gen import TransformGen @TRANSFORM_REGISTRY.register() @@ -79,3 +80,95 @@ def apply_kspace(self, kspace, channel_first: bool = True) -> torch.Tensor: def _eq_attrs(self) -> Tuple[str]: return ("std_dev", "seed", "_generator_state") + + +@TRANSFORM_REGISTRY.register() +class MRIMultiShotMotion(Transform): + """A model that simulates motion artifacts in multi-shot MRI. + + To simulate motion, the coil-combined image is augmented with random + affine transformations. The number of augmentations corresponds to the + number of echo trains (i.e. number of shots) used during acquisition. + The multi-coil kspace for each of these augmented versions is combined based + on the trajectory. + """ + + def __init__( + self, + tfm_gens: Sequence[TransformGen], + trajectory: torch.Tensor, + seed: Optional[int] = None, + generator: Optional[torch.Generator] = None, + ): + """ + Args: + tfm_gens (Sequence[TransformGen]): The transform generators to use. + These will be seeded prior to use. + trajectory (torch.Tensor): The trajectory to use for the multi-shot + motion simulation. + seed (int, optional): The seed to use for the random number generator. + generator (torch.Generator, optional): The random number generator to use. + Must be specified if ``seed`` is not set. + """ + self.tfm_gens = tfm_gens + self.trajectory = trajectory + self.seed = seed + + gen_state = None + if generator is not None: + gen_state = generator.get_state() + self._generator_state = gen_state + + def _generator(self, data: torch.Tensor): + seed = self.seed + + g = torch.Generator(device=data.device) + if seed is None: + g.set_state(self._generator_state) + else: + g = g.manual_seed(seed) + return g + + def _apply_x(self, x: torch.Tensor, *, maps: torch.Tensor, channel_first: bool, xtype: str): + g = self._generator(x) + seeds = torch.randint(0, 2**32, (len(self.tfm_gens),), device=g.device, generator=g) + seeds = [seed.cpu().item() for seed in seeds] + for tfm_gen, seed in zip(self.tfm_gens, seeds): + tfm_gen.seed(seed) + + return stf.add_affine_motion( + x, + transform_gens=self.tfm_gens, + trajectory=self.trajectory, + maps=maps, + is_batch=True, + channels_first=channel_first, + xtype=xtype, + ) + + def apply_kspace( + self, + kspace: torch.Tensor, + *, + maps: torch.Tensor = None, + channel_first: bool = True, + ) -> torch.Tensor: + """Performs motion corruption on kspace image. + + Args: + kspace (torch.Tensor): The complex tensor. Shape ``(N, #coils, Y, X, [2])``. + maps (torch.Tensor, optional): The sensitivity maps. + Shape ``(N, #coils, #maps, Y, X, [2])``. + + Returns: + torch.Tensor: The motion corrupted kspace. + """ + return self._apply_x(kspace, maps=maps, channel_first=channel_first, xtype="kspace") + + def apply_image( + self, image: torch.Tensor, *, maps: torch.Tensor = None, channel_first: bool = True + ) -> torch.Tensor: + return self._apply_x(image, maps=maps, channel_first=channel_first, xtype="image") + + def _eq_attrs(self) -> Tuple[str]: + return ("tfm_gens", "trajectory", "seed", "_generator_state") diff --git a/meddlr/transforms/gen/motion.py b/meddlr/transforms/gen/motion.py index 1db6d962..23fbe581 100644 --- a/meddlr/transforms/gen/motion.py +++ b/meddlr/transforms/gen/motion.py @@ -2,7 +2,7 @@ import torch -from meddlr.transforms.base.motion import MRIMotionTransform +from meddlr.transforms.base.motion import MRIMotionTransform, MRIMultiShotMotion from meddlr.transforms.build import TRANSFORM_REGISTRY from meddlr.transforms.transform import NoOpTransform from meddlr.transforms.transform_gen import TransformGen @@ -53,3 +53,47 @@ def get_transform(self, input: torch.Tensor): if gen is None or gen.device != input.device: gen = torch.Generator(device=input.device).manual_seed(int(self._rand() * 1e10)) return MRIMotionTransform(std_dev=std_dev, generator=gen) + + +@TRANSFORM_REGISTRY.register() +class RandomMRIMultiShotMotion(TransformGen): + """A model that corrupts kspace inputs with motion. + Motion is a common artifact experienced during the MR imaging forward problem. + When a patient moves, the recorded (expected) location of the kspace sample is + different than the actual location where the kspace sample that was acquired. + This module is responsible for simulating different motion artifacts. + Args: + seed (int, optional): The fixed seed. + Attributes: + generator (torch.Generator): The generator that should be used for all + random logic in this class. + Things to consider: + 1. What other information is relevant for inducing motion corruption? + This could include: + - ``traj``: The scan trajectory + - ``etl``: The echo train length - how many readouts per shot. + - ``num_shots``: Number of shots. + 2. What would a simple translational motion model look? + Note: + We do not store this as a module or else it would be saved to the model + definition, which we dont want. + """ + + _base_transform = MRIMultiShotMotion + + def __init__( + self, + seq_tfm_gens: Union[Sequence[TransformGen], Sequence[Sequence[TransformGen]]], + p: float = 0.0, + ): + super().__init__(params={"seq_tfm_gens": seq_tfm_gens}, p=p) + + def get_transform(self, input: torch.Tensor): + params = self._get_param_values(use_schedulers=True) + if self._rand() >= params["p"]: + return NoOpTransform() + tfm_gens = self._rand_range(*params["seq_std_devs"]) + gen = self._generator + if gen is None or gen.device != input.device: + gen = torch.Generator(device=input.device).manual_seed(int(self._rand() * 1e10)) + return MRIMultiShotMotion(tfm_gens=tfm_gens, generator=gen) diff --git a/motion_eval_unet_ssdu_vortex.sh b/motion_eval_unet_ssdu_vortex.sh new file mode 100755 index 00000000..fb80e8ce --- /dev/null +++ b/motion_eval_unet_ssdu_vortex.sh @@ -0,0 +1,20 @@ +#!/bin/bash +export MEDDLR_DATASETS_DIR=/mnt/dense/ozt/dl-ss-recon/data +export MEDDLR_CACHE_DIR=/mnt/dense/deepro/cache +export MEDDLR_RESULTS_DIR=/mnt/dense/deepro/results/Summer_2022_2023/motion_eval/unet_official/ssdu/wandb + +gpu=$(python get_available_gpu.py) +echo "The first available gpu is $gpu" + +# Run normal unet yaml +# took off --debug to allow wb to work. +WANDB__SERVICE_WAIT=300 CUDA_VISIBLE_DEVICES=$gpu$ python tools/eval_net.py --config-file /mnt/dense/deepro/results/Summer_2022_2023/unet_official/ssdu/wandb/vortex-rm/mridata_knee_3dfse/unet/ssdu/ssdu_unet_13_scan/version_001/config.yaml --metric val_psnr_mag --save-scans --angle 30 --translation 0.1 --nshots 5 --trajectory interleaved --mri_dim 2 --motion standard + + +# gpu=$(python get_available_gpu.py) +# echo "The first available gpu for VORTEX is $gpu" + +# # Run vortex unet yaml +# # took off --debug to allow wb to work. +WANDB__SERVICE_WAIT=300 CUDA_VISIBLE_DEVICES=$gpu$ python tools/eval_net.py --config-file /mnt/dense/deepro/results/Summer_2022_2023/unet_official/ssdu/wandb/vortex-rm/mridata_knee_3dfse/unet/ssdu/ssdu_aug_unet_13_scan/version_001/config.yaml --metric val_psnr_mag --save-scans --angle 30 --translation 0.1 --nshots 5 --trajectory interleaved --mri_dim 2 --motion standard + diff --git a/motion_eval_unet_supervised_vortex.sh b/motion_eval_unet_supervised_vortex.sh new file mode 100755 index 00000000..8138bf8e --- /dev/null +++ b/motion_eval_unet_supervised_vortex.sh @@ -0,0 +1,28 @@ +#!/bin/bash +export MEDDLR_DATASETS_DIR=/mnt/dense/ozt/dl-ss-recon/data +export MEDDLR_CACHE_DIR=/mnt/dense/deepro/cache +export MEDDLR_RESULTS_DIR=/mnt/dense/deepro/results/Summer_2022_2023/motion_eval/unet_official/supervised/wandb + +gpu=$(python get_available_gpu.py) +echo "The first available gpu is $gpu" + +# took off --debug to allow wb to work. +WANDB__SERVICE_WAIT=300 CUDA_VISIBLE_DEVICES=$gpu$ python tools/eval_net.py --config-file /mnt/dense/deepro/results/Summer_2022_2023/unet_official/supervised/wandb/vortex-rm/mridata_knee_3dfse/unet/supervised/supervised_unet_1_scan/version_002/config.yaml --metric val_psnr_mag --save-scans --angle 30 --translation 0.1 --nshots 5 --trajectory interleaved --mri_dim 2 --motion standard + +gpu=$(python get_available_gpu.py) +echo "The first available gpu is $gpu" + +# took off --debug to allow wb to work. +WANDB__SERVICE_WAIT=300 CUDA_VISIBLE_DEVICES=$gpu$ python tools/eval_net.py --config-file /mnt/dense/deepro/results/Summer_2022_2023/unet_official/supervised/wandb/vortex-rm/mridata_knee_3dfse/unet/supervised/supervised_unet_14_scan/version_002/config.yaml --metric val_psnr_mag --save-scans --angle 30 --translation 0.1 --nshots 5 --trajectory interleaved --mri_dim 2 --motion standard + +gpu=$(python get_available_gpu.py) +echo "The first available gpu for VORTEX is $gpu" + +# # took off --debug to allow wb to work. +WANDB__SERVICE_WAIT=300 CUDA_VISIBLE_DEVICES=$gpu$ python tools/eval_net.py --config-file /mnt/dense/deepro/results/Summer_2022_2023/unet_official/supervised/wandb/vortex-rm/mridata_knee_3dfse/unet/supervised/supervised_aug_unet_1_scan/version_002/config.yaml --metric val_psnr_mag --save-scans --angle 30 --translation 0.1 --nshots 5 --trajectory interleaved --mri_dim 2 --motion standard + +gpu=$(python get_available_gpu.py) +echo "The first available gpu for VORTEX is $gpu" + +# # took off --debug to allow wb to work. +WANDB__SERVICE_WAIT=300 CUDA_VISIBLE_DEVICES=$gpu$ python tools/eval_net.py --config-file /mnt/dense/deepro/results/Summer_2022_2023/unet_official/supervised/wandb/vortex-rm/mridata_knee_3dfse/unet/supervised/supervised_aug_unet_14_scan/version_002/config.yaml --metric val_psnr_mag --save-scans --angle 30 --translation 0.1 --nshots 5 --trajectory interleaved --mri_dim 2 --motion standard diff --git a/motion_eval_unrolled_ssdu_vortex.sh b/motion_eval_unrolled_ssdu_vortex.sh new file mode 100755 index 00000000..af89ee07 --- /dev/null +++ b/motion_eval_unrolled_ssdu_vortex.sh @@ -0,0 +1,18 @@ +#!/bin/bash +export MEDDLR_DATASETS_DIR=/mnt/dense/ozt/dl-ss-recon/data +export MEDDLR_CACHE_DIR=/mnt/dense/deepro/cache +export MEDDLR_RESULTS_DIR=/mnt/dense/deepro/results/Summer_2022_2023/motion_eval/unrolled_official/ssdu/wandb + +gpu=$(python get_available_gpu.py) +echo "The first available gpu is $gpu" + +# took off --debug to allow wb to work. +WANDB__SERVICE_WAIT=300 CUDA_VISIBLE_DEVICES=$gpu$ python tools/eval_net.py --config-file /mnt/dense/deepro/results/Summer_2022_2023/unrolled_official/ssdu/wandb/vortex-rm/mridata_knee_3dfse/unrolled/ssdu/ssdu_unrolled_13_scan/version_001/config.yaml --metric val_psnr_mag --save-scans --angle 30 --translation 0.1 --nshots 5 --trajectory interleaved --mri_dim 2 --motion standard + + +gpu=$(python get_available_gpu.py) +echo "The first available gpu for VORTEX is $gpu" + +# # took off --debug to allow wb to work. +WANDB__SERVICE_WAIT=300 CUDA_VISIBLE_DEVICES=$gpu$ python tools/eval_net.py --config-file /mnt/dense/deepro/results/Summer_2022_2023/unrolled_official/ssdu/wandb/vortex-rm/mridata_knee_3dfse/unrolled/ssdu/ssdu_aug_unrolled_13_scan/version_001/config.yaml --metric val_psnr_mag --save-scans --angle 30 --translation 0.1 --nshots 5 --trajectory interleaved --mri_dim 2 --motion standard + diff --git a/motion_eval_unrolled_supervised_vortex.sh b/motion_eval_unrolled_supervised_vortex.sh new file mode 100755 index 00000000..2ca1fa0f --- /dev/null +++ b/motion_eval_unrolled_supervised_vortex.sh @@ -0,0 +1,29 @@ +#!/bin/bash +export MEDDLR_DATASETS_DIR=/mnt/dense/ozt/dl-ss-recon/data +export MEDDLR_CACHE_DIR=/mnt/dense/deepro/cache +export MEDDLR_RESULTS_DIR=/mnt/dense/deepro/results/Summer_2022_2023/motion_eval/unrolled_official/supervised/wandb + +gpu=$(python get_available_gpu.py) +echo "The first available gpu is $gpu" + +# took off --debug to allow wb to work. +WANDB__SERVICE_WAIT=300 CUDA_VISIBLE_DEVICES=$gpu$ python tools/eval_net.py --config-file /mnt/dense/deepro/results/Summer_2022_2023/unrolled_official/supervised/wandb/vortex-rm/mridata_knee_3dfse/unrolled/supervised/supervised_unrolled_1_scan/version_001/config.yaml --metric val_psnr_mag --save-scans --angle 30 --translation 0.1 --nshots 5 --trajectory interleaved --mri_dim 2 --motion standard + +gpu=$(python get_available_gpu.py) +echo "The first available gpu is $gpu" + +# took off --debug to allow wb to work. +WANDB__SERVICE_WAIT=300 CUDA_VISIBLE_DEVICES=$gpu$ python tools/eval_net.py --config-file /mnt/dense/deepro/results/Summer_2022_2023/unrolled_official/supervised/wandb/vortex-rm/mridata_knee_3dfse/unrolled/supervised/supervised_unrolled_14_scan/version_001/config.yaml --metric val_psnr_mag --save-scans --angle 30 --translation 0.1 --nshots 5 --trajectory interleaved --mri_dim 2 --motion standard + +gpu=$(python get_available_gpu.py) +echo "The first available gpu for VORTEX is $gpu" + +# # took off --debug to allow wb to work. +WANDB__SERVICE_WAIT=300 CUDA_VISIBLE_DEVICES=$gpu$ python tools/eval_net.py --config-file /mnt/dense/deepro/results/Summer_2022_2023/unrolled_official/supervised/wandb/vortex-rm/mridata_knee_3dfse/unrolled/supervised/supervised_aug_unrolled_1_scan/version_001/config.yaml --metric val_psnr_mag --save-scans --angle 30 --translation 0.1 --nshots 5 --trajectory interleaved --mri_dim 2 --motion standard + +gpu=$(python get_available_gpu.py) +echo "The first available gpu for VORTEX is $gpu" + +# # took off --debug to allow wb to work. +WANDB__SERVICE_WAIT=300 CUDA_VISIBLE_DEVICES=$gpu$ python tools/eval_net.py --config-file /mnt/dense/deepro/results/Summer_2022_2023/unrolled_official/supervised/wandb/vortex-rm/mridata_knee_3dfse/unrolled/supervised/supervised_aug_unrolled_14_scan/version_001/config.yaml --metric val_psnr_mag --save-scans --angle 30 --translation 0.1 --nshots 5 --trajectory interleaved --mri_dim 2 --motion standard + diff --git a/run_unet_norm_vortex.sh b/run_unet_norm_vortex.sh new file mode 100755 index 00000000..59910732 --- /dev/null +++ b/run_unet_norm_vortex.sh @@ -0,0 +1,18 @@ +#!/bin/bash +export MEDDLR_DATASETS_DIR=/mnt/dense/ozt/dl-ss-recon/data +export MEDDLR_CACHE_DIR=/mnt/dense/deepro/cache +export MEDDLR_RESULTS_DIR=/mnt/dense/deepro/results/Summer_2022_2023/wandb + +gpu=$(python get_available_gpu.py) +echo "The first available gpu is $gpu" + +# Run normal unet yaml +# took off --debug to allow wb to work. +WANDB__SERVICE_WAIT=300 CUDA_VISIBLE_DEVICES=$gpu$ python tools/train_net.py --config-file configs/mri-recon/mridata-3dfse-knee/unet.yaml --auto-version + +gpu=$(python get_available_gpu.py) +echo "The first available gpu for VORTEX is $gpu" + +# Run vortex unet yaml +# took off --debug to allow wb to work. +WANDB__SERVICE_WAIT=300 CUDA_VISIBLE_DEVICES=$gpu$ python tools/train_net.py --config-file configs/mri-recon/mridata-3dfse-knee/vortex_rm_unet.yaml --auto-version diff --git a/run_unet_supervised_vortex.sh b/run_unet_supervised_vortex.sh new file mode 100755 index 00000000..57b920ff --- /dev/null +++ b/run_unet_supervised_vortex.sh @@ -0,0 +1,33 @@ +#!/bin/bash + +export MEDDLR_DATASETS_DIR=/mnt/dense/ozt/dl-ss-recon/data +export MEDDLR_CACHE_DIR=/mnt/dense/deepro/cache +export MEDDLR_RESULTS_DIR=/mnt/dense/deepro/results/Summer_2022_2023/unet_official/supervised/wandb + +gpu=$(python get_available_gpu.py) +echo "The first available gpu is $gpu" + +# Run normal unet yaml +# took off --debug to allow wb to work. +WANDB__SERVICE_WAIT=300 CUDA_VISIBLE_DEVICES=$gpu$ python tools/train_net.py --config-file configs/mri-recon/mridata-3dfse-knee/unet/supervised/Supervised_UNET_1_Scan.yaml --auto-version + +gpu=$(python get_available_gpu.py) +echo "The first available gpu is $gpu" + +# Run normal unet yaml +# took off --debug to allow wb to work. +WANDB__SERVICE_WAIT=300 CUDA_VISIBLE_DEVICES=$gpu$ python tools/train_net.py --config-file configs/mri-recon/mridata-3dfse-knee/unet/supervised/Supervised_UNET_14_Scan.yaml --auto-version + +gpu=$(python get_available_gpu.py) +echo "The first available gpu is $gpu" + +# Run normal unet yaml +# took off --debug to allow wb to work. +WANDB__SERVICE_WAIT=300 CUDA_VISIBLE_DEVICES=$gpu$ python tools/train_net.py --config-file configs/mri-recon/mridata-3dfse-knee/unet/supervised/Supervised_Aug_UNET_1_Scan.yaml --auto-version + +gpu=$(python get_available_gpu.py) +echo "The first available gpu for VORTEX is $gpu" + +# Run vortex unet yaml +# took off --debug to allow wb to work. +WANDB__SERVICE_WAIT=300 CUDA_VISIBLE_DEVICES=$gpu$ python tools/train_net.py --config-file configs/mri-recon/mridata-3dfse-knee/unet/supervised/Supervised_Aug_UNET_14_Scan.yaml --auto-version diff --git a/run_unrolled_norm_vortex.sh b/run_unrolled_norm_vortex.sh new file mode 100755 index 00000000..81dd3f91 --- /dev/null +++ b/run_unrolled_norm_vortex.sh @@ -0,0 +1,16 @@ +#!/bin/bash +export MEDDLR_DATASETS_DIR=/mnt/dense/ozt/dl-ss-recon/data +export MEDDLR_CACHE_DIR=/mnt/dense/deepro/cache +export MEDDLR_RESULTS_DIR=/mnt/dense/deepro/results/Summer_2022_2023/wandb + +gpu=$(python get_available_gpu.py) +echo "The first available gpu is $gpu" + +# Run normal unrolled yaml +WANDB__SERVICE_WAIT=300 CUDA_VISIBLE_DEVICES=$gpu$ python tools/train_net.py --config-file configs/mri-recon/mridata-3dfse-knee/unrolled.yaml --auto-version + +gpu=$(python get_available_gpu.py) +echo "The first available gpu for VORTEX is $gpu" + +# Run vortex unrolled yaml +WANDB__SERVICE_WAIT=300 CUDA_VISIBLE_DEVICES=$gpu$ python tools/train_net.py --config-file configs/mri-recon/mridata-3dfse-knee/vortex_rm_unrolled.yaml --auto-version \ No newline at end of file diff --git a/run_unrolled_ssdu_vortex.sh b/run_unrolled_ssdu_vortex.sh new file mode 100755 index 00000000..7789ead8 --- /dev/null +++ b/run_unrolled_ssdu_vortex.sh @@ -0,0 +1,16 @@ +#!/bin/bash +export MEDDLR_DATASETS_DIR=/mnt/dense/ozt/dl-ss-recon/data +export MEDDLR_CACHE_DIR=/mnt/dense/deepro/cache +export MEDDLR_RESULTS_DIR=/mnt/dense/deepro/results/Summer_2022_2023/unrolled_official/ssdu/wandb + +gpu=$(python get_available_gpu.py) +echo "The first available gpu is $gpu" + +# Run normal unrolled yaml +WANDB__SERVICE_WAIT=300 CUDA_VISIBLE_DEVICES=$gpu$ python tools/train_net.py --config-file configs/mri-recon/mridata-3dfse-knee/unrolled/ssdu/SSDU_Unrolled_13_Scan.yaml --auto-version + +gpu=$(python get_available_gpu.py) +echo "The first available gpu for VORTEX is $gpu" + +# Run vortex unrolled yaml +WANDB__SERVICE_WAIT=300 CUDA_VISIBLE_DEVICES=$gpu$ python tools/train_net.py --config-file configs/mri-recon/mridata-3dfse-knee/unrolled/ssdu/SSDU_Aug_Unrolled_13_Scan.yaml --auto-version \ No newline at end of file diff --git a/run_unrolled_supervised_vortex.sh b/run_unrolled_supervised_vortex.sh new file mode 100755 index 00000000..db0bdd8e --- /dev/null +++ b/run_unrolled_supervised_vortex.sh @@ -0,0 +1,34 @@ +#!/bin/bash + +export MEDDLR_DATASETS_DIR=/mnt/dense/ozt/dl-ss-recon/data +export MEDDLR_CACHE_DIR=/mnt/dense/deepro/cache +export MEDDLR_RESULTS_DIR=/mnt/dense/deepro/results/Summer_2022_2023/unrolled_official/supervised/wandb + +gpu=$(python get_available_gpu.py) +echo "The first available gpu is $gpu" + +# Run normal unet yaml +# took off --debug to allow wb to work. +WANDB__SERVICE_WAIT=300 CUDA_VISIBLE_DEVICES=$gpu$ python tools/train_net.py --config-file configs/mri-recon/mridata-3dfse-knee/unrolled/supervised/Supervised_Unrolled_1_Scan.yaml --auto-version + +gpu=$(python get_available_gpu.py) +echo "The first available gpu is $gpu" + +# Run normal unet yaml +# took off --debug to allow wb to work. +WANDB__SERVICE_WAIT=300 CUDA_VISIBLE_DEVICES=$gpu$ python tools/train_net.py --config-file configs/mri-recon/mridata-3dfse-knee/unrolled/supervised/Supervised_Unrolled_14_Scan.yaml --auto-version + +gpu=$(python get_available_gpu.py) +echo "The first available gpu is $gpu" + +# Run normal unet yaml +# took off --debug to allow wb to work. +WANDB__SERVICE_WAIT=300 CUDA_VISIBLE_DEVICES=$gpu$ python tools/train_net.py --config-file configs/mri-recon/mridata-3dfse-knee/unrolled/supervised/Supervised_Aug_Unrolled_1_Scan.yaml --auto-version + +gpu=$(python get_available_gpu.py) +echo "The first available gpu for VORTEX is $gpu" + +# Run vortex unet yaml +# took off --debug to allow wb to work. +WANDB__SERVICE_WAIT=300 CUDA_VISIBLE_DEVICES=$gpu$ python tools/train_net.py --config-file configs/mri-recon/mridata-3dfse-knee/unrolled/supervised/Supervised_Aug_Unrolled_14_Scan.yaml --auto-version + From 7c00a565f7b1edf41e68cdf7fa77b4ac550ac0a6 Mon Sep 17 00:00:00 2001 From: Deepro Pasha Date: Fri, 18 Aug 2023 17:05:52 -0700 Subject: [PATCH 29/33] before run push --- .../templates/template_multi_aug_1.yaml | 26 --- .../templates/template_multi_aug_2.yaml | 34 ---- .../templates/template_multi_aug_2D.yaml | 28 ++++ .../templates/unet_multi_aug.yaml | 14 ++ .../templates/unrolled_multi_aug.yaml | 20 +++ .../ssdu/SSDU_Multi_Aug_UNET_13_Scan.yaml | 66 ++++++++ .../Supervised_Multi_Aug_UNET_14_Scan.yaml | 29 ++++ .../Supervised_Multi_Aug_UNET_1_Scan.yaml | 28 ++++ .../ssdu/SSDU_Multi_Aug_Unrolled_13_Scan.yaml | 67 ++++++++ ...Supervised_Multi_Aug_Unrolled_14_Scan.yaml | 26 +++ .../Supervised_Multi_Aug_Unrolled_1_Scan.yaml | 25 +++ meddlr/transforms/base/motion.py | 2 +- meddlr/transforms/functional/mri.py | 155 +++++++++++++++++- meddlr/transforms/gen/motion.py | 110 ++++++++++--- run_unet_multi_vortex.sh | 25 +++ run_unrolled_multi_vortex.sh | 25 +++ run_unrolled_ssdu_vortex.sh | 5 - run_unrolled_supervised_vortex.sh | 24 +-- 18 files changed, 605 insertions(+), 104 deletions(-) delete mode 100644 configs/mri-recon/mridata-3dfse-knee/templates/template_multi_aug_1.yaml delete mode 100644 configs/mri-recon/mridata-3dfse-knee/templates/template_multi_aug_2.yaml create mode 100644 configs/mri-recon/mridata-3dfse-knee/templates/template_multi_aug_2D.yaml create mode 100644 configs/mri-recon/mridata-3dfse-knee/templates/unet_multi_aug.yaml create mode 100644 configs/mri-recon/mridata-3dfse-knee/templates/unrolled_multi_aug.yaml create mode 100644 configs/mri-recon/mridata-3dfse-knee/unet/ssdu/SSDU_Multi_Aug_UNET_13_Scan.yaml create mode 100644 configs/mri-recon/mridata-3dfse-knee/unet/supervised/Supervised_Multi_Aug_UNET_14_Scan.yaml create mode 100644 configs/mri-recon/mridata-3dfse-knee/unet/supervised/Supervised_Multi_Aug_UNET_1_Scan.yaml create mode 100644 configs/mri-recon/mridata-3dfse-knee/unrolled/ssdu/SSDU_Multi_Aug_Unrolled_13_Scan.yaml create mode 100644 configs/mri-recon/mridata-3dfse-knee/unrolled/supervised/Supervised_Multi_Aug_Unrolled_14_Scan.yaml create mode 100644 configs/mri-recon/mridata-3dfse-knee/unrolled/supervised/Supervised_Multi_Aug_Unrolled_1_Scan.yaml create mode 100644 run_unet_multi_vortex.sh create mode 100644 run_unrolled_multi_vortex.sh diff --git a/configs/mri-recon/mridata-3dfse-knee/templates/template_multi_aug_1.yaml b/configs/mri-recon/mridata-3dfse-knee/templates/template_multi_aug_1.yaml deleted file mode 100644 index c6e25904..00000000 --- a/configs/mri-recon/mridata-3dfse-knee/templates/template_multi_aug_1.yaml +++ /dev/null @@ -1,26 +0,0 @@ -_BASE_: "template.yaml" -AUG_TRAIN: - MRI_RECON: - AUG_SENSITIVITY_MAPS: true - TRANSFORMS: - - name: RandomMRIMultiShotMotion - p: 0.2 - seq_tfm_gens: - - name: RandomAffine - p: 0.2 - angle: 10 - translate: 0.5 - scale: 0.2 - shear: 30 - - name: RandomAffine - p: 0.2 - angle: 0 - translate: 0.5 - scale: 0.2 - shear: 30 - - name: RandomAffine - p: 0.2 - angle: 10 - translate: 0 - scale: 0.2 - shear: 30 \ No newline at end of file diff --git a/configs/mri-recon/mridata-3dfse-knee/templates/template_multi_aug_2.yaml b/configs/mri-recon/mridata-3dfse-knee/templates/template_multi_aug_2.yaml deleted file mode 100644 index 9513c1f7..00000000 --- a/configs/mri-recon/mridata-3dfse-knee/templates/template_multi_aug_2.yaml +++ /dev/null @@ -1,34 +0,0 @@ -_BASE_: "template.yaml" -AUG_TRAIN: - MRI_RECON: - AUG_SENSITIVITY_MAPS: true - TRANSFORMS: - - name: RandomMRIMultiShotMotion - p: 0.2 - seq_tfm_gens: - - name: SequenceOne - - name: RandomAffine - p: 0.2 - angle: 10 - translate: 0.5 - scale: 0.2 - shear: 30 - - name: RandomAffine - p: 0.2 - angle: 0 - translate: 0.5 - scale: 0.2 - shear: 30 - - name: SequenceTwo - - name: RandomAffine - p: 0.2 - angle: 10 - translate: 0.5 - scale: 0.2 - shear: 30 - - name: RandomAffine - p: 0.2 - angle: 0 - translate: 0.5 - scale: 0.2 - shear: 30 diff --git a/configs/mri-recon/mridata-3dfse-knee/templates/template_multi_aug_2D.yaml b/configs/mri-recon/mridata-3dfse-knee/templates/template_multi_aug_2D.yaml new file mode 100644 index 00000000..d50f6fc6 --- /dev/null +++ b/configs/mri-recon/mridata-3dfse-knee/templates/template_multi_aug_2D.yaml @@ -0,0 +1,28 @@ +_BASE_: "template.yaml" +AUG_TRAIN: + MRI_RECON: + AUG_SENSITIVITY_MAPS: true + TRANSFORMS: + - name: RandomMRIMultiShotMotion + tfms_or_gens: + - name: RandomAffine + p: 1.0 + angle: 7 + translate: 0.1 + scale: 1.0 + shear: 0 + - name: RandomAffine + p: 1.0 + angle: 0 + translate: 0.2 + scale: 1.0 + shear: 0 + - name: RandomAffine + p: 1.0 + angle: 15 + translate: 0 + scale: 1.0 + shear: 0 + nshots: 5 + trajectory: interleaved + p: 1.0 diff --git a/configs/mri-recon/mridata-3dfse-knee/templates/unet_multi_aug.yaml b/configs/mri-recon/mridata-3dfse-knee/templates/unet_multi_aug.yaml new file mode 100644 index 00000000..525a26b2 --- /dev/null +++ b/configs/mri-recon/mridata-3dfse-knee/templates/unet_multi_aug.yaml @@ -0,0 +1,14 @@ +# Configuration for DL-CS Paper +_BASE_: "template_multi_aug_2D.yaml" +MODEL: + META_ARCHITECTURE: "UnetModel" + UNET: + IN_CHANNELS: 2 + OUT_CHANNELS: 2 + CHANNELS: 32 + NUM_POOL_LAYERS: 4 + DROPOUT: 0. +SOLVER: + TRAIN_BATCH_SIZE: 16 + TEST_BATCH_SIZE: 16 + BASE_LR: 1e-3 \ No newline at end of file diff --git a/configs/mri-recon/mridata-3dfse-knee/templates/unrolled_multi_aug.yaml b/configs/mri-recon/mridata-3dfse-knee/templates/unrolled_multi_aug.yaml new file mode 100644 index 00000000..a6ca2d08 --- /dev/null +++ b/configs/mri-recon/mridata-3dfse-knee/templates/unrolled_multi_aug.yaml @@ -0,0 +1,20 @@ +_BASE_: "template_multi_aug_2D.yaml" +MODEL: + META_ARCHITECTURE: "GeneralizedUnrolledCNN" + UNROLLED: + NUM_UNROLLED_STEPS: 8 + NUM_RESBLOCKS: 2 + NUM_FEATURES: 128 + DROPOUT: 0. + CONV_BLOCK: + ACTIVATION: "relu" + NORM: "none" + ORDER: ("act", "conv") +SOLVER: + OPTIMIZER: "Adam" + LR_SCHEDULER_NAME: "" + TRAIN_BATCH_SIZE: 4 + GRAD_ACCUM_ITERS: 4 + TEST_BATCH_SIZE: 12 + BASE_LR: 1e-4 +VERSION: 1 \ No newline at end of file diff --git a/configs/mri-recon/mridata-3dfse-knee/unet/ssdu/SSDU_Multi_Aug_UNET_13_Scan.yaml b/configs/mri-recon/mridata-3dfse-knee/unet/ssdu/SSDU_Multi_Aug_UNET_13_Scan.yaml new file mode 100644 index 00000000..bd08e581 --- /dev/null +++ b/configs/mri-recon/mridata-3dfse-knee/unet/ssdu/SSDU_Multi_Aug_UNET_13_Scan.yaml @@ -0,0 +1,66 @@ +# Inherit fields from the unet.yaml config. +_BASE_: "../../templates/unet.yaml" +DATALOADER: + SUBSAMPLE_TRAIN: + # 1 supervised and 13 unsupervised scans for training + NUM_TOTAL: 14 + NUM_UNDERSAMPLED: 13 +# Specify an output directory that is unique for this experiment. +OUTPUT_DIR: results://vortex-rm/mridata_knee_3dfse/unet/ssdu/ssdu_multi_aug_unet_13_scan +DESCRIPTION: + # Use f-strings to specify reading values in braces ({}) the config file. + # This can only be done for string fields. + BRIEF: f"SSDU Multi Aug U-Net {AUG_TRAIN.UNDERSAMPLE.ACCELERATIONS}x mridata - {MODEL.SSDU.MASKER.PARAMS.kind}/loss={MODEL.RECON_LOSS.NAME}" + EXP_NAME: mridata_knee_3dfse/unet/SSDU_Multi_Aug + TAGS: + - baseline + - ssdu + - 16x + - mridata_knee_3dfse + - unet +# For motion aug, A2R and Consistency were kept with std_dev range of 0.2 to 0.5. +MODEL: + META_ARCHITECTURE: SSDUModel + RECON_LOSS: + NAME: k_l1 + RENORMALIZE_DATA: false + SSDU: + # For UNET, the META_ARCHITECTURE of SSDUModel is UnetModel + META_ARCHITECTURE: UnetModel + MASKER: + # These paramters were taken from the VORTEX/Noise2Recon papers. + PARAMS: + kind: uniform + rhos: 0.4 # Default value in SSDU code + p: 1.0 + per_example: true + AUGMENTOR: + TRANSFORMS: + - name: RandomMRIMultiShotMotion + tfms_or_gens: + - name: RandomAffine + p: 1.0 + angle: 7 + translate: 0.1 + scale: 1.0 + shear: 0 + - name: RandomAffine + p: 1.0 + angle: 0 + translate: 0.2 + scale: 1.0 + shear: 0 + - name: RandomAffine + p: 1.0 + angle: 15 + translate: 0 + scale: 1.0 + shear: 0 + nshots: 5 + trajectory: interleaved + p: 1.0 +AUG_TRAIN: + MRI_RECON: + TRANSFORMS: [] +VERSION: 1 + diff --git a/configs/mri-recon/mridata-3dfse-knee/unet/supervised/Supervised_Multi_Aug_UNET_14_Scan.yaml b/configs/mri-recon/mridata-3dfse-knee/unet/supervised/Supervised_Multi_Aug_UNET_14_Scan.yaml new file mode 100644 index 00000000..870d3217 --- /dev/null +++ b/configs/mri-recon/mridata-3dfse-knee/unet/supervised/Supervised_Multi_Aug_UNET_14_Scan.yaml @@ -0,0 +1,29 @@ +# Inherit fields from the unet.yaml config. +_BASE_: "../../templates/unet_multi_aug.yaml" +# DATALOADER has 14 supervised scans +DATALOADER: + SUBSAMPLE_TRAIN: + NUM_TOTAL: 14 + NUM_UNDERSAMPLED: 0 +# Specify an output directory that is unique for this experiment. +OUTPUT_DIR: results://vortex-rm/mridata_knee_3dfse/unet/supervised/supervised_multi_aug_unet_14_scan +DESCRIPTION: + # Use f-strings to specify reading values in braces ({}) the config file. + # This can only be done for string fields. + BRIEF: f"Supervised Multi Aug U-Net 14 Scan {AUG_TRAIN.UNDERSAMPLE.ACCELERATIONS}x mridata - loss={MODEL.RECON_LOSS.NAME}" + EXP_NAME: mridata_knee_3dfse/unet/supervised_multi_aug_unet_14 + TAGS: + - baseline + - supervised + - 14_scan + - 16x + - mridata_knee_3dfse + - unet +# For no motion aug, A2R and Consistency was removed. +# For UNET, kept META_ARCHITECTURE as UnetModel +MODEL: + META_ARCHITECTURE: UnetModel + RECON_LOSS: + NAME: l1 + RENORMALIZE_DATA: false +VERSION: 1 \ No newline at end of file diff --git a/configs/mri-recon/mridata-3dfse-knee/unet/supervised/Supervised_Multi_Aug_UNET_1_Scan.yaml b/configs/mri-recon/mridata-3dfse-knee/unet/supervised/Supervised_Multi_Aug_UNET_1_Scan.yaml new file mode 100644 index 00000000..fd517fe4 --- /dev/null +++ b/configs/mri-recon/mridata-3dfse-knee/unet/supervised/Supervised_Multi_Aug_UNET_1_Scan.yaml @@ -0,0 +1,28 @@ +# Inherit fields from the unet.yaml config. +_BASE_: "../../templates/unet_multi_aug.yaml" +# DATALOADER has only 1 supervised scan +DATALOADER: + SUBSAMPLE_TRAIN: + NUM_TOTAL: 1 + NUM_UNDERSAMPLED: 0 +# Specify an output directory that is unique for this experiment. +OUTPUT_DIR: results://vortex-rm/mridata_knee_3dfse/unet/supervised/supervised_multi_aug_unet_1_scan +DESCRIPTION: + # Use f-strings to specify reading values in braces ({}) the config file. + # This can only be done for string fields. + BRIEF: f"Supervised Multi Aug U-Net 1 Scan {AUG_TRAIN.UNDERSAMPLE.ACCELERATIONS}x mridata - loss={MODEL.RECON_LOSS.NAME}" + EXP_NAME: mridata_knee_3dfse/unet/supervised_multi_aug_unet_1 + TAGS: + - baseline + - supervised + - one_scan + - 16x + - mridata_knee_3dfse + - unet +# For UNET, kept META_ARCHITECTURE as UnetModel +MODEL: + META_ARCHITECTURE: UnetModel + RECON_LOSS: + NAME: l1 + RENORMALIZE_DATA: false +VERSION: 1 diff --git a/configs/mri-recon/mridata-3dfse-knee/unrolled/ssdu/SSDU_Multi_Aug_Unrolled_13_Scan.yaml b/configs/mri-recon/mridata-3dfse-knee/unrolled/ssdu/SSDU_Multi_Aug_Unrolled_13_Scan.yaml new file mode 100644 index 00000000..ff4eb830 --- /dev/null +++ b/configs/mri-recon/mridata-3dfse-knee/unrolled/ssdu/SSDU_Multi_Aug_Unrolled_13_Scan.yaml @@ -0,0 +1,67 @@ +# Inherit fields from the unrolled.yaml config. +_BASE_: "../../templates/unrolled.yaml" +DATALOADER: + SUBSAMPLE_TRAIN: + # 1 supervised and 13 unsupervised scans for training + NUM_TOTAL: 14 + NUM_UNDERSAMPLED: 13 +# Specify an output directory that is unique for this experiment. +OUTPUT_DIR: results://vortex-rm/mridata_knee_3dfse/unrolled/ssdu/ssdu_multi_aug_unrolled_13_scan +DESCRIPTION: + # Use f-strings to specify reading values in braces ({}) the config file. + # This can only be done for string fields. + BRIEF: f"SSDU Multi Aug Unrolled {AUG_TRAIN.UNDERSAMPLE.ACCELERATIONS}x mridata - {MODEL.SSDU.MASKER.PARAMS.kind}/loss={MODEL.RECON_LOSS.NAME}" + EXP_NAME: mridata_knee_3dfse/unrolled/SSDU_Multi_Aug + TAGS: + - baseline + - ssdu + - 16x + - mridata_knee_3dfse + - unrolled +# For motion aug, A2R and Consistency were kept with std_dev range of 0.2 to 0.5. +# For Unrolled, the META_ARCHITECTURE of SSDUModel is GeneralizedUnrolledCNN +MODEL: + META_ARCHITECTURE: SSDUModel + RECON_LOSS: + NAME: k_l1 + RENORMALIZE_DATA: false + SSDU: + META_ARCHITECTURE: GeneralizedUnrolledCNN + MASKER: + # These parameters were taken from the VORTEX/Noise2Recon papers. + PARAMS: + kind: uniform + rhos: 0.4 # Default value in SSDU code + p: 1.0 + per_example: true + AUGMENTOR: + TRANSFORMS: + - name: RandomMRIMultiShotMotion + tfms_or_gens: + - name: RandomAffine + p: 1.0 + angle: 7 + translate: 0.1 + scale: 1.0 + shear: 0 + - name: RandomAffine + p: 1.0 + angle: 0 + translate: 0.2 + scale: 1.0 + shear: 0 + - name: RandomAffine + p: 1.0 + angle: 15 + translate: 0 + scale: 1.0 + shear: 0 + nshots: 5 + trajectory: interleaved + p: 1.0 +AUG_TRAIN: + MRI_RECON: + TRANSFORMS: [] +VERSION: 1 + + diff --git a/configs/mri-recon/mridata-3dfse-knee/unrolled/supervised/Supervised_Multi_Aug_Unrolled_14_Scan.yaml b/configs/mri-recon/mridata-3dfse-knee/unrolled/supervised/Supervised_Multi_Aug_Unrolled_14_Scan.yaml new file mode 100644 index 00000000..3db3b255 --- /dev/null +++ b/configs/mri-recon/mridata-3dfse-knee/unrolled/supervised/Supervised_Multi_Aug_Unrolled_14_Scan.yaml @@ -0,0 +1,26 @@ +# Inherit fields from the unrolled.yaml config. +_BASE_: "../../templates/unrolled_multi_aug.yaml" +# DATALOADER has 14 supervised scan +DATALOADER: + SUBSAMPLE_TRAIN: + NUM_TOTAL: 14 + NUM_UNDERSAMPLED: 0 +OUTPUT_DIR: results://vortex-rm/mridata_knee_3dfse/unrolled/supervised/supervised_multi_aug_unrolled_14_scan +DESCRIPTION: + BRIEF: f"Supervised Multi Aug Unrolled 14 Scan {AUG_TRAIN.UNDERSAMPLE.ACCELERATIONS}x mridata - loss={MODEL.RECON_LOSS.NAME}" + EXP_NAME: mridata_knee_3dfse/unrolled/supervised_multi_aug_14_scan + TAGS: + - baseline + - supervised + - 14_scan + - 16x + - mridata_knee_3dfse + - unrolled +# For no motion aug, A2R and Consistency was removed. +# For Unrolled, made META_ARCHITECTURE GeneralizedUnrolledCNN +MODEL: + META_ARCHITECTURE: GeneralizedUnrolledCNN + RECON_LOSS: + NAME: l1 + RENORMALIZE_DATA: false +VERSION: 1 \ No newline at end of file diff --git a/configs/mri-recon/mridata-3dfse-knee/unrolled/supervised/Supervised_Multi_Aug_Unrolled_1_Scan.yaml b/configs/mri-recon/mridata-3dfse-knee/unrolled/supervised/Supervised_Multi_Aug_Unrolled_1_Scan.yaml new file mode 100644 index 00000000..26249944 --- /dev/null +++ b/configs/mri-recon/mridata-3dfse-knee/unrolled/supervised/Supervised_Multi_Aug_Unrolled_1_Scan.yaml @@ -0,0 +1,25 @@ +# Inherit fields from the unrolled.yaml config. +_BASE_: "../../templates/unrolled_multi_aug.yaml" +# DATALOADER has only 1 supervised scan +DATALOADER: + SUBSAMPLE_TRAIN: + NUM_TOTAL: 1 + NUM_UNDERSAMPLED: 0 +OUTPUT_DIR: results://vortex-rm/mridata_knee_3dfse/unrolled/supervised/supervised_multi_aug_unrolled_1_scan +DESCRIPTION: + BRIEF: f"Supervised Multi Aug Unrolled 1 Scan {AUG_TRAIN.UNDERSAMPLE.ACCELERATIONS}x mridata - loss={MODEL.RECON_LOSS.NAME}" + EXP_NAME: mridata_knee_3dfse/unrolled/supervised_multi_aug_1_scan + TAGS: + - baseline + - supervised + - one_scan + - 16x + - mridata_knee_3dfse + - unrolled +# For Unrolled, made META_ARCHITECTURE GeneralizedUnrolledCNN +MODEL: + META_ARCHITECTURE: GeneralizedUnrolledCNN + RECON_LOSS: + NAME: l1 + RENORMALIZE_DATA: false +VERSION: 1 diff --git a/meddlr/transforms/base/motion.py b/meddlr/transforms/base/motion.py index d134b21b..f84eed67 100644 --- a/meddlr/transforms/base/motion.py +++ b/meddlr/transforms/base/motion.py @@ -2,7 +2,7 @@ import torch -import meddlr.transforms.functional as stf +import meddlr.transforms.functional.mri as stf from meddlr.transforms.build import TRANSFORM_REGISTRY from meddlr.transforms.transform import Transform from meddlr.transforms.transform_gen import TransformGen diff --git a/meddlr/transforms/functional/mri.py b/meddlr/transforms/functional/mri.py index eeb7acaf..92d74fa8 100644 --- a/meddlr/transforms/functional/mri.py +++ b/meddlr/transforms/functional/mri.py @@ -1,11 +1,12 @@ import math -from typing import Optional +from typing import Sequence, Tuple import numpy as np import torch -import meddlr.ops as F +import meddlr.ops as oF import meddlr.ops.complex as cplx +from meddlr.forward import SenseModel def add_even_odd_motion( @@ -41,4 +42,154 @@ def add_even_odd_motion( return aug_kspace +def add_affine_motion( + x: torch.Tensor, + *, + transform_gens, + trajectory: torch.Tensor, + maps: torch.Tensor = None, + is_batch: bool = False, + channels_first: bool = True, + xtype: str = "image", +) -> torch.Tensor: + """Simulate 2D motion for multi-shot Cartesian MRI. + This function supports two trajectories: + - 'blocked': Where each shot corresponds to a consecutive block of kspace. + (e.g. 1 1 2 2 3 3) + - 'interleaved': Where shots are interleaved (e.g. 1 2 3 1 2 3). + + We assume the phase encode direction is left to right + (i.e. along width dimension). + TODO: Add support for sensitivity maps. + + Args: + image: The complex-valued image. Shape [(batch,), height, width, 1]. + transforms: A sequence of random transform generators. These transforms + will be used to augment images in the image domain. We recommend using + [RandomTranslation, RandomAffine] in that order. This matches the MRAugment + augmentation strategy. + trajectory: The trajectory tensor. Shape [height, width]. + maps: The sensitivity maps. Shape [(batch,) height, width, ncoils, nmaps]. + is_batch: Whether the image (and maps) are batched. + Note, the trajectory should not be batched. To run each example with + different trajectories, call this method multiple times (once per example). + + Returns: + A motion corrupted image. + """ + from meddlr.transforms.transform import TransformList + from meddlr.transforms.transform_gen import TransformGen + + if xtype not in ["image", "kspace"]: + raise ValueError(f"Invalid xtype: {xtype}. Must be one of ['image', 'kspace'].") + + if not channels_first: + if maps is not None: + if is_batch: + dims = (0, maps.ndim - 2, maps.ndim - 1, *range(1, maps.ndim - 2)) + else: + dims = (maps.ndim - 2, maps.ndim - 1, *range(0, maps.ndim - 2)) + maps = maps.permute(dims) # Shape: [(batch), ncoils, nmaps, ...] + x = cplx.channels_first(x) + + def _maps_channels_first(_maps): + if is_batch: + dims = (0, *range(3, maps.ndim), 1, 2) + else: + dims = (*range(2, maps.ndim), 0, 1) + return _maps.permute(dims) + + def _to_image(_x, _maps): + if _maps is None: + _maps = maps + if _maps is None: + return oF.ifft2c(_x) + else: + _maps = _maps_channels_first(_maps) + out = SenseModel(_maps)(cplx.channels_last(_x), adjoint=True) + return cplx.channels_first(out) + + def _to_kspace(_x, _maps): + if _maps is None: + _maps = maps + if _maps is None: + return oF.fft2c(_x) + else: + _maps = _maps_channels_first(_maps) + out = SenseModel(_maps)(cplx.channels_last(_x), adjoint=False) + return cplx.channels_first(out) + + image = x if xtype == "image" else _to_image(x, maps) + + transform_gens: Sequence[TransformGen] = transform_gens + + if maps is None: + shape = image.shape + elif is_batch: + shape = (image.shape[0], maps.shape[1], *image.shape[-2:]) + else: + shape = (maps.shape[1], *image.shape[-2:]) + kspace = torch.zeros(shape, device=image.device, dtype=image.dtype) + + shot_ids = torch.unique(trajectory) # the sorted shot ids. + assert trajectory.shape == kspace.shape[-2:], f"{trajectory.shape} != {kspace.shape[-2:]}" + trajectory = trajectory.expand_as(kspace) + + for shot_id in shot_ids: + motion_image = image + motion_maps = maps + # Apply sequence of random transforms to the image. + tfms = TransformList([]) + for tfm_gen in transform_gens: + tfm = tfm_gen.get_transform(motion_image) + tfms += tfm + motion_image = tfm.apply_image(motion_image) + if motion_maps is not None: + motion_maps = tfms.apply_image(motion_maps) + + motion_kspace = _to_kspace(motion_image, motion_maps) + + # Replace locations in the kspace with the motion corrupted kspace. + kspace[trajectory == shot_id] = motion_kspace[trajectory == shot_id] + + if xtype == "kspace": + return cplx.channels_last(kspace) if not channels_first else kspace + image = _to_image(kspace, maps) + return cplx.channels_last(image) if not channels_first else image + + +def get_multishot_trajectory( + kind: str, nshots: int, shape: Tuple[int], device="cpu" +) -> torch.Tensor: + """Build a multi-shot cartesian trajectory. + + This function supports two trajectories: + - 'blocked': Where each shot corresponds to a consecutive block of kspace. + (e.g. 1 1 2 2 3 3) + - 'interleaved': Where shots are interleaved (e.g. 1 2 3 1 2 3). + + Args: + kind: One of 'interleaved' or 'blocked'. + nshots: The number of shots in the image. + This should be equivalent to ceil(phase_encode_dim / echo_train_length). + shape: The shape of the 2D kspace tensor (height, width). + + Returns: + torch.Tensor: A categorical tensor of shape [height, width]. + Values range from [0, nshots-1] which correspond to the readouts + per shot. + """ + if kind not in ["blocked", "interleaved"]: + raise ValueError( + f"trajectory '{kind}' not supported. " "Must be one of 'blocked' or 'interleaved'." + ) + + trajectory = torch.zeros(shape, dtype=torch.long, device=device) + offset = int(math.ceil(shape[-1] / nshots)) + for shot in range(nshots): + if kind == "blocked": + trajectory[..., shot * offset : (shot + 1) * offset] = shot + elif kind == "interleaved": + trajectory[..., shot::nshots] = shot + return trajectory diff --git a/meddlr/transforms/gen/motion.py b/meddlr/transforms/gen/motion.py index 23fbe581..dfb1b0c3 100644 --- a/meddlr/transforms/gen/motion.py +++ b/meddlr/transforms/gen/motion.py @@ -1,9 +1,13 @@ -from typing import Sequence, Union +from typing import Any, List, Mapping, Sequence, Tuple, Union import torch +import meddlr.transforms.functional.mri as tF +from meddlr.config.config import CfgNode from meddlr.transforms.base.motion import MRIMotionTransform, MRIMultiShotMotion -from meddlr.transforms.build import TRANSFORM_REGISTRY +from meddlr.transforms.build import TRANSFORM_REGISTRY, build_transforms +from meddlr.transforms.mixins import DeviceMixin +from meddlr.transforms.tf_scheduler import SchedulableMixin from meddlr.transforms.transform import NoOpTransform from meddlr.transforms.transform_gen import TransformGen @@ -57,23 +61,11 @@ def get_transform(self, input: torch.Tensor): @TRANSFORM_REGISTRY.register() class RandomMRIMultiShotMotion(TransformGen): - """A model that corrupts kspace inputs with motion. - Motion is a common artifact experienced during the MR imaging forward problem. - When a patient moves, the recorded (expected) location of the kspace sample is - different than the actual location where the kspace sample that was acquired. - This module is responsible for simulating different motion artifacts. - Args: - seed (int, optional): The fixed seed. - Attributes: - generator (torch.Generator): The generator that should be used for all - random logic in this class. - Things to consider: - 1. What other information is relevant for inducing motion corruption? - This could include: - - ``traj``: The scan trajectory - - ``etl``: The echo train length - how many readouts per shot. - - ``num_shots``: Number of shots. - 2. What would a simple translational motion model look? + """A model that corrupts kspace inputs with affine motion. + + Similar to :class:`RandomMRIMotion`, but supports affine transformations. + Transformations are performed in image space and filled in based on a trajectory. + Note: We do not store this as a module or else it would be saved to the model definition, which we dont want. @@ -83,17 +75,87 @@ class RandomMRIMultiShotMotion(TransformGen): def __init__( self, - seq_tfm_gens: Union[Sequence[TransformGen], Sequence[Sequence[TransformGen]]], + tfms_or_gens: Sequence[TransformGen], + nshots: Union[int, Tuple[int, int]], + trajectory: str = "blocked", p: float = 0.0, ): - super().__init__(params={"seq_tfm_gens": seq_tfm_gens}, p=p) + self.tfms_or_gens = tfms_or_gens + self.trajectory = trajectory + if isinstance(nshots, float): + if not nshots.is_integer(): + raise ValueError("`nshots` must be an integer") + nshots = int(nshots) + if isinstance(nshots, int): + nshots = (nshots, nshots) + super().__init__(params={"nshots": nshots}, p=p) - def get_transform(self, input: torch.Tensor): + def get_transform(self, input: torch.Tensor, channel_first: bool = True): params = self._get_param_values(use_schedulers=True) if self._rand() >= params["p"]: return NoOpTransform() - tfm_gens = self._rand_range(*params["seq_std_devs"]) + + shape = input.shape[-2:] if channel_first else input.shape[1:3] + + nshots = self._rand_range(*params["nshots"]) + trajectory = tF.get_multishot_trajectory( + kind=self.trajectory, + nshots=int(round(nshots)), + shape=shape, + device=input.device, + ) + gen = self._generator if gen is None or gen.device != input.device: gen = torch.Generator(device=input.device).manual_seed(int(self._rand() * 1e10)) - return MRIMultiShotMotion(tfm_gens=tfm_gens, generator=gen) + + return MRIMultiShotMotion( + tfm_gens=self.tfms_or_gens, + trajectory=trajectory, + generator=gen, + ) + + def schedulers(self): + tfms: List[SchedulableMixin] = self._get_tfm_by_type(SchedulableMixin) + schedulers = list(self._schedulers) + schedulers.extend([sch for tfm in tfms for sch in tfm.schedulers()]) + return schedulers + + def seed(self, value: int): + self._generator = torch.Generator(device=self._device).manual_seed(value) + tfms: List[TransformGen] = self._get_tfm_by_type(TransformGen) + for t in tfms: + t.seed(value) + return self + + def to(self, device): + super().to(device) + tfms: List[DeviceMixin] = self._get_tfm_by_type(DeviceMixin) + for t in tfms: + t.to(device) + return self + + def _get_tfm_by_type(self, klass): + tfms = [] + for tfm in self.tfms_or_gens: + if isinstance(tfm, (list, tuple)): + tfms.extend(t for t in tfm if isinstance(t, klass)) + elif isinstance(tfm, klass): + tfms.append(tfm) + return tfms + + def __repr__(self): + classname = type(self).__name__ + argstr = ",\n ".join("{}".format(repr(t)) for t in zip(self.tfms_or_gens)) + params = ["nshots", "trajectory", "p"] + params_str = ",\n ".join("{}={}".format(k, repr(getattr(self, k))) for k in params) + return "{}(\n {},\n {}\n)".format(classname, argstr, params_str) + + @classmethod + def from_dict(cls, cfg: CfgNode, init_kwargs: Mapping[str, Any], **kwargs): + init_kwargs = init_kwargs.copy() + tfms_or_gens = [] + for tfm_cfg in init_kwargs.pop("tfms_or_gens"): + tfms_or_gens.append(build_transforms(cfg, tfm_cfg, **kwargs)) + + return cls(tfms_or_gens, **init_kwargs) diff --git a/run_unet_multi_vortex.sh b/run_unet_multi_vortex.sh new file mode 100644 index 00000000..c3aa405a --- /dev/null +++ b/run_unet_multi_vortex.sh @@ -0,0 +1,25 @@ +#!/bin/bash + +export MEDDLR_DATASETS_DIR=/mnt/dense/ozt/dl-ss-recon/data +export MEDDLR_CACHE_DIR=/mnt/dense/deepro/cache +export MEDDLR_RESULTS_DIR=/mnt/dense/deepro/results/Summer_2022_2023/unet_official/supervised/wandb + +gpu=$(python get_available_gpu.py) +echo "The first available gpu is $gpu" + +# Run normal unet yaml +# took off --debug to allow wb to work. +WANDB__SERVICE_WAIT=300 CUDA_VISIBLE_DEVICES=$gpu$ python tools/train_net.py --config-file configs/mri-recon/mridata-3dfse-knee/unrolled/supervised/Supervised_Multi_Aug_UNET_1_Scan.yaml --auto-version + +gpu=$(python get_available_gpu.py) +echo "The first available gpu for VORTEX is $gpu" + +# Run vortex unet yaml +# took off --debug to allow wb to work. +WANDB__SERVICE_WAIT=300 CUDA_VISIBLE_DEVICES=$gpu$ python tools/train_net.py --config-file configs/mri-recon/mridata-3dfse-knee/unrolled/supervised/Supervised_Multi_Aug_UNET_14_Scan.yaml --auto-version + +gpu=$(python get_available_gpu.py) +echo "The first available gpu for VORTEX is $gpu" + +# Run vortex unrolled yaml +WANDB__SERVICE_WAIT=300 CUDA_VISIBLE_DEVICES=$gpu$ python tools/train_net.py --config-file configs/mri-recon/mridata-3dfse-knee/unrolled/ssdu/SSDU_Multi_Aug_UNET_13_Scan.yaml --auto-version \ No newline at end of file diff --git a/run_unrolled_multi_vortex.sh b/run_unrolled_multi_vortex.sh new file mode 100644 index 00000000..a4d87887 --- /dev/null +++ b/run_unrolled_multi_vortex.sh @@ -0,0 +1,25 @@ +#!/bin/bash + +export MEDDLR_DATASETS_DIR=/mnt/dense/ozt/dl-ss-recon/data +export MEDDLR_CACHE_DIR=/mnt/dense/deepro/cache +export MEDDLR_RESULTS_DIR=/mnt/dense/deepro/results/Summer_2022_2023/unrolled_official/supervised/wandb + +gpu=$(python get_available_gpu.py) +echo "The first available gpu is $gpu" + +# Run normal unet yaml +# took off --debug to allow wb to work. +WANDB__SERVICE_WAIT=300 CUDA_VISIBLE_DEVICES=$gpu$ python tools/train_net.py --config-file configs/mri-recon/mridata-3dfse-knee/unrolled/supervised/Supervised_Multi_Aug_Unrolled_1_Scan.yaml --auto-version + +gpu=$(python get_available_gpu.py) +echo "The first available gpu for VORTEX is $gpu" + +# Run vortex unet yaml +# took off --debug to allow wb to work. +WANDB__SERVICE_WAIT=300 CUDA_VISIBLE_DEVICES=$gpu$ python tools/train_net.py --config-file configs/mri-recon/mridata-3dfse-knee/unrolled/supervised/Supervised_Multi_Aug_Unrolled_14_Scan.yaml --auto-version + +gpu=$(python get_available_gpu.py) +echo "The first available gpu for VORTEX is $gpu" + +# Run vortex unrolled yaml +WANDB__SERVICE_WAIT=300 CUDA_VISIBLE_DEVICES=$gpu$ python tools/train_net.py --config-file configs/mri-recon/mridata-3dfse-knee/unrolled/ssdu/SSDU_Multi_Aug_Unrolled_13_Scan.yaml --auto-version \ No newline at end of file diff --git a/run_unrolled_ssdu_vortex.sh b/run_unrolled_ssdu_vortex.sh index 7789ead8..5c9be7db 100755 --- a/run_unrolled_ssdu_vortex.sh +++ b/run_unrolled_ssdu_vortex.sh @@ -9,8 +9,3 @@ echo "The first available gpu is $gpu" # Run normal unrolled yaml WANDB__SERVICE_WAIT=300 CUDA_VISIBLE_DEVICES=$gpu$ python tools/train_net.py --config-file configs/mri-recon/mridata-3dfse-knee/unrolled/ssdu/SSDU_Unrolled_13_Scan.yaml --auto-version -gpu=$(python get_available_gpu.py) -echo "The first available gpu for VORTEX is $gpu" - -# Run vortex unrolled yaml -WANDB__SERVICE_WAIT=300 CUDA_VISIBLE_DEVICES=$gpu$ python tools/train_net.py --config-file configs/mri-recon/mridata-3dfse-knee/unrolled/ssdu/SSDU_Aug_Unrolled_13_Scan.yaml --auto-version \ No newline at end of file diff --git a/run_unrolled_supervised_vortex.sh b/run_unrolled_supervised_vortex.sh index db0bdd8e..9288fe37 100755 --- a/run_unrolled_supervised_vortex.sh +++ b/run_unrolled_supervised_vortex.sh @@ -4,31 +4,31 @@ export MEDDLR_DATASETS_DIR=/mnt/dense/ozt/dl-ss-recon/data export MEDDLR_CACHE_DIR=/mnt/dense/deepro/cache export MEDDLR_RESULTS_DIR=/mnt/dense/deepro/results/Summer_2022_2023/unrolled_official/supervised/wandb -gpu=$(python get_available_gpu.py) -echo "The first available gpu is $gpu" +# gpu=$(python get_available_gpu.py) +# echo "The first available gpu is $gpu" -# Run normal unet yaml -# took off --debug to allow wb to work. -WANDB__SERVICE_WAIT=300 CUDA_VISIBLE_DEVICES=$gpu$ python tools/train_net.py --config-file configs/mri-recon/mridata-3dfse-knee/unrolled/supervised/Supervised_Unrolled_1_Scan.yaml --auto-version +# # Run normal unet yaml +# # took off --debug to allow wb to work. +# WANDB__SERVICE_WAIT=300 CUDA_VISIBLE_DEVICES=$gpu$ python tools/train_net.py --config-file configs/mri-recon/mridata-3dfse-knee/unrolled/supervised/Supervised_Unrolled_1_Scan.yaml --auto-version -gpu=$(python get_available_gpu.py) -echo "The first available gpu is $gpu" +# gpu=$(python get_available_gpu.py) +# echo "The first available gpu is $gpu" -# Run normal unet yaml -# took off --debug to allow wb to work. -WANDB__SERVICE_WAIT=300 CUDA_VISIBLE_DEVICES=$gpu$ python tools/train_net.py --config-file configs/mri-recon/mridata-3dfse-knee/unrolled/supervised/Supervised_Unrolled_14_Scan.yaml --auto-version +# # Run normal unet yaml +# # took off --debug to allow wb to work. +# WANDB__SERVICE_WAIT=300 CUDA_VISIBLE_DEVICES=$gpu$ python tools/train_net.py --config-file configs/mri-recon/mridata-3dfse-knee/unrolled/supervised/Supervised_Unrolled_14_Scan.yaml --auto-version gpu=$(python get_available_gpu.py) echo "The first available gpu is $gpu" # Run normal unet yaml # took off --debug to allow wb to work. -WANDB__SERVICE_WAIT=300 CUDA_VISIBLE_DEVICES=$gpu$ python tools/train_net.py --config-file configs/mri-recon/mridata-3dfse-knee/unrolled/supervised/Supervised_Aug_Unrolled_1_Scan.yaml --auto-version +WANDB__SERVICE_WAIT=300 CUDA_VISIBLE_DEVICES=$gpu$ python tools/train_net.py --config-file configs/mri-recon/mridata-3dfse-knee/unrolled/supervised/Supervised_Multi_Aug_Unrolled_1_Scan.yaml --auto-version gpu=$(python get_available_gpu.py) echo "The first available gpu for VORTEX is $gpu" # Run vortex unet yaml # took off --debug to allow wb to work. -WANDB__SERVICE_WAIT=300 CUDA_VISIBLE_DEVICES=$gpu$ python tools/train_net.py --config-file configs/mri-recon/mridata-3dfse-knee/unrolled/supervised/Supervised_Aug_Unrolled_14_Scan.yaml --auto-version +WANDB__SERVICE_WAIT=300 CUDA_VISIBLE_DEVICES=$gpu$ python tools/train_net.py --config-file configs/mri-recon/mridata-3dfse-knee/unrolled/supervised/Supervised_Multi_Aug_Unrolled_14_Scan.yaml --auto-version From cef3a6da51bcaebfe05a0c78bd58343dc2beb60d Mon Sep 17 00:00:00 2001 From: Deepro Pasha Date: Mon, 21 Aug 2023 20:56:24 -0700 Subject: [PATCH 30/33] VORTEX training implementation --- .../vortex/VORTEX_Multi_Aug_UNET_14_Scan.yaml | 64 +++++++++++++++++++ .../VORTEX_Multi_Aug_Unrolled_14_Scan.yaml | 64 +++++++++++++++++++ 2 files changed, 128 insertions(+) create mode 100644 configs/mri-recon/mridata-3dfse-knee/unet/vortex/VORTEX_Multi_Aug_UNET_14_Scan.yaml create mode 100644 configs/mri-recon/mridata-3dfse-knee/unrolled/vortex/VORTEX_Multi_Aug_Unrolled_14_Scan.yaml diff --git a/configs/mri-recon/mridata-3dfse-knee/unet/vortex/VORTEX_Multi_Aug_UNET_14_Scan.yaml b/configs/mri-recon/mridata-3dfse-knee/unet/vortex/VORTEX_Multi_Aug_UNET_14_Scan.yaml new file mode 100644 index 00000000..b3fe5932 --- /dev/null +++ b/configs/mri-recon/mridata-3dfse-knee/unet/vortex/VORTEX_Multi_Aug_UNET_14_Scan.yaml @@ -0,0 +1,64 @@ +# Inherit fields from the unet.yaml config. +_BASE_: "../../templates/unet_multi_aug.yaml" +# DATALOADER has 14 supervised scans +DATALOADER: + SUBSAMPLE_TRAIN: + NUM_TOTAL: 14 + NUM_UNDERSAMPLED: 13 +# Specify an output directory that is unique for this experiment. +OUTPUT_DIR: results://vortex-rm/mridata_knee_3dfse/unet/vortex/vortex_multi_aug_unet_14_scan +DESCRIPTION: + # Use f-strings to specify reading values in braces ({}) the config file. + # This can only be done for string fields. + BRIEF: f"VORTEX Multi Aug U-Net 14 Scan {AUG_TRAIN.UNDERSAMPLE.ACCELERATIONS}x mridata - loss={MODEL.RECON_LOSS.NAME}" + EXP_NAME: mridata_knee_3dfse/unet/vortex_multi_aug_unet_14 + TAGS: + - baseline + - vortex + - 14_scan + - 16x + - mridata_knee_3dfse + - unet +# For no motion aug, A2R and Consistency was removed. +# For UNET, kept META_ARCHITECTURE as UnetModel +MODEL: + META_ARCHITECTURE: VortexModel + UNET: + CHANNELS: 32 + DROPOUT: 0.0 + IN_CHANNELS: 2 + NUM_POOL_LAYERS: 4 + OUT_CHANNELS: 2 + RECON_LOSS: + NAME: l1 + RENORMALIZE_DATA: false + A2R: + META_ARCHITECTURE: UnetModel + CONSISTENCY: + AUG: + MRI_RECON: + TRANSFORMS: + - name: RandomMRIMultiShotMotion + tfms_or_gens: + - name: RandomAffine + p: 1.0 + angle: 7 + translate: 0.1 + scale: 1.0 + shear: 0 + - name: RandomAffine + p: 1.0 + angle: 0 + translate: 0.2 + scale: 1.0 + shear: 0 + - name: RandomAffine + p: 1.0 + angle: 15 + translate: 0 + scale: 1.0 + shear: 0 + nshots: 5 + trajectory: interleaved + p: 1.0 +VERSION: 1 \ No newline at end of file diff --git a/configs/mri-recon/mridata-3dfse-knee/unrolled/vortex/VORTEX_Multi_Aug_Unrolled_14_Scan.yaml b/configs/mri-recon/mridata-3dfse-knee/unrolled/vortex/VORTEX_Multi_Aug_Unrolled_14_Scan.yaml new file mode 100644 index 00000000..ad03e1d8 --- /dev/null +++ b/configs/mri-recon/mridata-3dfse-knee/unrolled/vortex/VORTEX_Multi_Aug_Unrolled_14_Scan.yaml @@ -0,0 +1,64 @@ +# Inherit fields from the unrolled.yaml config. +_BASE_: "../../templates/unrolled_multi_aug.yaml" +# DATALOADER has 14 supervised scan +DATALOADER: + SUBSAMPLE_TRAIN: + NUM_TOTAL: 14 + NUM_UNDERSAMPLED: 0 +OUTPUT_DIR: results://vortex-rm/mridata_knee_3dfse/unrolled/vortex/vortex_multi_aug_unrolled_14_scan +DESCRIPTION: + BRIEF: f"VORTEX Multi Aug Unrolled 14 Scan {AUG_TRAIN.UNDERSAMPLE.ACCELERATIONS}x mridata - loss={MODEL.RECON_LOSS.NAME}" + EXP_NAME: mridata_knee_3dfse/unrolled/vortex_multi_aug_14_scan + TAGS: + - baseline + - vortex + - 14_scan + - 16x + - mridata_knee_3dfse + - unrolled +# For no motion aug, A2R and Consistency was removed. +# For Unrolled, made META_ARCHITECTURE GeneralizedUnrolledCNN +MODEL: + META_ARCHITECTURE: VortexModel + UNROLLED: + NUM_UNROLLED_STEPS: 8 + NUM_RESBLOCKS: 2 + NUM_FEATURES: 128 + DROPOUT: 0. + CONV_BLOCK: + ACTIVATION: "relu" + NORM: "none" + ORDER: ("act", "conv") + RECON_LOSS: + NAME: l1 + RENORMALIZE_DATA: false + A2R: + META_ARCHITECTURE: GeneralizedUnrolledCNN + CONSISTENCY: + AUG: + MRI_RECON: + TRANSFORMS: + - name: RandomMRIMultiShotMotion + tfms_or_gens: + - name: RandomAffine + p: 1.0 + angle: 7 + translate: 0.1 + scale: 1.0 + shear: 0 + - name: RandomAffine + p: 1.0 + angle: 0 + translate: 0.2 + scale: 1.0 + shear: 0 + - name: RandomAffine + p: 1.0 + angle: 15 + translate: 0 + scale: 1.0 + shear: 0 + nshots: 5 + trajectory: interleaved + p: 1.0 +VERSION: 1 \ No newline at end of file From eba221ac04d52c540ebc6226d04eaf6b670bd1c2 Mon Sep 17 00:00:00 2001 From: Deepro Pasha Date: Tue, 22 Aug 2023 13:07:25 -0700 Subject: [PATCH 31/33] changed where vortex multi aug files inherit from --- .../unet/vortex/VORTEX_Multi_Aug_UNET_14_Scan.yaml | 2 +- .../unrolled/vortex/VORTEX_Multi_Aug_Unrolled_14_Scan.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/configs/mri-recon/mridata-3dfse-knee/unet/vortex/VORTEX_Multi_Aug_UNET_14_Scan.yaml b/configs/mri-recon/mridata-3dfse-knee/unet/vortex/VORTEX_Multi_Aug_UNET_14_Scan.yaml index b3fe5932..42cad231 100644 --- a/configs/mri-recon/mridata-3dfse-knee/unet/vortex/VORTEX_Multi_Aug_UNET_14_Scan.yaml +++ b/configs/mri-recon/mridata-3dfse-knee/unet/vortex/VORTEX_Multi_Aug_UNET_14_Scan.yaml @@ -1,5 +1,5 @@ # Inherit fields from the unet.yaml config. -_BASE_: "../../templates/unet_multi_aug.yaml" +_BASE_: "../../templates/unet.yaml" # DATALOADER has 14 supervised scans DATALOADER: SUBSAMPLE_TRAIN: diff --git a/configs/mri-recon/mridata-3dfse-knee/unrolled/vortex/VORTEX_Multi_Aug_Unrolled_14_Scan.yaml b/configs/mri-recon/mridata-3dfse-knee/unrolled/vortex/VORTEX_Multi_Aug_Unrolled_14_Scan.yaml index ad03e1d8..6a91553a 100644 --- a/configs/mri-recon/mridata-3dfse-knee/unrolled/vortex/VORTEX_Multi_Aug_Unrolled_14_Scan.yaml +++ b/configs/mri-recon/mridata-3dfse-knee/unrolled/vortex/VORTEX_Multi_Aug_Unrolled_14_Scan.yaml @@ -1,5 +1,5 @@ # Inherit fields from the unrolled.yaml config. -_BASE_: "../../templates/unrolled_multi_aug.yaml" +_BASE_: "../../templates/unrolled.yaml" # DATALOADER has 14 supervised scan DATALOADER: SUBSAMPLE_TRAIN: From b1dfc13c0ba5e6ce164b046aa7bc491b13f1707a Mon Sep 17 00:00:00 2001 From: Deepro Pasha Date: Tue, 22 Aug 2023 14:15:13 -0700 Subject: [PATCH 32/33] addressed PR comments --- .../unet/vortex/VORTEX_Multi_Aug_UNET_14_Scan.yaml | 14 +------------- .../vortex/VORTEX_Multi_Aug_Unrolled_14_Scan.yaml | 14 +------------- meddlr/config/defaults.py | 2 +- 3 files changed, 3 insertions(+), 27 deletions(-) diff --git a/configs/mri-recon/mridata-3dfse-knee/unet/vortex/VORTEX_Multi_Aug_UNET_14_Scan.yaml b/configs/mri-recon/mridata-3dfse-knee/unet/vortex/VORTEX_Multi_Aug_UNET_14_Scan.yaml index 42cad231..cb04e53c 100644 --- a/configs/mri-recon/mridata-3dfse-knee/unet/vortex/VORTEX_Multi_Aug_UNET_14_Scan.yaml +++ b/configs/mri-recon/mridata-3dfse-knee/unet/vortex/VORTEX_Multi_Aug_UNET_14_Scan.yaml @@ -30,7 +30,7 @@ MODEL: NUM_POOL_LAYERS: 4 OUT_CHANNELS: 2 RECON_LOSS: - NAME: l1 + NAME: k_l1 RENORMALIZE_DATA: false A2R: META_ARCHITECTURE: UnetModel @@ -46,18 +46,6 @@ MODEL: translate: 0.1 scale: 1.0 shear: 0 - - name: RandomAffine - p: 1.0 - angle: 0 - translate: 0.2 - scale: 1.0 - shear: 0 - - name: RandomAffine - p: 1.0 - angle: 15 - translate: 0 - scale: 1.0 - shear: 0 nshots: 5 trajectory: interleaved p: 1.0 diff --git a/configs/mri-recon/mridata-3dfse-knee/unrolled/vortex/VORTEX_Multi_Aug_Unrolled_14_Scan.yaml b/configs/mri-recon/mridata-3dfse-knee/unrolled/vortex/VORTEX_Multi_Aug_Unrolled_14_Scan.yaml index 6a91553a..6ffa0b92 100644 --- a/configs/mri-recon/mridata-3dfse-knee/unrolled/vortex/VORTEX_Multi_Aug_Unrolled_14_Scan.yaml +++ b/configs/mri-recon/mridata-3dfse-knee/unrolled/vortex/VORTEX_Multi_Aug_Unrolled_14_Scan.yaml @@ -30,7 +30,7 @@ MODEL: NORM: "none" ORDER: ("act", "conv") RECON_LOSS: - NAME: l1 + NAME: k_l1 RENORMALIZE_DATA: false A2R: META_ARCHITECTURE: GeneralizedUnrolledCNN @@ -46,18 +46,6 @@ MODEL: translate: 0.1 scale: 1.0 shear: 0 - - name: RandomAffine - p: 1.0 - angle: 0 - translate: 0.2 - scale: 1.0 - shear: 0 - - name: RandomAffine - p: 1.0 - angle: 15 - translate: 0 - scale: 1.0 - shear: 0 nshots: 5 trajectory: interleaved p: 1.0 diff --git a/meddlr/config/defaults.py b/meddlr/config/defaults.py index 4e975a75..ee46f85c 100644 --- a/meddlr/config/defaults.py +++ b/meddlr/config/defaults.py @@ -99,7 +99,7 @@ _C.MODEL.CONSISTENCY.LATENT_LOSS_NAME = "mag_l1" _C.MODEL.CONSISTENCY.NUM_LATENT_LAYERS = 1 # The consistency loss. -_C.MODEL.CONSISTENCY.LOSS_NAME = "l1" +_C.MODEL.CONSISTENCY.LOSS_NAME = "k_l1" # The weighting for consistency loss. _C.MODEL.CONSISTENCY.LOSS_WEIGHT = 0.1 # The weighting for latent space consistency loss. From db0c52d696ecfb39e15a062888b40787d31df451 Mon Sep 17 00:00:00 2001 From: Deepro Pasha Date: Wed, 23 Aug 2023 12:45:48 -0700 Subject: [PATCH 33/33] changed defaults.py back, added consistency loss name correction to configs --- .../unet/vortex/VORTEX_Multi_Aug_UNET_14_Scan.yaml | 1 + .../unrolled/vortex/VORTEX_Multi_Aug_Unrolled_14_Scan.yaml | 1 + meddlr/config/defaults.py | 2 +- 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/configs/mri-recon/mridata-3dfse-knee/unet/vortex/VORTEX_Multi_Aug_UNET_14_Scan.yaml b/configs/mri-recon/mridata-3dfse-knee/unet/vortex/VORTEX_Multi_Aug_UNET_14_Scan.yaml index cb04e53c..5f100619 100644 --- a/configs/mri-recon/mridata-3dfse-knee/unet/vortex/VORTEX_Multi_Aug_UNET_14_Scan.yaml +++ b/configs/mri-recon/mridata-3dfse-knee/unet/vortex/VORTEX_Multi_Aug_UNET_14_Scan.yaml @@ -49,4 +49,5 @@ MODEL: nshots: 5 trajectory: interleaved p: 1.0 + LOSS_NAME: k_l1 VERSION: 1 \ No newline at end of file diff --git a/configs/mri-recon/mridata-3dfse-knee/unrolled/vortex/VORTEX_Multi_Aug_Unrolled_14_Scan.yaml b/configs/mri-recon/mridata-3dfse-knee/unrolled/vortex/VORTEX_Multi_Aug_Unrolled_14_Scan.yaml index 6ffa0b92..0386e859 100644 --- a/configs/mri-recon/mridata-3dfse-knee/unrolled/vortex/VORTEX_Multi_Aug_Unrolled_14_Scan.yaml +++ b/configs/mri-recon/mridata-3dfse-knee/unrolled/vortex/VORTEX_Multi_Aug_Unrolled_14_Scan.yaml @@ -49,4 +49,5 @@ MODEL: nshots: 5 trajectory: interleaved p: 1.0 + LOSS_NAME: k_l1 VERSION: 1 \ No newline at end of file diff --git a/meddlr/config/defaults.py b/meddlr/config/defaults.py index ee46f85c..4e975a75 100644 --- a/meddlr/config/defaults.py +++ b/meddlr/config/defaults.py @@ -99,7 +99,7 @@ _C.MODEL.CONSISTENCY.LATENT_LOSS_NAME = "mag_l1" _C.MODEL.CONSISTENCY.NUM_LATENT_LAYERS = 1 # The consistency loss. -_C.MODEL.CONSISTENCY.LOSS_NAME = "k_l1" +_C.MODEL.CONSISTENCY.LOSS_NAME = "l1" # The weighting for consistency loss. _C.MODEL.CONSISTENCY.LOSS_WEIGHT = 0.1 # The weighting for latent space consistency loss.