From 6f973cb96a8f75f10a6a120d140370e49269ba8b Mon Sep 17 00:00:00 2001 From: Pawel Date: Wed, 26 Jan 2022 12:04:42 +0000 Subject: [PATCH 01/14] slight output modification for bias field corr --- niftypet/nimpa/prc/prc.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/niftypet/nimpa/prc/prc.py b/niftypet/nimpa/prc/prc.py index 87f70141..f96c35dc 100644 --- a/niftypet/nimpa/prc/prc.py +++ b/niftypet/nimpa/prc/prc.py @@ -1286,6 +1286,8 @@ def bias_field_correction(fmr, fimout='', outpath='', fcomment='_N4bias', execut if len(outdct['fim']) == 1: outdct['fim'] = outdct['fim'][0] + outdct['fmsk'] = outdct['fmsk'][0] + return outdct From 18999106b0557d4e99aaada4382266af60187243 Mon Sep 17 00:00:00 2001 From: Pawel Date: Wed, 16 Feb 2022 00:33:34 +0000 Subject: [PATCH 02/14] added institution to DICOM properties to be extracted --- niftypet/nimpa/prc/imio.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/niftypet/nimpa/prc/imio.py b/niftypet/nimpa/prc/imio.py index 81b60ab9..00495245 100644 --- a/niftypet/nimpa/prc/imio.py +++ b/niftypet/nimpa/prc/imio.py @@ -183,6 +183,11 @@ def dcminfo(dcmvar, Cnt=None, output='detail', t1_name='mprage'): cmmnt = dhdr[0x0020, 0x4000].value log.debug(' Comments: {}'.format(cmmnt)) + # > institution + inst = '' + if [0x008, 0x080] in dhdr: + inst = dhdr[0x008, 0x080].value + prtcl = '' if [0x18, 0x1030] in dhdr: prtcl = dhdr[0x18, 0x1030].value @@ -297,7 +302,7 @@ def dcminfo(dcmvar, Cnt=None, output='detail', t1_name='mprage'): if validTs: mrdct = { - 'series': srs, 'protocol': prtcl, 'units': unt, 'study_time': study_time, + 'series': srs, 'protocol': prtcl, 'units': unt, 'study_time': study_time, 'inst':inst, 'series_time': series_time, 'acq_time': acq_time, 'scanner_id': scanner_id, 'TR': TR, 'TE': TE} # --------------------------------------------- @@ -324,7 +329,7 @@ def dcminfo(dcmvar, Cnt=None, output='detail', t1_name='mprage'): elif isPET: petdct = { 'series': srs, 'protocol': prtcl, 'study_time': study_time, 'series_time': series_time, - 'acq_time': acq_time, 'scanner_id': scanner_id, 'type': srs_type, 'units': unt, + 'inst':inst, 'acq_time': acq_time, 'scanner_id': scanner_id, 'type': srs_type, 'units': unt, 'recon': recon, 'decay_corr': decay_corr, 'dcf': dcf, 'attenuation': atten, 'scatter': scat, 'scf': scf, 'randoms': rand, 'dose_calib': dscf, 'dead_time': dt, 'tracer': tracer, 'total_dose': tdose, 'half_life': hlife, 'positron_fract': pfract, From 3c8d14f3053a0996c5277417a190815e26dee312 Mon Sep 17 00:00:00 2001 From: Pawel Date: Wed, 9 Mar 2022 00:38:16 +0000 Subject: [PATCH 03/14] added MGH image file support --- niftypet/nimpa/__init__.py | 4 +- niftypet/nimpa/prc/__init__.py | 4 +- niftypet/nimpa/prc/imio.py | 109 +++++++++++++++++++++++++++++++++ niftypet/nimpa/prc/prc.py | 3 +- 4 files changed, 117 insertions(+), 3 deletions(-) diff --git a/niftypet/nimpa/__init__.py b/niftypet/nimpa/__init__.py index dc036549..128a3988 100644 --- a/niftypet/nimpa/__init__.py +++ b/niftypet/nimpa/__init__.py @@ -30,7 +30,7 @@ 'centre_mass_img', 'centre_mass_corr', 'coreg_spm', 'coreg_vinci', 'create_dir', 'create_mask', 'ct2mu', 'dcm2im', 'dcm2nii', 'dcmanonym', 'dcminfo', 'dcmsort', - 'dice_coeff', 'dice_coeff_multiclass', 'fwhm2sig', 'getnii', + 'dice_coeff', 'dice_coeff_multiclass', 'fwhm2sig', 'getmgh', 'getnii', 'mgh2nii' 'getnii_descr', 'im_cut', 'imfill', 'imsmooth', 'iyang', 'motion_reg', 'nii_gzip', 'nii_modify', 'nii_ugzip', 'niisort', 'orientnii', 'pet2pet_rigid', 'pick_t1w', 'psf_gaussian', 'psf_measured', 'pvc_iyang', 'realign_mltp_spm', 'resample_fsl', @@ -68,6 +68,8 @@ dice_coeff, dice_coeff_multiclass, fwhm2sig, + getmgh, + mgh2nii, getnii, getnii_descr, im_cut, diff --git a/niftypet/nimpa/prc/__init__.py b/niftypet/nimpa/prc/__init__.py index a3d36665..d1859359 100644 --- a/niftypet/nimpa/prc/__init__.py +++ b/niftypet/nimpa/prc/__init__.py @@ -2,7 +2,7 @@ __all__ = [ # imio 'array2nii', 'create_dir', 'dcm2im', 'dcm2nii', 'dcmanonym', 'dcminfo', 'dcmsort', 'fwhm2sig', - 'getnii', 'getnii_descr', 'nii_gzip', 'nii_ugzip', 'niisort', 'orientnii', 'pick_t1w', + 'mgh2nii', 'getmgh', 'getnii', 'getnii_descr', 'nii_gzip', 'nii_ugzip', 'niisort', 'orientnii', 'pick_t1w', 'time_stamp', # prc 'bias_field_correction', 'centre_mass_img', 'centre_mass_corr', 'ct2mu', 'im_cut', @@ -26,7 +26,9 @@ dcminfo, dcmsort, fwhm2sig, + mgh2nii, getnii, + getmgh, getnii_descr, nii_gzip, nii_ugzip, diff --git a/niftypet/nimpa/prc/imio.py b/niftypet/nimpa/prc/imio.py index 00495245..50699f1c 100644 --- a/niftypet/nimpa/prc/imio.py +++ b/niftypet/nimpa/prc/imio.py @@ -5,6 +5,7 @@ import pathlib import re import shutil +import numbers from subprocess import run from textwrap import dedent @@ -17,6 +18,7 @@ from miutil.imio.nii import nii_ugzip # NOQA: F401 # yapf: disable from miutil.imio.nii import niisort # NOQA: F401 # yapf: disable + # > NiftyPET resources from .. import resources as rs @@ -50,6 +52,113 @@ def fwhm2sig(fwhm, voxsize=2.0): return (fwhm/voxsize) / (2 * (2 * np.log(2))**.5) + +def mgh2nii(fim, fout=None, output=None): + ''' Convert `*.mgh` or `*.mgz` FreeSurfer image to NIfTI. + Arguments: + fim: path to the input MGH file + fout: path to the output NIfTI file, if None then + creates based on `fim` + output: if not None and an applicable string it will + output a dictionary or an array (see below) + Return: + None: returns nothing + 'image' or 'im': outputs just the image + 'affine': outputs just the affine matrix + 'all': outputs all as a dictionary + ''' + + if not os.path.isfile(fim): + raise ValueError('The input path is incorrect!') + + # > get the image dictionary + mghd = getmgh(fim, output='all') + im = mghd['im'] + + # > sort out the output + if fout is None: + fout = fim.parent / (fim.name.split('.')[0]+'.nii.gz') + + out = fout + if output=='image' or output=='im': + out = fout, im + elif output=='affine': + out = fout, mghd['affine'] + elif output=='all': + out = mghd + out.update(dict(fout=fout)) + + array2nii( + mghd['im'], + mghd['affine'], + fout, + trnsp = (mghd['transpose'].index(0), mghd['transpose'].index(1), mghd['transpose'].index(2)), + flip = mghd['flip']) + + return out + + +def getmgh(fim, nan_replace=None, output='image'): + ''' + Get image from `*.mgz` or `*.mgh` file (FreeSurfer). + Arguments: + fim: input file name for the MGH/Z image + output: option for choosing output: 'image', 'affine' matrix or + 'all' for a dictionary with all the info. + Return: + 'image': outputs just the image + 'affine': outputs just the affine matrix + 'all': outputs all as a dictionary + ''' + + if not os.path.isfile(fim): + raise ValueError('The input path is incorrect!') + + mgh = nib.freesurfer.load(str(fim)) + + if output == 'image' or output == 'all': + + imr = np.asanyarray(mgh.dataobj) + # replace NaNs if requested + if isinstance(nan_replace, numbers.Number): + imr[np.isnan(imr)] = nan_replace + + imr = np.squeeze(imr) + dimno = imr.ndim + + # > get orientations from the affine + ornt = nib.io_orientation(mgh.affine) + trnsp = tuple(np.flip(np.argsort(ornt[:, 0]))) + flip = tuple(np.int8(ornt[:, 1])) + + # > flip y-axis and z-axis and then transpose + if dimno == 4: # dynamic + imr = np.transpose(imr[::-flip[0], ::-flip[1], ::-flip[2], :], (3,) + trnsp) + elif dimno == 3: # static + imr = np.transpose(imr[::-flip[0], ::-flip[1], ::-flip[2]], trnsp) + + + # # > voxel size + # voxsize = mgh.header.get('pixdim')[1:mgh.header.get('dim')[0] + 1] + # # > rearrange voxel size according to the orientation + # voxsize = voxsize[np.array(trnsp)] + + + if output == 'all': + out = { + 'im': imr, 'affine': mgh.affine, 'fim': fim, 'dtype': mgh.get_data_dtype(), 'shape': imr.shape, + 'hdr': mgh.header, 'transpose': trnsp, 'flip': flip} + elif output == 'image': + out = imr + elif output == 'affine': + out = mgh.affine + else: + raise NameError("Unrecognised output request!") + + return out + + + def getnii_descr(fim): '''Extracts the custom description header field to dictionary''' nim = nib.load(fim) diff --git a/niftypet/nimpa/prc/prc.py b/niftypet/nimpa/prc/prc.py index f96c35dc..e7e1c218 100644 --- a/niftypet/nimpa/prc/prc.py +++ b/niftypet/nimpa/prc/prc.py @@ -1286,7 +1286,8 @@ def bias_field_correction(fmr, fimout='', outpath='', fcomment='_N4bias', execut if len(outdct['fim']) == 1: outdct['fim'] = outdct['fim'][0] - outdct['fmsk'] = outdct['fmsk'][0] + if 'fmsk' in outdct: + outdct['fmsk'] = outdct['fmsk'][0] return outdct From 12cb849556ed4e0ddf6c3bb7e1e1500d9c550679 Mon Sep 17 00:00:00 2001 From: Casper da Costa-Luis Date: Tue, 15 Mar 2022 11:56:25 +0000 Subject: [PATCH 04/14] bump ninst --- pyproject.toml | 2 +- setup.cfg | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 0a28fa3c..2d988c72 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [build-system] requires = ["setuptools>=42", "wheel", "setuptools_scm[toml]>=3.4", - "ninst>=0.8.0", "cuvec>=2.8.0", "miutil[cuda]>=0.4.0", + "ninst>=0.9.0", "cuvec>=2.8.0", "miutil[cuda]>=0.4.0", "scikit-build>=0.11.0", "cmake>=3.18", "ninja"] [tool.setuptools_scm] diff --git a/setup.cfg b/setup.cfg index d0a5c3f2..842a0746 100644 --- a/setup.cfg +++ b/setup.cfg @@ -39,7 +39,7 @@ setup_requires= setuptools>=42 wheel setuptools_scm[toml] - ninst>=0.8.0 + ninst>=0.9.0 scikit-build>=0.11.0 cmake>=3.18 ninja From c8fe89b84d05f37df2bbed8a4ed5334b6482fe56 Mon Sep 17 00:00:00 2001 From: Casper da Costa-Luis Date: Mon, 9 May 2022 05:23:29 +0100 Subject: [PATCH 05/14] rename pydcm2niix => dcm2niix - as per https://github.com/rordenlab/dcm2niix/pull/605 --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index 842a0746..6c08b63b 100644 --- a/setup.cfg +++ b/setup.cfg @@ -47,7 +47,7 @@ setup_requires= miutil[cuda]>=0.4.0 install_requires= cuvec>=2.3.1 - pydcm2niix>=1.0.20220116 + dcm2niix>=1.0.20220116 dipy>=1.3.0 miutil[nii]>=0.10.0 nibabel>=2.4.0 From 21b3e14e787e959d7e4da0a7dee1b8b2e7e114be Mon Sep 17 00:00:00 2001 From: Casper da Costa-Luis Date: Mon, 9 May 2022 07:52:50 +0100 Subject: [PATCH 06/14] lint --- niftypet/nimpa/__init__.py | 4 +-- niftypet/nimpa/prc/__init__.py | 8 +++--- niftypet/nimpa/prc/imio.py | 47 +++++++++++++++------------------- niftypet/nimpa/prc/prc.py | 2 +- 4 files changed, 27 insertions(+), 34 deletions(-) diff --git a/niftypet/nimpa/__init__.py b/niftypet/nimpa/__init__.py index 128a3988..6c3cd3d9 100644 --- a/niftypet/nimpa/__init__.py +++ b/niftypet/nimpa/__init__.py @@ -30,7 +30,7 @@ 'centre_mass_img', 'centre_mass_corr', 'coreg_spm', 'coreg_vinci', 'create_dir', 'create_mask', 'ct2mu', 'dcm2im', 'dcm2nii', 'dcmanonym', 'dcminfo', 'dcmsort', - 'dice_coeff', 'dice_coeff_multiclass', 'fwhm2sig', 'getmgh', 'getnii', 'mgh2nii' + 'dice_coeff', 'dice_coeff_multiclass', 'fwhm2sig', 'getmgh', 'getnii', 'mgh2nii', 'getnii_descr', 'im_cut', 'imfill', 'imsmooth', 'iyang', 'motion_reg', 'nii_gzip', 'nii_modify', 'nii_ugzip', 'niisort', 'orientnii', 'pet2pet_rigid', 'pick_t1w', 'psf_gaussian', 'psf_measured', 'pvc_iyang', 'realign_mltp_spm', 'resample_fsl', @@ -69,7 +69,6 @@ dice_coeff_multiclass, fwhm2sig, getmgh, - mgh2nii, getnii, getnii_descr, im_cut, @@ -77,6 +76,7 @@ imsmooth, isub, iyang, + mgh2nii, motion_reg, nii_gzip, nii_modify, diff --git a/niftypet/nimpa/prc/__init__.py b/niftypet/nimpa/prc/__init__.py index d1859359..57463c04 100644 --- a/niftypet/nimpa/prc/__init__.py +++ b/niftypet/nimpa/prc/__init__.py @@ -2,8 +2,8 @@ __all__ = [ # imio 'array2nii', 'create_dir', 'dcm2im', 'dcm2nii', 'dcmanonym', 'dcminfo', 'dcmsort', 'fwhm2sig', - 'mgh2nii', 'getmgh', 'getnii', 'getnii_descr', 'nii_gzip', 'nii_ugzip', 'niisort', 'orientnii', 'pick_t1w', - 'time_stamp', + 'mgh2nii', 'getmgh', 'getnii', 'getnii_descr', 'nii_gzip', 'nii_ugzip', 'niisort', 'orientnii', + 'pick_t1w', 'time_stamp', # prc 'bias_field_correction', 'centre_mass_img', 'centre_mass_corr', 'ct2mu', 'im_cut', 'imsmooth', 'imtrimup', @@ -26,10 +26,10 @@ dcminfo, dcmsort, fwhm2sig, - mgh2nii, - getnii, getmgh, + getnii, getnii_descr, + mgh2nii, nii_gzip, nii_ugzip, niisort, diff --git a/niftypet/nimpa/prc/imio.py b/niftypet/nimpa/prc/imio.py index 50699f1c..79327c5d 100644 --- a/niftypet/nimpa/prc/imio.py +++ b/niftypet/nimpa/prc/imio.py @@ -1,11 +1,11 @@ """image input/output functionalities.""" import datetime import logging +import numbers import os import pathlib import re import shutil -import numbers from subprocess import run from textwrap import dedent @@ -18,7 +18,6 @@ from miutil.imio.nii import nii_ugzip # NOQA: F401 # yapf: disable from miutil.imio.nii import niisort # NOQA: F401 # yapf: disable - # > NiftyPET resources from .. import resources as rs @@ -52,13 +51,12 @@ def fwhm2sig(fwhm, voxsize=2.0): return (fwhm/voxsize) / (2 * (2 * np.log(2))**.5) - def mgh2nii(fim, fout=None, output=None): ''' Convert `*.mgh` or `*.mgz` FreeSurfer image to NIfTI. Arguments: fim: path to the input MGH file fout: path to the output NIfTI file, if None then - creates based on `fim` + creates based on `fim` output: if not None and an applicable string it will output a dictionary or an array (see below) Return: @@ -77,23 +75,21 @@ def mgh2nii(fim, fout=None, output=None): # > sort out the output if fout is None: - fout = fim.parent / (fim.name.split('.')[0]+'.nii.gz') + fout = fim.parent / (fim.name.split('.')[0] + '.nii.gz') out = fout - if output=='image' or output=='im': + if output == 'image' or output == 'im': out = fout, im - elif output=='affine': + elif output == 'affine': out = fout, mghd['affine'] - elif output=='all': + elif output == 'all': out = mghd - out.update(dict(fout=fout)) + out['fout'] = fout array2nii( - mghd['im'], - mghd['affine'], - fout, - trnsp = (mghd['transpose'].index(0), mghd['transpose'].index(1), mghd['transpose'].index(2)), - flip = mghd['flip']) + mghd['im'], mghd['affine'], fout, + trnsp=(mghd['transpose'].index(0), mghd['transpose'].index(1), mghd['transpose'].index(2)), + flip=mghd['flip']) return out @@ -103,7 +99,7 @@ def getmgh(fim, nan_replace=None, output='image'): Get image from `*.mgz` or `*.mgh` file (FreeSurfer). Arguments: fim: input file name for the MGH/Z image - output: option for choosing output: 'image', 'affine' matrix or + output: option for choosing output: 'image', 'affine' matrix or 'all' for a dictionary with all the info. Return: 'image': outputs just the image @@ -117,7 +113,7 @@ def getmgh(fim, nan_replace=None, output='image'): mgh = nib.freesurfer.load(str(fim)) if output == 'image' or output == 'all': - + imr = np.asanyarray(mgh.dataobj) # replace NaNs if requested if isinstance(nan_replace, numbers.Number): @@ -137,17 +133,15 @@ def getmgh(fim, nan_replace=None, output='image'): elif dimno == 3: # static imr = np.transpose(imr[::-flip[0], ::-flip[1], ::-flip[2]], trnsp) - # # > voxel size # voxsize = mgh.header.get('pixdim')[1:mgh.header.get('dim')[0] + 1] # # > rearrange voxel size according to the orientation # voxsize = voxsize[np.array(trnsp)] - if output == 'all': out = { - 'im': imr, 'affine': mgh.affine, 'fim': fim, 'dtype': mgh.get_data_dtype(), 'shape': imr.shape, - 'hdr': mgh.header, 'transpose': trnsp, 'flip': flip} + 'im': imr, 'affine': mgh.affine, 'fim': fim, 'dtype': mgh.get_data_dtype(), + 'shape': imr.shape, 'hdr': mgh.header, 'transpose': trnsp, 'flip': flip} elif output == 'image': out = imr elif output == 'affine': @@ -158,7 +152,6 @@ def getmgh(fim, nan_replace=None, output='image'): return out - def getnii_descr(fim): '''Extracts the custom description header field to dictionary''' nim = nib.load(fim) @@ -411,7 +404,7 @@ def dcminfo(dcmvar, Cnt=None, output='detail', t1_name='mprage'): if validTs: mrdct = { - 'series': srs, 'protocol': prtcl, 'units': unt, 'study_time': study_time, 'inst':inst, + 'series': srs, 'protocol': prtcl, 'units': unt, 'study_time': study_time, 'inst': inst, 'series_time': series_time, 'acq_time': acq_time, 'scanner_id': scanner_id, 'TR': TR, 'TE': TE} # --------------------------------------------- @@ -438,11 +431,11 @@ def dcminfo(dcmvar, Cnt=None, output='detail', t1_name='mprage'): elif isPET: petdct = { 'series': srs, 'protocol': prtcl, 'study_time': study_time, 'series_time': series_time, - 'inst':inst, 'acq_time': acq_time, 'scanner_id': scanner_id, 'type': srs_type, 'units': unt, - 'recon': recon, 'decay_corr': decay_corr, 'dcf': dcf, 'attenuation': atten, - 'scatter': scat, 'scf': scf, 'randoms': rand, 'dose_calib': dscf, 'dead_time': dt, - 'tracer': tracer, 'total_dose': tdose, 'half_life': hlife, 'positron_fract': pfract, - 'radio_start_time': ttime0, 'radio_stop_time': ttime1} + 'inst': inst, 'acq_time': acq_time, 'scanner_id': scanner_id, 'type': srs_type, + 'units': unt, 'recon': recon, 'decay_corr': decay_corr, 'dcf': dcf, + 'attenuation': atten, 'scatter': scat, 'scf': scf, 'randoms': rand, 'dose_calib': dscf, + 'dead_time': dt, 'tracer': tracer, 'total_dose': tdose, 'half_life': hlife, + 'positron_fract': pfract, 'radio_start_time': ttime0, 'radio_stop_time': ttime1} out = ['pet', tracer.lower(), srs_type.lower(), scanner_id, petdct] diff --git a/niftypet/nimpa/prc/prc.py b/niftypet/nimpa/prc/prc.py index e7e1c218..c96c7c26 100644 --- a/niftypet/nimpa/prc/prc.py +++ b/niftypet/nimpa/prc/prc.py @@ -1288,7 +1288,7 @@ def bias_field_correction(fmr, fimout='', outpath='', fcomment='_N4bias', execut outdct['fim'] = outdct['fim'][0] if 'fmsk' in outdct: outdct['fmsk'] = outdct['fmsk'][0] - + return outdct From aeba6d9a289169c1e4e7ddc11c980be2ae1c0728 Mon Sep 17 00:00:00 2001 From: Casper da Costa-Luis Date: Mon, 9 May 2022 07:53:11 +0100 Subject: [PATCH 07/14] no install_tools --- setup.cfg | 3 ++- setup.py | 36 ------------------------------------ 2 files changed, 2 insertions(+), 37 deletions(-) diff --git a/setup.cfg b/setup.cfg index 6c08b63b..1bb95103 100644 --- a/setup.cfg +++ b/setup.cfg @@ -47,7 +47,6 @@ setup_requires= miutil[cuda]>=0.4.0 install_requires= cuvec>=2.3.1 - dcm2niix>=1.0.20220116 dipy>=1.3.0 miutil[nii]>=0.10.0 nibabel>=2.4.0 @@ -68,6 +67,8 @@ dev= pytest-timeout pytest-xdist plot=miutil[plot] +dcm2niix=dcm2niix>=1.0.20220116 +niftyreg=niftyreg [flake8] max_line_length=99 diff --git a/setup.py b/setup.py index d8af94a4..bda42974 100755 --- a/setup.py +++ b/setup.py @@ -50,42 +50,6 @@ resources = cs.get_resources() # get the current setup, if any Cnt = resources.get_setup() -# check the installation of tools -chck_tls = tls.check_version(Cnt, chcklst=["RESPATH", "REGPATH"]) - -# ------------------------------------------- -# NiftyPET tools: -# ------------------------------------------- -if "sdist" not in sys.argv or any(i in sys.argv for i in ["build", "bdist", "wheel"]): - # NiftyReg - if not chck_tls["REGPATH"] or not chck_tls["RESPATH"]: - reply = True - if not gpuarch: - try: - reply = tls.query_yesno( - "q> the latest compatible version of NiftyReg seems to be missing.\n" - " Do you want to install it?") - except BaseException: - pass - - if reply: - log.info( - dedent("""\ - -------------------------------------------------------------- - Installing NiftyReg ... - --------------------------------------------------------------""")) - Cnt = tls.install_tool("niftyreg", Cnt) - log.info( - dedent("""\ - -------------------------------------------------------------- - Installation of NiftyPET-tools is done. - --------------------------------------------------------------""")) -else: - log.info( - dedent("""\ - -------------------------------------------------------------- - Skipping installation of NiftyPET-tools. - --------------------------------------------------------------""")) build_ver = ".".join(__version__.split('.')[:3]).split(".dev")[0] setup_kwargs = { From e33f9f0a975a54d556017a79eda493eb88d3d991 Mon Sep 17 00:00:00 2001 From: Casper da Costa-Luis Date: Mon, 9 May 2022 08:18:15 +0100 Subject: [PATCH 08/14] document optional deps --- README.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.rst b/README.rst index 4598b356..7e55b4ca 100644 --- a/README.rst +++ b/README.rst @@ -34,6 +34,8 @@ It's also recommended (but not required) to use `conda`. ipykernel numpy scipy scikit-image matplotlib ipywidgets pip install "nimpa>=2" +For optional `dcm2niix `_ and/or `niftyreg `_ support, use ``pip install nimpa[dcm2niix,niftyreg]>=2``. + External CMake Projects ~~~~~~~~~~~~~~~~~~~~~~~ From 8b3de3180f7755f036e95953f52ae8da41a0ff2c Mon Sep 17 00:00:00 2001 From: Casper da Costa-Luis Date: Mon, 9 May 2022 08:46:53 +0100 Subject: [PATCH 09/14] niftyreg package detection --- niftypet/nimpa/prc/prc.py | 1 - niftypet/nimpa/prc/regseg.py | 26 +++++++++++++++----------- 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/niftypet/nimpa/prc/prc.py b/niftypet/nimpa/prc/prc.py index c96c7c26..fd6d586d 100644 --- a/niftypet/nimpa/prc/prc.py +++ b/niftypet/nimpa/prc/prc.py @@ -727,7 +727,6 @@ def pvc_iyang( ft1w, outpath=os.path.join(outpath, 'PET', 'positioning'), fcomment=fcomment, - executable=Cnt['REGPATH'], omp=multiprocessing.cpu_count() / 2, rigOnly=True, affDirect=False, diff --git a/niftypet/nimpa/prc/regseg.py b/niftypet/nimpa/prc/regseg.py index d1dcb51b..e89b252f 100644 --- a/niftypet/nimpa/prc/regseg.py +++ b/niftypet/nimpa/prc/regseg.py @@ -7,6 +7,7 @@ import os import shutil import sys +from os import fspath from subprocess import call from textwrap import dedent @@ -276,7 +277,7 @@ def affine_niftyreg( fname_aff='', pickname='ref', fcomment='', - executable='', + executable=None, omp=1, rigOnly=False, affDirect=False, @@ -294,10 +295,13 @@ def affine_niftyreg( fthrsh=0.05, verbose=True, ): - - # check if the executable exists: + if not executable: + executable = getattr(rs, 'REGPATH', None) + if not executable: + from niftyreg import bin_path + executable = fspath(next(bin_path.glob("reg_aladin*"))) if not os.path.isfile(executable): - raise IOError('Incorrect path to executable file for registration.') + raise IOError(f"executable not found:{executable}") # create a folder for images registered to ref if outpath != '': @@ -377,16 +381,16 @@ def resample_niftyreg( fcomment='', pickname='ref', intrp=1, - executable='', + executable=None, verbose=True, ): - - # check if the executable exists: - # if executable=='' and 'RESPATH' in Cnt and os.path.isfile(Cnt['RESPATH']): - # executable = Cnt['RESPATH'] - + if not executable: + executable = getattr(rs, 'RESPATH', None) + if not executable: + from niftyreg import bin_path + executable = fspath(next(bin_path.glob("reg_resample*"))) if not os.path.isfile(executable): - raise IOError('Incorrect path to executable file for registration.') + raise IOError(f"executable not found:{executable}") # > output path if outpath == '' and fimout != '': From 243d73cd3d7970eb2ae5a93b1ec49efe20012f81 Mon Sep 17 00:00:00 2001 From: Casper da Costa-Luis Date: Tue, 10 May 2022 06:25:00 +0100 Subject: [PATCH 10/14] make CuVec optional --- niftypet/nimpa/__init__.py | 5 ++++- pyproject.toml | 3 ++- setup.cfg | 16 ++-------------- setup.py | 25 ++++++++----------------- 4 files changed, 16 insertions(+), 33 deletions(-) diff --git a/niftypet/nimpa/__init__.py b/niftypet/nimpa/__init__.py index 6c3cd3d9..d29a243a 100644 --- a/niftypet/nimpa/__init__.py +++ b/niftypet/nimpa/__init__.py @@ -37,7 +37,10 @@ 'resample_mltp_spm', 'resample_niftyreg', 'resample_spm', 'resample_vinci', 'resample_dipy', 'time_stamp'] # yapf: disable -from numcu import add, div, mul +try: + from numcu import add, div, mul +except ImportError: + pass from pkg_resources import resource_filename from niftypet.ninst import cudasetup as cs diff --git a/pyproject.toml b/pyproject.toml index 2d988c72..6582a23d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,7 @@ [build-system] +# cuvec>=2.8.0 requires = ["setuptools>=42", "wheel", "setuptools_scm[toml]>=3.4", - "ninst>=0.9.0", "cuvec>=2.8.0", "miutil[cuda]>=0.4.0", + "ninst>=0.9.0", "cuvec-base", "miutil[cuda]>=0.4.0", "scikit-build>=0.11.0", "cmake>=3.18", "ninja"] [tool.setuptools_scm] diff --git a/setup.cfg b/setup.cfg index 1bb95103..20b20fc5 100644 --- a/setup.cfg +++ b/setup.cfg @@ -43,21 +43,9 @@ setup_requires= scikit-build>=0.11.0 cmake>=3.18 ninja - cuvec>=2.8.0 + cuvec-base miutil[cuda]>=0.4.0 -install_requires= - cuvec>=2.3.1 - dipy>=1.3.0 - miutil[nii]>=0.10.0 - nibabel>=2.4.0 - ninst>=0.4.0 - numcu - numpy>=1.14 - pydicom>=1.0.2 - scipy - setuptools - spm12 - # SimpleITK>=1.2.0 +# cuvec>=2.8.0 python_requires=>=3.6 [options.extras_require] dev= diff --git a/setup.py b/setup.py index bda42974..063b36b2 100755 --- a/setup.py +++ b/setup.py @@ -7,7 +7,6 @@ import re import sys from pathlib import Path -from textwrap import dedent from setuptools import find_packages, setup from setuptools_scm import get_version @@ -24,26 +23,12 @@ tls.check_platform() ext = tls.check_depends() # external dependencies -if not ext["git"]: - raise SystemError( - dedent("""\ - -------------------------------------------------------------- - Git is not installed but is required for tools installation. - --------------------------------------------------------------""")) - cs.resources_setup(gpu=False) # install resources.py try: - gpuarch = cs.dev_setup() # update resources.py with a supported GPU device + cs.dev_setup() # update resources.py with a supported GPU device except Exception as exc: log.error("could not set up CUDA:\n%s", exc) - gpuarch = None -# First install third party apps for NiftyPET tools -log.info( - dedent("""\ - -------------------------------------------------------------- - Setting up NiftyPET tools ... - --------------------------------------------------------------""")) # get the local path to NiftyPET resources.py path_resources = cs.path_niftypet_local() # if exists, import the resources and get the constants @@ -54,13 +39,18 @@ build_ver = ".".join(__version__.split('.')[:3]).split(".dev")[0] setup_kwargs = { "use_scm_version": True, "packages": find_packages(exclude=["tests"]), - "package_data": {"niftypet": ["nimpa/auxdata/*"]}} + "package_data": {"niftypet": ["nimpa/auxdata/*"]}, "install_requires": [ + 'dipy>=1.3.0', 'miutil[nii]>=0.10.0', 'nibabel>=2.4.0', 'ninst>=0.4.0', 'numpy>=1.14', + 'pydicom>=1.0.2', 'scipy', 'setuptools', 'spm12']} +# 'SimpleITK>=1.2.0' cmake_args = [ f"-DNIMPA_BUILD_VERSION={build_ver}", f"-DPython3_ROOT_DIR={sys.prefix}", f"-DNIMPA_KERNEL_RADIUS={getattr(resources, 'RSZ_PSF_KRNL', 8)}"] try: + import cuvec as cu from skbuild import setup as sksetup + assert cu.include_path.is_dir() nvcc_arches = {"{2:d}{3:d}".format(*i) for i in dinf.gpuinfo() if i[2:4] >= (3, 5)} if nvcc_arches: cmake_args.append("-DCMAKE_CUDA_ARCHITECTURES=" + ";".join(sorted(nvcc_arches))) @@ -68,6 +58,7 @@ log.warning("Import or CUDA device detection error:\n%s", exc) setup(**setup_kwargs) else: + setup_kwargs['install_requires'].extend(["cuvec>=2.3.1", "numcu"]) for i in (Path(__file__).resolve().parent / "_skbuild").rglob("CMakeCache.txt"): i.write_text(re.sub("^//.*$\n^[^#].*pip-build-env.*$", "", i.read_text(), flags=re.M)) sksetup(cmake_source_dir="niftypet", cmake_languages=("C", "CXX", "CUDA"), From c06190a455b9c391d401ace81a4fe4e1bda53543 Mon Sep 17 00:00:00 2001 From: Casper da Costa-Luis Date: Tue, 10 May 2022 06:29:38 +0100 Subject: [PATCH 11/14] cuda option (for nipet) --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index 20b20fc5..0ea1fbed 100644 --- a/setup.cfg +++ b/setup.cfg @@ -45,7 +45,6 @@ setup_requires= ninja cuvec-base miutil[cuda]>=0.4.0 -# cuvec>=2.8.0 python_requires=>=3.6 [options.extras_require] dev= @@ -55,6 +54,7 @@ dev= pytest-timeout pytest-xdist plot=miutil[plot] +cuda=cuvec>=2.3.1; numcu dcm2niix=dcm2niix>=1.0.20220116 niftyreg=niftyreg From 34f48805331ea421e72b9a9e705d1e0daf317f6d Mon Sep 17 00:00:00 2001 From: Casper da Costa-Luis Date: Tue, 10 May 2022 06:32:23 +0100 Subject: [PATCH 12/14] better installation note --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 7e55b4ca..666b6701 100644 --- a/README.rst +++ b/README.rst @@ -34,7 +34,7 @@ It's also recommended (but not required) to use `conda`. ipykernel numpy scipy scikit-image matplotlib ipywidgets pip install "nimpa>=2" -For optional `dcm2niix `_ and/or `niftyreg `_ support, use ``pip install nimpa[dcm2niix,niftyreg]>=2``. +For optional `dcm2niix `_ and/or `niftyreg `_ support, simply install them separately (``conda install dcm2niix`` and/or ``pip install niftyreg``). External CMake Projects ~~~~~~~~~~~~~~~~~~~~~~~ From 9aa5dcb58f6655ae8018745b887a56eeef822710 Mon Sep 17 00:00:00 2001 From: Casper da Costa-Luis Date: Tue, 10 May 2022 06:39:41 +0100 Subject: [PATCH 13/14] drop PATHTOOLS --- .github/workflows/test.yml | 2 -- README.rst | 12 ++++-------- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 82a69e61..038ab33c 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -69,8 +69,6 @@ jobs: gpg_key: ${{ secrets.GPG_KEY }} password: ${{ secrets.PYPI_TOKEN }} upload: ${{ github.event_name == 'push' && startsWith(github.ref, 'refs/tags') }} - env: - PATHTOOLS: ${{ github.workspace }}/NiftyPET_tools - id: meta name: Changelog run: | diff --git a/README.rst b/README.rst index 666b6701..c9c09f67 100644 --- a/README.rst +++ b/README.rst @@ -21,20 +21,16 @@ In order to facilitate these operations, NIMPA relies on third-party software fo Quick Install ~~~~~~~~~~~~~ -Note that installation prompts for setting the path to ``NiftyPET_tools``. -This can be avoided by setting the environment variables ``PATHTOOLS``. -It's also recommended (but not required) to use `conda`. +Note that it's recommended (but not required) to use `conda`. .. code:: sh - # optional (Linux syntax) to avoid prompts - export PATHTOOLS=$HOME/NiftyPET_tools # cross-platform install conda install -c conda-forge python=3 \ - ipykernel numpy scipy scikit-image matplotlib ipywidgets + ipykernel numpy scipy scikit-image matplotlib ipywidgets dcm2niix pip install "nimpa>=2" -For optional `dcm2niix `_ and/or `niftyreg `_ support, simply install them separately (``conda install dcm2niix`` and/or ``pip install niftyreg``). +For optional `dcm2niix `_ and/or `niftyreg `_ support, simply install them separately (``pip install dcm2niix niftyreg``). External CMake Projects ~~~~~~~~~~~~~~~~~~~~~~~ @@ -70,7 +66,7 @@ Licence Copyright 2018-21 - `Pawel J. Markiewicz `__ @ University College London -- `Casper O. da Costa-Luis `__ @ King's College London +- `Casper O. da Costa-Luis `__ @ University College London/King's College London - `Contributors `__ .. |Docs| image:: https://readthedocs.org/projects/niftypet/badge/?version=latest From 35384a97021c5d24115037a7d7808111979195af Mon Sep 17 00:00:00 2001 From: Casper da Costa-Luis Date: Tue, 10 May 2022 08:29:01 +0100 Subject: [PATCH 14/14] another comment --- README.rst | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/README.rst b/README.rst index c9c09f67..e529f29e 100644 --- a/README.rst +++ b/README.rst @@ -14,8 +14,6 @@ The upsampling is needed for more accurate extraction (sampling) of PET data usi PVC is needed to correct for the spill-in and spill-out of PET signal from defined ROIs (specific for any given application). -In order to facilitate these operations, NIMPA relies on third-party software for image conversion from DICOM to NIfTI (dcm2niix) and image registration (NiftyReg). The additional software is installed automatically to a user specified location. - **Documentation with installation manual and tutorials**: https://niftypet.readthedocs.io/ Quick Install @@ -30,7 +28,7 @@ Note that it's recommended (but not required) to use `conda`. ipykernel numpy scipy scikit-image matplotlib ipywidgets dcm2niix pip install "nimpa>=2" -For optional `dcm2niix `_ and/or `niftyreg `_ support, simply install them separately (``pip install dcm2niix niftyreg``). +For optional `dcm2niix `_ (image conversion from DICOM to NIfTI) and/or `niftyreg `_ (image registration) support, simply install them separately (``pip install dcm2niix niftyreg``). External CMake Projects ~~~~~~~~~~~~~~~~~~~~~~~