-
Notifications
You must be signed in to change notification settings - Fork 3
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Caps generator #9
base: main
Are you sure you want to change the base?
Changes from all commits
a55ac7e
d4322be
6b0c5cf
b136dd2
93e48a8
f5e6c8b
853c569
ae69b47
7824137
e69fdc0
59a7c55
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
from .generator import CAPSGenerator |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
from pathlib import Path | ||
from typing import Optional | ||
|
||
from clinicaio.models.bids_entities import ( | ||
DescriptionEntity, | ||
ResolutionEntity, | ||
SessionEntity, | ||
SpaceEntity, | ||
SubjectEntity, | ||
SUVREntity, | ||
TracerEntity, | ||
) | ||
from clinicaio.models.caps import Description, Extension, Suffix | ||
|
||
|
||
def _get_caps_filename( | ||
subject: int, | ||
session: int, | ||
suffix: str, | ||
extension: str, | ||
tracer: Optional[str] = None, | ||
space: Optional[str] = None, | ||
crop: bool = False, | ||
resolution: Optional[str] = None, | ||
suvr_ref_region: Optional[str] = None, | ||
) -> Path: | ||
"""Returns a BIDS-compliant filename from entity values, a suffix and a extension.""" | ||
entities_list = [] | ||
for entity in [ # order matters | ||
SubjectEntity(subject), | ||
SessionEntity(session), | ||
TracerEntity(tracer) if tracer else None, | ||
SpaceEntity(space) if space else None, | ||
DescriptionEntity(Description.CROP) if crop else None, | ||
ResolutionEntity(resolution) if resolution else None, | ||
SUVREntity(suvr_ref_region) if suvr_ref_region else None, | ||
Suffix(suffix).value, | ||
]: | ||
if entity is not None: | ||
entities_list.append(entity) | ||
|
||
return Path("_".join(entities_list)).with_suffix(Extension(extension).value) |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,67 @@ | ||||||
from pathlib import Path | ||||||
from typing import Union | ||||||
|
||||||
import nibabel as nib | ||||||
import numpy as np | ||||||
from scipy.io import savemat | ||||||
|
||||||
from clinicaio.models.bids_entities import SessionEntity, SubjectEntity | ||||||
from clinicaio.models.caps import Extension, Resolution, Space, Suffix | ||||||
|
||||||
from .filename import _get_caps_filename | ||||||
|
||||||
|
||||||
def _build_flair_linear( | ||||||
root: Union[str, Path], subject: int, session: int, crop: bool = True | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is a private function called by the public
Suggested change
The validation would be done in There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes indeed, at first those functions were public so the validation was made in there, but if they are private, I do agree that it should be done in |
||||||
): | ||||||
""" | ||||||
Simulates flair-linear by creating fake output files in `root`. | ||||||
""" | ||||||
dummy_nifti_img = nib.Nifti1Image(np.ones((1, 1, 1)).astype(np.int8), np.eye(4)) | ||||||
dummy_mat = { | ||||||
"AffineTransform_double_3_3": np.ones((1, 1)).astype(np.int8), | ||||||
"fixed": np.ones((1, 1)).astype(np.int8), | ||||||
} | ||||||
|
||||||
space = Space.MNI | ||||||
resolution = Resolution.ONE | ||||||
directory = ( | ||||||
Path(root) | ||||||
/ "subjects" | ||||||
/ SubjectEntity(subject) | ||||||
/ SessionEntity(session) | ||||||
/ "flair_linear" | ||||||
) | ||||||
directory.mkdir(parents=True, exist_ok=True) | ||||||
|
||||||
uncropped_file = directory / _get_caps_filename( | ||||||
subject, | ||||||
session, | ||||||
space=space, | ||||||
resolution=resolution, | ||||||
suffix=Suffix.FLAIR, | ||||||
extension=Extension.NIIGZ, | ||||||
) | ||||||
nib.save(dummy_nifti_img, uncropped_file) | ||||||
|
||||||
mat_file = directory / _get_caps_filename( | ||||||
subject, | ||||||
session, | ||||||
space=space, | ||||||
resolution=resolution, | ||||||
suffix=Suffix.AFFINE, | ||||||
extension=Extension.MAT, | ||||||
) | ||||||
savemat(mat_file, dummy_mat) | ||||||
|
||||||
if crop: | ||||||
cropped_file = directory / _get_caps_filename( | ||||||
subject, | ||||||
session, | ||||||
space=space, | ||||||
crop=True, | ||||||
resolution=resolution, | ||||||
suffix=Suffix.FLAIR, | ||||||
extension=Extension.NIIGZ, | ||||||
) | ||||||
nib.save(dummy_nifti_img, cropped_file) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
import shutil | ||
from pathlib import Path | ||
from typing import List, Union | ||
|
||
from clinicaio.models.bids_entities import SessionEntity, SubjectEntity | ||
from clinicaio.models.caps import Pipeline | ||
|
||
from .flair_linear import _build_flair_linear | ||
from .pet_linear import _build_pet_linear | ||
from .t1_linear import _build_t1_linear | ||
|
||
|
||
class CAPSGenerator: | ||
""" | ||
To build fake CAPS. A CAPSGenerator simulates preprocessing pipelines | ||
by creating fake output files. | ||
|
||
Parameters | ||
---------- | ||
directory : Union[str, Path] | ||
the directory of the CAPS. | ||
""" | ||
|
||
def __init__(self, directory: Union[str, Path]) -> None: | ||
self.dir = Path(directory) | ||
|
||
def build_pipeline( | ||
self, | ||
pipeline: Union[str, Pipeline], | ||
subjects: List[int], | ||
sessions: List[int], | ||
**kwargs, | ||
) -> None: | ||
""" | ||
Simulates a preprocessing pipeline for every subject in `subjects` and every session | ||
in `sessions`. | ||
|
||
Parameters | ||
---------- | ||
pipeline : Union[str, Pipeline] | ||
the pipeline to simulate. Supported pipelines are `t1-linear`, | ||
`flair-linear` and `pet-linear`. | ||
subjects : List[int] | ||
the list of subject IDs | ||
sessions : List[int] | ||
the list of session IDs. | ||
**kwargs | ||
any argument accepted by the pipeline: | ||
- `t1_linear`: `crop`; | ||
- `flair_linear`: `crop`; | ||
- `pet-linear`: `tracer`, `suvr_ref_region`, `crop` and `save_pet_in_t1w_space`. | ||
|
||
Raises | ||
------ | ||
ValueError | ||
if `pipeline` is not in `t1-linear`, `flair-linear` and `pet-linear`. | ||
""" | ||
pipeline = Pipeline(pipeline) | ||
if pipeline == Pipeline.T1_LINEAR: | ||
builder = _build_t1_linear | ||
elif pipeline == Pipeline.FLAIR_LINEAR: | ||
builder = _build_flair_linear | ||
elif pipeline == Pipeline.PET_LINEAR: | ||
builder = _build_pet_linear | ||
else: | ||
raise ValueError(f"pipeline {pipeline} is not yet implemented.") | ||
|
||
for subject in subjects: | ||
for session in sessions: | ||
builder(self.dir, subject, session, **kwargs) | ||
|
||
def remove_pipeline( | ||
self, pipeline: Union[str, Pipeline], subject: int, session: int | ||
) -> None: | ||
""" | ||
Removes a preprocessing pipeline for a specific (subject, session). | ||
|
||
Parameters | ||
---------- | ||
pipeline : Union[str, Pipeline] | ||
the pipeline to remove. Supported pipelines are `t1-linear`, | ||
`flair-linear` and `pet-linear`. | ||
subject : int | ||
the subject ID. | ||
session : int | ||
the session ID. | ||
|
||
Raises | ||
------ | ||
ValueError | ||
if `pipeline` is not in `t1-linear`, `flair-linear` and `pet-linear`. | ||
""" | ||
pipeline = Pipeline(pipeline) | ||
if pipeline == Pipeline.T1_LINEAR: | ||
directory = "t1_linear" | ||
elif pipeline == Pipeline.FLAIR_LINEAR: | ||
directory = "flair_linear" | ||
elif pipeline == Pipeline.PET_LINEAR: | ||
directory = "pet_linear" | ||
else: | ||
raise ValueError(f"pipeline {pipeline} is not yet implemented.") | ||
|
||
full_dir = ( | ||
self.dir | ||
/ "subjects" | ||
/ SubjectEntity(subject) | ||
/ SessionEntity(session) | ||
/ directory | ||
) | ||
try: | ||
shutil.rmtree(full_dir) | ||
except FileNotFoundError: # there is not such subject/session for this pipeline | ||
pass |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
from pathlib import Path | ||
from typing import Union | ||
|
||
import nibabel as nib | ||
import numpy as np | ||
from scipy.io import savemat | ||
|
||
from clinicaio.models.bids_entities import SessionEntity, SubjectEntity | ||
from clinicaio.models.caps import Extension, Resolution, Space, Suffix | ||
from clinicaio.models.pet import SUVRReferenceRegion, Tracer | ||
|
||
from .filename import _get_caps_filename | ||
|
||
|
||
def _build_pet_linear( | ||
root: Union[str, Path], | ||
subject: int, | ||
session: int, | ||
tracer: str = Tracer.FDG, | ||
suvr_ref_region: str = SUVRReferenceRegion.PONS, | ||
crop: bool = True, | ||
save_pet_in_t1w_space: bool = False, | ||
): | ||
""" | ||
Simulates pet-linear by creating fake output files in `root`. | ||
""" | ||
dummy_nifti_img = nib.Nifti1Image(np.ones((1, 1, 1)).astype(np.int8), np.eye(4)) | ||
dummy_mat = { | ||
"AffineTransform_double_3_3": np.ones((1, 1)).astype(np.int8), | ||
"fixed": np.ones((1, 1)).astype(np.int8), | ||
} | ||
|
||
resolution = Resolution.ONE | ||
trc = Tracer(tracer) | ||
suvr = SUVRReferenceRegion(suvr_ref_region) | ||
directory = ( | ||
Path(root) | ||
/ "subjects" | ||
/ SubjectEntity(subject) | ||
/ SessionEntity(session) | ||
/ "pet_linear" | ||
) | ||
directory.mkdir(parents=True, exist_ok=True) | ||
|
||
uncropped_file = directory / _get_caps_filename( | ||
subject, | ||
session, | ||
tracer=trc, | ||
space=Space.MNI, | ||
crop=False, | ||
resolution=resolution, | ||
suvr_ref_region=suvr, | ||
suffix=Suffix.PET, | ||
extension=Extension.NIIGZ, | ||
) | ||
nib.save(dummy_nifti_img, uncropped_file) | ||
|
||
mat_file = directory / _get_caps_filename( | ||
subject, | ||
session, | ||
tracer=trc, | ||
space=Space.T1W, | ||
suffix=Suffix.RIGID, | ||
extension=Extension.MAT, | ||
) | ||
savemat(mat_file, dummy_mat) | ||
|
||
if crop: | ||
cropped_file = directory / _get_caps_filename( | ||
subject, | ||
session, | ||
tracer=trc, | ||
space=Space.MNI, | ||
crop=True, | ||
resolution=resolution, | ||
suvr_ref_region=suvr, | ||
suffix=Suffix.PET, | ||
extension=Extension.NIIGZ, | ||
) | ||
nib.save(dummy_nifti_img, cropped_file) | ||
|
||
if save_pet_in_t1w_space: | ||
nifti_file = directory / _get_caps_filename( | ||
subject, | ||
session, | ||
tracer=trc, | ||
space=Space.T1W, | ||
suffix=Suffix.PET, | ||
extension=Extension.NIIGZ, | ||
) | ||
nib.save(dummy_nifti_img, nifti_file) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
from pathlib import Path | ||
from typing import Union | ||
|
||
import nibabel as nib | ||
import numpy as np | ||
from scipy.io import savemat | ||
|
||
from clinicaio.models.bids_entities import SessionEntity, SubjectEntity | ||
from clinicaio.models.caps import Extension, Resolution, Space, Suffix | ||
|
||
from .filename import _get_caps_filename | ||
|
||
|
||
def _build_t1_linear( | ||
root: Union[str, Path], subject: int, session: int, crop: bool = True | ||
): | ||
""" | ||
Simulates t1-linear by creating fake output files in `root`. | ||
""" | ||
dummy_nifti_img = nib.Nifti1Image(np.ones((1, 1, 1)).astype(np.int8), np.eye(4)) | ||
dummy_mat = { | ||
"AffineTransform_double_3_3": np.ones((1, 1)).astype(np.int8), | ||
"fixed": np.ones((1, 1)).astype(np.int8), | ||
} | ||
|
||
space = Space.MNI | ||
resolution = Resolution.ONE | ||
directory = ( | ||
Path(root) | ||
/ "subjects" | ||
/ SubjectEntity(subject) | ||
/ SessionEntity(session) | ||
/ "t1_linear" | ||
) | ||
directory.mkdir(parents=True, exist_ok=True) | ||
|
||
uncropped_file = directory / _get_caps_filename( | ||
subject, | ||
session, | ||
space=space, | ||
resolution=resolution, | ||
suffix=Suffix.T1W, | ||
extension=Extension.NIIGZ, | ||
) | ||
nib.save(dummy_nifti_img, uncropped_file) | ||
|
||
mat_file = directory / _get_caps_filename( | ||
subject, | ||
session, | ||
space=space, | ||
resolution=resolution, | ||
suffix=Suffix.AFFINE, | ||
extension=Extension.MAT, | ||
) | ||
savemat(mat_file, dummy_mat) | ||
|
||
if crop: | ||
cropped_file = directory / _get_caps_filename( | ||
subject, | ||
session, | ||
space=space, | ||
crop=True, | ||
resolution=resolution, | ||
suffix=Suffix.T1W, | ||
extension=Extension.NIIGZ, | ||
) | ||
nib.save(dummy_nifti_img, cropped_file) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Probably means that this function should be public
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it that uncommon to import private functions that are located in another file, but very closely (i.e. in the same module)?
What I mean is that I split
caps/generator
into several.py
files only not to have one huge script, but I would likeCAPSGenerator
to be the only public object so I turned all the rest into private objects.