antspymm 1.5.6__tar.gz → 1.5.7__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.
- {antspymm-1.5.6 → antspymm-1.5.7}/PKG-INFO +1 -1
- {antspymm-1.5.6 → antspymm-1.5.7}/antspymm/__init__.py +2 -0
- {antspymm-1.5.6 → antspymm-1.5.7}/antspymm/mm.py +34 -0
- {antspymm-1.5.6 → antspymm-1.5.7}/antspymm.egg-info/PKG-INFO +1 -1
- {antspymm-1.5.6 → antspymm-1.5.7}/antspymm.egg-info/SOURCES.txt +1 -1
- antspymm-1.5.7/docs/dti_distortion_correction_voxelwise_varying_bvectors_example.py +353 -0
- {antspymm-1.5.6 → antspymm-1.5.7}/docs/dti_reconstruction_voxelwise_varying_bvectors_localint.py +3 -2
- {antspymm-1.5.6 → antspymm-1.5.7}/pyproject.toml +1 -1
- antspymm-1.5.6/docs/dti_distortion_correction_voxelwise_varying_bvectors_example_WIP.py +0 -238
- {antspymm-1.5.6 → antspymm-1.5.7}/MANIFEST.in +0 -0
- {antspymm-1.5.6 → antspymm-1.5.7}/README.md +0 -0
- {antspymm-1.5.6 → antspymm-1.5.7}/antspymm.egg-info/dependency_links.txt +0 -0
- {antspymm-1.5.6 → antspymm-1.5.7}/antspymm.egg-info/requires.txt +0 -0
- {antspymm-1.5.6 → antspymm-1.5.7}/antspymm.egg-info/top_level.txt +0 -0
- {antspymm-1.5.6 → antspymm-1.5.7}/docs/adni_rsfmri_2_nrg_conversion.py +0 -0
- {antspymm-1.5.6 → antspymm-1.5.7}/docs/antspymm_annotated_output_tree.pages +0 -0
- {antspymm-1.5.6 → antspymm-1.5.7}/docs/antspymm_annotated_output_tree.txt +0 -0
- {antspymm-1.5.6 → antspymm-1.5.7}/docs/antspymm_data_dictionary.csv +0 -0
- {antspymm-1.5.6 → antspymm-1.5.7}/docs/aslprep_perfusion_run_localint.py +0 -0
- {antspymm-1.5.6 → antspymm-1.5.7}/docs/bids_2_nrg.py +0 -0
- {antspymm-1.5.6 → antspymm-1.5.7}/docs/bids_cohort_example.py +0 -0
- {antspymm-1.5.6 → antspymm-1.5.7}/docs/bind_mm_wide.R +0 -0
- {antspymm-1.5.6 → antspymm-1.5.7}/docs/blind_qc.Rmd +0 -0
- {antspymm-1.5.6 → antspymm-1.5.7}/docs/blind_qc.html +0 -0
- {antspymm-1.5.6 → antspymm-1.5.7}/docs/blind_qc.py +0 -0
- {antspymm-1.5.6 → antspymm-1.5.7}/docs/convert_adni_dti_to_nrg.R +0 -0
- {antspymm-1.5.6 → antspymm-1.5.7}/docs/deepnbm.jpg +0 -0
- {antspymm-1.5.6 → antspymm-1.5.7}/docs/deformation_gradient_reo.py +0 -0
- {antspymm-1.5.6 → antspymm-1.5.7}/docs/describe_mm_data.R +0 -0
- {antspymm-1.5.6 → antspymm-1.5.7}/docs/dipy_dti_recon.py +0 -0
- {antspymm-1.5.6 → antspymm-1.5.7}/docs/dti_recon.py +0 -0
- {antspymm-1.5.6 → antspymm-1.5.7}/docs/dti_reg.py +0 -0
- {antspymm-1.5.6 → antspymm-1.5.7}/docs/dwi_rebasing.py +0 -0
- {antspymm-1.5.6 → antspymm-1.5.7}/docs/dwi_run.py +0 -0
- {antspymm-1.5.6 → antspymm-1.5.7}/docs/dwi_run_ptbp_scrub.py +0 -0
- {antspymm-1.5.6 → antspymm-1.5.7}/docs/ex_rsfmri_run_minimal_ptbp.py +0 -0
- {antspymm-1.5.6 → antspymm-1.5.7}/docs/ex_sr.py +0 -0
- {antspymm-1.5.6 → antspymm-1.5.7}/docs/example_antspymm_output.csv +0 -0
- {antspymm-1.5.6 → antspymm-1.5.7}/docs/example_run_from_directory.py +0 -0
- {antspymm-1.5.6 → antspymm-1.5.7}/docs/flair_run_localint.py +0 -0
- {antspymm-1.5.6 → antspymm-1.5.7}/docs/joint_dti_recon_localint.py +0 -0
- {antspymm-1.5.6 → antspymm-1.5.7}/docs/make_dict_table.Rmd +0 -0
- {antspymm-1.5.6 → antspymm-1.5.7}/docs/make_dict_table.html +0 -0
- {antspymm-1.5.6 → antspymm-1.5.7}/docs/mm.py +0 -0
- {antspymm-1.5.6 → antspymm-1.5.7}/docs/mm_csv_ex_2.py +0 -0
- {antspymm-1.5.6 → antspymm-1.5.7}/docs/mm_csv_localint.py +0 -0
- {antspymm-1.5.6 → antspymm-1.5.7}/docs/mm_nrg.py +0 -0
- {antspymm-1.5.6 → antspymm-1.5.7}/docs/nrg_cohort_example.py +0 -0
- {antspymm-1.5.6 → antspymm-1.5.7}/docs/parallel_study_aggregation_example.py +0 -0
- {antspymm-1.5.6 → antspymm-1.5.7}/docs/perfusion_ptbp.py +0 -0
- {antspymm-1.5.6 → antspymm-1.5.7}/docs/perfusion_run_nnl.py +0 -0
- {antspymm-1.5.6 → antspymm-1.5.7}/docs/ppmi_step1_blind_qc.py +0 -0
- {antspymm-1.5.6 → antspymm-1.5.7}/docs/ppmi_step2_outlierness.py +0 -0
- {antspymm-1.5.6 → antspymm-1.5.7}/docs/ppmi_step3_mm_nrg_csv.py +0 -0
- {antspymm-1.5.6 → antspymm-1.5.7}/docs/ppmi_step4_aggregate.py +0 -0
- {antspymm-1.5.6 → antspymm-1.5.7}/docs/ptbp_nrg.py +0 -0
- {antspymm-1.5.6 → antspymm-1.5.7}/docs/roi_visualization.py +0 -0
- {antspymm-1.5.6 → antspymm-1.5.7}/docs/roi_visualization_ppmi.py +0 -0
- {antspymm-1.5.6 → antspymm-1.5.7}/docs/rsfmri_run_minimal_localint.py +0 -0
- {antspymm-1.5.6 → antspymm-1.5.7}/docs/run_local_integration_scripts.py +0 -0
- {antspymm-1.5.6 → antspymm-1.5.7}/docs/run_mm_example.sh +0 -0
- {antspymm-1.5.6 → antspymm-1.5.7}/docs/template_overlays.py +0 -0
- {antspymm-1.5.6 → antspymm-1.5.7}/docs/ukbb_rsfmri.py +0 -0
- {antspymm-1.5.6 → antspymm-1.5.7}/docs/ukbb_to_nrg_processing.py +0 -0
- {antspymm-1.5.6 → antspymm-1.5.7}/docs/ukbb_to_nrg_processing2.py +0 -0
- {antspymm-1.5.6 → antspymm-1.5.7}/docs/visualize_tractogram.py +0 -0
- {antspymm-1.5.6 → antspymm-1.5.7}/setup.cfg +0 -0
- {antspymm-1.5.6 → antspymm-1.5.7}/tests/test_loop.py +0 -0
- {antspymm-1.5.6 → antspymm-1.5.7}/tests/test_nrg_validation.py +0 -0
- {antspymm-1.5.6 → antspymm-1.5.7}/tests/test_reference_run.py +0 -0
- {antspymm-1.5.6 → antspymm-1.5.7}/tests/voxelwise_bvec_dti_recon_test.py +0 -0
@@ -2138,6 +2138,40 @@ def bvec_reorientation( motion_parameters, bvecs, rebase=None ):
|
|
2138
2138
|
bvecs[myidx,:] = np.dot( rebase, bvecs[myidx,:] )
|
2139
2139
|
return bvecs
|
2140
2140
|
|
2141
|
+
|
2142
|
+
def distortion_correct_bvecs(bvecs, def_grad, A_img, A_ref):
|
2143
|
+
"""
|
2144
|
+
Vectorized computation of voxel-wise distortion corrected b-vectors.
|
2145
|
+
|
2146
|
+
Parameters
|
2147
|
+
----------
|
2148
|
+
bvecs : ndarray (N, 3)
|
2149
|
+
def_grad : ndarray (X, Y, Z, 3, 3) containing rotations derived from the deformation gradient
|
2150
|
+
A_img : ndarray (3, 3) direction matrix of the fixed image (target undistorted space)
|
2151
|
+
A_ref : ndarray (3, 3) direction matrix of the moving image (being corrected)
|
2152
|
+
|
2153
|
+
Returns
|
2154
|
+
-------
|
2155
|
+
bvecs_5d : ndarray (X, Y, Z, N, 3)
|
2156
|
+
"""
|
2157
|
+
X, Y, Z = def_grad.shape[:3]
|
2158
|
+
N = bvecs.shape[0]
|
2159
|
+
# Combined rotation: R_voxel = A_ref.T @ A_img @ def_grad
|
2160
|
+
A = A_ref.T @ A_img
|
2161
|
+
R_voxel = np.einsum('ij,xyzjk->xyzik', A, def_grad) # (X, Y, Z, 3, 3)
|
2162
|
+
# Apply R_voxel.T @ bvecs
|
2163
|
+
# First, reshape R_voxel: (X*Y*Z, 3, 3)
|
2164
|
+
R_voxel_reshaped = R_voxel.reshape(-1, 3, 3)
|
2165
|
+
# Rotate all bvecs for each voxel
|
2166
|
+
# Output: (X*Y*Z, N, 3)
|
2167
|
+
rotated = np.einsum('vij,nj->vni', R_voxel_reshaped, bvecs)
|
2168
|
+
# Normalize
|
2169
|
+
norms = np.linalg.norm(rotated, axis=2, keepdims=True)
|
2170
|
+
rotated /= np.clip(norms, 1e-8, None)
|
2171
|
+
# Reshape back to (X, Y, Z, N, 3)
|
2172
|
+
bvecs_5d = rotated.reshape(X, Y, Z, N, 3)
|
2173
|
+
return bvecs_5d
|
2174
|
+
|
2141
2175
|
def get_dti( reference_image, tensormodel, upper_triangular=True, return_image=False ):
|
2142
2176
|
"""
|
2143
2177
|
extract DTI data from a dipy tensormodel
|
@@ -24,7 +24,7 @@ docs/deepnbm.jpg
|
|
24
24
|
docs/deformation_gradient_reo.py
|
25
25
|
docs/describe_mm_data.R
|
26
26
|
docs/dipy_dti_recon.py
|
27
|
-
docs/
|
27
|
+
docs/dti_distortion_correction_voxelwise_varying_bvectors_example.py
|
28
28
|
docs/dti_recon.py
|
29
29
|
docs/dti_reconstruction_voxelwise_varying_bvectors_localint.py
|
30
30
|
docs/dti_reg.py
|
@@ -0,0 +1,353 @@
|
|
1
|
+
import numpy as np
|
2
|
+
import ants
|
3
|
+
import os
|
4
|
+
from dipy.io.gradients import read_bvals_bvecs
|
5
|
+
from scipy.stats import pearsonr
|
6
|
+
import antspymm
|
7
|
+
import numpy as np
|
8
|
+
from scipy.stats import pearsonr
|
9
|
+
nt=8
|
10
|
+
|
11
|
+
##################################################################
|
12
|
+
# for easier to access data with a full mm_csv example, see:
|
13
|
+
# github.com:stnava/ANTPD_antspymm
|
14
|
+
##################################################################
|
15
|
+
from os.path import exists
|
16
|
+
import os
|
17
|
+
import signal
|
18
|
+
import urllib.request
|
19
|
+
import zipfile
|
20
|
+
import tempfile
|
21
|
+
from pathlib import Path
|
22
|
+
from tqdm import tqdm
|
23
|
+
import antspynet
|
24
|
+
|
25
|
+
REQUIRED_FILES = [
|
26
|
+
"PPMI/101018/20210412/T1w/1496225/PPMI-101018-20210412-T1w-1496225.nii.gz",
|
27
|
+
"PPMI/101018/20210412/DTI_LR/1496234/PPMI-101018-20210412-DTI_LR-1496234.nii.gz"
|
28
|
+
]
|
29
|
+
|
30
|
+
def broadcast_bvecs_voxelwise(rotated_bvecs, shape):
|
31
|
+
return np.broadcast_to(rotated_bvecs, shape + rotated_bvecs.shape).copy()
|
32
|
+
|
33
|
+
|
34
|
+
def _validate_required_files(base_dir, required_files):
|
35
|
+
for rel_path in required_files:
|
36
|
+
full_path = os.path.join(base_dir, rel_path)
|
37
|
+
if not os.path.isfile(full_path):
|
38
|
+
print(f"❌ Missing required file: {rel_path}")
|
39
|
+
return False
|
40
|
+
return True
|
41
|
+
|
42
|
+
def _download_with_progress(url, destination):
|
43
|
+
with urllib.request.urlopen(url) as response, open(destination, 'wb') as out_file:
|
44
|
+
total = int(response.getheader('Content-Length', 0))
|
45
|
+
with tqdm(total=total, unit='B', unit_scale=True, desc="Downloading", ncols=80) as pbar:
|
46
|
+
while True:
|
47
|
+
chunk = response.read(8192)
|
48
|
+
if not chunk:
|
49
|
+
break
|
50
|
+
out_file.write(chunk)
|
51
|
+
pbar.update(len(chunk))
|
52
|
+
|
53
|
+
def find_data_dir(candidate_paths=None, max_tries=5, timeout=22, allow_download=None, required_files=REQUIRED_FILES):
|
54
|
+
"""
|
55
|
+
Attempts to locate or download the ANTsPyMM testing dataset.
|
56
|
+
|
57
|
+
Parameters
|
58
|
+
----------
|
59
|
+
candidate_paths : list of str or None
|
60
|
+
Directories to search for the data. If None, uses sensible defaults.
|
61
|
+
max_tries : int
|
62
|
+
Number of chances to enter a valid path manually.
|
63
|
+
timeout : int
|
64
|
+
Seconds to wait for user input before timing out.
|
65
|
+
allow_download : None | str
|
66
|
+
If not None, will download to {allow_download}/nrgdata_test if needed.
|
67
|
+
required_files : list of str
|
68
|
+
Relative paths that must exist inside the data directory.
|
69
|
+
|
70
|
+
Returns
|
71
|
+
-------
|
72
|
+
str
|
73
|
+
Path to a valid data directory.
|
74
|
+
"""
|
75
|
+
if candidate_paths is None:
|
76
|
+
candidate_paths = [
|
77
|
+
"~/Downloads/temp/shortrun/nrgdata_test",
|
78
|
+
"~/Downloads/ANTsPyMM_testing_data/nrgdata_test",
|
79
|
+
"~/data/ppmi/nrgdata_test",
|
80
|
+
"/mnt/data/nrgdata_test"
|
81
|
+
]
|
82
|
+
|
83
|
+
# First, search known paths
|
84
|
+
for path in candidate_paths:
|
85
|
+
full_path = os.path.expanduser(path)
|
86
|
+
if os.path.isdir(full_path) and _validate_required_files(full_path, required_files):
|
87
|
+
print(f"✅ Found valid data directory: {full_path}")
|
88
|
+
return full_path
|
89
|
+
|
90
|
+
# Handle automatic download
|
91
|
+
if isinstance(allow_download, str):
|
92
|
+
base_dir = os.path.expanduser(allow_download)
|
93
|
+
target_dir = os.path.join(base_dir, "nrgdata_test")
|
94
|
+
if not os.path.isdir(target_dir) or not _validate_required_files(target_dir, required_files):
|
95
|
+
print(f"📥 Will download data to: {target_dir}")
|
96
|
+
url = "https://figshare.com/ndownloader/articles/29391236/versions/1"
|
97
|
+
os.makedirs(base_dir, exist_ok=True)
|
98
|
+
zip_path = os.path.join(tempfile.gettempdir(), "antspymm_testdata.zip")
|
99
|
+
|
100
|
+
try:
|
101
|
+
_download_with_progress(url, zip_path)
|
102
|
+
print("📦 Extracting...")
|
103
|
+
with zipfile.ZipFile(zip_path, 'r') as zip_ref:
|
104
|
+
zip_ref.extractall(base_dir)
|
105
|
+
print(f"✅ Extracted to {target_dir}")
|
106
|
+
except Exception as e:
|
107
|
+
raise RuntimeError(f"❌ Download or extraction failed: {e}")
|
108
|
+
|
109
|
+
if not _validate_required_files(target_dir, required_files):
|
110
|
+
raise RuntimeError(f"❌ Downloaded data is missing required files in {target_dir}")
|
111
|
+
return target_dir
|
112
|
+
|
113
|
+
# Timeout handler for POSIX
|
114
|
+
def timeout_handler(signum, frame):
|
115
|
+
raise TimeoutError("⏳ No input received in time.")
|
116
|
+
|
117
|
+
if os.name == 'posix':
|
118
|
+
signal.signal(signal.SIGALRM, timeout_handler)
|
119
|
+
|
120
|
+
# Manual user prompt
|
121
|
+
print("🔍 Could not find valid data. You may enter a directory manually.")
|
122
|
+
print("🔗 Dataset info: https://figshare.com/articles/dataset/ANTsPyMM_testing_data/29391236")
|
123
|
+
|
124
|
+
for attempt in range(1, max_tries + 1):
|
125
|
+
try:
|
126
|
+
if os.name == 'posix':
|
127
|
+
signal.alarm(timeout)
|
128
|
+
user_input = input(f"⏱️ Attempt {attempt}/{max_tries} — Enter data directory (or 'q' to quit): ").strip()
|
129
|
+
if os.name == 'posix':
|
130
|
+
signal.alarm(0)
|
131
|
+
|
132
|
+
if user_input.lower() == 'q':
|
133
|
+
break
|
134
|
+
|
135
|
+
path = os.path.expanduser(user_input)
|
136
|
+
if os.path.isdir(path) and _validate_required_files(path, required_files):
|
137
|
+
print(f"✅ Using user-provided directory: {path}")
|
138
|
+
return path
|
139
|
+
else:
|
140
|
+
print("❌ Invalid or incomplete directory.")
|
141
|
+
|
142
|
+
except TimeoutError as e:
|
143
|
+
raise RuntimeError(str(e))
|
144
|
+
except KeyboardInterrupt:
|
145
|
+
raise RuntimeError("User interrupted execution. Exiting.")
|
146
|
+
|
147
|
+
raise RuntimeError("❗ No valid data directory found and download not permitted.")
|
148
|
+
|
149
|
+
candidate_rdirs = [
|
150
|
+
"~/Downloads/nrgdata_test/",
|
151
|
+
"~/Downloads/temp/nrgdata_test/",
|
152
|
+
"~/nrgdata_test/",
|
153
|
+
"~/data/ppmi/nrgdata_test/",
|
154
|
+
"/mnt/data/ppmi_testing/nrgdata_test/"]
|
155
|
+
|
156
|
+
|
157
|
+
rdir = find_data_dir( candidate_rdirs, allow_download="~/Downloads" )
|
158
|
+
print(f"Using data directory: {rdir}")
|
159
|
+
|
160
|
+
nthreads = str(8)
|
161
|
+
os.environ["TF_NUM_INTEROP_THREADS"] = nthreads
|
162
|
+
os.environ["TF_NUM_INTRAOP_THREADS"] = nthreads
|
163
|
+
os.environ["ITK_GLOBAL_DEFAULT_NUMBER_OF_THREADS"] = nthreads
|
164
|
+
os.environ["OPENBLAS_NUM_THREADS"] = nthreads
|
165
|
+
os.environ["MKL_NUM_THREADS"] = nthreads
|
166
|
+
import numpy as np
|
167
|
+
import glob as glob
|
168
|
+
import antspymm
|
169
|
+
import ants
|
170
|
+
import random
|
171
|
+
import re
|
172
|
+
|
173
|
+
def read_bvecs_rotated(bvec_file, rotmat):
|
174
|
+
bvecs = np.loadtxt(bvec_file)
|
175
|
+
if bvecs.shape[0] != 3:
|
176
|
+
bvecs = bvecs.T
|
177
|
+
rotated_bvecs = (rotmat @ bvecs).T
|
178
|
+
return rotated_bvecs
|
179
|
+
|
180
|
+
def mean_rgb_correlation(img1, img2, mask):
|
181
|
+
"""
|
182
|
+
Compute the mean correlation between two RGB images.
|
183
|
+
|
184
|
+
Parameters
|
185
|
+
----------
|
186
|
+
img1 : np.ndarray
|
187
|
+
First RGB image as a (H, W, 3) NumPy array.
|
188
|
+
img2 : np.ndarray
|
189
|
+
Second RGB image as a (H, W, 3) NumPy array.
|
190
|
+
|
191
|
+
Returns
|
192
|
+
-------
|
193
|
+
float
|
194
|
+
Mean Pearson correlation across the three RGB channels.
|
195
|
+
"""
|
196
|
+
if img1.shape != img2.shape:
|
197
|
+
raise ValueError("Input images must have the same shape.")
|
198
|
+
correlations = []
|
199
|
+
img1c = ants.split_channels(img1)
|
200
|
+
img2c = ants.split_channels(img2)
|
201
|
+
for c in range(3): # R, G, B
|
202
|
+
x = extract_masked_values( img1c[c], mask)
|
203
|
+
y = extract_masked_values( img2c[c], mask)
|
204
|
+
if np.std(x) == 0 or np.std(y) == 0:
|
205
|
+
corr = 0.0 # Handle flat images
|
206
|
+
else:
|
207
|
+
corr, _ = pearsonr(x, y)
|
208
|
+
correlations.append(corr)
|
209
|
+
return np.mean(correlations)
|
210
|
+
|
211
|
+
import numpy as np
|
212
|
+
import ants
|
213
|
+
|
214
|
+
def mean_rgb_mae(img1, img2, mask):
|
215
|
+
"""
|
216
|
+
Compute the mean absolute error (MAE) between two RGB images.
|
217
|
+
|
218
|
+
Parameters
|
219
|
+
----------
|
220
|
+
img1 : np.ndarray
|
221
|
+
First RGB image as a (H, W, 3) NumPy array.
|
222
|
+
img2 : np.ndarray
|
223
|
+
Second RGB image as a (H, W, 3) NumPy array.
|
224
|
+
mask : ants.ANTsImage
|
225
|
+
Binary mask defining valid pixels for error calculation.
|
226
|
+
|
227
|
+
Returns
|
228
|
+
-------
|
229
|
+
float
|
230
|
+
Mean absolute error across the three RGB channels.
|
231
|
+
"""
|
232
|
+
if img1.shape != img2.shape:
|
233
|
+
raise ValueError("Input images must have the same shape.")
|
234
|
+
|
235
|
+
mae_values = []
|
236
|
+
img1c = ants.split_channels(img1)
|
237
|
+
img2c = ants.split_channels(img2)
|
238
|
+
|
239
|
+
for c in range(3): # R, G, B
|
240
|
+
x = extract_masked_values(img1c[c], mask)
|
241
|
+
y = extract_masked_values(img2c[c], mask)
|
242
|
+
mae = np.mean(np.abs(x - y))
|
243
|
+
mae_values.append(mae)
|
244
|
+
|
245
|
+
return np.mean(mae_values)
|
246
|
+
|
247
|
+
def extract_masked_values(image, mask):
|
248
|
+
return image.numpy()[mask.numpy() > 0]
|
249
|
+
|
250
|
+
import numpy as np
|
251
|
+
|
252
|
+
def verify_unit_bvecs(bvecs, tol=1e-5):
|
253
|
+
"""
|
254
|
+
Verifies that each b-vector has unit norm within a tolerance.
|
255
|
+
|
256
|
+
Parameters
|
257
|
+
----------
|
258
|
+
bvecs : array-like, shape (N, 3)
|
259
|
+
Array of b-vectors, one per diffusion direction.
|
260
|
+
tol : float
|
261
|
+
Tolerance for unit norm.
|
262
|
+
|
263
|
+
Returns
|
264
|
+
-------
|
265
|
+
is_unit : np.ndarray, shape (N,)
|
266
|
+
Boolean array indicating if each b-vector is unit norm.
|
267
|
+
norms : np.ndarray, shape (N,)
|
268
|
+
Norms of each b-vector.
|
269
|
+
"""
|
270
|
+
bvecs = np.asarray(bvecs)
|
271
|
+
norms = np.linalg.norm(bvecs, axis=1)
|
272
|
+
is_unit = np.abs(norms - 1) < tol
|
273
|
+
return is_unit, norms
|
274
|
+
|
275
|
+
mydir = rdir + "PPMI/"
|
276
|
+
outdir = re.sub( 'nrgdata_test', 'antspymmoutput', rdir )
|
277
|
+
import glob as glob
|
278
|
+
|
279
|
+
t1fn=glob.glob(mydir+"101018/20210412/T1w/1496225/*.nii.gz")
|
280
|
+
if len(t1fn) > 0:
|
281
|
+
t1fn=t1fn[0]
|
282
|
+
print("Begin " + t1fn)
|
283
|
+
dtfn=glob.glob(mydir+"101018/20210412/DTI*/*/*.nii.gz")
|
284
|
+
dtfn.sort()
|
285
|
+
|
286
|
+
import re
|
287
|
+
|
288
|
+
# def test_efficient_dwi_fit_voxelwise_distortion_correction():
|
289
|
+
if len(dtfn) > 0:
|
290
|
+
img_LR_in = ants.image_read(dtfn[0])
|
291
|
+
img_LR_in_avg = ants.get_average_of_timeseries( img_LR_in )
|
292
|
+
mask = img_LR_in_avg.get_mask()
|
293
|
+
bvalfn = re.sub( 'nii.gz', 'bval', dtfn[0] )
|
294
|
+
bvecfn = re.sub( 'nii.gz', 'bvec', dtfn[0] )
|
295
|
+
if not exists(bvalfn) or not exists(bvecfn):
|
296
|
+
raise RuntimeError(f"Required bval/bvec files not found: {bvalfn}, {bvecfn}")
|
297
|
+
print(f"📁 Loading subject LR data from {bvalfn} ")
|
298
|
+
bvals, bvecs = read_bvals_bvecs(bvalfn, bvecfn)
|
299
|
+
bvecs = np.asarray(bvecs)
|
300
|
+
shape = img_LR_in.shape[:3]
|
301
|
+
|
302
|
+
print("📁 Loading subject T1 data...")
|
303
|
+
t1w = ants.image_read(t1fn)
|
304
|
+
t1w = ants.resample_image(t1w, [2, 2, 2], use_voxels=False)
|
305
|
+
bxt = antspynet.brain_extraction(t1w, modality='t1', verbose=False).threshold_image(0.5, 1.5)
|
306
|
+
|
307
|
+
if not "mytx" in globals():
|
308
|
+
dwianat = ants.slice_image( img_LR_in, idx=0, axis=3)
|
309
|
+
mytx = ants.registration( t1w, dwianat, 'SyNCC', syn_metric='CC', syn_sampling=2, total_sigma=0.5 )
|
310
|
+
mytx2 = ants.apply_transforms(t1w, img_LR_in_avg, mytx['fwdtransforms'],
|
311
|
+
interpolator='linear', imagetype=0, compose='/tmp/comptxDT2T1' )
|
312
|
+
print( mytx2 )
|
313
|
+
|
314
|
+
print("🔄 now with distortion correction...")
|
315
|
+
mydef = ants.image_read( mytx2 )
|
316
|
+
mywarp = ants.transform_from_displacement_field( mydef )
|
317
|
+
img_w = antspymm.timeseries_transform(mywarp, img_LR_in, reference=t1w)
|
318
|
+
print("🧠 Running warped fit...")
|
319
|
+
|
320
|
+
if not "FA_w" in globals():
|
321
|
+
mydefgrad = antspymm.deformation_gradient_optimized( mydef,
|
322
|
+
to_rotation=False, to_inverse_rotation=True )
|
323
|
+
bvecsdc = antspymm.distortion_correct_bvecs( bvecs, mydefgrad, t1w.direction, img_LR_in_avg.direction )
|
324
|
+
FA_w, MD_w, RGB_w = antspymm.efficient_dwi_fit_voxelwise(
|
325
|
+
imagein=img_w,
|
326
|
+
maskin=bxt,
|
327
|
+
bvals=bvals,
|
328
|
+
bvecs_5d=bvecsdc,
|
329
|
+
model_params={},
|
330
|
+
bvals_to_use=None,
|
331
|
+
num_threads=nt,
|
332
|
+
verbose=False
|
333
|
+
)
|
334
|
+
|
335
|
+
if not "FA_w2" in globals():
|
336
|
+
FA_w2, MD_w2, RGB_w2 = antspymm.efficient_dwi_fit_voxelwise(
|
337
|
+
imagein=img_w,
|
338
|
+
maskin=bxt,
|
339
|
+
bvals=bvals,
|
340
|
+
bvecs_5d=broadcast_bvecs_voxelwise(bvecs, t1w.shape),
|
341
|
+
model_params={},
|
342
|
+
bvals_to_use=None,
|
343
|
+
num_threads=nt,
|
344
|
+
verbose=False
|
345
|
+
)
|
346
|
+
|
347
|
+
print("📊 Comparing results...")
|
348
|
+
maske=ants.iMath(bxt,'ME',3)
|
349
|
+
fa_corr = mean_rgb_correlation( RGB_w, RGB_w2, maske )
|
350
|
+
print(f"✅ Direction-weighted FA correlation (original vs distortion corrected): {fa_corr:.4f}")
|
351
|
+
|
352
|
+
ants.image_write( t1w, '/tmp/t1w.nii.gz' )
|
353
|
+
ants.image_write( RGB_w, '/tmp/rgbw.nii.gz' )
|
{antspymm-1.5.6 → antspymm-1.5.7}/docs/dti_reconstruction_voxelwise_varying_bvectors_localint.py
RENAMED
@@ -234,9 +234,10 @@ if True:
|
|
234
234
|
mydef=ants.image_read(comptx)
|
235
235
|
mydefgrad = antspymm.deformation_gradient_optimized( mydef,
|
236
236
|
to_rotation=False, to_inverse_rotation=True )
|
237
|
-
bvecsRLw = antspymm.generate_voxelwise_bvecs( bvecs_rotated, mydefgrad, transpose=False )
|
238
|
-
mywarp = ants.transform_from_displacement_field( mydef )
|
237
|
+
# bvecsRLw = antspymm.generate_voxelwise_bvecs( bvecs_rotated, mydefgrad, transpose=False )
|
239
238
|
img_rotated_avg = ants.get_average_of_timeseries( img_rotated )
|
239
|
+
bvecsRLw = antspymm.distortion_correct_bvecs( bvecs_rotated, mydefgrad, img_LR_in_avg.direction, img_rotated_avg.direction )
|
240
|
+
mywarp = ants.transform_from_displacement_field( mydef )
|
240
241
|
img_w = antspymm.timeseries_transform(mywarp, img_rotated, reference=img_rotated_avg )
|
241
242
|
|
242
243
|
correlations = []
|
@@ -1,238 +0,0 @@
|
|
1
|
-
import numpy as np
|
2
|
-
import ants
|
3
|
-
import os
|
4
|
-
from dipy.io.gradients import read_bvals_bvecs
|
5
|
-
from scipy.stats import pearsonr
|
6
|
-
import antspymm
|
7
|
-
nt = 2
|
8
|
-
|
9
|
-
import numpy as np
|
10
|
-
from scipy.stats import pearsonr
|
11
|
-
|
12
|
-
def read_bvecs_rotated(bvec_file, rotmat):
|
13
|
-
bvecs = np.loadtxt(bvec_file)
|
14
|
-
if bvecs.shape[0] != 3:
|
15
|
-
bvecs = bvecs.T
|
16
|
-
rotated_bvecs = (rotmat @ bvecs).T
|
17
|
-
return rotated_bvecs
|
18
|
-
|
19
|
-
def mean_rgb_correlation(img1, img2, mask):
|
20
|
-
"""
|
21
|
-
Compute the mean correlation between two RGB images.
|
22
|
-
|
23
|
-
Parameters
|
24
|
-
----------
|
25
|
-
img1 : np.ndarray
|
26
|
-
First RGB image as a (H, W, 3) NumPy array.
|
27
|
-
img2 : np.ndarray
|
28
|
-
Second RGB image as a (H, W, 3) NumPy array.
|
29
|
-
|
30
|
-
Returns
|
31
|
-
-------
|
32
|
-
float
|
33
|
-
Mean Pearson correlation across the three RGB channels.
|
34
|
-
"""
|
35
|
-
if img1.shape != img2.shape:
|
36
|
-
raise ValueError("Input images must have the same shape.")
|
37
|
-
correlations = []
|
38
|
-
img1c = ants.split_channels(img1)
|
39
|
-
img2c = ants.split_channels(img2)
|
40
|
-
for c in range(3): # R, G, B
|
41
|
-
x = extract_masked_values( img1c[c], mask)
|
42
|
-
y = extract_masked_values( img2c[c], mask)
|
43
|
-
if np.std(x) == 0 or np.std(y) == 0:
|
44
|
-
corr = 0.0 # Handle flat images
|
45
|
-
else:
|
46
|
-
corr, _ = pearsonr(x, y)
|
47
|
-
correlations.append(corr)
|
48
|
-
return np.mean(correlations)
|
49
|
-
|
50
|
-
import numpy as np
|
51
|
-
import ants
|
52
|
-
|
53
|
-
def mean_rgb_mae(img1, img2, mask):
|
54
|
-
"""
|
55
|
-
Compute the mean absolute error (MAE) between two RGB images.
|
56
|
-
|
57
|
-
Parameters
|
58
|
-
----------
|
59
|
-
img1 : np.ndarray
|
60
|
-
First RGB image as a (H, W, 3) NumPy array.
|
61
|
-
img2 : np.ndarray
|
62
|
-
Second RGB image as a (H, W, 3) NumPy array.
|
63
|
-
mask : ants.ANTsImage
|
64
|
-
Binary mask defining valid pixels for error calculation.
|
65
|
-
|
66
|
-
Returns
|
67
|
-
-------
|
68
|
-
float
|
69
|
-
Mean absolute error across the three RGB channels.
|
70
|
-
"""
|
71
|
-
if img1.shape != img2.shape:
|
72
|
-
raise ValueError("Input images must have the same shape.")
|
73
|
-
|
74
|
-
mae_values = []
|
75
|
-
img1c = ants.split_channels(img1)
|
76
|
-
img2c = ants.split_channels(img2)
|
77
|
-
|
78
|
-
for c in range(3): # R, G, B
|
79
|
-
x = extract_masked_values(img1c[c], mask)
|
80
|
-
y = extract_masked_values(img2c[c], mask)
|
81
|
-
mae = np.mean(np.abs(x - y))
|
82
|
-
mae_values.append(mae)
|
83
|
-
|
84
|
-
return np.mean(mae_values)
|
85
|
-
|
86
|
-
def extract_masked_values(image, mask):
|
87
|
-
return image.numpy()[mask.numpy() > 0]
|
88
|
-
|
89
|
-
import numpy as np
|
90
|
-
|
91
|
-
def verify_unit_bvecs(bvecs, tol=1e-5):
|
92
|
-
"""
|
93
|
-
Verifies that each b-vector has unit norm within a tolerance.
|
94
|
-
|
95
|
-
Parameters
|
96
|
-
----------
|
97
|
-
bvecs : array-like, shape (N, 3)
|
98
|
-
Array of b-vectors, one per diffusion direction.
|
99
|
-
tol : float
|
100
|
-
Tolerance for unit norm.
|
101
|
-
|
102
|
-
Returns
|
103
|
-
-------
|
104
|
-
is_unit : np.ndarray, shape (N,)
|
105
|
-
Boolean array indicating if each b-vector is unit norm.
|
106
|
-
norms : np.ndarray, shape (N,)
|
107
|
-
Norms of each b-vector.
|
108
|
-
"""
|
109
|
-
bvecs = np.asarray(bvecs)
|
110
|
-
norms = np.linalg.norm(bvecs, axis=1)
|
111
|
-
is_unit = np.abs(norms - 1) < tol
|
112
|
-
return is_unit, norms
|
113
|
-
|
114
|
-
# def test_efficient_dwi_fit_voxelwise_distortion_correction():
|
115
|
-
if True:
|
116
|
-
print("simple test for distortion_correction consistency of efficient_dwi_fit_voxelwise")
|
117
|
-
ex_path = os.path.expanduser( "~/.antspyt1w/" )
|
118
|
-
ex_path_mm = os.path.expanduser( "~/.antspymm/" )
|
119
|
-
#### Load in data ####
|
120
|
-
print("Load in subject data ...")
|
121
|
-
lrid = "I1499279_Anon_20210819142214_5"
|
122
|
-
rlid = "I1499337_Anon_20210819142214_6"
|
123
|
-
# Load paths
|
124
|
-
print("📁 Loading subject LR data...")
|
125
|
-
lrid = os.path.join(ex_path_mm, lrid )
|
126
|
-
img_LR_in = ants.image_read(lrid + '.nii.gz')
|
127
|
-
img_LR_in_avg = ants.get_average_of_timeseries( img_LR_in )
|
128
|
-
mask = img_LR_in_avg.get_mask()
|
129
|
-
bvals, bvecs = read_bvals_bvecs(lrid + '.bval', lrid + '.bvec')
|
130
|
-
bvecs = np.asarray(bvecs)
|
131
|
-
shape = img_LR_in.shape[:3]
|
132
|
-
|
133
|
-
print("📁 Loading subject RL data...")
|
134
|
-
rlid = os.path.join(ex_path_mm, rlid )
|
135
|
-
img_RL_in = ants.image_read(rlid + '.nii.gz')
|
136
|
-
bvalsRL, bvecsRL = read_bvals_bvecs(rlid + '.bval', rlid + '.bvec')
|
137
|
-
|
138
|
-
img_RL_in_avg = ants.get_average_of_timeseries( img_RL_in )
|
139
|
-
maskRL = img_RL_in_avg.get_mask()
|
140
|
-
|
141
|
-
print("🧠 Running baseline LR fit...")
|
142
|
-
bvecs_5d_orig = np.broadcast_to(bvecs, shape + bvecs.shape).copy()
|
143
|
-
if not "FA_orig" in globals():
|
144
|
-
FA_orig, MD_orig, RGB_orig = antspymm.efficient_dwi_fit_voxelwise(
|
145
|
-
imagein=img_LR_in,
|
146
|
-
maskin=mask,
|
147
|
-
bvals=bvals,
|
148
|
-
bvecs_5d=bvecs_5d_orig,
|
149
|
-
model_params={},
|
150
|
-
bvals_to_use=None,
|
151
|
-
num_threads=nt,
|
152
|
-
verbose=False
|
153
|
-
)
|
154
|
-
|
155
|
-
bvecs_5d_origRL = np.broadcast_to(np.asarray(bvecsRL), shape + bvecsRL.shape).copy()
|
156
|
-
if not "FA_origRL" in globals():
|
157
|
-
FA_origRL, MD_origRL, RGB_origRL = antspymm.efficient_dwi_fit_voxelwise(
|
158
|
-
imagein=img_RL_in,
|
159
|
-
maskin=maskRL,
|
160
|
-
bvals=bvalsRL,
|
161
|
-
bvecs_5d=bvecs_5d_origRL,
|
162
|
-
model_params={},
|
163
|
-
bvals_to_use=None,
|
164
|
-
num_threads=nt,
|
165
|
-
verbose=False
|
166
|
-
)
|
167
|
-
|
168
|
-
print("dist corr")
|
169
|
-
if not "mytx" in globals():
|
170
|
-
mytx = ants.registration( FA_orig, FA_origRL, 'SyNBold' )
|
171
|
-
mytx2 = ants.apply_transforms(FA_orig, FA_origRL, mytx['fwdtransforms'],
|
172
|
-
interpolator='linear', imagetype=0, compose='/tmp/comptx' )
|
173
|
-
print( mytx2 )
|
174
|
-
|
175
|
-
print("🔄 now with distortion correction...")
|
176
|
-
mydef = ants.image_read( mytx2 )
|
177
|
-
mywarp = ants.transform_from_displacement_field( mydef )
|
178
|
-
img_w = antspymm.timeseries_transform(mywarp, img_RL_in, reference=img_LR_in_avg)
|
179
|
-
mask_w = ants.apply_ants_transform_to_image(mywarp, maskRL, reference=img_LR_in_avg, interpolation='nearestNeighbor')
|
180
|
-
print("🧠 Running warped fit...")
|
181
|
-
if not "FA_w" in globals():
|
182
|
-
bvecsRL = np.asarray(bvecsRL)
|
183
|
-
mydefgrad = antspymm.deformation_gradient_optimized( mydef,
|
184
|
-
to_rotation=False, to_inverse_rotation=True )
|
185
|
-
bvecsRLw = antspymm.generate_voxelwise_bvecs( bvecsRL, mydefgrad, transpose=False )
|
186
|
-
FA_w, MD_w, RGB_w = antspymm.efficient_dwi_fit_voxelwise(
|
187
|
-
imagein=img_w,
|
188
|
-
maskin=mask_w,
|
189
|
-
bvals=bvalsRL,
|
190
|
-
bvecs_5d=bvecsRLw,
|
191
|
-
model_params={},
|
192
|
-
bvals_to_use=None,
|
193
|
-
num_threads=nt,
|
194
|
-
verbose=False
|
195
|
-
)
|
196
|
-
|
197
|
-
if not "FA_w2" in globals():
|
198
|
-
bvecsRL = np.asarray(bvecsRL)
|
199
|
-
FA_w2, MD_w2, RGB_w2 = antspymm.efficient_dwi_fit_voxelwise(
|
200
|
-
imagein=img_w,
|
201
|
-
maskin=mask_w,
|
202
|
-
bvals=bvalsRL,
|
203
|
-
bvecs_5d=bvecs_5d_origRL,
|
204
|
-
model_params={},
|
205
|
-
bvals_to_use=None,
|
206
|
-
num_threads=nt,
|
207
|
-
verbose=False
|
208
|
-
)
|
209
|
-
|
210
|
-
fff = mean_rgb_correlation
|
211
|
-
print("📊 Comparing results...")
|
212
|
-
maskJoined = ants.threshold_image( mask + mask_w, 1.05, 2.0 )
|
213
|
-
maske=ants.iMath(maskJoined,'ME',3)
|
214
|
-
fa_corr = fff( RGB_orig, RGB_w, maske )
|
215
|
-
print(f"✅ FA correlation (original vs distortion corrected): {fa_corr:.4f}")
|
216
|
-
|
217
|
-
fa_corrX = fff( RGB_orig, RGB_w2, maske )
|
218
|
-
print(f"✅ FA correlation (original vs distortion corrected global recon): {fa_corrX:.4f}")
|
219
|
-
|
220
|
-
RGB_origRLc = ants.split_channels(RGB_origRL)
|
221
|
-
for c in range(3):
|
222
|
-
RGB_origRLc[c] = ants.apply_ants_transform_to_image(
|
223
|
-
mywarp, RGB_origRLc[c],
|
224
|
-
reference=img_LR_in_avg, interpolation='linear'
|
225
|
-
)
|
226
|
-
|
227
|
-
fa_corrY = fff( RGB_orig, ants.merge_channels(RGB_origRLc), maske )
|
228
|
-
print(f"✅ FA correlation (original vs warped RGB global recon): {fa_corrY:.4f}")
|
229
|
-
|
230
|
-
# assert fa_corr > 0.80, "FA correlation too low"
|
231
|
-
|
232
|
-
print("🎉 Test passed: model is distortion-consistent.")
|
233
|
-
|
234
|
-
# ants.image_write( FA_orig, '/tmp/xxx.nii.gz' )
|
235
|
-
# ants.image_write( FA_w, '/tmp/yyy.nii.gz' )
|
236
|
-
#
|
237
|
-
# Example usage:
|
238
|
-
# test_efficient_dwi_fit_voxelwise_distortion_correction()
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|