Skip to content

Commit f25e6ac

Browse files
authored
Merge pull request #10 from NiftyPET/devel
2 parents 8e4d82b + 6f030b8 commit f25e6ac

File tree

4 files changed

+125
-191
lines changed

4 files changed

+125
-191
lines changed

niftypet/ninst/install_tools.py

+93-52
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,23 @@
88
import os
99
import platform
1010
import re
11-
import shutil
1211
import sys
12+
from functools import wraps
1313
from os import fspath, path
1414
from pathlib import Path
15-
from subprocess import PIPE, CalledProcessError, check_output, run
15+
from shutil import copyfileobj, rmtree
16+
from subprocess import CalledProcessError, check_output, run
1617
from textwrap import dedent
18+
from urllib import request
19+
from urllib.parse import urlparse
20+
from zipfile import ZipFile
21+
22+
from tqdm.auto import tqdm
23+
from tqdm.utils import CallbackIOWrapper
1724

1825
from . import cudasetup as cs
1926

2027
if os.getenv("DISPLAY", False):
21-
from functools import wraps
2228
from tkinter import Tk
2329
from tkinter.filedialog import askdirectory as ask
2430

@@ -37,21 +43,6 @@ def askdir(title, initialdir):
3743
return initialdir if res == "" else res
3844

3945

40-
def askdirectory(title="Folder", initialdir="~", name=""):
41-
"""
42-
Args:
43-
initialdir (str): default: "~"
44-
Returns (str):
45-
one of (decreasing Precedence):
46-
- `os.getenv(name)`
47-
- `input()` or `tkinter.filedialog.askdirectory()`
48-
- `initialdir`
49-
"""
50-
initialdir = path.expanduser(initialdir)
51-
res = os.getenv(name, None)
52-
return askdir(title, initialdir) if res is None else res
53-
54-
5546
log = logging.getLogger(__name__)
5647

5748
# NiftyReg
@@ -82,6 +73,7 @@ def askdirectory(title="Folder", initialdir="~", name=""):
8273
# 'dcm2niix_27-Jun-2018_win.zip'
8374
# sha1_dcm = '4b641113273d86ad73123816993092fc643ac62f'
8475
# dcm_ver = '1.0.20180622'
76+
http_dcm = {"Windows": http_dcm_win, "Linux": http_dcm_lin, "Darwin": http_dcm_mac}
8577

8678
# source and build folder names
8779
dirsrc = "_src"
@@ -91,6 +83,7 @@ def askdirectory(title="Folder", initialdir="~", name=""):
9183
ncpu = multiprocessing.cpu_count()
9284

9385
LOG_FORMAT = "%(levelname)s:%(asctime)s:%(name)s:%(funcName)s\n> %(message)s"
86+
CHUNK_SIZE = 2 ** 15 # 32 kiB
9487

9588

9689
class LogHandler(logging.StreamHandler):
@@ -102,6 +95,73 @@ def __init__(self, *args, **kwargs):
10295
self.setFormatter(fmt)
10396

10497

98+
def askdirectory(title="Folder", initialdir="~", name=""):
99+
"""
100+
Args:
101+
initialdir (str): default: "~"
102+
Returns (str):
103+
one of (decreasing Precedence):
104+
- `os.getenv(name)`
105+
- `input()` or `tkinter.filedialog.askdirectory()`
106+
- `initialdir`
107+
"""
108+
initialdir = path.expanduser(initialdir)
109+
res = os.getenv(name, None)
110+
return askdir(title, initialdir) if res is None else res
111+
112+
113+
def urlopen_cached(url, outdir, fname=None, mode="rb"):
114+
"""
115+
Download `url` to `outdir/fname`.
116+
Cache based on `url` at `outdir/fname`.url
117+
118+
Args:
119+
url (str): source
120+
outdir (path-like): destination
121+
fname (str): optional, auto-detected from `url` if not given
122+
mode (str): for returned file object
123+
Returns:
124+
file
125+
"""
126+
outdir = Path(outdir).expanduser()
127+
outdir.mkdir(exist_ok=True)
128+
if fname is None:
129+
fname = Path(urlparse(url).path).name
130+
fout = outdir / fname
131+
cache = outdir / f"{fname}.url"
132+
if not fout.is_file() or not cache.is_file() or cache.read_text().strip() != url:
133+
req = request.Request(url=url)
134+
with request.urlopen(req) as raw:
135+
with tqdm.wrapattr(raw, "read", total=getattr(raw, "length", None)) as fd:
136+
with fout.open("wb") as fo:
137+
i = fd.read(CHUNK_SIZE)
138+
while i:
139+
fo.write(i)
140+
i = fd.read(CHUNK_SIZE)
141+
cache.write_text(url)
142+
return fout.open(mode)
143+
144+
145+
def extractall(fzip, dest, desc="Extracting"):
146+
"""zipfile.Zipfile(fzip).extractall(dest) with progress"""
147+
dest = Path(dest).expanduser()
148+
with ZipFile(fzip) as zipf:
149+
with tqdm(
150+
desc=desc,
151+
unit="B",
152+
unit_scale=True,
153+
unit_divisor=1024,
154+
total=sum(getattr(i, "file_size", 0) for i in zipf.infolist()),
155+
) as pbar:
156+
for i in zipf.infolist():
157+
if not getattr(i, "file_size", 0): # directory
158+
zipf.extract(i, fspath(dest))
159+
else:
160+
with zipf.open(i) as fi:
161+
with open(fspath(dest / i.filename), "wb") as fo:
162+
copyfileobj(CallbackIOWrapper(pbar.update, fi), fo)
163+
164+
105165
def query_yesno(question):
106166
valid = {"yes": True, "y": True, "ye": True, "no": False, "n": False}
107167
prompt = " [Y/n]: "
@@ -243,32 +303,29 @@ def check_version(Cnt, chcklst=None):
243303
# niftyreg reg_resample first
244304
if "RESPATH" in chcklst and "RESPATH" in Cnt:
245305
try:
246-
p = run([Cnt["RESPATH"], "--version"], stdout=PIPE)
247-
out = p.stdout.decode("utf-8")
306+
out = check_output([Cnt["RESPATH"], "--version"]).decode("U8")
248307
if reg_ver in out:
249308
output["RESPATH"] = True
250-
except OSError:
309+
except (CalledProcessError, FileNotFoundError):
251310
log.error("NiftyReg (reg_resample) either is NOT installed or is corrupt.")
252311

253312
# niftyreg reg_aladin
254313
if "REGPATH" in chcklst and "REGPATH" in Cnt:
255314
try:
256-
p = run([Cnt["REGPATH"], "--version"], stdout=PIPE)
257-
out = p.stdout.decode("utf-8")
315+
out = check_output([Cnt["REGPATH"], "--version"]).decode("U8")
258316
if reg_ver in out:
259317
output["REGPATH"] = True
260-
except OSError:
318+
except (CalledProcessError, FileNotFoundError):
261319
log.error("NiftyReg (reg_aladin) either is NOT installed or is corrupt.")
262320

263321
# dcm2niix
264322
if "DCM2NIIX" in chcklst and "DCM2NIIX" in Cnt:
265323
try:
266-
p = run([Cnt["DCM2NIIX"], "-h"], stdout=PIPE)
267-
out = p.stdout.decode("utf-8")
324+
out = check_output([Cnt["DCM2NIIX"], "-h"]).decode("U8")
268325
ver_str = re.search(r"(?<=dcm2niiX version v)\d{1,2}.\d{1,2}.\d*", out)
269326
if ver_str and dcm_ver in ver_str.group(0):
270327
output["DCM2NIIX"] = True
271-
except OSError:
328+
except (CalledProcessError, FileNotFoundError):
272329
log.error("dcm2niix either is NOT installed or is corrupt.")
273330

274331
# hdw mu-map list
@@ -295,25 +352,13 @@ def download_dcm2niix(Cnt, dest):
295352
)
296353

297354
# -create the installation folder
298-
if not dest.is_dir():
299-
dest.mkdir()
355+
dest.mkdir(exist_ok=True)
300356
binpath = dest / "bin"
301-
if not binpath.is_dir():
302-
binpath.mkdir()
357+
binpath.mkdir(exist_ok=True)
303358

304-
import urllib.error
305-
import urllib.parse
306-
import urllib.request
307-
import zipfile
308-
309-
http_dcm = {"Windows": http_dcm_win, "Linux": http_dcm_lin, "Darwin": http_dcm_mac}
310-
urllib.request.urlretrieve(
311-
http_dcm[platform.system()], fspath(dest / "dcm2niix.zip")
312-
)
359+
with urlopen_cached(http_dcm[platform.system()], dest) as fd:
360+
extractall(fd, binpath)
313361

314-
zipf = zipfile.ZipFile(fspath(dest / "dcm2niix.zip"), "r")
315-
zipf.extractall(fspath(binpath))
316-
zipf.close()
317362
Cnt["DCM2NIIX"] = fspath(next(binpath.glob("dcm2niix*")))
318363
# ensure the permissions are given to the executable
319364
os.chmod(Cnt["DCM2NIIX"], 755)
@@ -333,14 +378,12 @@ def install_tool(app, Cnt):
333378
# pick the target installation folder for tools
334379
if Cnt.get("PATHTOOLS", None):
335380
path_tools = Path(Cnt["PATHTOOLS"])
336-
if not path_tools.is_dir():
337-
path_tools.mkdir()
381+
path_tools.mkdir(exist_ok=True)
338382
else:
339383
path_tools = Path(
340384
askdirectory(title="Path to place NiftyPET tools", name="PATHTOOLS")
341385
)
342-
if not path_tools.is_dir():
343-
path_tools.mkdir()
386+
path_tools.mkdir(exist_ok=True)
344387
if path_tools.name != Cnt["DIRTOOLS"]:
345388
path_tools /= Cnt["DIRTOOLS"]
346389
Cnt["PATHTOOLS"] = fspath(path_tools)
@@ -357,8 +400,7 @@ def install_tool(app, Cnt):
357400
)
358401

359402
# create the main tools folder
360-
if not path_tools.is_dir():
361-
path_tools.mkdir()
403+
path_tools.mkdir(exist_ok=True)
362404
# identify the specific path for the requested app
363405
if app == "niftyreg":
364406
repo = repo_reg
@@ -378,7 +420,7 @@ def install_tool(app, Cnt):
378420

379421
# Check if the source folder exists and delete it, if it does
380422
if dest.is_dir():
381-
shutil.rmtree(fspath(dest))
423+
rmtree(fspath(dest))
382424
dest.mkdir()
383425
os.chdir(dest)
384426

@@ -389,8 +431,7 @@ def install_tool(app, Cnt):
389431
run(["git", "checkout", sha1])
390432
os.chdir(cwd)
391433

392-
if not dirbld.is_dir():
393-
os.mkdir(dirbld)
434+
dirbld.mkdir(exist_ok=True)
394435
os.chdir(dirbld)
395436
# run cmake with arguments
396437
if platform.system() == "Windows":

niftypet/ninst/raw/resources.py

-131
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,6 @@
11
"""Resources file for NiftyPET NIPET and NIMPA etc."""
2-
# ---------------------------------------------------------------
32
__author__ = ("Pawel J. Markiewicz", "Casper O. da Costa-Luis")
43
__copyright__ = "Copyright 2018-20"
5-
# ---------------------------------------------------------------
6-
7-
import errno
8-
import os
94
from math import ceil, pi
105

116
try:
@@ -437,129 +432,3 @@ def get_mmr_constants():
437432
Cnt = get_setup(Cnt=Cnt)
438433

439434
return Cnt
440-
441-
442-
def get_refimg(pathsrc):
443-
"""Reference images reconstructed for testing future versions and editions of NiftyPET.
444-
It is based on the open source list-mode amyloid PET data available at:
445-
https://doi.org/10.5281/zenodo.1472951
446-
"""
447-
448-
if (
449-
isinstance(pathsrc, dict)
450-
and "REFPATH" in pathsrc
451-
and os.path.exists(pathsrc["REFPATH"])
452-
):
453-
folder_ref = pathsrc["REFPATH"]
454-
elif isinstance(pathsrc, str) and os.path.exists(pathsrc):
455-
folder_ref = pathsrc
456-
else:
457-
raise FileNotFoundError(errno.ENOENT, os.strerror(errno.ENOENT), folder_ref)
458-
459-
# ------------------------------------------------------------------------------------------------
460-
# > predetermined structure of the reference folder as a dictionary:
461-
refpaths = {
462-
"histo": {"p": 1570707830, "d": 817785422},
463-
"basic": {},
464-
"aligned": {"spm": {}, "niftyreg": {}},
465-
}
466-
467-
refpaths["basic"] = {
468-
"pet": os.path.join(
469-
folder_ref, "basic", "17598013_t-3000-3600sec_itr-4_suvr.nii.gz"
470-
),
471-
"omu": os.path.join(
472-
folder_ref, "basic", "mumap-from-DICOM_no-alignment.nii.gz"
473-
),
474-
"hmu": os.path.join(folder_ref, "basic", "hardware_umap.nii.gz"),
475-
}
476-
refpaths["aligned"]["niftyreg"] = {
477-
"hmu": os.path.join(
478-
folder_ref, "dyn_aligned", "niftyreg", "hardware_umap.nii.gz"
479-
),
480-
"omu": os.path.join(
481-
folder_ref,
482-
"dyn_aligned",
483-
"niftyreg",
484-
"mumap-PCT-aligned-to_t0-3600_AC.nii.gz",
485-
),
486-
"pos": os.path.join(
487-
folder_ref,
488-
"dyn_aligned",
489-
"niftyreg",
490-
"17598013_t0-3600sec_itr2_AC-UTE.nii.gz",
491-
),
492-
"pet": os.path.join(
493-
folder_ref, "dyn_aligned", "niftyreg", "17598013_nfrm-2_itr-4.nii.gz"
494-
),
495-
"trm": os.path.join(
496-
folder_ref,
497-
"dyn_aligned",
498-
"niftyreg",
499-
"17598013_nfrm-2_itr-4_trimmed-upsampled-scale-2.nii.gz",
500-
),
501-
"pvc": os.path.join(
502-
folder_ref,
503-
"dyn_aligned",
504-
"niftyreg",
505-
"17598013_nfrm-2_itr-4_trimmed-upsampled-scale-2_PVC.nii.gz",
506-
),
507-
}
508-
refpaths["aligned"]["spm"] = {
509-
"hmu": os.path.join(folder_ref, "dyn_aligned", "spm", "hardware_umap.nii.gz"),
510-
"omu": os.path.join(
511-
folder_ref, "dyn_aligned", "spm", "mumap-PCT-aligned-to_t0-3600_AC.nii.gz"
512-
),
513-
"pos": os.path.join(
514-
folder_ref, "dyn_aligned", "spm", "17598013_t0-3600sec_itr2_AC-UTE.nii.gz"
515-
),
516-
"pet": os.path.join(
517-
folder_ref, "dyn_aligned", "spm", "17598013_nfrm-2_itr-4.nii.gz"
518-
),
519-
"trm": os.path.join(
520-
folder_ref,
521-
"dyn_aligned",
522-
"spm",
523-
"17598013_nfrm-2_itr-4_trimmed-upsampled-scale-2.nii.gz",
524-
),
525-
"pvc": os.path.join(
526-
folder_ref,
527-
"dyn_aligned",
528-
"spm",
529-
"17598013_nfrm-2_itr-4_trimmed-upsampled-scale-2_PVC.nii.gz",
530-
),
531-
}
532-
# ------------------------------------------------------------------------------------------------
533-
534-
testext = {"basic": {}, "aligned": {}}
535-
testext["basic"]["pet"] = "static reconstruction with unaligned UTE mu-map"
536-
testext["basic"]["hmu"] = "hardware mu-map for the static unaligned reconstruction"
537-
testext["basic"]["omu"] = "object mu-map for the static unaligned reconstruction"
538-
# ---
539-
testext["aligned"]["pet"] = "2-frame scan with aligned UTE mu-map"
540-
testext["aligned"]["hmu"] = "hardware mu-map for the 2-frame aligned reconstruction"
541-
testext["aligned"]["omu"] = "object mu-map for the 2-frame aligned reconstruction"
542-
testext["aligned"][
543-
"pos"
544-
] = "AC reconstruction for positioning (full acquisition used)"
545-
testext["aligned"]["trm"] = "trimming post reconstruction"
546-
testext["aligned"]["pvc"] = "PVC post reconstruction"
547-
# ------------------------------------------------------------------------------------------------
548-
549-
# > check if all files are in
550-
# > basic
551-
frefs = refpaths["basic"]
552-
for k in frefs:
553-
if not os.path.isfile(frefs[k]):
554-
raise FileNotFoundError(errno.ENOENT, os.strerror(errno.ENOENT), frefs[k])
555-
556-
# > reg tools: niftyreg and spm
557-
frefs = refpaths["aligned"]
558-
for r in frefs:
559-
for k in frefs[r]:
560-
if not os.path.isfile(frefs[r][k]):
561-
raise FileNotFoundError(
562-
errno.ENOENT, os.strerror(errno.ENOENT), frefs[r][k]
563-
)
564-
565-
return refpaths, testext

0 commit comments

Comments
 (0)