Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 13 additions & 69 deletions b0realsim/label_to_chi.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,36 +6,13 @@
import json
import datetime
import git

from utils.tissue2MR import run_tissue2MR

def label_to_chi(bids_subject_dir):

bids_subject_dir = Path(bids_subject_dir)
subject = str(bids_subject_dir.stem)
merged_labels_path = bids_subject_dir / ".." / 'derivatives' / 'labels' / subject / 'anat' / (subject + '_T1w_label-all.nii.gz')


vol = nib.load(merged_labels_path.resolve())

vol_data = vol.get_fdata()

vol_data.astype(np.float64)

vol_data[vol_data==0] = 0.35
vol_data[vol_data==1] = -9.05
vol_data[vol_data==2] = -2
vol_data[vol_data==3] = -2
vol_data[vol_data==4] = -4.2
vol_data[vol_data==5] = -4.2
vol_data[vol_data==6] = -4.2
vol_data[vol_data==56] = -9.04
vol_data[vol_data==60] = -9.05
vol_data[vol_data==91] = -11
vol_data[vol_data==92] = -11
vol_data[vol_data==93] = -9.055
vol_data[vol_data==100] = -9.055

new_volume = nib.Nifti1Image(vol_data, vol.affine, vol.header, dtype=np.float64)
merged_labels_path = bids_subject_dir / ".." / 'derivatives' /'labels' / subject / 'anat' / (subject + '_T1w_label-all.nii.gz')

# Check if the directory bids_subject_dir / ".." / 'derivatives' / subject / exists and if no, create it

Expand All @@ -47,9 +24,16 @@ def label_to_chi(bids_subject_dir):
if not os.path.exists(bids_subject_dir / ".." / 'derivatives' / subject / 'anat' ):
os.makedirs(bids_subject_dir / ".." / 'derivatives' / subject / 'anat' )

chi_file = bids_subject_dir / ".." / 'derivatives' / subject / 'anat' / (subject + '_T1w-chi.nii.gz')
chi_file = bids_subject_dir / ".." / 'derivatives' / 'b0sim' /subject / 'anat' / (subject + '_T1w-chi.nii.gz')
Copy link
Member

Choose a reason for hiding this comment

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

Thanks for updating this! Maybe a comment could be left explaining why the change (i.e. previously was made for wholespine version X, but in version Y this was updated.


nib.save(new_volume, chi_file)
vol = nib.load(merged_labels_path.resolve())
run_tissue2MR(
in_file = str(merged_labels_path.resolve()),
seg_tool = "compare_fm",
version = "ds005616",
tissue_prop = "sus",
out_file = str(chi_file)
)

# Save json

Expand All @@ -65,48 +49,8 @@ def label_to_chi(bids_subject_dir):
bids_sidecar['input file'] = str(merged_labels_path.resolve())
bids_sidecar['command'] = 'python label_to_chi.py -s ' + str(bids_subject_dir)

bids_sidecar['anatomy'] = {}
Copy link
Member

Choose a reason for hiding this comment

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

Why remove the bids sidecar that report the values that were used to generate the map from the label? I think this is valuable - especially since the values can change with new information, as you have discovered re: chi.

An alternative would maybe be to report the tissue2MR version / commit, but this makes it more difficult to actually go find the values / fetch them in the json sidecar files in a script.

bids_sidecar['anatomy']['background'] = {}
bids_sidecar['anatomy']['background']['label'] = 0
bids_sidecar['anatomy']['background']['chi'] = 0.35
bids_sidecar['anatomy']['body'] = {}
bids_sidecar['anatomy']['body']['label'] = '1'
bids_sidecar['anatomy']['body']['chi'] = -9.05
bids_sidecar['anatomy']['sinus'] = {}
bids_sidecar['anatomy']['sinus'] ['label'] = 2
bids_sidecar['anatomy']['sinus'] ['chi'] = -2
bids_sidecar['anatomy']['earcanal'] = {}
bids_sidecar['anatomy']['earcanal'] ['label'] = 3
bids_sidecar['anatomy']['earcanal']['chi'] = -2
bids_sidecar['anatomy']['trachea'] = {}
bids_sidecar['anatomy']['trachea']['label'] = 4
bids_sidecar['anatomy']['trachea']['chi'] = -4.2
bids_sidecar['anatomy']['rightlung'] = {}
bids_sidecar['anatomy']['rightlung']['label'] = 5
bids_sidecar['anatomy']['rightlung']['chi'] = -4.2
bids_sidecar['anatomy']['leftlung'] = {}
bids_sidecar['anatomy']['leftlung']['label'] = 6
bids_sidecar['anatomy']['leftlung']['chi'] = -4.2
bids_sidecar['anatomy']['brain'] = {}
bids_sidecar['anatomy']['brain']['label'] = 56
bids_sidecar['anatomy']['brain']['chi'] = -9.04
bids_sidecar['anatomy']['eyes'] = {}
bids_sidecar['anatomy']['eyes']['label'] = 60
bids_sidecar['anatomy']['eyes']['chi'] = -9.05
bids_sidecar['anatomy']['skull'] = {}
bids_sidecar['anatomy']['skull']['label'] = 91
bids_sidecar['anatomy']['skull']['chi'] = -11
bids_sidecar['anatomy']['verterbae'] = {}
bids_sidecar['anatomy']['verterbae']['label'] = 92
bids_sidecar['anatomy']['verterbae']['chi'] = -11
bids_sidecar['anatomy']['disks'] = {}
bids_sidecar['anatomy']['disks']['label'] = 93
bids_sidecar['anatomy']['disks']['chi'] = -9.055
bids_sidecar['anatomy']['canal'] = {}
bids_sidecar['anatomy']['canal']['label'] = 100
bids_sidecar['anatomy']['canal']['chi'] = -9.055

json_file = bids_subject_dir / ".." / 'derivatives' / subject / 'anat' / (subject + '_T1w-chi.json')

json_file = bids_subject_dir / ".." / 'derivatives' / 'b0sim' /subject / 'anat' / (subject + '_T1w-chi.json')
if os.path.exists(json_file):
os.remove(json_file)

Expand Down
90 changes: 90 additions & 0 deletions b0realsim/label_to_perm_cond.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import nibabel as nib
Copy link
Member

Choose a reason for hiding this comment

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

This whole file duplicates most of the code in the label2chi.py. It would be better to reuse that code; there could be one file / function that would be called label_to_property, and add a flag depending on the property you want to use (eg chi, perm_cond, etc). That way you won't need to add new files for every other new map you'll want to create in the future (eg T2, T1, etc), you may just need to change one line or two in that one function.

The other reason this is a better approach is from a review side - since there are no tests and many similar lines, I would need to go line by line to check that you properly copied / didn't accidentally change a line.

Overall though, it looks like it should work the way you intend it to, but you would know best if it works or not.

import numpy as np
import argparse
from pathlib import Path
import os
import json
import datetime
import git
from utils.tissue2MR import run_tissue2MR


def label_to_perm_cond(bids_subject_dir):

bids_subject_dir = Path(bids_subject_dir)
subject = str(bids_subject_dir.stem)
merged_labels_path = bids_subject_dir / ".." / 'derivatives' / 'labels' / subject / 'anat' / (subject + '_T1w_label-all.nii.gz')

# Check if the directory bids_subject_dir / ".." / 'derivatives' / subject / exists and if no, create it

if not os.path.exists(bids_subject_dir / ".." / 'derivatives' / subject ):
os.makedirs(bids_subject_dir / ".." / 'derivatives' / subject )

# Check if the directory bids_subject_dir / ".." / 'derivatives' / subject / 'anat' exists and if no, create it

if not os.path.exists(bids_subject_dir / ".." / 'derivatives' / subject / 'anat' ):
os.makedirs(bids_subject_dir / ".." / 'derivatives' / subject / 'anat' )

elecsim_anat_dir = bids_subject_dir / ".." / 'derivatives' / 'elecsim' / subject / 'anat'
os.makedirs(elecsim_anat_dir, exist_ok=True)

perm7t_file = bids_subject_dir / ".." / 'derivatives' / 'elecsim' / subject / 'anat' / (subject + '_T1w-perm7T.nii.gz')
chi7t_file = bids_subject_dir / ".." / 'derivatives' / 'elecsim' / subject / 'anat' / (subject + '_T1w-cond7T.nii.gz')

run_tissue2MR(
in_file = str(merged_labels_path.resolve()),
seg_tool = "compare_fm",
version = "ds005616",
tissue_prop = "perm7T",
out_file = str(perm7t_file)
)

run_tissue2MR(
in_file = str(merged_labels_path.resolve()),
seg_tool = "compare_fm",
version = "ds005616",
tissue_prop = "cond7T",
out_file = str(chi7t_file)
)
# Save json

repo = git.Repo(search_parent_directories=True)


bids_sidecar = {}
bids_sidecar['author'] = os.getenv('USERNAME')
bids_sidecar['date'] = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
bids_sidecar['script'] = str(Path(os.path.abspath(__file__)).resolve())
bids_sidecar['script source'] = repo.remotes.origin.url
bids_sidecar['script commit hash'] = repo.head.object.hexsha
bids_sidecar['input file'] = str(merged_labels_path.resolve())
bids_sidecar['command'] = 'python label_to_perm_cond.py -s ' + str(bids_subject_dir)

json_perm7t_file = bids_subject_dir / ".." / 'derivatives' / subject / 'anat' / (subject + '_T1w-perm7T.json')
json_cond7t_file = bids_subject_dir / ".." / 'derivatives' / subject / 'anat' / (subject + '_T1w-cond7T.json')
if os.path.exists(json_perm7t_file):
os.remove(json_perm7t_file)

with open(json_perm7t_file, 'w', encoding='utf-8') as f:
json.dump(bids_sidecar, f, ensure_ascii=False, indent=4)

if os.path.exists(json_cond7t_file):
os.remove(json_cond7t_file)

with open(json_cond7t_file, 'w', encoding='utf-8') as f:
json.dump(bids_sidecar, f, ensure_ascii=False, indent=4)


if __name__ == "__main__":

current_directory = os.path.dirname(os.path.abspath(__file__))

# Create an argument parser
parser = argparse.ArgumentParser(description="Process subject directory path and other arguments.")

# Add the -s argument to the parser
parser.add_argument("-s", "--bids_subject_dir", required=True, help="Path to the subject directory in BIDS format")

# Parse the arguments
args = parser.parse_args()
label_to_perm_cond(args.bids_subject_dir)
89 changes: 89 additions & 0 deletions b0realsim/step_2_1_generate_perm_cond.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
#!/bin/bash

# Find all the subjects in the bids directory and create a list of them
# This is useful for the batch processing of subjects

# Parse the command line arguments -b --bids_dir
while getopts ":b:" opt; do
case ${opt} in
b )
BIDS_DIR=$OPTARG
;;
\? )
echo "Usage: cmd [-b] bids_dir"
exit 1
;;
esac
done

# Handle Windows-style paths like E:/... or C:/...
if [[ "$BIDS_DIR" =~ ^[A-Z]:/ ]]; then
drive=$(echo $BIDS_DIR | cut -c1 | tr 'A-Z' 'a-z')
path=$(echo $BIDS_DIR | cut -c3-)
BIDS_DIR="/cygdrive/$drive/$path"
fi

# Print the bids directory to the screen
echo "BIDS directory: $BIDS_DIR"

# Check if BIDS_DIR exists
if [ ! -d "$BIDS_DIR" ]; then
echo "[ERROR] BIDS directory not found: $BIDS_DIR"
exit 1
fi

# If subjects.txt exists, remove it and create a new one
if [ -f subjects.txt ]; then
rm subjects.txt
touch subjects.txt
fi

# Find all the subjects in the bids directory
SUBJECTS=$(ls $BIDS_DIR | grep sub-)

# Remove sub-unfErssm001 and sub-unfErssm021 from the list of subjects
SUBJECTS=$(echo $SUBJECTS | sed 's/sub-unfErssm001//g' | sed 's/sub-unfErssm021//g')

echo "Subjects found:"
echo $SUBJECTS

# Create a list of the subjects
echo $SUBJECTS > subjects.txt

# Get script directory
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"

# Convert DIR to Windows path for Windows Python
WIN_DIR=$(echo "$DIR" | sed 's|/cygdrive/\([a-z]\)/|\U\1:/|')

for subject in $SUBJECTS
do
# Skip empty entries from excluded subjects
if [ -z "$subject" ]; then
continue
fi

echo "[INFO] Processing $subject"

# Convert BIDS_DIR to Windows path
WIN_BIDS_DIR=$(echo "$BIDS_DIR" | sed 's|/cygdrive/\([a-z]\)/|\U\1:/|')
Copy link
Member

Choose a reason for hiding this comment

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

Is this line needed for every OS?


# Check if subject folder exists
if [ ! -d "$WIN_BIDS_DIR/$subject" ]; then
echo "[WARNING] Subject folder not found: $WIN_BIDS_DIR/$subject, skipping..."
continue
fi

# Check if Python script exists
if [ ! -f "$WIN_DIR/label_to_perm_cond.py" ]; then
echo "[ERROR] Python script not found: $WIN_DIR/label_to_perm_cond.py"
exit 1
fi

# Run Python to generate perm/cond maps
"C:/Users/Admin/Miniconda3/python.exe" "$WIN_DIR/label_to_perm_cond.py" -s "$WIN_BIDS_DIR/$subject"
Copy link
Member

Choose a reason for hiding this comment

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

This is too hardcoded; a user without a miniconda installation or with it installed in a different username won't be able to run this script when it is hardcoded like this.

done

echo "[INFO] All subjects processed."


Empty file added b0realsim/utils/__init__.py
Empty file.
46 changes: 46 additions & 0 deletions b0realsim/utils/tissue2MR.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import subprocess
from pathlib import Path

def run_tissue2MR(
in_file: str,
seg_tool: str,
version: str,
tissue_prop: str,
out_file: str,
):
"""
Run tissue_to_MR from command line inside Python.
Parameters
----------
in_file : str
Path to input NIfTI file (.nii.gz).
seg_tool : str
The tool used to segmented the input file (argument to -s).
version : str
Version of the tool used to select approriate dictionary (argument to -v).
tissue_prop : str
Tissue type desired (argument to -t).
out_file : str
Path to output NIfTI file (.nii.gz).
"""

# Ensure paths are safe
in_file = Path(in_file).resolve()
out_file = Path(out_file).resolve()

cmd = [
"tissue_to_MR",
"-i", str(in_file),
"-s", seg_tool,
"-v", version,
"-t", tissue_prop,
"-o", str(out_file)
]

try:
subprocess.run(cmd, check=True)
Copy link
Member

Choose a reason for hiding this comment

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

This isn't good practice unfortunately - it will make it very difficult to debug / test, and also make it challenging for cross-platform functionality.

A better approach would be to import your tissue2MR python module and call the python functions directly.

print(f"[INFO] tissue_to_MR finished successfully. Output: {out_file}")
except subprocess.CalledProcessError as e:
print("[ERROR] tissue_to_MR failed!")
print(e)