Skip to content

Commit 7efedae

Browse files
committed
added script to generate synthetic data
1 parent 92b29d5 commit 7efedae

5 files changed

Lines changed: 169 additions & 86 deletions

File tree

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
"""Functions for creating synthetic data."""
2+
import argparse
3+
import os
4+
5+
import nibabel as nib
6+
import numpy as np
7+
import pandas as pd
8+
9+
10+
def load(fn:str)->tuple:
11+
"""Load a NIfTI file."""
12+
img = nib.load(fn)
13+
vol = img.get_fdata()
14+
return img, vol
15+
16+
"""
17+
def rotate3d(vol, angles):
18+
# r = Rotation.from_euler('xyz', angles, degrees=True)
19+
# vol = r.apply(vol)
20+
# vol = Rotate(vol, angles[0], (1,0))
21+
# vol = Rotate(vol, angles[1], (1,2))
22+
# vol = Rotate(vol, angles[2], (0,2))
23+
24+
return vol
25+
"""
26+
27+
def save_section(vol:str, y:int, affine:np.array, out_fn:str)->None:
28+
"""Save a 2D section of a 3D volume as a NIfTI file.
29+
30+
Args:
31+
vol (ndarray): The 3D volume.
32+
y (int): The y-coordinate of the section to save.
33+
affine (ndarray): The affine transformation matrix.
34+
out_fn (str): The output file name.
35+
36+
Returns:
37+
None
38+
"""
39+
section = vol[:, y, :]
40+
# imageio.imwrite(out_fn, section)
41+
nib.Nifti1Image(section, affine).to_filename(out_fn)
42+
43+
44+
if __name__ == "__main__":
45+
parser = argparse.ArgumentParser()
46+
parser.add_argument("--input", dest="input_fn", type=str)
47+
parser.add_argument("--output-dir", dest="out_dir", type=str)
48+
parser.add_argument("--gm-surf", dest="gm_surf_fn", type=str)
49+
parser.add_argument("--wm-surf", dest="wm_surf_fn", type=str)
50+
parser.add_argument("--sub", dest="sub", type=str)
51+
parser.add_argument("--hemisphere", dest="hemisphere", type=str)
52+
53+
args = parser.parse_args()
54+
sub = args.sub
55+
hemisphere = args.hemisphere
56+
out_dir = args.out_dir
57+
58+
59+
def save_coronal_sections(input_fn:str, out_dir:str, raw_dir:str, sub:str, hemisphere:str, chunk:int, ystep:int=4, clobber:bool=False)->str:
60+
"""Save coronal sections of a volume as NIfTI files."""
61+
input_img, input_vol = load(input_fn)
62+
63+
ymax = input_img.shape[1]
64+
65+
sect_info_csv = f"{out_dir}/sect_info.csv"
66+
67+
#angles = np.random.uniform(-30, 30, 3)
68+
#input_vol = rotate3d(input_vol, angles)
69+
#gm_vol = rotate3d(gm_vol, angles)
70+
71+
if not os.path.exists(sect_info_csv) or clobber:
72+
affine = input_img.affine
73+
74+
df = pd.DataFrame({})
75+
76+
section_max = np.max([np.sum(input_vol[:, y, :]) for y in range(0, ymax, 4)])
77+
78+
for y in range(0, ymax, ystep):
79+
raw_sec_fn = f"{raw_dir}/sub-{sub}_chunk-{chunk}_sample-{y}_synth.nii.gz"
80+
81+
if np.sum(input_vol[:, y, :]) < section_max * 0.05:
82+
continue
83+
84+
save_section(input_vol, y, affine, raw_sec_fn)
85+
86+
row_dict = {
87+
"raw": [raw_sec_fn],
88+
"sub": [sub],
89+
"hemisphere": [hemisphere],
90+
"acquisition": ["synth"],
91+
"sample": [y],
92+
"chunk": [chunk],
93+
}
94+
95+
df = pd.concat([df, pd.DataFrame(row_dict)])
96+
97+
df.to_csv(sect_info_csv, index=False)
98+
99+
else :
100+
df = pd.read_csv(sect_info_csv)
101+
102+
return df
103+
104+
def generate_synthetic_data(
105+
input_fn: str ='data/mni_icbm152_01_tal_nlin_asym_09c.nii.gz',
106+
out_dir: str ='/tmp/brainbuilder/test_output',
107+
gm_surf_fn: str='data/MR1_gray_surface_R_81920.surf.gii',
108+
wm_surf_fn: str='data/MR1_white_surface_R_81920.surf.gii',
109+
sub: str='01',
110+
hemisphere: str='both',
111+
chunk:int=1,
112+
ystep:int=4,
113+
clobber:bool=False,
114+
)->tuple:
115+
"""Generate synthetic data using an input volume file and surface files.
116+
117+
:param input_fn: Input volume file.
118+
:param out_dir: Output directory.
119+
:param gm_surf_fn: Gray matter surface file.
120+
:param wm_surf_fn: White matter surface file.
121+
:param sub: Subject ID.
122+
:param hemisphere: Hemisphere.
123+
:param chunk: Chunk number.
124+
:param clobber: Overwrite existing files.
125+
:return: tuple of section info CSV file, chunk info CSV file, and hemisphere info CSV file.
126+
"""
127+
print('Generating synthetic data')
128+
raw_dir = f"{out_dir}/raw_dir/"
129+
hemi_info_csv = f"{out_dir}/hemi_info.csv"
130+
sect_info_csv = f"{out_dir}/sect_info.csv"
131+
chunk_info_csv = f"{out_dir}/chunk_info.csv"
132+
133+
chunk = 1
134+
135+
for dir_path in [out_dir, raw_dir]:
136+
os.makedirs(dir_path, exist_ok=True)
137+
138+
139+
save_coronal_sections(input_fn, out_dir, raw_dir, sub, hemisphere, chunk, ystep=ystep, clobber=clobber )
140+
141+
142+
chunk_info_df = pd.DataFrame(
143+
{
144+
"sub": [sub],
145+
"chunk": [chunk],
146+
"hemisphere": [hemisphere],
147+
"pixel_size_0": [1],
148+
"pixel_size_1": [1],
149+
"section_thickness": [1],
150+
"direction": ["caudal_to_rostral"],
151+
}
152+
)
153+
154+
chunk_info_df.to_csv(chunk_info_csv, index=False)
155+
156+
hemi_info_df = pd.DataFrame(
157+
{
158+
"sub": [sub],
159+
"hemisphere": [hemisphere],
160+
"struct_ref_vol": [input_fn],
161+
"gm_surf": [gm_surf_fn],
162+
"wm_surf": [wm_surf_fn],
163+
}
164+
)
165+
166+
hemi_info_df.to_csv(hemi_info_csv, index=False)
167+
168+
return sect_info_csv, chunk_info_csv, hemi_info_csv

docker/Dockerfile

Lines changed: 0 additions & 35 deletions
This file was deleted.

docker/base/Dockerfile

Lines changed: 0 additions & 24 deletions
This file was deleted.

docker/base/requirements.txt

Lines changed: 0 additions & 25 deletions
This file was deleted.

requirements.txt

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ pandas
55
stripy
66
h5py
77
guppy3
8-
cython
98
psutil
109
statsmodels
1110
configparser
@@ -19,7 +18,6 @@ pydot
1918
matplotlib
2019
nibabel
2120
seaborn
22-
pykrige
2321
torch
2422
torchvision
2523
antspyx
@@ -29,3 +27,4 @@ ruff
2927
pre-commit
3028
nnunet
3129
nnunetv2
30+
medpy

0 commit comments

Comments
 (0)