pyLQT 1.0.dev1__py3-none-any.whl → 1.0.dev2__py3-none-any.whl
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.
- pyLQT/__init__.py +3 -0
- pyLQT/cli.py +71 -0
- {lesion_metrics → pyLQT/lesion_metrics}/gray_matter/metrics.py +1 -1
- {lesion_metrics → pyLQT/lesion_metrics}/utils/downloads.py +82 -9
- {lesion_metrics → pyLQT/lesion_metrics}/utils/registration.py +13 -9
- pyLQT/pipeline.py +183 -0
- pylqt-1.0.dev2.dist-info/METADATA +136 -0
- pylqt-1.0.dev2.dist-info/RECORD +23 -0
- {pylqt-1.0.dev1.dist-info → pylqt-1.0.dev2.dist-info}/WHEEL +1 -1
- pylqt-1.0.dev2.dist-info/entry_points.txt +2 -0
- pylqt-1.0.dev2.dist-info/top_level.txt +1 -0
- pyLQT_cli.py +0 -180
- pylqt-1.0.dev1.dist-info/METADATA +0 -62
- pylqt-1.0.dev1.dist-info/RECORD +0 -21
- pylqt-1.0.dev1.dist-info/entry_points.txt +0 -2
- pylqt-1.0.dev1.dist-info/top_level.txt +0 -2
- {lesion_metrics → pyLQT/lesion_metrics}/__init__.py +0 -0
- {lesion_metrics → pyLQT/lesion_metrics}/gray_matter/__init__.py +0 -0
- {lesion_metrics → pyLQT/lesion_metrics}/gray_matter/utils.py +0 -0
- {lesion_metrics → pyLQT/lesion_metrics}/network/__init__.py +0 -0
- {lesion_metrics → pyLQT/lesion_metrics}/network/metrics.py +0 -0
- {lesion_metrics → pyLQT/lesion_metrics}/network/utils.py +0 -0
- {lesion_metrics → pyLQT/lesion_metrics}/utils/__init__.py +0 -0
- {lesion_metrics → pyLQT/lesion_metrics}/utils/configs.py +0 -0
- {lesion_metrics → pyLQT/lesion_metrics}/white_matter/__init__.py +0 -0
- {lesion_metrics → pyLQT/lesion_metrics}/white_matter/metrics.py +0 -0
- {lesion_metrics → pyLQT/lesion_metrics}/white_matter/utils.py +0 -0
- {pylqt-1.0.dev1.dist-info → pylqt-1.0.dev2.dist-info}/licenses/LICENSE +0 -0
pyLQT/__init__.py
ADDED
pyLQT/cli.py
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
|
|
3
|
+
from pipeline import run_pyLQT
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def main():
|
|
7
|
+
"""Run the pyLQT pipeline through the command-line interface."""
|
|
8
|
+
|
|
9
|
+
parser = argparse.ArgumentParser(
|
|
10
|
+
prog="pyLQT",
|
|
11
|
+
description="Run the pyLQT lesion quantification pipeline."
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
parser.add_argument(
|
|
15
|
+
"--lesion_cfg",
|
|
16
|
+
required=True,
|
|
17
|
+
help='[Path] Path to lesion config'
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
parser.add_argument(
|
|
21
|
+
"--atlas_cfg",
|
|
22
|
+
default=None,
|
|
23
|
+
help='[Path, optional] Path to atlas config. If none provided, a default atlas is downloaded.'
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
parser.add_argument(
|
|
27
|
+
"--tracts_cfg",
|
|
28
|
+
default=None,
|
|
29
|
+
help='[Path, optional] Path to tracts config. If none provided, default tracts are downloaded.'
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
parser.add_argument(
|
|
33
|
+
"--out_folder",
|
|
34
|
+
required=True,
|
|
35
|
+
help='[Path] Output folder path'
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
parser.add_argument(
|
|
39
|
+
"--registration_type",
|
|
40
|
+
choices=['affine', 'syn'],
|
|
41
|
+
default='syn',
|
|
42
|
+
help='[str, default=syn] Registration type, from lesion space and atlas space to tract space'
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
parser.add_argument(
|
|
46
|
+
"--spared_threshold",
|
|
47
|
+
default=0.5,
|
|
48
|
+
help='[float (0,1), default=0.5] Defines threshold for spared parcel-to-parcel connectivity'
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
parser.add_argument(
|
|
52
|
+
"--delete_tmp",
|
|
53
|
+
default=False,
|
|
54
|
+
help='[bool, default=False] Delete temporary folder'
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
args = parser.parse_args()
|
|
58
|
+
|
|
59
|
+
run_pyLQT(
|
|
60
|
+
lesion_cfg=args.lesion_cfg,
|
|
61
|
+
atlas_cfg=args.atlas_cfg,
|
|
62
|
+
tracts_cfg=args.tracts_cfg,
|
|
63
|
+
out_folder=args.out_folder,
|
|
64
|
+
registration_type=args.registration_type,
|
|
65
|
+
spared_threshold=args.spared_threshold,
|
|
66
|
+
delete_tmp=args.delete_tmp,
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
if __name__ == "__main__":
|
|
71
|
+
main()
|
|
@@ -14,7 +14,7 @@ def get_parcel_damage(
|
|
|
14
14
|
):
|
|
15
15
|
"""Compute lesion damage for each parcel of an atlas.
|
|
16
16
|
|
|
17
|
-
The lesion is resampled to the atlas
|
|
17
|
+
The lesion is resampled to the atlas grid, and the proportion of
|
|
18
18
|
each parcel overlapping with the lesion is computed. A parcel damage
|
|
19
19
|
map is created, where each parcel voxel is assigned the damage proportion
|
|
20
20
|
of its parcel.
|
|
@@ -6,6 +6,7 @@ import shutil
|
|
|
6
6
|
import gzip
|
|
7
7
|
import nibabel as nib
|
|
8
8
|
import numpy as np
|
|
9
|
+
import pandas as pd
|
|
9
10
|
|
|
10
11
|
from templateflow import api as tf
|
|
11
12
|
from pathlib import Path
|
|
@@ -135,7 +136,72 @@ def uncompress_trk_gz(
|
|
|
135
136
|
|
|
136
137
|
return out_path
|
|
137
138
|
|
|
138
|
-
def
|
|
139
|
+
def default_atlas_download(out_folder: Path):
|
|
140
|
+
atlas_cfg_folder = out_folder / "glasser360_config"
|
|
141
|
+
atlas_cfg_folder.mkdir(parents=True, exist_ok=True)
|
|
142
|
+
|
|
143
|
+
atlas_path = atlas_cfg_folder / "glasser360_MNI152NLin2009cAsym.nii.gz"
|
|
144
|
+
atlas_labels_path = atlas_cfg_folder / "glasser360_labels.csv"
|
|
145
|
+
atlas_labels_path_txt = atlas_labels_path.with_suffix(".txt")
|
|
146
|
+
atlas_cfg_path = atlas_cfg_folder / "glasser360_config.json"
|
|
147
|
+
|
|
148
|
+
if atlas_path.exists():
|
|
149
|
+
warnings.warn(
|
|
150
|
+
f"atlas already exists, using it: {atlas_path}"
|
|
151
|
+
)
|
|
152
|
+
else:
|
|
153
|
+
url_atlas = "https://ndownloader.figshare.com/files/43043653"
|
|
154
|
+
|
|
155
|
+
response = requests.get(url_atlas, stream=True, timeout=60)
|
|
156
|
+
response.raise_for_status()
|
|
157
|
+
|
|
158
|
+
with atlas_path.open("wb") as f:
|
|
159
|
+
for chunk in response.iter_content(chunk_size=1024*1024):
|
|
160
|
+
if chunk:
|
|
161
|
+
f.write(chunk)
|
|
162
|
+
|
|
163
|
+
if atlas_labels_path.exists():
|
|
164
|
+
warnings.warn(
|
|
165
|
+
f"atlas labels already exist, using it: {atlas_labels_path}"
|
|
166
|
+
)
|
|
167
|
+
else:
|
|
168
|
+
url_labels = "https://ndownloader.figshare.com/files/43041079"
|
|
169
|
+
|
|
170
|
+
response = requests.get(url_labels, stream=True, timeout=60)
|
|
171
|
+
response.raise_for_status()
|
|
172
|
+
|
|
173
|
+
with atlas_labels_path_txt.open("wb") as f:
|
|
174
|
+
for chunk in response.iter_content(chunk_size=1024*1024):
|
|
175
|
+
if chunk:
|
|
176
|
+
f.write(chunk)
|
|
177
|
+
|
|
178
|
+
with open(atlas_labels_path_txt, "r") as f:
|
|
179
|
+
labels = [line.strip() for line in f if line.strip()]
|
|
180
|
+
|
|
181
|
+
atlas_labels = pd.DataFrame({
|
|
182
|
+
"Parcel": range(1, len(labels) + 1),
|
|
183
|
+
"Label": labels
|
|
184
|
+
})
|
|
185
|
+
|
|
186
|
+
atlas_labels.to_csv(atlas_labels_path, index=False)
|
|
187
|
+
atlas_labels_path_txt.unlink()
|
|
188
|
+
|
|
189
|
+
config = {
|
|
190
|
+
"name": "glasser360",
|
|
191
|
+
"atlas": "glasser360_MNI152NLin2009cAsym.nii.gz",
|
|
192
|
+
"atlas_labels": "glasser360_labels.csv",
|
|
193
|
+
"space": "MNI152NLin2009cAsym"
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
atlas_cfg = Path(atlas_cfg_folder / "glasser360_config.json")
|
|
197
|
+
|
|
198
|
+
with open(atlas_cfg, "w") as f:
|
|
199
|
+
json.dump(config, f, indent=4)
|
|
200
|
+
|
|
201
|
+
return atlas_cfg
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def default_tracts_download(out_folder: Path):
|
|
139
205
|
"""Download the default HCP1065 tractography dataset and create its configuration.
|
|
140
206
|
|
|
141
207
|
The function downloads the HCP1065 average tractograms and their
|
|
@@ -162,7 +228,9 @@ def default_download(out_folder: Path):
|
|
|
162
228
|
|
|
163
229
|
if tracts_folder.exists():
|
|
164
230
|
warnings.warn(
|
|
165
|
-
f"Tracts folder already exists, using it: {tracts_folder}"
|
|
231
|
+
f"Tracts folder already exists, using it: {tracts_folder}",
|
|
232
|
+
UserWarning,
|
|
233
|
+
stacklevel=2
|
|
166
234
|
)
|
|
167
235
|
else:
|
|
168
236
|
url = "https://github.com/data-others/atlas/releases/download/hcp1065/hcp1065_avg_tracts_trk.zip"
|
|
@@ -185,7 +253,9 @@ def default_download(out_folder: Path):
|
|
|
185
253
|
|
|
186
254
|
if references_folder.exists():
|
|
187
255
|
warnings.warn(
|
|
188
|
-
f"References folder already exists, using it: {references_folder}"
|
|
256
|
+
f"References folder already exists, using it: {references_folder}",
|
|
257
|
+
UserWarning,
|
|
258
|
+
stacklevel=2
|
|
189
259
|
)
|
|
190
260
|
else:
|
|
191
261
|
url = "https://github.com/data-others/atlas/releases/download/hcp1065/hcp1065_avg_tracts_nifti.zip"
|
|
@@ -200,14 +270,13 @@ def default_download(out_folder: Path):
|
|
|
200
270
|
f.write(chunk)
|
|
201
271
|
|
|
202
272
|
with zipfile.ZipFile(zip_path) as zip_file:
|
|
203
|
-
zip_file.extractall(
|
|
273
|
+
zip_file.extractall(tracts_cfg_folder)
|
|
204
274
|
|
|
205
275
|
zip_path.unlink()
|
|
206
276
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
)
|
|
277
|
+
extracted_niftis = tracts_cfg_folder / "nifti"
|
|
278
|
+
if extracted_niftis.exists():
|
|
279
|
+
extracted_niftis.rename(references_folder)
|
|
211
280
|
|
|
212
281
|
tracts_name = "HCP1065"
|
|
213
282
|
tracts_space = "MNI152NLin2009aAsym"
|
|
@@ -247,4 +316,8 @@ def get_ref_space_tf(space: str, res: int = 1):
|
|
|
247
316
|
ref_space_volume[brain_mask.get_fdata() <= 0] = 0
|
|
248
317
|
ref_space = nib.Nifti1Image(ref_space_volume, t1w_ref.affine, t1w_ref.header)
|
|
249
318
|
|
|
250
|
-
return ref_space
|
|
319
|
+
return ref_space
|
|
320
|
+
|
|
321
|
+
if __name__=="__main__":
|
|
322
|
+
|
|
323
|
+
default_atlas_download(Path("D:/internship_myspace_lab/pyLQT/test_download_atlas"))
|
|
@@ -26,14 +26,18 @@ def register_affine(
|
|
|
26
26
|
AffineMap
|
|
27
27
|
Affine mapping from the moving image space to the static image space.
|
|
28
28
|
"""
|
|
29
|
-
|
|
29
|
+
|
|
30
|
+
moving_data = moving.get_fdata()
|
|
31
|
+
static_data = static.get_fdata()
|
|
32
|
+
|
|
33
|
+
metric = MutualInformationMetric(nbins=32, sampling_proportion=0.2)
|
|
30
34
|
|
|
31
|
-
affreg = AffineRegistration(metric=metric)
|
|
35
|
+
affreg = AffineRegistration(metric=metric, level_iters=[500, 250, 100])
|
|
32
36
|
|
|
33
37
|
transform = TranslationTransform3D()
|
|
34
38
|
translation = affreg.optimize(
|
|
35
|
-
|
|
36
|
-
|
|
39
|
+
static_data,
|
|
40
|
+
moving_data,
|
|
37
41
|
transform,
|
|
38
42
|
params0=None,
|
|
39
43
|
static_grid2world=static.affine,
|
|
@@ -42,8 +46,8 @@ def register_affine(
|
|
|
42
46
|
|
|
43
47
|
transform = RigidTransform3D()
|
|
44
48
|
rigid = affreg.optimize(
|
|
45
|
-
|
|
46
|
-
|
|
49
|
+
static_data,
|
|
50
|
+
moving_data,
|
|
47
51
|
transform,
|
|
48
52
|
starting_affine=translation.affine,
|
|
49
53
|
params0=None,
|
|
@@ -53,8 +57,8 @@ def register_affine(
|
|
|
53
57
|
|
|
54
58
|
transform = AffineTransform3D()
|
|
55
59
|
mapping = affreg.optimize(
|
|
56
|
-
|
|
57
|
-
|
|
60
|
+
static_data,
|
|
61
|
+
moving_data,
|
|
58
62
|
transform,
|
|
59
63
|
starting_affine=rigid.affine,
|
|
60
64
|
params0=None,
|
|
@@ -93,7 +97,7 @@ def register_syn(
|
|
|
93
97
|
"""
|
|
94
98
|
metric = CCMetric(dim=3)
|
|
95
99
|
|
|
96
|
-
syn = SymmetricDiffeomorphicRegistration(metric)
|
|
100
|
+
syn = SymmetricDiffeomorphicRegistration(metric, level_iters=[20, 10, 5])
|
|
97
101
|
|
|
98
102
|
mapping = syn.optimize(static.get_fdata(),
|
|
99
103
|
moving.get_fdata(),
|
pyLQT/pipeline.py
ADDED
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
from lesion_metrics.white_matter.metrics import get_tracts_disconnections_severity, get_tracts_disconnections_map
|
|
2
|
+
from lesion_metrics.white_matter.utils import get_streamlines_disconnection, get_aggregated_tracts, clip_tractogram_to_grid
|
|
3
|
+
from lesion_metrics.network.metrics import get_sspl_increase, get_parcel_wise_disconnection_severity
|
|
4
|
+
from lesion_metrics.gray_matter.metrics import get_parcel_damage
|
|
5
|
+
from lesion_metrics.utils.configs import atlas_cfg_read, tracts_cfg_read, lesion_cfg_read
|
|
6
|
+
from lesion_metrics.utils.downloads import default_tracts_download, default_atlas_download, create_tract_list, uncompress_trk_gz, get_ref_space_tf
|
|
7
|
+
from lesion_metrics.utils.registration import register_affine, register_syn
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
import nibabel as nib
|
|
11
|
+
import shutil
|
|
12
|
+
import tempfile
|
|
13
|
+
|
|
14
|
+
def run_pyLQT(
|
|
15
|
+
lesion_cfg,
|
|
16
|
+
out_folder,
|
|
17
|
+
atlas_cfg=None,
|
|
18
|
+
tracts_cfg=None,
|
|
19
|
+
registration_type="syn",
|
|
20
|
+
spared_threshold=0.5,
|
|
21
|
+
delete_tmp=False,
|
|
22
|
+
):
|
|
23
|
+
"""Run the pyLQT lesion quantification pipeline.
|
|
24
|
+
|
|
25
|
+
Parameters
|
|
26
|
+
----------
|
|
27
|
+
lesion_cfg : str or pathlib.Path
|
|
28
|
+
Path to the lesion configuration file.
|
|
29
|
+
out_folder : str or pathlib.Path
|
|
30
|
+
Output directory.
|
|
31
|
+
atlas_cfg : str or pathlib.Path, optional
|
|
32
|
+
Path to the atlas configuration file. If None, the default atlas
|
|
33
|
+
is downloaded.
|
|
34
|
+
tracts_cfg : str or pathlib.Path, optional
|
|
35
|
+
Path to the tractography configuration file. If None, the default
|
|
36
|
+
tractography dataset is downloaded.
|
|
37
|
+
registration_type : {"affine", "syn"}, default="syn"
|
|
38
|
+
Registration method used to map the lesion and atlas to tract space.
|
|
39
|
+
spared_threshold : float, default=0.5
|
|
40
|
+
Threshold in (0, 1) for spared parcel-to-parcel connectivity.
|
|
41
|
+
delete_tmp : bool, default=False
|
|
42
|
+
Whether to delete the temporary directory after processing.
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
if registration_type not in ("affine", "syn"):
|
|
46
|
+
raise ValueError("registration_type must be 'affine' or 'syn'")
|
|
47
|
+
|
|
48
|
+
spared_threshold = float(spared_threshold)
|
|
49
|
+
if not 0 < spared_threshold < 1:
|
|
50
|
+
raise ValueError("spared_threshold must be between 0 and 1")
|
|
51
|
+
|
|
52
|
+
out_folder = Path(out_folder)
|
|
53
|
+
out_folder.mkdir(parents=True, exist_ok=True)
|
|
54
|
+
tmp_folder = Path(tempfile.mkdtemp(prefix="LQT_tmp_", dir=out_folder))
|
|
55
|
+
|
|
56
|
+
lesion_cfg_path = Path(lesion_cfg)
|
|
57
|
+
atlas_cfg_path = (
|
|
58
|
+
default_atlas_download(out_folder)
|
|
59
|
+
if atlas_cfg is None
|
|
60
|
+
else Path(atlas_cfg)
|
|
61
|
+
)
|
|
62
|
+
tracts_cfg_path = (
|
|
63
|
+
default_tracts_download(out_folder)
|
|
64
|
+
if tracts_cfg is None
|
|
65
|
+
else Path(tracts_cfg)
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
lesion_cohort_name, lesion_folder, lesion_space = lesion_cfg_read(lesion_cfg_path)
|
|
69
|
+
atlas_name, atlas, atlas_labels, atlas_space = atlas_cfg_read(atlas_cfg_path)
|
|
70
|
+
tracts_name, tracts_folder, references_folder, tracts_space = (tracts_cfg_read(tracts_cfg_path))
|
|
71
|
+
|
|
72
|
+
if lesion_space.endswith((".nii.gz", ".nii")):
|
|
73
|
+
lesion_ref_space = nib.load(lesion_space)
|
|
74
|
+
else:
|
|
75
|
+
lesion_ref_space = get_ref_space_tf(lesion_space)
|
|
76
|
+
|
|
77
|
+
if atlas_space.endswith((".nii.gz", ".nii")):
|
|
78
|
+
atlas_ref_space = nib.load(atlas_space)
|
|
79
|
+
else:
|
|
80
|
+
atlas_ref_space = get_ref_space_tf(atlas_space)
|
|
81
|
+
|
|
82
|
+
if tracts_space.endswith((".nii.gz", ".nii")):
|
|
83
|
+
tracts_ref_space = nib.load(tracts_space)
|
|
84
|
+
else:
|
|
85
|
+
tracts_ref_space = get_ref_space_tf(tracts_space)
|
|
86
|
+
|
|
87
|
+
registrator = (register_affine if registration_type == "affine" else register_syn)
|
|
88
|
+
|
|
89
|
+
lesion_tracts_mapping = registrator(lesion_ref_space, tracts_ref_space)
|
|
90
|
+
atlas_tracts_mapping = registrator(atlas_ref_space, tracts_ref_space)
|
|
91
|
+
|
|
92
|
+
atlas_trans_volume = atlas_tracts_mapping.transform(atlas.get_fdata(), interpolation="nearest")
|
|
93
|
+
atlas_trans = nib.Nifti1Image(
|
|
94
|
+
atlas_trans_volume,
|
|
95
|
+
tracts_ref_space.affine,
|
|
96
|
+
tracts_ref_space.header,
|
|
97
|
+
)
|
|
98
|
+
nib.save(atlas_trans, tmp_folder / "atlas_trans.nii.gz")
|
|
99
|
+
|
|
100
|
+
tracts_pathlist_gz, references_pathlist = create_tract_list(
|
|
101
|
+
references_folder=references_folder,
|
|
102
|
+
tracts_folder=tracts_folder,
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
tracts_pathlist = []
|
|
106
|
+
for tract_path in tracts_pathlist_gz:
|
|
107
|
+
relative_path = tract_path.relative_to(tracts_folder)
|
|
108
|
+
out_path = tmp_folder / relative_path.with_suffix("")
|
|
109
|
+
out_path.parent.mkdir(parents=True, exist_ok=True)
|
|
110
|
+
|
|
111
|
+
tracts_pathlist.append(uncompress_trk_gz(tract_path, out_path))
|
|
112
|
+
|
|
113
|
+
aggregated_tracts_path = tmp_folder / "aggregated_tracts.trk"
|
|
114
|
+
aggregated_tracts = get_aggregated_tracts(
|
|
115
|
+
tracts_pathlist=tracts_pathlist,
|
|
116
|
+
out_path=aggregated_tracts_path,
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
for lesion_path in lesion_folder.glob("*.nii*"):
|
|
120
|
+
lesion_name = lesion_path.name.removesuffix(".nii.gz").removesuffix(".nii")
|
|
121
|
+
out_dir = out_folder / lesion_name
|
|
122
|
+
out_dir.mkdir(parents=True, exist_ok=True)
|
|
123
|
+
|
|
124
|
+
tmp_dir = tmp_folder / lesion_name
|
|
125
|
+
tmp_dir.mkdir(parents=True, exist_ok=True)
|
|
126
|
+
|
|
127
|
+
lesion = nib.load(lesion_path)
|
|
128
|
+
lesion_trans_volume = lesion_tracts_mapping.transform(lesion.get_fdata(), interpolation="linear")
|
|
129
|
+
lesion_trans = nib.Nifti1Image(
|
|
130
|
+
lesion_trans_volume,
|
|
131
|
+
tracts_ref_space.affine,
|
|
132
|
+
tracts_ref_space.header,
|
|
133
|
+
)
|
|
134
|
+
nib.save(lesion_trans, tmp_dir / "lesion_trans.nii.gz")
|
|
135
|
+
|
|
136
|
+
aggregated_tracts_disconnected_path = tmp_dir / "aggregated_disconnected.trk"
|
|
137
|
+
aggregated_tracts_disconnected, _ = get_streamlines_disconnection(
|
|
138
|
+
tract=aggregated_tracts,
|
|
139
|
+
lesion=lesion_trans,
|
|
140
|
+
out_path=aggregated_tracts_disconnected_path,
|
|
141
|
+
)
|
|
142
|
+
aggregated_tracts_disconnected = clip_tractogram_to_grid(aggregated_tracts_disconnected)
|
|
143
|
+
|
|
144
|
+
get_parcel_damage(
|
|
145
|
+
lesion=lesion_trans,
|
|
146
|
+
atlas=atlas_trans,
|
|
147
|
+
atlas_labels=atlas_labels,
|
|
148
|
+
parcel_column="Parcel",
|
|
149
|
+
out_path=out_dir / "parcel_damage",
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
get_tracts_disconnections_severity(
|
|
153
|
+
tracts_path=tracts_pathlist,
|
|
154
|
+
references_path=references_pathlist,
|
|
155
|
+
lesion=lesion_trans,
|
|
156
|
+
disc_tracts_out_path=tmp_dir / "wm_disconnections_severity",
|
|
157
|
+
disc_frame_out_path=out_dir / "wm_disconnections_severity",
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
get_tracts_disconnections_map(
|
|
161
|
+
tracts=aggregated_tracts,
|
|
162
|
+
tracts_disconnected=aggregated_tracts_disconnected,
|
|
163
|
+
out_path=out_dir / "wm_disconnections_map",
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
structural_connectivity, _, _, per_spared_connections = (
|
|
167
|
+
get_parcel_wise_disconnection_severity(
|
|
168
|
+
aggregated_tracts=aggregated_tracts,
|
|
169
|
+
aggregated_tracts_disconnected=aggregated_tracts_disconnected,
|
|
170
|
+
atlas=atlas_trans,
|
|
171
|
+
out_path=out_dir / "parcel_wise_disconnection_severity",
|
|
172
|
+
)
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
get_sspl_increase(
|
|
176
|
+
structural_connectivity=structural_connectivity,
|
|
177
|
+
per_spared_connections=per_spared_connections,
|
|
178
|
+
out_path=out_dir / "sspl_increase",
|
|
179
|
+
spared_threshold=spared_threshold,
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
if delete_tmp:
|
|
183
|
+
shutil.rmtree(tmp_folder)
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pyLQT
|
|
3
|
+
Version: 1.0.dev2
|
|
4
|
+
Summary: Python version of LQT from Griffis et al. 2021 (https://www.sciencedirect.com/science/article/pii/S2213158221000838)
|
|
5
|
+
Author-email: Antonin HUDRY <hudryantoninm@gmail.com>
|
|
6
|
+
License-Expression: GPL-3.0-only
|
|
7
|
+
Keywords: LQT,lqt,lesion,brain,
|
|
8
|
+
Classifier: Development Status :: 2 - Pre-Alpha
|
|
9
|
+
Classifier: Intended Audience :: Science/Research
|
|
10
|
+
Classifier: Intended Audience :: Healthcare Industry
|
|
11
|
+
Classifier: Programming Language :: Python
|
|
12
|
+
Requires-Python: >=3.10
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
License-File: LICENSE
|
|
15
|
+
Requires-Dist: numpy<2.0
|
|
16
|
+
Requires-Dist: nibabel>5.0
|
|
17
|
+
Requires-Dist: pandas>3.0
|
|
18
|
+
Requires-Dist: scipy>1.15
|
|
19
|
+
Requires-Dist: requests>2.0
|
|
20
|
+
Requires-Dist: dipy>1.10
|
|
21
|
+
Requires-Dist: templateflow
|
|
22
|
+
Requires-Dist: networkx>3.6.1
|
|
23
|
+
Dynamic: license-file
|
|
24
|
+
|
|
25
|
+
# pyLQT
|
|
26
|
+
|
|
27
|
+
pyLQT, standing for “Python Lesion Quantification Toolkit”, is a toolbox designed for analysis brain-lesioned patients scans with minimal resources, regrouping several metrics for lesion quantification. It is a sucessor to LQT [[1]](#ref1) which was originally developped on MATLAB and remastered on R [[2]](#ref2).
|
|
28
|
+
|
|
29
|
+
The metrics are divided into three main domains:
|
|
30
|
+
|
|
31
|
+
* **Grey matter:** lesion effects on grey-matter regions and parcels.
|
|
32
|
+
* **White matter:** lesion effects on white-matter tracts and streamlines.
|
|
33
|
+
* **Network:** lesion effects on structural or functional brain networks.
|
|
34
|
+
|
|
35
|
+
## installation
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
pip install pyLQT
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Running pyLQT
|
|
42
|
+
|
|
43
|
+
### Configuration files
|
|
44
|
+
|
|
45
|
+
pyLQT uses three JSON configuration files to define the lesion cohort, atlas, and tractography dataset. Each file specifies the relevant data paths and reference space. For the simplest usage, only the lesion cohort configuration file is required, in which case the atlas and tractogram files are automatically downloaded.
|
|
46
|
+
|
|
47
|
+
##### Lesion configuration
|
|
48
|
+
|
|
49
|
+
Defines the lesion cohort, the folder containing the lesion NIfTI files, and their reference space.
|
|
50
|
+
|
|
51
|
+
```json
|
|
52
|
+
{
|
|
53
|
+
"cohort_name": "ExampleCohort",
|
|
54
|
+
"lesion_folder": "lesions",
|
|
55
|
+
"space": "MNI152NLin6Asym"
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
##### Atlas configuration
|
|
60
|
+
|
|
61
|
+
Specifies the atlas NIfTI file, its corresponding label CSV, and its reference space. The CSV must contain a `Parcel` column with parcel indices matching the atlas NIfTI and a `Label` column with the corresponding parcel names.
|
|
62
|
+
|
|
63
|
+
```json
|
|
64
|
+
{
|
|
65
|
+
"name": "glasser360",
|
|
66
|
+
"atlas": "glasser360.nii.gz",
|
|
67
|
+
"atlas_labels": "glasser360_labels.csv",
|
|
68
|
+
"space": "MNI152NLin2009cAsym"
|
|
69
|
+
}
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
##### Tractography configuration
|
|
73
|
+
|
|
74
|
+
Specifies the tractography dataset, the folder containing its `.trk` files, the folder containing its reference NIfTI files, and its reference space.
|
|
75
|
+
|
|
76
|
+
```json
|
|
77
|
+
{
|
|
78
|
+
"name": "HCP1065",
|
|
79
|
+
"tracts": "hcp1065_avg_tracts_trk",
|
|
80
|
+
"references": "hcp1065_avg_tracts",
|
|
81
|
+
"space": "MNI152NLin2009aAsym"
|
|
82
|
+
}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
**Notes:**
|
|
86
|
+
- All paths are relative to the corresponding configuration file.
|
|
87
|
+
- NIfTI files can be uncompressed (`.nii`) or gzipped (`.nii.gz`).
|
|
88
|
+
- The `space` field can specify either a standard space supported by TemplateFlow (e.g., `MNI152NLin2009cAsym`) or a relative path to a reference NIfTI file (`.nii` or `.nii.gz`).
|
|
89
|
+
- All paths and names above are examples and should be adapted to your dataset.
|
|
90
|
+
|
|
91
|
+
### basic use
|
|
92
|
+
|
|
93
|
+
Below is the simplest usage that only requires the [lesion cohort configuration file](#lesion-configuration). By default, the Glasser360 atlas and the HCP1065 tractogram are downloaded if not provided in the arguments.
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
pyLQT --lesion_cfg <path/to/lesion_config> --out_folder <path/to/outputs>
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
| Argument | Default | Options / Range | Description |
|
|
101
|
+
|---|---|---|---|
|
|
102
|
+
| `--lesion_cfg` | — | Path to a `.json` file | Path to the lesion cohort configuration file. |
|
|
103
|
+
| `--atlas_cfg` | `None` | Path to a `.json` file or `None` | Path to the atlas configuration file. |
|
|
104
|
+
| `--tracts_cfg` | `None` | Path to a `.json` file or `None` | Path to the tracts configuration file. |
|
|
105
|
+
| `--out_folder` | — | Path to a directory | Path to the output folder. |
|
|
106
|
+
| `--registration_type` | `syn` | `syn`, `affine` | Registration type used to transform the lesion and atlas from their respective spaces to tract space. |
|
|
107
|
+
| `--spared_threshold` | `0.5` | Float between `0` and `1` | Threshold used to define spared parcel-to-parcel connectivity. |
|
|
108
|
+
| `--delete_tmp` | `True` | `True`, `False` | Whether to delete temporary files generated during processing. |
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
## Dependencies
|
|
112
|
+
|
|
113
|
+
pyLQT relies on the following libraries:
|
|
114
|
+
- `numpy`
|
|
115
|
+
- `nibabel`
|
|
116
|
+
- `pandas`
|
|
117
|
+
- `scipy`
|
|
118
|
+
- `requests`
|
|
119
|
+
- `dipy`
|
|
120
|
+
- `templateflow`
|
|
121
|
+
- `networkx`
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
## Links
|
|
125
|
+
|
|
126
|
+
- [Source](https://github.com/AntoninHUDRY/pyLQT)
|
|
127
|
+
- [documentation]() [TODO, deploy via github]
|
|
128
|
+
- [PyPI](https://pypi.org/project/pyLQT/)
|
|
129
|
+
|
|
130
|
+
## References
|
|
131
|
+
|
|
132
|
+
<a id="ref1"></a>
|
|
133
|
+
[1] Griffis et al. (2021). [Lesion Quantification Toolkit: A MATLAB software tool for estimating grey matter damage and white matter disconnections in patients with focal brain lesions](https://doi.org/10.1016/j.nicl.2021.102639). *NeuroImage: Clinical*.
|
|
134
|
+
|
|
135
|
+
<a id="ref2"></a>
|
|
136
|
+
[2] Dworkin Jordan (https://github.com/jdwor/LQT)
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
pyLQT/__init__.py,sha256=y4DvjD5OqKInZ-wJary448UmEBajl8gNbZv50dgabwI,58
|
|
2
|
+
pyLQT/cli.py,sha256=aQHoOa8ZXhFOgJbJTRbAVCCeFLPGUc4qpgVnL1QLEfA,1881
|
|
3
|
+
pyLQT/pipeline.py,sha256=PRS_yzaASATGOYz6GjNhYq733szgwF8k2vPlDqeTjsE,7313
|
|
4
|
+
pyLQT/lesion_metrics/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
5
|
+
pyLQT/lesion_metrics/gray_matter/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
6
|
+
pyLQT/lesion_metrics/gray_matter/metrics.py,sha256=38R3etfmmKo3hFHkYh7Jw2EUrXbW7I0Jkl8So_-5JWA,2616
|
|
7
|
+
pyLQT/lesion_metrics/gray_matter/utils.py,sha256=czxyAESP0neVCue424L0hon-5Tdbsl0pN0zJHWG5HiQ,840
|
|
8
|
+
pyLQT/lesion_metrics/network/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
9
|
+
pyLQT/lesion_metrics/network/metrics.py,sha256=Ka3G8_dIq-8H3b8mvRR96QM7p_gR2yFLKUxQOLYMWM4,8281
|
|
10
|
+
pyLQT/lesion_metrics/network/utils.py,sha256=fcakfKSMortNIE3TfJP8zwanzBiT1McsYnHcTgwbxE4,3283
|
|
11
|
+
pyLQT/lesion_metrics/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
12
|
+
pyLQT/lesion_metrics/utils/configs.py,sha256=FFur04Sh48IlxUTmz4kRkyLKWffi7bvzyw8B2GclpRU,4961
|
|
13
|
+
pyLQT/lesion_metrics/utils/downloads.py,sha256=dXmB_bjt3uEPjJfGThkXw0lojidTTSm_Rx08wyJYafk,10567
|
|
14
|
+
pyLQT/lesion_metrics/utils/registration.py,sha256=iqD9qIPLSxsmAPkGbnvyPZBAeVGfnG5_9P0UYZ0JGIg,3307
|
|
15
|
+
pyLQT/lesion_metrics/white_matter/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
16
|
+
pyLQT/lesion_metrics/white_matter/metrics.py,sha256=sujuHry19XpVWFNo52Ntq7h3JkWEcgsZ1-rNFr7E940,6155
|
|
17
|
+
pyLQT/lesion_metrics/white_matter/utils.py,sha256=oo492yzxsPACMvHEyYhoYVqgVRrv8mpqP7FOf5OU_Ms,7087
|
|
18
|
+
pylqt-1.0.dev2.dist-info/licenses/LICENSE,sha256=IwGE9guuL-ryRPEKi6wFPI_zOhg7zDZbTYuHbSt_SAk,35823
|
|
19
|
+
pylqt-1.0.dev2.dist-info/METADATA,sha256=KiPMLFsDYUD4RwjwyrciKoxH4SrAcMPevyL8aecYkek,5225
|
|
20
|
+
pylqt-1.0.dev2.dist-info/WHEEL,sha256=mOdXR9C3wMVov7cSY8PY-XCzo3SmmWCHvGQr5csJoao,91
|
|
21
|
+
pylqt-1.0.dev2.dist-info/entry_points.txt,sha256=GHGfSFJ0r77qGtkRMJeCuNj2YHm5aynNxdi3JXxyyCc,41
|
|
22
|
+
pylqt-1.0.dev2.dist-info/top_level.txt,sha256=TwID1Q-SuDZ2HjBLKSwruNTXKrRGgAh16rMZ5pjUk4U,6
|
|
23
|
+
pylqt-1.0.dev2.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
pyLQT
|
pyLQT_cli.py
DELETED
|
@@ -1,180 +0,0 @@
|
|
|
1
|
-
from lesion_metrics.white_matter.metrics import get_tracts_disconnections_severity, get_tracts_disconnections_map
|
|
2
|
-
from lesion_metrics.white_matter.utils import get_streamlines_disconnection, get_aggregated_tracts, clip_tractogram_to_grid
|
|
3
|
-
from lesion_metrics.network.metrics import get_sspl_increase, get_parcel_wise_disconnection_severity
|
|
4
|
-
from lesion_metrics.gray_matter.metrics import get_parcel_damage
|
|
5
|
-
from lesion_metrics.utils.configs import atlas_cfg_read, tracts_cfg_read, lesion_cfg_read
|
|
6
|
-
from lesion_metrics.utils.downloads import default_download, create_tract_list, uncompress_trk_gz, get_ref_space_tf
|
|
7
|
-
from lesion_metrics.utils.registration import register_affine, register_syn
|
|
8
|
-
from pathlib import Path
|
|
9
|
-
|
|
10
|
-
import nibabel as nib
|
|
11
|
-
import shutil
|
|
12
|
-
import tempfile
|
|
13
|
-
import argparse
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
def run_pyLQT():
|
|
17
|
-
"""Run the pyLQT lesion quantification pipeline.
|
|
18
|
-
|
|
19
|
-
Parses the command-line arguments, loads the lesion, atlas, and
|
|
20
|
-
tractography configurations, performs the required registrations,
|
|
21
|
-
and computes the different lesion quantification measures.
|
|
22
|
-
"""
|
|
23
|
-
|
|
24
|
-
parser = argparse.ArgumentParser()
|
|
25
|
-
|
|
26
|
-
parser.add_argument(
|
|
27
|
-
"--lesion_cfg",
|
|
28
|
-
required=True,
|
|
29
|
-
help='[Path] Path to lesion config'
|
|
30
|
-
)
|
|
31
|
-
|
|
32
|
-
parser.add_argument(
|
|
33
|
-
"--atlas_cfg",
|
|
34
|
-
required=True,
|
|
35
|
-
help='[Path] Path to atlas config'
|
|
36
|
-
)
|
|
37
|
-
|
|
38
|
-
parser.add_argument(
|
|
39
|
-
"--tracts_cfg",
|
|
40
|
-
default=None,
|
|
41
|
-
help='[Path] Path to tracts config'
|
|
42
|
-
)
|
|
43
|
-
|
|
44
|
-
parser.add_argument(
|
|
45
|
-
"--out_folder",
|
|
46
|
-
required=True,
|
|
47
|
-
help='[Path] Output folder path'
|
|
48
|
-
)
|
|
49
|
-
|
|
50
|
-
parser.add_argument(
|
|
51
|
-
"--registration_type",
|
|
52
|
-
choices=['affine', 'syn'],
|
|
53
|
-
default='syn',
|
|
54
|
-
help='[str, default=syn] Registration type, from lesion space and atlas space to tract space'
|
|
55
|
-
)
|
|
56
|
-
|
|
57
|
-
parser.add_argument(
|
|
58
|
-
"--spared_threshold",
|
|
59
|
-
default=0.5,
|
|
60
|
-
help='[float (0,1), default=0.5] Defines threshold for spared parcel-to-parcel connectivity'
|
|
61
|
-
)
|
|
62
|
-
|
|
63
|
-
parser.add_argument(
|
|
64
|
-
"--delete_tmp",
|
|
65
|
-
default=False,
|
|
66
|
-
help='[bool, default=False] Delete temporary folder'
|
|
67
|
-
)
|
|
68
|
-
|
|
69
|
-
args = parser.parse_args()
|
|
70
|
-
|
|
71
|
-
out_folder = Path(args.out_folder)
|
|
72
|
-
out_folder.mkdir(parents=True, exist_ok=True)
|
|
73
|
-
tmp_folder = Path(tempfile.mkdtemp(prefix="LQT_tmp_", dir=out_folder))
|
|
74
|
-
|
|
75
|
-
lesion_cfg_path = Path(args.lesion_cfg)
|
|
76
|
-
atlas_cfg_path = Path(args.atlas_cfg)
|
|
77
|
-
if not args.tracts_cfg:
|
|
78
|
-
tracts_cfg_path = default_download(out_folder)
|
|
79
|
-
else:
|
|
80
|
-
tracts_cfg_path = Path(args.tracts_cfg)
|
|
81
|
-
|
|
82
|
-
lesion_cohort_name, lesion_folder, lesion_space = lesion_cfg_read(lesion_cfg_path)
|
|
83
|
-
atlas_name, atlas, atlas_labels, atlas_space = atlas_cfg_read(atlas_cfg_path)
|
|
84
|
-
tracts_name, tracts_folder, references_folder, tracts_space = tracts_cfg_read(tracts_cfg_path)
|
|
85
|
-
|
|
86
|
-
if lesion_space.endswith(".nii.gz") or lesion_space.endswith(".nii"):
|
|
87
|
-
lesion_ref_space = nib.nifti1.load(lesion_space)
|
|
88
|
-
else: lesion_ref_space = get_ref_space_tf(lesion_space)
|
|
89
|
-
if atlas_space.endswith(".nii.gz") or atlas_space.endswith(".nii"):
|
|
90
|
-
atlas_ref_space = nib.nifti1.load(atlas_space)
|
|
91
|
-
else: atlas_ref_space = get_ref_space_tf(atlas_space)
|
|
92
|
-
if tracts_space.endswith(".nii.gz") or tracts_space.endswith(".nii"):
|
|
93
|
-
tracts_ref_space = nib.nifti1.load(tracts_space)
|
|
94
|
-
else: tracts_ref_space = get_ref_space_tf(tracts_space)
|
|
95
|
-
|
|
96
|
-
if args.registration_type == "affine":
|
|
97
|
-
registrator = register_affine
|
|
98
|
-
else:
|
|
99
|
-
registrator = register_syn
|
|
100
|
-
lesion_tracts_mapping = registrator(lesion_ref_space, tracts_ref_space)
|
|
101
|
-
atlas_tracts_mapping = registrator(atlas_ref_space, tracts_ref_space)
|
|
102
|
-
|
|
103
|
-
atlas_trans_volume = atlas_tracts_mapping.transform(atlas.get_fdata(), interpolation='nearest')
|
|
104
|
-
atlas_trans = nib.Nifti1Image(atlas_trans_volume, tracts_ref_space.affine, tracts_ref_space.header)
|
|
105
|
-
nib.nifti1.save(atlas_trans, tmp_folder / "atlas_trans.nii.gz")
|
|
106
|
-
|
|
107
|
-
spared_threshold = float(args.spared_threshold)
|
|
108
|
-
tracts_pathlist_gz, references_pathlist = create_tract_list(references_folder=references_folder,
|
|
109
|
-
tracts_folder=tracts_folder)
|
|
110
|
-
|
|
111
|
-
tracts_pathlist = []
|
|
112
|
-
for tract_path in tracts_pathlist_gz:
|
|
113
|
-
relative_path = tract_path.relative_to(tracts_folder)
|
|
114
|
-
out_path = tmp_folder / relative_path.with_suffix("")
|
|
115
|
-
|
|
116
|
-
out_path.parent.mkdir(parents=True, exist_ok=True)
|
|
117
|
-
|
|
118
|
-
tracts_pathlist.append(
|
|
119
|
-
uncompress_trk_gz(tract_path, out_path)
|
|
120
|
-
)
|
|
121
|
-
|
|
122
|
-
aggregated_tracts_path = tmp_folder / "aggregated_tracts.trk"
|
|
123
|
-
aggregated_tracts = get_aggregated_tracts(tracts_pathlist=tracts_pathlist,
|
|
124
|
-
out_path=aggregated_tracts_path)
|
|
125
|
-
|
|
126
|
-
for lesion_path in lesion_folder.glob("*.nii*"):
|
|
127
|
-
|
|
128
|
-
lesion_name = lesion_path.name.removesuffix(".nii.gz").removesuffix(".nii")
|
|
129
|
-
out_dir = out_folder / lesion_name
|
|
130
|
-
out_dir.mkdir(parents=True, exist_ok=True)
|
|
131
|
-
tmp_dir = tmp_folder / lesion_name
|
|
132
|
-
tmp_dir.mkdir(parents=True, exist_ok=True)
|
|
133
|
-
|
|
134
|
-
lesion = nib.nifti1.load(lesion_path)
|
|
135
|
-
lesion_trans_volume = lesion_tracts_mapping.transform(lesion.get_fdata(), interpolation='linear')
|
|
136
|
-
lesion_trans = nib.Nifti1Image(lesion_trans_volume, tracts_ref_space.affine, tracts_ref_space.header)
|
|
137
|
-
nib.nifti1.save(lesion_trans, tmp_dir / "lesion_trans.nii.gz")
|
|
138
|
-
|
|
139
|
-
aggregated_tracts_disconnected_path = tmp_dir / f"aggregated_disconnected.trk"
|
|
140
|
-
aggregated_tracts_disconnected, _ = get_streamlines_disconnection(tract=aggregated_tracts,
|
|
141
|
-
lesion=lesion_trans,
|
|
142
|
-
out_path=aggregated_tracts_disconnected_path)
|
|
143
|
-
aggregated_tracts_disconnected = clip_tractogram_to_grid(aggregated_tracts_disconnected)
|
|
144
|
-
|
|
145
|
-
get_parcel_damage(lesion=lesion_trans,
|
|
146
|
-
atlas=atlas_trans,
|
|
147
|
-
atlas_labels=atlas_labels,
|
|
148
|
-
parcel_column="Parcel",
|
|
149
|
-
out_path=out_dir / "parcel_damage")
|
|
150
|
-
|
|
151
|
-
get_tracts_disconnections_severity(tracts_path=tracts_pathlist,
|
|
152
|
-
references_path=references_pathlist,
|
|
153
|
-
lesion=lesion_trans,
|
|
154
|
-
disc_tracts_out_path=tmp_dir / "wm_disconnections_severity",
|
|
155
|
-
disc_frame_out_path=out_dir / "wm_disconnections_severity")
|
|
156
|
-
|
|
157
|
-
get_tracts_disconnections_map(tracts=aggregated_tracts,
|
|
158
|
-
tracts_disconnected=aggregated_tracts_disconnected,
|
|
159
|
-
out_path=out_dir / "wm_disconnections_map")
|
|
160
|
-
|
|
161
|
-
structural_connectivity, _, _, per_spared_connections = (
|
|
162
|
-
get_parcel_wise_disconnection_severity(
|
|
163
|
-
aggregated_tracts=aggregated_tracts,
|
|
164
|
-
aggregated_tracts_disconnected=aggregated_tracts_disconnected,
|
|
165
|
-
atlas=atlas_trans,
|
|
166
|
-
out_path=out_dir / "parcel_wise_disconnection_severity",
|
|
167
|
-
)
|
|
168
|
-
)
|
|
169
|
-
|
|
170
|
-
get_sspl_increase(structural_connectivity=structural_connectivity,
|
|
171
|
-
per_spared_connections=per_spared_connections,
|
|
172
|
-
out_path=out_dir / "sspl_increase",
|
|
173
|
-
spared_threshold=spared_threshold)
|
|
174
|
-
|
|
175
|
-
if args.delete_tmp == True:
|
|
176
|
-
shutil.rmtree(tmp_folder)
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
if __name__ == "__main__":
|
|
180
|
-
run_pyLQT()
|
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: pyLQT
|
|
3
|
-
Version: 1.0.dev1
|
|
4
|
-
Summary: Python version of LQT from Griffis et al. 2021 (https://www.sciencedirect.com/science/article/pii/S2213158221000838)
|
|
5
|
-
Author-email: Antonin HUDRY <hudryantoninm@gmail.com>
|
|
6
|
-
License-Expression: GPL-3.0-only
|
|
7
|
-
Keywords: LQT,lqt,lesion,brain,
|
|
8
|
-
Classifier: Development Status :: 2 - Pre-Alpha
|
|
9
|
-
Classifier: Intended Audience :: Science/Research
|
|
10
|
-
Classifier: Intended Audience :: Healthcare Industry
|
|
11
|
-
Classifier: Programming Language :: Python
|
|
12
|
-
Requires-Python: >=3.10
|
|
13
|
-
Description-Content-Type: text/markdown
|
|
14
|
-
License-File: LICENSE
|
|
15
|
-
Requires-Dist: numpy<2.0
|
|
16
|
-
Requires-Dist: nibabel>5.0
|
|
17
|
-
Requires-Dist: pandas>3.0
|
|
18
|
-
Requires-Dist: scipy>1.15
|
|
19
|
-
Requires-Dist: requests>2.0
|
|
20
|
-
Requires-Dist: dipy>1.10
|
|
21
|
-
Requires-Dist: templateflow
|
|
22
|
-
Dynamic: license-file
|
|
23
|
-
|
|
24
|
-
# pyLQT
|
|
25
|
-
Python library to revamp the work on LQT, since it is no longer actively maintained.
|
|
26
|
-
|
|
27
|
-
# install
|
|
28
|
-
|
|
29
|
-
Make sure to update your python environment with the `requirements.txt`: `pip install -r requirements.txt`
|
|
30
|
-
|
|
31
|
-
# Running pyLQT
|
|
32
|
-
|
|
33
|
-
The main pyLQT pipeline can be run using the `full_pipeline.py` file.
|
|
34
|
-
|
|
35
|
-
### Usage
|
|
36
|
-
|
|
37
|
-
```bash
|
|
38
|
-
python full_pipeline.py \
|
|
39
|
-
--lesion_cfg <path/to/lesion_config> \
|
|
40
|
-
--atlas_cfg <path/to/atlas_config> \
|
|
41
|
-
--tracts_cfg <path/to/tracts_config> \
|
|
42
|
-
--out_folder <path/to/output> \
|
|
43
|
-
--spared_threshold
|
|
44
|
-
```
|
|
45
|
-
|
|
46
|
-
| Argument | Required | Default | Description |
|
|
47
|
-
| -------------------- | -------- | ------- | -------------------------------------------------------------------------------------------------------------------------- |
|
|
48
|
-
| `--lesion_cfg` | Yes | — | Path to the lesion configuration file (`.json`). |
|
|
49
|
-
| `--atlas_cfg` | Yes | — | Path to the atlas configuration file (`.json`). |
|
|
50
|
-
| `--tracts_cfg` | No | `None` | Path to the tracts configuration file (`.json`). |
|
|
51
|
-
| `--out_folder` | Yes | — | Path to the output folder. |
|
|
52
|
-
| `--spared_threshold` | No | `0.5` | Threshold between 0 and 1 used to define spared parcel-to-parcel connectivity. |
|
|
53
|
-
| `--delete_tmp` | No | `True` | Whether to delete the temporary files generated during processing. |
|
|
54
|
-
|
|
55
|
-
### Configuration files
|
|
56
|
-
|
|
57
|
-
The CLI version of pyLQT relies on 3 configuration files, one for the lesions, one for the atlas and one for the tracts:
|
|
58
|
-
|
|
59
|
-
- Lesions: "cohort_name": name of the cohort of lesion used mainly for the naming of subsequent files; "lesion_folder": relative folder (should be next the config file) containing all the lesion niftis. The folder is scanned integrally so that all niftis in the folder and subfolders are processed; "space": world space in which the lesion are registered, can be a nifti directly given as relative path (with extensions) or a standard space name that will be fetched via templateflow (i.e. MNI152NLin2009cAsym).
|
|
60
|
-
- Atlas: "name": name of the atlas used mainly for naming subsequent files; "atlas": relative path to nifti file corresponding to atlas; "atlas_labels": relative path to `.csv` file containing the labels of the atlas. Format should contain columns "Parcel" and "Label", corresponding to the parcel number inside the nifti and the name of the parcel respectively; "space": world space in which the lesion are registered, can be a nifti directly given as relative path (with extensions) or a standard space name that will be fetched via templateflow (i.e. MNI152NLin2009cAsym).
|
|
61
|
-
|
|
62
|
-
Note: niftis can be given as raw or gzipped niftis (`.nii` or `.nii.gz`)
|
pylqt-1.0.dev1.dist-info/RECORD
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
pyLQT_cli.py,sha256=FlTLPGcoq-xwPeGwVUfe_W_dg8yMpYV1X1jPBf-EefA,7937
|
|
2
|
-
lesion_metrics/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
3
|
-
lesion_metrics/gray_matter/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
|
-
lesion_metrics/gray_matter/metrics.py,sha256=RzUS9glbin9Xn5ZldB7fGg2XiPS1P21e_7l02ZErk-I,2617
|
|
5
|
-
lesion_metrics/gray_matter/utils.py,sha256=czxyAESP0neVCue424L0hon-5Tdbsl0pN0zJHWG5HiQ,840
|
|
6
|
-
lesion_metrics/network/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
7
|
-
lesion_metrics/network/metrics.py,sha256=Ka3G8_dIq-8H3b8mvRR96QM7p_gR2yFLKUxQOLYMWM4,8281
|
|
8
|
-
lesion_metrics/network/utils.py,sha256=fcakfKSMortNIE3TfJP8zwanzBiT1McsYnHcTgwbxE4,3283
|
|
9
|
-
lesion_metrics/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
10
|
-
lesion_metrics/utils/configs.py,sha256=FFur04Sh48IlxUTmz4kRkyLKWffi7bvzyw8B2GclpRU,4961
|
|
11
|
-
lesion_metrics/utils/downloads.py,sha256=yu-rcuIDcgsDa7gxoLGyIewsk2DF2WGgCYe5bCOso0M,8031
|
|
12
|
-
lesion_metrics/utils/registration.py,sha256=rB4vEMBrlshofOxaKrSqyE3Wvgq2_o3y5T3dCS_9sM0,3174
|
|
13
|
-
lesion_metrics/white_matter/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
14
|
-
lesion_metrics/white_matter/metrics.py,sha256=sujuHry19XpVWFNo52Ntq7h3JkWEcgsZ1-rNFr7E940,6155
|
|
15
|
-
lesion_metrics/white_matter/utils.py,sha256=oo492yzxsPACMvHEyYhoYVqgVRrv8mpqP7FOf5OU_Ms,7087
|
|
16
|
-
pylqt-1.0.dev1.dist-info/licenses/LICENSE,sha256=IwGE9guuL-ryRPEKi6wFPI_zOhg7zDZbTYuHbSt_SAk,35823
|
|
17
|
-
pylqt-1.0.dev1.dist-info/METADATA,sha256=gMSuT2w08jR1QSTH_C9TRsLVsJBEnCh98jSDH4_eCGs,3990
|
|
18
|
-
pylqt-1.0.dev1.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
|
|
19
|
-
pylqt-1.0.dev1.dist-info/entry_points.txt,sha256=1mhG9fCXeDnL9VEBbsIN34GU90953e85325pG_wo6l8,46
|
|
20
|
-
pylqt-1.0.dev1.dist-info/top_level.txt,sha256=KT_xuwXScSnV5QoZAXnCzeTOq23xQ_V9ESEh4jrfxBM,25
|
|
21
|
-
pylqt-1.0.dev1.dist-info/RECORD,,
|
|
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
|