-
Notifications
You must be signed in to change notification settings - Fork 78
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
MIRIAD converter to BIDS #1290
base: dev
Are you sure you want to change the base?
MIRIAD converter to BIDS #1290
Changes from 2 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -26,6 +26,7 @@ class StudyName(str, Enum): | |||||
OASIS3 = "OASIS3" | ||||||
UKB = "UKB" | ||||||
IXI = "IXI" | ||||||
MIRIAD = "MIRIAD" | ||||||
|
||||||
|
||||||
BIDS_VALIDATOR_CONFIG = { | ||||||
|
@@ -93,7 +94,8 @@ def bids_id_factory(study: StudyName) -> Type[BIDSSubjectID]: | |||||
return HABSBIDSSubjectID | ||||||
if study == StudyName.IXI: | ||||||
return IXIBIDSSubjectID | ||||||
|
||||||
if study == StudyName.MIRIAD: | ||||||
return MIRIADBIDSSubjectID | ||||||
|
||||||
class ADNIBIDSSubjectID(BIDSSubjectID): | ||||||
"""Implementation for ADNI of the BIDSSubjectIDClass, allowing to go from the source id XXX_S_XXXX | ||||||
|
@@ -319,6 +321,29 @@ def from_original_study_id(cls, study_id: str) -> str: | |||||
def to_original_study_id(self) -> str: | ||||||
return str(self.replace("sub-", "")) | ||||||
|
||||||
class MIRIADBIDSSubjectID(BIDSSubjectID): | ||||||
"""Implementation for MIRIAD of the BIDSSubjectIDClass, allowing to go from the source id MIRIAD### | ||||||
to a bids id sub-MIRAD### and reciprocally.""" | ||||||
|
||||||
def validate(self, value: str) -> str: | ||||||
if re.fullmatch(r"sub-MIRIAD\d{3}", value): | ||||||
return value | ||||||
raise ValueError( | ||||||
f"BIDS MIRIAD subject ID {value} is not properly formatted. " | ||||||
"Expecting a 'sub-MIRIAD' format." | ||||||
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.
Suggested change
|
||||||
) | ||||||
|
||||||
@classmethod | ||||||
def from_original_study_id(cls, study_id: str) -> str: | ||||||
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. As written you should have ids of 3 digits always (so there would need to be a padding if it can be 1 or 2). Though I don't think you are using that class for now |
||||||
if re.fullmatch(r"MIRIAD\d{3}", study_id): | ||||||
return f"sub-{study_id}" | ||||||
raise ValueError( | ||||||
f"Raw MIRIAD subject ID {study_id} is not properly formatted. " | ||||||
"Expecting a 'Y' format." | ||||||
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.
Suggested change
|
||||||
) | ||||||
|
||||||
def to_original_study_id(self) -> str: | ||||||
return str(self.replace("sub-", "")) | ||||||
|
||||||
# -- Methods for the clinical data -- | ||||||
def create_participants_df( | ||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
from .miriad_to_bids import convert | ||
|
||
__all__ = ["convert"] |
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,64 @@ | ||||||||||||||||||||||||||||||
"""Convert MIRIAD dataset to BIDS.""" | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
from pathlib import Path | ||||||||||||||||||||||||||||||
from typing import Optional | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
import os | ||||||||||||||||||||||||||||||
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.
Suggested change
Having consistent Path objects (see below !) allows to use only path lib, so we don't need os anymore |
||||||||||||||||||||||||||||||
import shutil | ||||||||||||||||||||||||||||||
import csv | ||||||||||||||||||||||||||||||
from clinica.utils.filemanip import UserProvidedPath | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
# Paths | ||||||||||||||||||||||||||||||
input_dir = 'your_input_directory' # Where the original data is located | ||||||||||||||||||||||||||||||
output_dir = 'your_output_directory' # Where the BIDS data will be written | ||||||||||||||||||||||||||||||
csv_file = 'metadata.csv' # Metadata CSV file to store extracted information | ||||||||||||||||||||||||||||||
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.
Suggested change
You can directly use the paths given to the
And if you use 'metadata.csv' only once I would not define a variable specifically for this, you can use it as is inside |
||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
def convert( | ||||||||||||||||||||||||||||||
path_to_dataset: UserProvidedPath, | ||||||||||||||||||||||||||||||
bids_dir: UserProvidedPath, | ||||||||||||||||||||||||||||||
path_to_clinical: UserProvidedPath, | ||||||||||||||||||||||||||||||
subjects: Optional[UserProvidedPath] = None, | ||||||||||||||||||||||||||||||
n_procs: Optional[int] = 1, | ||||||||||||||||||||||||||||||
**kwargs, | ||||||||||||||||||||||||||||||
): | ||||||||||||||||||||||||||||||
"""_summary_ | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
Args: | ||||||||||||||||||||||||||||||
path_to_dataset (UserProvidedPath): _description_ | ||||||||||||||||||||||||||||||
bids_dir (UserProvidedPath): _description_ | ||||||||||||||||||||||||||||||
path_to_clinical (UserProvidedPath): _description_ | ||||||||||||||||||||||||||||||
subjects (Optional[UserProvidedPath], optional): _description_. Defaults to None. | ||||||||||||||||||||||||||||||
n_procs (Optional[int], optional): _description_. Defaults to 1. | ||||||||||||||||||||||||||||||
""" | ||||||||||||||||||||||||||||||
from clinica.iotools.converters.miriad_to_bids.miriad_to_bids_utils import create_bids_structure | ||||||||||||||||||||||||||||||
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.
Suggested change
First step there would be to check the inputs of the convert function :
Makes me wonder though, you do not plan on using any clinical data as an input ? |
||||||||||||||||||||||||||||||
# Prepare CSV | ||||||||||||||||||||||||||||||
with open(csv_file, 'w', newline='') as csvfile: | ||||||||||||||||||||||||||||||
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.
Suggested change
There I am assuming you want to write the .csv at the root of the new bids directory but that might not be what you want. Maybe you could use path_to_clinical here |
||||||||||||||||||||||||||||||
csvwriter = csv.writer(csvfile) | ||||||||||||||||||||||||||||||
csvwriter.writerow(['cohort', 'subject_id', 'diagnosis', 'gender', 'session', 'input_file', 'output_file']) | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
# Traverse the input directory | ||||||||||||||||||||||||||||||
for root, dirs, files in os.walk(path_to_dataset): | ||||||||||||||||||||||||||||||
for file in files: | ||||||||||||||||||||||||||||||
if file.endswith('.nii'): | ||||||||||||||||||||||||||||||
# Example: miriad_215_AD_M_01_MR_1.nii | ||||||||||||||||||||||||||||||
parts = file.split('_') | ||||||||||||||||||||||||||||||
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.
Suggested change
|
||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
# Extract information from filename | ||||||||||||||||||||||||||||||
cohort = parts[0] # miriad | ||||||||||||||||||||||||||||||
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.
Suggested change
If it is always the same as I assumed above in my regex you do not need to retrieve the info anymore |
||||||||||||||||||||||||||||||
subject_id = parts[1] # 215 | ||||||||||||||||||||||||||||||
diagnosis = parts[2] # AD (Alzheimer's) or HC (Healthy Control) | ||||||||||||||||||||||||||||||
gender = parts[3] # M or F | ||||||||||||||||||||||||||||||
session = parts[4] # Session number | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
# Full path of input file | ||||||||||||||||||||||||||||||
input_file = os.path.join(root, file) | ||||||||||||||||||||||||||||||
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.
Suggested change
You can use |
||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
# Create BIDS structure and move the file | ||||||||||||||||||||||||||||||
create_bids_structure(subject_id, session, cohort, diagnosis, gender, input_file, path_to_dataset, bids_dir, path_to_clinical) | ||||||||||||||||||||||||||||||
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.
Suggested change
|
||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
# Write the extracted information to CSV | ||||||||||||||||||||||||||||||
bids_filename = f"sub-{subject_id}_ses-{session}_T1w.nii.gz" | ||||||||||||||||||||||||||||||
output_file = os.path.join(f"sub-{subject_id}", f"ses-{session}", 'anat', bids_filename) | ||||||||||||||||||||||||||||||
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.
Suggested change
|
||||||||||||||||||||||||||||||
csvwriter.writerow([cohort, subject_id, diagnosis, gender, session, input_file, output_file]) | ||||||||||||||||||||||||||||||
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. Do you want the "subject_id" to be MIRIADXXX or XXX ? |
||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
print("Conversion to BIDS format and metadata extraction completed.") |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
from os import PathLike | ||
from typing import Optional | ||
|
||
import click | ||
|
||
from clinica.iotools.converters import cli_param | ||
|
||
|
||
@click.command(name="miriad-to-bids") | ||
@cli_param.dataset_directory | ||
@cli_param.bids_directory | ||
@cli_param.clinical_data_directory | ||
@cli_param.subjects_list | ||
def cli( | ||
dataset_directory: PathLike, | ||
bids_directory: PathLike, | ||
clinical_data_directory: PathLike, | ||
subjects_list: Optional[PathLike] = None, | ||
) -> None: | ||
"""MIRIAD to BIDS converter.""" | ||
from .miriad_to_bids import convert | ||
|
||
convert(dataset_directory, bids_directory, clinical_data_directory, subjects_list) | ||
|
||
|
||
if __name__ == "__main__": | ||
cli() |
Original file line number | Diff line number | Diff line change | ||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,30 @@ | ||||||||||||||||||
import os | ||||||||||||||||||
import shutil | ||||||||||||||||||
|
||||||||||||||||||
# Helper function to create BIDS folders and move files | ||||||||||||||||||
def create_bids_structure(subject_id, session, cohort, diagnosis, gender, input_file, path_to_dataset, output_dir, path_to_clinical | ||||||||||||||||||
): | ||||||||||||||||||
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.
Suggested change
|
||||||||||||||||||
|
||||||||||||||||||
"""_summary_ | ||||||||||||||||||
|
||||||||||||||||||
Args: | ||||||||||||||||||
session (_type_): _description_ | ||||||||||||||||||
cohort (_type_): _description_ | ||||||||||||||||||
diagnosis (_type_): _description_ | ||||||||||||||||||
gender (_type_): _description_ | ||||||||||||||||||
input_file (_type_): _description_ | ||||||||||||||||||
output_dir (_type_): _description_ | ||||||||||||||||||
path_to_dataset (_type_, optional): _description_. Defaults to None, n_procs: Optional[int] = 1, **kwargs, ):#subject_id. | ||||||||||||||||||
""" | ||||||||||||||||||
sub_id = f"sub-MIRIAD{subject_id}" | ||||||||||||||||||
ses_id = f"ses-{session}" | ||||||||||||||||||
|
||||||||||||||||||
# Create output directory for this subject/session | ||||||||||||||||||
anat_dir = os.path.join(output_dir, sub_id, ses_id, 'anat') | ||||||||||||||||||
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.
Suggested change
|
||||||||||||||||||
os.makedirs(anat_dir, exist_ok=True) | ||||||||||||||||||
|
||||||||||||||||||
# Destination filename in BIDS format | ||||||||||||||||||
bids_filename = f"{sub_id}_{ses_id}_T1w.nii.gz" | ||||||||||||||||||
|
||||||||||||||||||
# Copy and rename the file to BIDS format | ||||||||||||||||||
shutil.copy(input_file, os.path.join(anat_dir, bids_filename)) | ||||||||||||||||||
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.
Suggested change
|
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.