import pymirc.fileio as pymf import pymirc.image_operations as pymi #--------------------------------------------------------------------------------------------- data_dir = os.path.join('..','..','data','nema_petct') if not os.path.exists(data_dir): url = 'https://kuleuven.box.com/s/wub9pk0yvt8kjqyj7p0bz11boca4334x' print('please first download example PET/CT data from:') print(url) print('and unzip into: ', data_dir) sys.exit() # read PET/CT nema phantom dicom data sets from pet_dcm = pymf.DicomVolume(os.path.join(data_dir,'PT','*.dcm')) pet_vol = pet_dcm.get_data() ct_dcm = pymf.DicomVolume(os.path.join(data_dir,'CT','*.dcm')) ct_vol = ct_dcm.get_data() # the PET and CT images are on different voxel grids # to view them in parallel, we interpolate the PET volume to the CT grid pet_vol_ct_grid = pymi.aff_transform(pet_vol, np.linalg.inv(pet_dcm.affine) @ ct_dcm.affine, output_shape = ct_vol.shape) imshow_kwargs = [{'cmap':py.cm.Greys}, {'cmap':py.cm.Greys_r,'vmin':-500,'vmax':500}, {'cmap':py.cm.Greys_r,'vmin':-500,'vmax':500}] oimshow_kwargs = {'cmap':py.cm.hot, 'alpha':0.3}
#------------------------------------------------------------------------------------- pymirc_path = os.path.join('..', '..') if not pymirc_path in sys.path: sys.path.append(pymirc_path) import pymirc.fileio as pymf import pymirc.viewer as pymv import numpy as np import pylab as py import nibabel as nib data_dir = '/users/nexuz/gschra2/tmp/data_sets/ibsi_1_ct_radiomics_phantom' # read CT vol that was used to generate ROIs in rtstruct file ct_dcm = pymf.DicomVolume(os.path.join(data_dir, 'dicom', 'image', '*.dcm')) ct_vol = ct_dcm.get_data() aff = ct_dcm.affine shape = ct_vol.shape #------------------------------------------------------------------------------------- # read the rt struct data rtstruct_file = os.path.join(data_dir, 'dicom', 'mask', 'DCM_RS_00060.dcm') # read the ROI contours (in world coordinates) contour_data = pymf.read_rtstruct_contour_data(rtstruct_file) # convert contour data to index arrays (voxel space) # in this example we have to ignore the orientation of the saved 2D contours (polygons) roi_inds = pymf.convert_contour_data_to_roi_indices(
import numpy as np import pylab as py # check of data is there data_dir = os.path.join('..', '..', 'data', 'nema_petct') if not os.path.exists(data_dir): url = 'https://kuleuven.box.com/s/wub9pk0yvt8kjqyj7p0bz11boca4334x' print('please first download example PET/CT data from:') print(url) print('and unzip into: ', data_dir) sys.exit() # read CT vol that was used to generate ROIs in rtstruct file ct_dcm = pymf.DicomVolume('../../data/nema_petct/CT/*.dcm') ct_vol = ct_dcm.get_data() aff = ct_dcm.affine shape = ct_vol.shape #------------------------------------------------------------------------------------- # read the rt struct data rtstruct_file = '../../data/nema_petct/rois/nonconvex_rtstruct.dcm' # read the ROI contours (in world coordinates) contour_data = pymf.read_rtstruct_contour_data(rtstruct_file) # convert contour data to index arrays (voxel space) roi_inds = pymf.convert_contour_data_to_roi_indices(contour_data, aff, shape)
if output_dir is None: output_dir = os.path.splitext(input_file)[0] + '_interpolated' # check if output dir already exists while os.path.exists(output_dir): output_dir += '_1' # load the list of dicom tags to copy from the reference header from an input text file with open(args.dcm_tag_file, 'r') as f: tags_to_copy = [x.strip() for x in f.read().splitlines()] #-------------------------------------------------------------------------------- #-------------------------------------------------------------------------------- dcm = pf.DicomVolume(input_file) vol = dcm.get_data() aff = dcm.affine # interpolate the volume to the target voxelsize using trilinear interpolation vol_interp = pi.zoom3d(vol, dcm.voxsize / target_voxsize) # generate the new affine of the interpolated array aff_interp = aff.copy() aff_interp[:, 0] *= (target_voxsize[0] / dcm.voxsize[0]) aff_interp[:, 1] *= (target_voxsize[1] / dcm.voxsize[1]) aff_interp[:, 2] *= (target_voxsize[2] / dcm.voxsize[2]) aff_interp[:-1, 3] = aff[:-1, -1] - 0.5 * dcm.voxsize + 0.5 * target_voxsize # create the dictionary of tags and values that are copied from the reference dicom header
from scipy.ndimage import find_objects import argparse parser = argparse.ArgumentParser( description='NEMA small animal IQ scan analyzer') parser.add_argument('dcm_dir', help='absolute path of input dicom directory') parser.add_argument('--phantom', help='phantom version', choices=['standard', 'mini'], default='standard') args = parser.parse_args() # read the PET volume from dicom dcm = pf.DicomVolume(args.dcm_dir) vol = dcm.get_data() # align the PET volume to "standard" space (a digitial version of the phantom) vol_aligned = nsa.align_nema_2008_small_animal_iq_phantom(vol, dcm.voxsize, version=args.phantom) # generate the ROI label volume roi_vol = nsa.nema_2008_small_animal_pet_rois(vol_aligned, dcm.voxsize, phantom=args.phantom) # generate the report nsa.nema_2008_small_animal_iq_phantom_report(vol_aligned, roi_vol)
import sys, os pymirc_path = os.path.join('..','..') if not pymirc_path in sys.path: sys.path.append(pymirc_path) import pymirc.fileio as pymf import pymirc.viewer as pymv from argparse import ArgumentParser parser = ArgumentParser() parser.add_argument('dcm_path', help = 'dicom folder') parser.add_argument('--dcm_pat', default = '*.dcm') parser.add_argument('--overlay_tag', default = 0x6002) args = parser.parse_args() #--------------------------------------------------------------------- dcm_data = pymf.DicomVolume(os.path.join(args.dcm_path,args.dcm_pat)) vol = dcm_data.get_data() voxsize = dcm_data.voxsize oli = dcm_data.get_3d_overlay_img(tag=args.overlay_tag) vi = pymv.ThreeAxisViewer([vol,oli], voxsize = voxsize)
def wb_nema_iq(): parser = argparse.ArgumentParser(description='NEMA WB IQ scan analyzer') parser.add_argument('dcm_dir', help='absolute path of input dicom directory') parser.add_argument('--dcm_pattern', default='*', help='file pattern for files in the dcm_dir') parser.add_argument( '--fwhm_mm', default=0, help= 'FWHM (mm) of Gaussian filter applied to the input volumes before the analysis', type=float) parser.add_argument( '--radii_mm', default=[None], help= 'The radii (mm) of the 6 spheres (seperated by blanks)). If not given this is set the values "18.5 14.0 11.0 8.5 6.5 5.0" are used', nargs='+') parser.add_argument( '--signal', default=None, help= 'Fixed signal in [Bq/ml] (or the units of the volume) used when fitting all spheres. If not provided, the fitted value from the biggest sphere is used for all spheres', type=float) parser.add_argument( '--wall_mm', default=1.5, help='Fixed glass wall thickness (mm). If not provided 1.5mm is used.', type=float) parser.add_argument('--earl', default=2, help='EARL version to use for limits in plots', type=int, choices=[1, 2]) parser.add_argument( '--true_act_conc', default=None, help= 'True activity concentration in the spheres in [Bq/ml] (or the units of the volume). If not given, it is obtained from the fitted signal of the biggest sphere.', type=float) parser.add_argument('--output_dir', help='name of the output directory', default=None) parser.add_argument('--show', help='show the results', action='store_true') parser.add_argument('--verbose', help='print (extra) verbose output', action='store_true') args = parser.parse_args() #------------------------------------------------------------------------------------------------- # load modules import matplotlib.pyplot as plt from scipy.ndimage import gaussian_filter import pymirc.fileio as pmf import pymirc.viewer as pv import pynemaiqpet import pynemaiqpet.nema_wb as nema #------------------------------------------------------------------------------------------------- # parse input parameters dcm_dir = args.dcm_dir dcm_pattern = args.dcm_pattern sm_fwhm_mm = args.fwhm_mm Rfix = args.radii_mm Sfix = args.signal dfix = args.wall_mm earlversion = args.earl true_act_conc = args.true_act_conc output_dir = args.output_dir show = args.show verbose = args.verbose if Rfix[0] is None: Rfix = [18.5, 14.0, 11.0, 8.5, 6.5, 5.] elif Rfix[0] == 'fit': Rfix = None else: if len(Rfix) != 6: raise ValueError( 'When manually specifying the sphere radii, 6 values must be given.' ) Rfix = [float(x) for x in Rfix] #------------------------------------------------------------------------------------------------- # load the dicom volume dcm = pmf.DicomVolume(os.path.join(dcm_dir, dcm_pattern)) vol = dcm.get_data() voxsize = dcm.voxsize # post smooth the image if sm_fwhm_mm > 0: vol = gaussian_filter(vol, sigma=sm_fwhm_mm / (2.35 * voxsize)) #------------------------------------------------------------------------------------------------- # do a fit where we force all spheres to have the same signal (assuming that the activity # concentration in all sphere was the same) # try also doing the fit without fixing the radii fitres, sphere_results = nema.fit_WB_NEMA_sphere_profiles(vol, voxsize, sameSignal=True, Rfix=Rfix, Sfix=Sfix, dfix=dfix, showBGROI=True) if verbose: print('fit with same signal and fixed radii') print(sphere_results) if output_dir is not None: os.makedirs(output_dir, exist_ok=True) sphere_results.to_csv(os.path.join(output_dir, 'fit_results.csv')) #------------------------------------------------------------------------------------------------- # show the results fig = nema.show_WB_NEMA_profiles(fitres) # plot the max and a50 recoveries # the 2nd argument should be the true (expected) activity concentration in the spheres # and also show the limits given by EARL (vesion 2) if true_act_conc == None: true_act_conc = sphere_results['signal'].values[0] fig2 = nema.show_WB_NEMA_recoveries(sphere_results, true_act_conc, earlversion=earlversion) # show the volume vi = pv.ThreeAxisViewer(vol, voxsize=voxsize) # save plots if output_dir is not None: fig.savefig(os.path.join(output_dir, 'sphere_profiles.pdf')) fig2.savefig(os.path.join(output_dir, 'recoveries.pdf')) vi.fig.savefig(os.path.join(output_dir, 'volume.png')) if show: plt.show()
if not pymirc_path in sys.path: sys.path.append(pymirc_path) import numpy as np import nibabel as nib import pymirc.fileio as pymf from glob import glob nii = nib.load('../../data/TestRTstruct/Multimask.nii.gz') nii = nib.as_closest_canonical(nii) roi_vol_ras = nii.get_data()[:, :, :, 0, 0, 0] roi_vol = np.flip(roi_vol_ras, (0, 1)) aff_ras = nii.affine.copy() aff = aff_ras.copy() aff[0, -1] = (-1 * aff_ras @ np.array([roi_vol.shape[0] - 1, 0, 0, 1]))[0] aff[1, -1] = (-1 * aff_ras @ np.array([0, roi_vol.shape[1] - 1, 0, 1]))[1] ct_files = glob('../../data/TestRTstruct/CT/*.dcm') ct_dcm = pymf.DicomVolume(ct_files) ct = ct_dcm.get_data() refdcm_file = ct_files #------------------------------------------------------------ pymf.labelvol_to_rtstruct(roi_vol, aff, refdcm_file, '../../data/TestRTstruct/t_rtstruct.dcm', tags_to_add={'SpecificCharacterSet': 'ISO_IR 192'})
earlversion = args.earl_version dcm_dir_pattern = args.dcm_dir_pattern dcm_file_pattern = args.dcm_file_pattern #------------------------------------------------------------------------------------------------ dcm_dirs = glob(dcm_dir_pattern) for dcm_dir in dcm_dirs: print(os.path.basename(dcm_dir)) # load example data set included in package dcm_pattern = os.path.join(dcm_dir, dcm_file_pattern) dcm = pmf.DicomVolume(dcm_pattern) vol = dcm.get_data() voxsize = dcm.voxsize # FWHM of Gaussian kernel to apply before analysis dcm_hdr = dcm.firstdcmheader # check if the data was already post-smoothed by analysing private GE tags # for trans-axial and axial filter if ((dcm_hdr[0x0009, 0x10ba].value == 0) and (dcm_hdr[0x0009, 0x10db].value == 0)) or force_smoothing: sm_str = f'_{sm_fwhm_mm}mm_ps' else: sm_fwhm_mm = 0 sm_str = ''
parser.add_argument('--output_dir', help = 'name of the output master directory, default = studyID', default = None) parser.add_argument('--output_subdir', help = 'name of the output sub directory, default = "kul_ct_recon"', default = 'kul_ct_recon') parser.add_argument('--ref_dcm_pat', help = 'file pattern for reference dicom files', default = '*') parser.add_argument('--dcm_tag_file', help = 'txt file with dcm tags to copy', default = 'ct_dcm_tags_to_copy.txt') parser.add_argument('--kul_var_name', help = 'name of recon variable in sav file', default = 'reconbone') parser.add_argument('--series_desc_prefix', help = 'prefix for dcm series description', default = '(UZL motion corrected)') args = parser.parse_args() #----------------------------------------------------------------------------------------- # read the reference dicom volume ref_dcm = pymf.DicomVolume(os.path.join(args.ref_dcm_dir, args.ref_dcm_pat)) ref_vol = ref_dcm.get_data() # restore the KUL recon from save file kul_recon = readsav(args.kul_sav_file)[args.kul_var_name] # due to the memory conventions we have to reverse the axis order kul_recon = np.swapaxes(kul_recon, 0, 2) # to get the KUL recon in LPS we have to reverse the last axix kul_recon = np.flip(kul_recon,2) # load the list of dicom tags to copy from the reference header from an input text file with open(args.dcm_tag_file,'r') as f: tags_to_copy = [x.strip() for x in f.read().splitlines()]
parser.add_argument('--output_fname', help='output file name', default='labelarray.nii') parser.add_argument('--dcm_pattern', help='dicom pattern for files in dicom directory', default='*') args = parser.parse_args() dcm_vol_dir = args.dcm_vol_dir rtstruct_file = args.rtstruct_file output_fname = args.output_fname dcm_pattern = args.dcm_pattern #------------------------------------------------------------------------------------- # read CT vol that was used to generate ROIs in rtstruct file ct_dcm = pymf.DicomVolume(os.path.join(dcm_vol_dir, dcm_pattern)) ct_vol = ct_dcm.get_data() aff = ct_dcm.affine shape = ct_vol.shape #------------------------------------------------------------------------------------- # read the rt struct data # read the ROI contours (in world coordinates) contour_data = pymf.read_rtstruct_contour_data(rtstruct_file) # convert contour data to index arrays (voxel space) roi_inds = pymf.convert_contour_data_to_roi_indices(contour_data, aff, shape) #---------------------------------------------------------------------------