niiprep 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- niiprep-0.1.0/PKG-INFO +17 -0
- niiprep-0.1.0/setup.cfg +4 -0
- niiprep-0.1.0/setup.py +40 -0
- niiprep-0.1.0/src/niiprep/__init__.py +6 -0
- niiprep-0.1.0/src/niiprep/cli.py +113 -0
- niiprep-0.1.0/src/niiprep/denoise_mp2rage.py +145 -0
- niiprep-0.1.0/src/niiprep/nii2mp4.py +56 -0
- niiprep-0.1.0/src/niiprep/registration.py +51 -0
- niiprep-0.1.0/src/niiprep/resample.py +30 -0
- niiprep-0.1.0/src/niiprep/round.py +22 -0
- niiprep-0.1.0/src/niiprep.egg-info/PKG-INFO +17 -0
- niiprep-0.1.0/src/niiprep.egg-info/SOURCES.txt +14 -0
- niiprep-0.1.0/src/niiprep.egg-info/dependency_links.txt +1 -0
- niiprep-0.1.0/src/niiprep.egg-info/entry_points.txt +6 -0
- niiprep-0.1.0/src/niiprep.egg-info/requires.txt +4 -0
- niiprep-0.1.0/src/niiprep.egg-info/top_level.txt +1 -0
niiprep-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: niiprep
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A CLI wrapper for TorchIO and ANTsPyX for NIfTI image processing
|
|
5
|
+
Author: Jinghang Li
|
|
6
|
+
Author-email: jinghang.li@pitt.edu
|
|
7
|
+
Classifier: Development Status :: 3 - Alpha
|
|
8
|
+
Classifier: Intended Audience :: Science/Research
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.7
|
|
13
|
+
Requires-Python: >=3.7
|
|
14
|
+
Requires-Dist: torchio>=0.18.0
|
|
15
|
+
Requires-Dist: antspyx>=0.3.0
|
|
16
|
+
Requires-Dist: nibabel>=3.0.0
|
|
17
|
+
Requires-Dist: numpy>=1.19.0
|
niiprep-0.1.0/setup.cfg
ADDED
niiprep-0.1.0/setup.py
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
from setuptools import setup, find_packages
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
setup(
|
|
5
|
+
name="niiprep",
|
|
6
|
+
version="0.1.0",
|
|
7
|
+
author="Jinghang Li",
|
|
8
|
+
author_email="jinghang.li@pitt.edu",
|
|
9
|
+
description="A CLI wrapper for TorchIO and ANTsPyX for NIfTI image processing",
|
|
10
|
+
package_dir={"": "src"},
|
|
11
|
+
packages=find_packages(where="src"),
|
|
12
|
+
package_data={
|
|
13
|
+
'niiprep': [''],
|
|
14
|
+
},
|
|
15
|
+
include_package_data=True,
|
|
16
|
+
classifiers=[
|
|
17
|
+
"Development Status :: 3 - Alpha",
|
|
18
|
+
"Intended Audience :: Science/Research",
|
|
19
|
+
"License :: OSI Approved :: MIT License",
|
|
20
|
+
"Operating System :: OS Independent",
|
|
21
|
+
"Programming Language :: Python :: 3",
|
|
22
|
+
"Programming Language :: Python :: 3.7",
|
|
23
|
+
],
|
|
24
|
+
python_requires=">=3.7",
|
|
25
|
+
install_requires=[
|
|
26
|
+
"torchio>=0.18.0",
|
|
27
|
+
"antspyx>=0.3.0",
|
|
28
|
+
"nibabel>=3.0.0",
|
|
29
|
+
"numpy>=1.19.0",
|
|
30
|
+
],
|
|
31
|
+
entry_points={
|
|
32
|
+
'console_scripts': [
|
|
33
|
+
'resample=niiprep.cli:resample_cli',
|
|
34
|
+
'registernii=niiprep.cli:register_cli',
|
|
35
|
+
'nii2mp4=niiprep.cli:nii_to_mp4_cli',
|
|
36
|
+
'roundnii=niiprep.cli:round_cli',
|
|
37
|
+
'denoiseMP2RAGE=niiprep.cli:denoise_mp2rage'
|
|
38
|
+
],
|
|
39
|
+
},
|
|
40
|
+
)
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
from .resample import resample
|
|
3
|
+
from .registration import register
|
|
4
|
+
from .nii2mp4 import nii_to_mp4
|
|
5
|
+
from .round import round_nifti
|
|
6
|
+
from .denoise_mp2rage import robust_combination
|
|
7
|
+
|
|
8
|
+
def resample_cli():
|
|
9
|
+
parser = argparse.ArgumentParser(description='Resample NIfTI image to specified resolution')
|
|
10
|
+
parser.add_argument('-i', '--input', required=True,
|
|
11
|
+
help='Path to input NIfTI file')
|
|
12
|
+
parser.add_argument('-o', '--output', required=True,
|
|
13
|
+
help='Path to save resampled NIfTI file')
|
|
14
|
+
parser.add_argument('-s', '--spacing', nargs=3, type=float, default=[1.0, 1.0, 1.0],
|
|
15
|
+
help='Target voxel spacing in mm (x y z), default: 1.0 1.0 1.0')
|
|
16
|
+
parser.add_argument('--interpolation', choices=['linear', 'nearest', 'bspline'],
|
|
17
|
+
default='linear',
|
|
18
|
+
help='Interpolation method (default: linear)')
|
|
19
|
+
|
|
20
|
+
args = parser.parse_args()
|
|
21
|
+
|
|
22
|
+
resample(
|
|
23
|
+
input_path=args.input,
|
|
24
|
+
output_path=args.output,
|
|
25
|
+
target_spacing=tuple(args.spacing),
|
|
26
|
+
interpolation=args.interpolation
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
def register_cli():
|
|
30
|
+
parser = argparse.ArgumentParser(description='Register moving image to fixed image')
|
|
31
|
+
parser.add_argument('-f', '--fixed', required=True,
|
|
32
|
+
help='Path to fixed/reference NIfTI file')
|
|
33
|
+
parser.add_argument('-m', '--moving', required=True,
|
|
34
|
+
help='Path to moving NIfTI file')
|
|
35
|
+
parser.add_argument('-o', '--output', required=True,
|
|
36
|
+
help='Path to save registered NIfTI file')
|
|
37
|
+
parser.add_argument('-t', '--type', choices=['rigid', 'affine', 'syn'],
|
|
38
|
+
default='syn',
|
|
39
|
+
help='Registration type (default: syn)')
|
|
40
|
+
parser.add_argument('--interpolation', default='linear',
|
|
41
|
+
help='Interpolation type (default: linear)')
|
|
42
|
+
|
|
43
|
+
args = parser.parse_args()
|
|
44
|
+
|
|
45
|
+
register(
|
|
46
|
+
fixed_path=args.fixed,
|
|
47
|
+
moving_path=args.moving,
|
|
48
|
+
output_path=args.output,
|
|
49
|
+
reg_type=args.type,
|
|
50
|
+
interpolation=args.interpolation
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
def nii_to_mp4_cli():
|
|
54
|
+
parser = argparse.ArgumentParser(description='Convert NIfTI file to MP4 video')
|
|
55
|
+
parser.add_argument('-i', '--input', required=True,
|
|
56
|
+
help='Path to input NIfTI file')
|
|
57
|
+
parser.add_argument('-o', '--output', required=True,
|
|
58
|
+
help='Path to save MP4 file')
|
|
59
|
+
parser.add_argument('-d', '--dimension', type=int, default=2, choices=[0, 1, 2],
|
|
60
|
+
help='Dimension to slice along (0: sagittal, 1: coronal, 2: axial (default))')
|
|
61
|
+
parser.add_argument('--fps', type=int, default=10,
|
|
62
|
+
help='Frames per second (default: 10)')
|
|
63
|
+
parser.add_argument('--no-normalize', action='store_false', dest='normalize',
|
|
64
|
+
help='Disable intensity normalization')
|
|
65
|
+
|
|
66
|
+
args = parser.parse_args()
|
|
67
|
+
|
|
68
|
+
# Ensure output path has .mp4 extension
|
|
69
|
+
output_path = args.output
|
|
70
|
+
if not output_path.endswith('.mp4'):
|
|
71
|
+
output_path += '.mp4'
|
|
72
|
+
|
|
73
|
+
nii_to_mp4(
|
|
74
|
+
input_path=args.input,
|
|
75
|
+
output_path=output_path,
|
|
76
|
+
dimension=args.dimension,
|
|
77
|
+
fps=args.fps,
|
|
78
|
+
normalize=args.normalize
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
def round_cli():
|
|
82
|
+
parser = argparse.ArgumentParser(description='Round NIfTI image pixel values')
|
|
83
|
+
parser.add_argument('-i', '--input', required=True,
|
|
84
|
+
help='Path to input NIfTI file (will be overwritten)')
|
|
85
|
+
|
|
86
|
+
args = parser.parse_args()
|
|
87
|
+
|
|
88
|
+
round_nifti(args.input)
|
|
89
|
+
|
|
90
|
+
def denoise_mp2rage():
|
|
91
|
+
|
|
92
|
+
parser = argparse.ArgumentParser(description='MP2RAGE robust combination processing')
|
|
93
|
+
|
|
94
|
+
parser.add_argument('--uni', type=str, required=True,
|
|
95
|
+
help='Path to UNI image (.nii or .nii.gz)')
|
|
96
|
+
parser.add_argument('--inv1', type=str, required=True,
|
|
97
|
+
help='Path to INV1 image (.nii or .nii.gz)')
|
|
98
|
+
parser.add_argument('--inv2', type=str, required=True,
|
|
99
|
+
help='Path to INV2 image (.nii or .nii.gz)')
|
|
100
|
+
parser.add_argument('--output', '-o', type=str, required=True,
|
|
101
|
+
help='Output path for processed image')
|
|
102
|
+
parser.add_argument('--regularization', '-r', type=float, default=None,
|
|
103
|
+
help='Noise regularization factor (default: None for interactive mode)')
|
|
104
|
+
|
|
105
|
+
args = parser.parse_args()
|
|
106
|
+
|
|
107
|
+
mp2rage_data = {
|
|
108
|
+
'filenameUNI': args.uni,
|
|
109
|
+
'filenameINV1': args.inv1,
|
|
110
|
+
'filenameINV2': args.inv2,
|
|
111
|
+
'filenameOUT': args.output
|
|
112
|
+
}
|
|
113
|
+
_, _ = robust_combination(mp2rage_data, regularization=args.regularization,)
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import numpy as np
|
|
3
|
+
import nibabel as nib
|
|
4
|
+
import json
|
|
5
|
+
from matplotlib import pyplot as plt
|
|
6
|
+
|
|
7
|
+
def robust_combination(mp2rage, regularization=None, figure=None):
|
|
8
|
+
"""
|
|
9
|
+
Creates MP2RAGE T1w images without strong background noise in air regions.
|
|
10
|
+
This python file is translated from
|
|
11
|
+
|
|
12
|
+
https://github.com/JosePMarques/MP2RAGE-related-scripts/tree/master
|
|
13
|
+
|
|
14
|
+
Parameters:
|
|
15
|
+
-----------
|
|
16
|
+
mp2rage : dict
|
|
17
|
+
Dictionary containing filenames:
|
|
18
|
+
- filenameUNI: Path to UNI image
|
|
19
|
+
- filenameINV1: Path to INV1 image
|
|
20
|
+
- filenameINV2: Path to INV2 image
|
|
21
|
+
- filenameOUT: Optional output path
|
|
22
|
+
regularization : float, optional
|
|
23
|
+
Noise regularization factor
|
|
24
|
+
figure : matplotlib.figure, optional
|
|
25
|
+
Figure handle for visualization
|
|
26
|
+
|
|
27
|
+
Returns:
|
|
28
|
+
--------
|
|
29
|
+
tuple: (mp2rage_robust, multiplying_factor)
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
# Set defaults
|
|
33
|
+
multiplying_factor = 1 if regularization is None else regularization
|
|
34
|
+
final_choice = 'n'
|
|
35
|
+
|
|
36
|
+
# Define helper functions
|
|
37
|
+
def mp2rage_robust_func(inv1, inv2, beta):
|
|
38
|
+
return (np.conj(inv1) * inv2 - beta) / (inv1**2 + inv2**2 + 2*beta)
|
|
39
|
+
|
|
40
|
+
def roots_pos(a, b, c):
|
|
41
|
+
return (-b + np.sqrt(b**2 - 4*a*c)) / (2*a)
|
|
42
|
+
|
|
43
|
+
def roots_neg(a, b, c):
|
|
44
|
+
return (-b - np.sqrt(b**2 - 4*a*c)) / (2*a)
|
|
45
|
+
|
|
46
|
+
# Load data
|
|
47
|
+
print(f"Loading images from: {os.path.dirname(mp2rage['filenameUNI'])}")
|
|
48
|
+
uni_img = nib.load(mp2rage['filenameUNI'])
|
|
49
|
+
uni_data = uni_img.get_fdata().astype(np.float64)
|
|
50
|
+
inv1_data = nib.load(mp2rage['filenameINV1']).get_fdata().astype(np.float64)
|
|
51
|
+
inv2_data = nib.load(mp2rage['filenameINV2']).get_fdata().astype(np.float64)
|
|
52
|
+
|
|
53
|
+
# Check if normalization is needed
|
|
54
|
+
if uni_data.min() >= 0 and uni_data.max() >= 0.51:
|
|
55
|
+
uni_data = (uni_data - uni_data.max()/2) / uni_data.max()
|
|
56
|
+
integer_format = True
|
|
57
|
+
else:
|
|
58
|
+
integer_format = False
|
|
59
|
+
|
|
60
|
+
# Compute correct INV1 dataset
|
|
61
|
+
inv1_data = np.sign(uni_data) * inv1_data
|
|
62
|
+
|
|
63
|
+
inv1_pos = roots_pos(-uni_data, inv2_data, -inv2_data**2 * uni_data)
|
|
64
|
+
inv1_neg = roots_neg(-uni_data, inv2_data, -inv2_data**2 * uni_data)
|
|
65
|
+
|
|
66
|
+
inv1_final = inv1_data.copy()
|
|
67
|
+
mask_neg = np.abs(inv1_data - inv1_pos) > np.abs(inv1_data - inv1_neg)
|
|
68
|
+
inv1_final[mask_neg] = inv1_neg[mask_neg]
|
|
69
|
+
inv1_final[~mask_neg] = inv1_pos[~mask_neg]
|
|
70
|
+
|
|
71
|
+
# Interactive regularization loop
|
|
72
|
+
while final_choice.lower() != 'y':
|
|
73
|
+
noise_level = multiplying_factor * np.mean(inv2_data[:, -10:, -10:])
|
|
74
|
+
mp2rage_robust = mp2rage_robust_func(inv1_final, inv2_data, noise_level**2)
|
|
75
|
+
|
|
76
|
+
if figure is not None:
|
|
77
|
+
# Visualization code here (simplified)
|
|
78
|
+
plt.figure(figure.number)
|
|
79
|
+
plt.subplot(211)
|
|
80
|
+
plt.imshow(uni_data[:, :, uni_data.shape[2]//2], cmap='gray', vmin=-0.5, vmax=0.4)
|
|
81
|
+
plt.title('MP2RAGE UNI-Image')
|
|
82
|
+
|
|
83
|
+
plt.subplot(212)
|
|
84
|
+
plt.imshow(mp2rage_robust[:, :, mp2rage_robust.shape[2]//2], cmap='gray', vmin=-0.5, vmax=0.4)
|
|
85
|
+
plt.title(f'MP2RAGE Robust (Noise level = {multiplying_factor})')
|
|
86
|
+
plt.show()
|
|
87
|
+
|
|
88
|
+
if regularization is None:
|
|
89
|
+
final_choice = input('Is it a satisfactory noise level?? (y/n) [n]: ') or 'n'
|
|
90
|
+
if final_choice.lower() != 'y':
|
|
91
|
+
multiplying_factor = float(input(f'New regularization noise level (current = {multiplying_factor}): '))
|
|
92
|
+
else:
|
|
93
|
+
final_choice = 'y'
|
|
94
|
+
else:
|
|
95
|
+
final_choice = 'y'
|
|
96
|
+
|
|
97
|
+
# Save output if filename provided
|
|
98
|
+
if 'filenameOUT' in mp2rage and mp2rage['filenameOUT']:
|
|
99
|
+
print(f"Saving: {mp2rage['filenameOUT']}")
|
|
100
|
+
out_data = mp2rage_robust if not integer_format else np.round(4095 * (mp2rage_robust + 0.5))
|
|
101
|
+
out_img = nib.Nifti1Image(out_data, uni_img.affine)
|
|
102
|
+
nib.save(out_img, mp2rage['filenameOUT'])
|
|
103
|
+
|
|
104
|
+
# Handle JSON sidecar
|
|
105
|
+
uni_json = os.path.splitext(mp2rage['filenameUNI'])[0] + '.json'
|
|
106
|
+
if os.path.exists(uni_json):
|
|
107
|
+
with open(uni_json, 'r') as f:
|
|
108
|
+
json_data = json.load(f)
|
|
109
|
+
|
|
110
|
+
json_data.update({
|
|
111
|
+
'BasedOn': [mp2rage['filenameUNI'], mp2rage['filenameINV1'], mp2rage['filenameINV2']],
|
|
112
|
+
'SeriesDescription': f"{json_data['ProtocolName']}_MP2RAGE_denoised_background",
|
|
113
|
+
'NoiseRegularization': multiplying_factor
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
out_json = os.path.splitext(mp2rage['filenameOUT'])[0] + '.json'
|
|
117
|
+
with open(out_json, 'w') as f:
|
|
118
|
+
json.dump(json_data, f)
|
|
119
|
+
|
|
120
|
+
return mp2rage_robust, multiplying_factor
|
|
121
|
+
|
|
122
|
+
if __name__ == "__main__":
|
|
123
|
+
import argparse
|
|
124
|
+
|
|
125
|
+
parser = argparse.ArgumentParser(description='MP2RAGE robust combination processing')
|
|
126
|
+
|
|
127
|
+
parser.add_argument('--uni', type=str, required=True,
|
|
128
|
+
help='Path to UNI image (.nii or .nii.gz)')
|
|
129
|
+
parser.add_argument('--inv1', type=str, required=True,
|
|
130
|
+
help='Path to INV1 image (.nii or .nii.gz)')
|
|
131
|
+
parser.add_argument('--inv2', type=str, required=True,
|
|
132
|
+
help='Path to INV2 image (.nii or .nii.gz)')
|
|
133
|
+
parser.add_argument('--output', '-o', type=str, required=True,
|
|
134
|
+
help='Output path for processed image')
|
|
135
|
+
parser.add_argument('--regularization', '-r', type=float, default=None,
|
|
136
|
+
help='Noise regularization factor (default: None for interactive mode)')
|
|
137
|
+
|
|
138
|
+
args = parser.parse_args()
|
|
139
|
+
mp2rage_data = {
|
|
140
|
+
'filenameUNI': args.uni,
|
|
141
|
+
'filenameINV1': args.inv1,
|
|
142
|
+
'filenameINV2': args.inv2,
|
|
143
|
+
'filenameOUT': args.output
|
|
144
|
+
}
|
|
145
|
+
robust_image, factor = robust_combination(mp2rage_data, regularization=args.regularization,)
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import nibabel as nib
|
|
2
|
+
import numpy as np
|
|
3
|
+
import cv2
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
def nii_to_mp4(
|
|
7
|
+
input_path: str,
|
|
8
|
+
output_path: str,
|
|
9
|
+
dimension: int = 2,
|
|
10
|
+
fps: int = 10,
|
|
11
|
+
normalize: bool = True
|
|
12
|
+
) -> None:
|
|
13
|
+
"""
|
|
14
|
+
Convert a NIfTI file to MP4 video along a specified dimension.
|
|
15
|
+
|
|
16
|
+
Args:
|
|
17
|
+
input_path (str): Path to input NIfTI file
|
|
18
|
+
output_path (str): Path to save MP4 file
|
|
19
|
+
dimension (int): Dimension to slice along (0: sagittal, 1: coronal, 2: axial)
|
|
20
|
+
fps (int): Frames per second for the output video
|
|
21
|
+
normalize (bool): Whether to normalize slice intensities
|
|
22
|
+
"""
|
|
23
|
+
# Load NIfTI file
|
|
24
|
+
img = nib.load(input_path)
|
|
25
|
+
data = img.get_fdata()
|
|
26
|
+
|
|
27
|
+
# Get slices based on dimension
|
|
28
|
+
if dimension == 0: # sagittal
|
|
29
|
+
n_slices = data.shape[0]
|
|
30
|
+
slices = [np.rot90(data[i, :, :]) for i in range(n_slices)]
|
|
31
|
+
elif dimension == 1: # coronal
|
|
32
|
+
n_slices = data.shape[1]
|
|
33
|
+
slices = [np.rot90(data[:, i, :]) for i in range(n_slices)]
|
|
34
|
+
else: # axial (dimension == 2)
|
|
35
|
+
n_slices = data.shape[2]
|
|
36
|
+
slices = [np.rot90(data[:, :, i]) for i in range(n_slices)]
|
|
37
|
+
|
|
38
|
+
# Normalize and convert to uint8
|
|
39
|
+
if normalize:
|
|
40
|
+
slices = [(((s - s.min()) / (s.max() - s.min())) * 255).astype(np.uint8) for s in slices]
|
|
41
|
+
else:
|
|
42
|
+
slices = [s.astype(np.uint8) for s in slices]
|
|
43
|
+
|
|
44
|
+
# Get dimensions for the video
|
|
45
|
+
height, width = slices[0].shape
|
|
46
|
+
|
|
47
|
+
# Initialize video writer
|
|
48
|
+
fourcc = cv2.VideoWriter_fourcc(*'mp4v')
|
|
49
|
+
out = cv2.VideoWriter(output_path, fourcc, fps, (width, height), isColor=False)
|
|
50
|
+
|
|
51
|
+
# Write slices to video
|
|
52
|
+
for slice_data in slices:
|
|
53
|
+
out.write(slice_data)
|
|
54
|
+
|
|
55
|
+
# Release video writer
|
|
56
|
+
out.release()
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import ants
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from typing import Literal, Optional, Union, Tuple
|
|
4
|
+
|
|
5
|
+
def register(
|
|
6
|
+
fixed_path: str,
|
|
7
|
+
moving_path: str,
|
|
8
|
+
output_path: str,
|
|
9
|
+
reg_type: Literal['rigid', 'affine', 'syn'] = 'syn',
|
|
10
|
+
interpolation: str = 'linear'
|
|
11
|
+
) -> Tuple[ants.core.ants_image.ANTsImage, dict]:
|
|
12
|
+
"""
|
|
13
|
+
Register a moving image to a fixed image using ANTsPyX.
|
|
14
|
+
|
|
15
|
+
Args:
|
|
16
|
+
fixed_path (str): Path to fixed/reference image
|
|
17
|
+
moving_path (str): Path to moving image to be registered
|
|
18
|
+
output_path (str): Path to save registered image
|
|
19
|
+
reg_type (str): Registration type ('rigid', 'affine', or 'syn')
|
|
20
|
+
interpolation (str): Interpolation type for resampling
|
|
21
|
+
|
|
22
|
+
Returns:
|
|
23
|
+
tuple: (registered_image, transform_params)
|
|
24
|
+
"""
|
|
25
|
+
# Load images
|
|
26
|
+
fixed_image = ants.image_read(fixed_path)
|
|
27
|
+
moving_image = ants.image_read(moving_path)
|
|
28
|
+
|
|
29
|
+
# Set up registration parameters
|
|
30
|
+
if reg_type == 'rigid':
|
|
31
|
+
transform = 'Rigid'
|
|
32
|
+
elif reg_type == 'affine':
|
|
33
|
+
transform = 'Affine'
|
|
34
|
+
elif reg_type == 'syn':
|
|
35
|
+
transform = 'SyN'
|
|
36
|
+
else:
|
|
37
|
+
raise ValueError("reg_type must be 'rigid', 'affine', or 'syn'")
|
|
38
|
+
|
|
39
|
+
# Perform registration
|
|
40
|
+
registration = ants.registration(
|
|
41
|
+
fixed=fixed_image,
|
|
42
|
+
moving=moving_image,
|
|
43
|
+
type_of_transform=transform,
|
|
44
|
+
interpolator=interpolation
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
# Save registered image
|
|
48
|
+
registered_image = registration['warpedmovout']
|
|
49
|
+
ants.image_write(registered_image, output_path)
|
|
50
|
+
|
|
51
|
+
return registered_image, registration
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import torchio as tio
|
|
2
|
+
import nibabel as nib
|
|
3
|
+
import numpy as np
|
|
4
|
+
|
|
5
|
+
def resample(
|
|
6
|
+
input_path: str,
|
|
7
|
+
output_path: str,
|
|
8
|
+
target_spacing: tuple = (1.0, 1.0, 1.0),
|
|
9
|
+
interpolation: str = 'linear'
|
|
10
|
+
) -> None:
|
|
11
|
+
"""
|
|
12
|
+
Resample a NIfTI image to specified voxel spacing.
|
|
13
|
+
|
|
14
|
+
Args:
|
|
15
|
+
input_path (str): Path to input NIfTI file
|
|
16
|
+
output_path (str): Path to save resampled NIfTI file
|
|
17
|
+
target_spacing (tuple): Target voxel spacing in mm (x, y, z)
|
|
18
|
+
interpolation (str): Interpolation method ('linear', 'nearest', 'bspline')
|
|
19
|
+
"""
|
|
20
|
+
# Load image
|
|
21
|
+
image = tio.ScalarImage(input_path)
|
|
22
|
+
|
|
23
|
+
# Create resampling transform
|
|
24
|
+
resample = tio.Resample(target_spacing)
|
|
25
|
+
|
|
26
|
+
# Apply transform
|
|
27
|
+
resampled_image = resample(image)
|
|
28
|
+
|
|
29
|
+
# Save resampled image
|
|
30
|
+
resampled_image.save(output_path)
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import nibabel as nib
|
|
2
|
+
import numpy as np
|
|
3
|
+
|
|
4
|
+
def round_nifti(input_path: str) -> None:
|
|
5
|
+
"""
|
|
6
|
+
Round pixel values in a NIfTI image and save with the same name.
|
|
7
|
+
|
|
8
|
+
Args:
|
|
9
|
+
input_path: Path to input NIfTI file
|
|
10
|
+
"""
|
|
11
|
+
# Load the image
|
|
12
|
+
img = nib.load(input_path)
|
|
13
|
+
data = img.get_fdata()
|
|
14
|
+
|
|
15
|
+
# Round the data
|
|
16
|
+
rounded_data = np.round(data)
|
|
17
|
+
|
|
18
|
+
# Create new NIfTI image with rounded data
|
|
19
|
+
rounded_img = nib.Nifti1Image(rounded_data, img.affine, img.header)
|
|
20
|
+
|
|
21
|
+
# Save the rounded image (overwrites original)
|
|
22
|
+
nib.save(rounded_img, input_path)
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: niiprep
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A CLI wrapper for TorchIO and ANTsPyX for NIfTI image processing
|
|
5
|
+
Author: Jinghang Li
|
|
6
|
+
Author-email: jinghang.li@pitt.edu
|
|
7
|
+
Classifier: Development Status :: 3 - Alpha
|
|
8
|
+
Classifier: Intended Audience :: Science/Research
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.7
|
|
13
|
+
Requires-Python: >=3.7
|
|
14
|
+
Requires-Dist: torchio>=0.18.0
|
|
15
|
+
Requires-Dist: antspyx>=0.3.0
|
|
16
|
+
Requires-Dist: nibabel>=3.0.0
|
|
17
|
+
Requires-Dist: numpy>=1.19.0
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
setup.py
|
|
2
|
+
src/niiprep/__init__.py
|
|
3
|
+
src/niiprep/cli.py
|
|
4
|
+
src/niiprep/denoise_mp2rage.py
|
|
5
|
+
src/niiprep/nii2mp4.py
|
|
6
|
+
src/niiprep/registration.py
|
|
7
|
+
src/niiprep/resample.py
|
|
8
|
+
src/niiprep/round.py
|
|
9
|
+
src/niiprep.egg-info/PKG-INFO
|
|
10
|
+
src/niiprep.egg-info/SOURCES.txt
|
|
11
|
+
src/niiprep.egg-info/dependency_links.txt
|
|
12
|
+
src/niiprep.egg-info/entry_points.txt
|
|
13
|
+
src/niiprep.egg-info/requires.txt
|
|
14
|
+
src/niiprep.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
niiprep
|