Functional initialization & motion correction#39
Conversation
kaitj
left a comment
There was a problem hiding this comment.
This will need a rebase from main to pull in all the changes + linting / formatting.
| ) | ||
|
|
||
| def motion_correction( | ||
| in_file: Path, ref_file: Path, output_prefix: str |
There was a problem hiding this comment.
I think there might be an extra tab in this line.
|
|
||
| func_out_dir = output_dir / bids(datatype="func", directory=True) | ||
|
|
||
| save_directory( |
There was a problem hiding this comment.
So would this save the directory as <directory>/sub-<subject>[/ses-<session>]/func/sub-<subject>_[ses-<session>]_desc-motion_mat/?
While nothing inherently wrong with this AFAIK with respect to bids derivatives, we would still want the files within the folder to be BIDS-esque for the end user (e.g. file names should be somewhat bids compliant). Alternatively, could just save the files directly into the output folder. Can chat more about this offline if this comment is confusing!
There was a problem hiding this comment.
Currently, inside /func, the directory is named sub-<subject>_ses-<session>_motionMatrices. The matrices are saved inside as MAT_0000, MAT_0001, etc., with a matrix for each volume (this is just the default naming from mcflirt I think). I thought saving them to a directory would clutter the /func folder a bit less, but if saving them directly into the output is better (with something like sub-<subject>_[ses-<session>]_desc-motion_mat0000), that works too!
There was a problem hiding this comment.
No I think this makes sense then. If its 1 matrix per volume having the folder is better than saving N number of matrices in the parent folder.
|
Also realized I missed your question:
We won't need to save everything, only what is important in the final output directory (@nx10 do we still have that list or I can find it later). The way I've been going about it is treating |
ba09bfa to
9f37771
Compare
|
Added unit + integration tests for this Also do we even want the transformation matrices saved after motion correction? I added it because it was in the reimplementation guide, but it's not in the output spreadsheet. @nx10 let me know if we don't need it and I'll remove from the workflow. otherwise, this should be ready for review |
|
Amazing! I'll have a closer look at this tomorrow, but re transform matrices: As far as I remember cpac/fmriprep save these as massive voxel-wise xfm nifti files which we want to avoid (as the affine transform matrices are much smaller). So I believe as you have them right now is exactly what we what we want. |
| motion_mat_dir = [ | ||
| d for d in Path(mc_result.root).iterdir() if d.is_dir() and d.suffix == ".mat" | ||
| ] | ||
|
|
||
| return SimpleNamespace( | ||
| bold=Path(mc_result.out_file), | ||
| par=Path(mc_result.par_file), | ||
| rms_rel=Path(mc_result.rmsrel_files), | ||
| rms_abs=Path(mc_result.rmsabs_files), | ||
| mat_dir=motion_mat_dir[0], | ||
| ) |
There was a problem hiding this comment.
| motion_mat_dir = [ | |
| d for d in Path(mc_result.root).iterdir() if d.is_dir() and d.suffix == ".mat" | |
| ] | |
| return SimpleNamespace( | |
| bold=Path(mc_result.out_file), | |
| par=Path(mc_result.par_file), | |
| rms_rel=Path(mc_result.rmsrel_files), | |
| rms_abs=Path(mc_result.rmsabs_files), | |
| mat_dir=motion_mat_dir[0], | |
| ) | |
| motion_mat_dir = None | |
| for d in Path(mc_result.root).iterdir(): | |
| if d.is_dir() and d.suffix == ".mat": | |
| motion_mat_dir = d | |
| break | |
| return SimpleNamespace( | |
| bold=Path(mc_result.out_file), | |
| par=Path(mc_result.par_file), | |
| rms_rel=Path(mc_result.rmsrel_files), | |
| rms_abs=Path(mc_result.rmsabs_files), | |
| mat_dir=motion_mat_dir, | |
| ) |
Two minor suggestions here:
- Break out of the loop once the first matrix is found; this step is probably quite quick anyways even without the break, but 🤷♂️
- This change also ensures there is not an index error if the
motion_mat_dirlist is empty. That being said, we probably want to raise some sort of error if there is no corresponding directory?
This is only true if the transforms are only linear (saving the affines only; which I think they were, but we should double check) |
Co-authored-by: Jason Kai <21226986+kaitj@users.noreply.github.com>
|
Once you've rebased/merged main and addressed all previous issues, mark the branch as ready for review and I'll take a look. |
d361182 to
e51a1ea
Compare
Coverage Report
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
These tests dont actually test any functions in this codebase, please remove.
| new_info = afni.v_3dinfo(dataset=[truncated_bold.output_file], nv=True) | ||
| assert int(new_info.info[0]) == original_count - start_tr |
There was a problem hiding this comment.
Just use nibabel here to read and inspect the header
There was a problem hiding this comment.
Same goes for all instances of afni.v_3dinfo below
| return SimpleNamespace( | ||
| bold=Path(mc_result.out_file), | ||
| par=Path(mc_result.par_file), | ||
| rms_rel=Path(mc_result.rmsrel_files), | ||
| rms_abs=Path(mc_result.rmsabs_files), | ||
| mat_dir=motion_mat_dir, | ||
| ) |
There was a problem hiding this comment.
Use a NamedTuple - I know there are more instances of SimpleNamespace used like this in the codebase right now, its on my todo list to remove them. SimpleNamespace is really more intended for dynamic output patterns and is quite bad for static type checking
| def save_directory(in_dir: str | Path, out_dir: str | Path, name: str) -> Path: | ||
| """Save a directory by copying it to a new location. | ||
|
|
||
| Args: | ||
| in_dir: Path to directory to copy. | ||
| out_dir: Path to destination directory. | ||
| name: BIDS compliant name for the directory. | ||
| """ | ||
| in_dir = Path(in_dir) | ||
| target = Path(out_dir) / name | ||
|
|
||
| if not in_dir.is_dir(): | ||
| raise ValueError(f"Input path {in_dir} is not a directory.") | ||
|
|
||
| shutil.copytree(in_dir, target, dirs_exist_ok=True) | ||
| return target |
There was a problem hiding this comment.
Why do I need this when I could just use shutil.copytree ? for the input directory check?
| testpaths = ["tests"] | ||
| markers = [ | ||
| "unit: Fast (<1s) unit tests (e.g. utilities, BIDS parsing, file operations", | ||
| "unit: Fast (<1s) unit tests (e.g. utilities, BIDS parsing, file operations)", |
There was a problem hiding this comment.
| "unit: Fast (<1s) unit tests (e.g. utilities, BIDS parsing, file operations)", | |
| "unit: Fast (<1s) unit tests (e.g. utilities, BIDS parsing, file operations", |
Correct, but unrelated change - separate PR
| Returns: | ||
| AFNI 3dcalc output object. | ||
| """ | ||
| total_vols = afni.v_3dinfo(dataset=[in_file], nv=True) |
There was a problem hiding this comment.
Use nibabel to read the header
| motion_mat_dir = None | ||
| for d in Path(mc_result.root).iterdir(): | ||
| if d.is_dir() and d.suffix == ".mat": | ||
| motion_mat_dir = d | ||
| break |
There was a problem hiding this comment.
I dont think we need to scan here, better match the output file directly with something like Path(mc_result.root) / f"{output_prefix}.mat"
There was a problem hiding this comment.
(if need be hardcode the prefix it produces)
| from rbc.core import functional | ||
| from rbc.core.common import reorient |
| ) | ||
|
|
||
|
|
||
| def scale(in_file: Path, scale_factor: float = 0.1) -> afni.V3drefitOutputs: |
There was a problem hiding this comment.
Rename to something more descriptive, e.g. scale_bold
4aa30a9 to
92866a8
Compare
702e376 to
4ff0e13
Compare
* add nibabel and fix syntax in pyproject.toml * parantheses in pyproject.toml * Functional initialization & motion correction (#39)
Starting functional workflow implementation addressing #16 & #17 -- sets up core initialization and motion reference + correction. Still need to add testing for these.
Currently, the workflow saves all intermediate outputs but I'm assuming we don't need all of that saved?