Skip to content

topup-eddy preproc script#153

Merged
MinooG merged 3 commits intopreproc-mainfrom
mg-topup
Apr 1, 2026
Merged

topup-eddy preproc script#153
MinooG merged 3 commits intopreproc-mainfrom
mg-topup

Conversation

@MinooG
Copy link
Copy Markdown

@MinooG MinooG commented Apr 1, 2026

preprocessing files for batch running topup and eddy

Copilot AI review requested due to automatic review settings April 1, 2026 15:48
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a Windows-hosted preprocessing workflow to batch-run FSL topup and eddy via WSL, including input discovery, per-patient looping, and WSL file staging/copy-back.

Changes:

  • Introduces a TOPED helper class that copies inputs into a WSL workdir and runs topup/eddy via wsl.exe.
  • Adds a runner script that scans patient folders (plus optional common_files), validates required inputs, and executes TOPUP→EDDY per patient.
  • Adds configurable filename identifiers and parameter dictionaries for TOPUP/EDDY flags.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 9 comments.

File Description
src/original/MG_LU/TOPED_fsl_runner.py Batch driver: config + file discovery + validation + per-patient TOPUP/EDDY execution.
src/original/MG_LU/TOPED_fsl_commands.py WSL/FSL command wrapper: file copy helpers and topup/eddy subprocess execution.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

# HELPERS - filenames and folders etc.
def split_nifti_gz(self, filename: str) -> Tuple[str, str]:
match = re.match(r"^(.*)(\.nii\.gz)$", filename)
return match.group(1), match.group(2) if match else os.path.splitext(filename)
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

split_nifti_gz will raise AttributeError when the filename is not .nii.gz because match.group(1) is evaluated even when match is None (ternary only applies to the second tuple element). Restructure this to handle the non-match case before accessing match.group(...) (and ensure it still returns (base, ext) for both .nii.gz and other extensions).

Suggested change
return match.group(1), match.group(2) if match else os.path.splitext(filename)
if match:
return match.group(1), match.group(2)
base, ext = os.path.splitext(filename)
return base, ext

Copilot uses AI. Check for mistakes.
Comment on lines +223 to +227
if fep is not None: cmd += f"--fep "
if nvoxhp is not None: cmd += f"--nvoxhp={nvoxhp} "
if ff is not None: cmd += f"--ff={ff} "
if dont_sep_offs_move is not None: cmd += f"--dont_sep_offs_move "
if dont_peas is not None: cmd += f"--dont_peas "
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Several boolean-style Eddy flags are appended when the parameter is merely "not None". If a caller passes False, the flag will still be added, unintentionally enabling it. For flags like --fep, --dont_sep_offs_move, and --dont_peas, only add the flag when the value is True (and omit it for False/None).

Suggested change
if fep is not None: cmd += f"--fep "
if nvoxhp is not None: cmd += f"--nvoxhp={nvoxhp} "
if ff is not None: cmd += f"--ff={ff} "
if dont_sep_offs_move is not None: cmd += f"--dont_sep_offs_move "
if dont_peas is not None: cmd += f"--dont_peas "
if fep is True: cmd += f"--fep "
if nvoxhp is not None: cmd += f"--nvoxhp={nvoxhp} "
if ff is not None: cmd += f"--ff={ff} "
if dont_sep_offs_move is True: cmd += f"--dont_sep_offs_move "
if dont_peas is True: cmd += f"--dont_peas "

Copilot uses AI. Check for mistakes.
Comment on lines +229 to +235
if repol is not None: cmd += f"--repol "
if ol_nstd is not None: cmd += f"--ol_nstd={ol_nstd} "
if ol_nvox is not None: cmd += f"--ol_nvox={ol_nvox} "
if ol_type is not None: cmd += f"--ol_type={ol_type} "
if ol_ss is not None: cmd += f"--ol_ss={ol_ss} "
if ol_pos is not None: cmd += f"--ol_pos "
if ol_sqr is not None: cmd += f"--ol_sqr "
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same issue as above for Eddy boolean flags: --repol, --ol_pos, and --ol_sqr are added when the value is False (since the check is is not None). This makes it impossible to explicitly disable these via config. Change these conditionals to only emit the flag when the value is True.

Suggested change
if repol is not None: cmd += f"--repol "
if ol_nstd is not None: cmd += f"--ol_nstd={ol_nstd} "
if ol_nvox is not None: cmd += f"--ol_nvox={ol_nvox} "
if ol_type is not None: cmd += f"--ol_type={ol_type} "
if ol_ss is not None: cmd += f"--ol_ss={ol_ss} "
if ol_pos is not None: cmd += f"--ol_pos "
if ol_sqr is not None: cmd += f"--ol_sqr "
if repol: cmd += f"--repol "
if ol_nstd is not None: cmd += f"--ol_nstd={ol_nstd} "
if ol_nvox is not None: cmd += f"--ol_nvox={ol_nvox} "
if ol_type is not None: cmd += f"--ol_type={ol_type} "
if ol_ss is not None: cmd += f"--ol_ss={ol_ss} "
if ol_pos: cmd += f"--ol_pos "
if ol_sqr: cmd += f"--ol_sqr "

Copilot uses AI. Check for mistakes.
ol_nstd: Optional[float] = None,
ol_nvox: Optional[int] = None,
ol_type: Optional[str] = None,
ol_ss: Optional[int] = None,
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ol_ss is documented/used as a string option (e.g. "sw"/"pooled"), but the type hint is Optional[int]. Update the annotation (and any downstream validation) to match the actual expected values to avoid misleading callers and static analysis.

Suggested change
ol_ss: Optional[int] = None,
ol_ss: Optional[str] = None,

Copilot uses AI. Check for mistakes.


# main loops start HERE - runs TOPUP and Eddy for each patient folder in main_dir
common_dir = (os.path.join(MAIN_DIR, COMMON_FOLDER_NAME) if COMMON_FOLDER_NAME else None)
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

common_dir is constructed even if the folder doesn't exist; later calls like find_file(common_dir, ...) will crash with FileNotFoundError from os.listdir. Add an existence check (and either set common_dir=None with a warning, or raise a clear error) when COMMON_FOLDER_NAME is set but the directory is missing.

Suggested change
common_dir = (os.path.join(MAIN_DIR, COMMON_FOLDER_NAME) if COMMON_FOLDER_NAME else None)
common_dir = None
if COMMON_FOLDER_NAME:
candidate_common_dir = os.path.join(MAIN_DIR, COMMON_FOLDER_NAME)
if os.path.isdir(candidate_common_dir):
common_dir = candidate_common_dir
else:
print(f"Warning: common folder '{candidate_common_dir}' does not exist. Proceeding without common files.")

Copilot uses AI. Check for mistakes.
Comment on lines +64 to +71
"""CONFIGURATION - Adjust these parameters according to your data""" # change things in this section!

#main directory in WINDOWS containing patient folders and common folder.
MAIN_DIR = r"C:\path\to\your\main_dir" # replace with path to your main_dir.

# folder inside main_dir that contains files which are common for all patients (e.g. acqparams, bvecs, bvals, index, slspec).
COMMON_FOLDER_NAME = "common_files" # replace with your path, or set to None if there is no common folder.
# The script looks in the patient folders first. if it does not find the required files there, it looks in the common folder.
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This script executes substantial work at import time (configuration + file checks + processing loop). Wrap the runnable portion in a main() function and guard it with if __name__ == "__main__": so the module can be imported (e.g., for reuse or testing) without side effects.

Copilot uses AI. Check for mistakes.
Comment on lines +248 to +250
if cnr_maps is not None: cmd += f"--cnr_maps "
if residuals is not None: cmd += f"--residuals "
if data_is_shelled is not None: cmd += f"--data_is_shelled "
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

More Eddy boolean flags are treated as enabled whenever the value is not None: --cnr_maps, --residuals, and --data_is_shelled will be added even if the caller passes False. These should only be included when the value is True (omit for False/None).

Suggested change
if cnr_maps is not None: cmd += f"--cnr_maps "
if residuals is not None: cmd += f"--residuals "
if data_is_shelled is not None: cmd += f"--data_is_shelled "
if cnr_maps is True: cmd += f"--cnr_maps "
if residuals is True: cmd += f"--residuals "
if data_is_shelled is True: cmd += f"--data_is_shelled "

Copilot uses AI. Check for mistakes.
"\n Consider renaming your files to contain the specified keyword or adjust the IDENTIFIERS dict accordingly. " \
"\n TOPUP/Eddy will run only for the 'OK' cases.")
else:
print("\n File check passed — all patients have all compulosry inputs.")
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo in user-facing output: "compulosry" → "compulsory".

Suggested change
print("\n File check passed — all patients have all compulosry inputs.")
print("\n File check passed — all patients have all compulsory inputs.")

Copilot uses AI. Check for mistakes.

# TOPUP compulsory inputs
if not b0_path: issues.append("TOPUP: cannot find b0 file")
if not acqp_path: issues.append("TOPUP: cannot find acqisition parameters file")
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo in message: "acqisition" → "acquisition".

Suggested change
if not acqp_path: issues.append("TOPUP: cannot find acqisition parameters file")
if not acqp_path: issues.append("TOPUP: cannot find acquisition parameters file")

Copilot uses AI. Check for mistakes.
@MinooG MinooG merged commit 55a12a1 into preproc-main Apr 1, 2026
0 of 8 checks passed
@MinooG MinooG deleted the mg-topup branch April 1, 2026 17:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants