import pylab as py pymirc_path = os.path.join('..', '..') if not pymirc_path in sys.path: sys.path.append(pymirc_path) import pymirc.viewer as pymv import pymirc.image_operations as pymi # seed random genrator for random deformation field np.random.seed(2) # setup demo volume n0 = 120 n1 = 110 n2 = 100 x0, x1, x2 = np.meshgrid(np.arange(n0), np.arange(n1), np.arange(n2)) vol = np.pad(0.5 * ((((-1)**(x0 // 6)) * ((-1)**(x1 // 6)) * ((-1)**(x2 // 6))) + 1), 5, mode='constant') # generate random deformation field d0, d1, d2 = pymi.random_deformation_field(vol.shape, shift=2.5) # apply warping warped_vol = pymi.backward_3d_warp(vol, d0, d1, d2) vi = pymv.ThreeAxisViewer([vol, warped_vol, np.sqrt(d0**2 + d1**2 + d2**2)])
pred_bows = [] for model_name in model_names: # load the model model = load_model(os.path.join('../../data/trained_models', model_name)) # make the prediction x = [np.expand_dims(np.expand_dims(pet,0),-1), np.expand_dims(np.expand_dims(mr,0),-1)] pred_bow = model.predict(x).squeeze() pred_bows.append(pred_bow) fig, ax = py.subplots(1,len(model_names), figsize = (len(model_names)*4,4), sharey = True) for i, pred_bow in enumerate(pred_bows): ax[i].plot(img[:,n//2,n//2], label = 'gt') ax[i].plot(pet[:,n//2,n//2], label = 'osem') ax[i].plot(pred_bow[:,n//2,n//2], label = 'pred') ax[i].set_title(model_names[i], fontsize = 'small') ax[i].grid(ls = ':') ax[0].legend() fig.tight_layout() fig.show() import pymirc.viewer as pv ims = {'vmin': 0, 'vmax':1.2} pv.ThreeAxisViewer([img,pet,mr,], imshow_kwargs = ims, ls = '', sl_x = 68, sl_y = 63, sl_z = 64) pv.ThreeAxisViewer(pred_bows, imshow_kwargs = ims, ls = '', sl_x = 68, sl_y = 63, sl_z = 64)
print('sum of abs. diff between ref. mask and read mask: ', np.abs(nii_mask - roi_vol).sum()) print('') #--------------------------------------------------------------------------- # print some ROI statistics print('ROI name.....:', [x['ROIName'] for x in contour_data]) print('ROI number...:', [x['ROINumber'] for x in contour_data]) print('ROI mean.....:', [ct_vol[x].mean() for x in roi_inds]) print('ROI max......:', [ct_vol[x].max() for x in roi_inds]) print('ROI min......:', [ct_vol[x].min() for x in roi_inds]) print('ROI # voxel..:', [len(ct_vol[x]) for x in roi_inds]) #--------------------------------------------------------------------------- # view the results imshow_kwargs = {'cmap': py.cm.Greys_r, 'vmin': -500, 'vmax': 500} oimshow_kwargs = { 'cmap': py.cm.nipy_spectral, 'alpha': 0.3, 'vmax': 1.2 * roi_vol.max() } vi = pymv.ThreeAxisViewer([ct_vol, ct_vol], ovols=[None, roi_vol], voxsize=ct_dcm.voxsize, imshow_kwargs=imshow_kwargs, oimshow_kwargs=oimshow_kwargs) print('\nPress "a" to hide/show overlay')
mfac = np.percentile(mr, 99.99) mlem /= pfac bow /= pfac mr /= mfac # predictions with original data p1 = model.predict([ np.expand_dims(np.expand_dims(mlem, 0), -1), np.expand_dims(np.expand_dims(mr, 0), -1) ]).squeeze() # prediction of 6mm res osem data mlem_5mm = gaussian_filter(mlem, np.sqrt(5**2 - 4.5**2) / (2.35 * training_voxsize)) mlem_6mm = gaussian_filter(mlem, np.sqrt(6**2 - 4.5**2) / (2.35 * training_voxsize)) p5 = model.predict([ np.expand_dims(np.expand_dims(mlem_5mm, 0), -1), np.expand_dims(np.expand_dims(mr, 0), -1) ]).squeeze() p6 = model.predict([ np.expand_dims(np.expand_dims(mlem_6mm, 0), -1), np.expand_dims(np.expand_dims(mr, 0), -1) ]).squeeze() imshow_kwargs = 4 * [{'vmin': 0, 'vmax': 1.2}] #pymv.ThreeAxisViewer([p1,p5,p6,bow], imshow_kwargs = imshow_kwargs) pymv.ThreeAxisViewer([mlem, mlem_5mm, mlem_6mm], imshow_kwargs=imshow_kwargs)
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} print('\nPress "a" to hide/show overlay') vi = pymv.ThreeAxisViewer([pet_vol_ct_grid,ct_vol,ct_vol], ovols = [None, None, pet_vol_ct_grid], voxsize = ct_dcm.voxsize, imshow_kwargs = imshow_kwargs, oimshow_kwargs = oimshow_kwargs)
# write the dicom volume if not os.path.exists(output_dcm_dir): write_3d_static_dicom(pred, output_dcm_dir, affine=o_aff, ReconstructionMethod='CNN MAP Bowsher', SeriesDescription=f'CNN MAP Bowsher {model_name}', **dcm_kwargs) else: warn('Output dicom directory already exists. Not ovewrting it') #------------------------------------------------------------------ # show the results import pymirc.viewer as pv pmax = np.percentile(pred, 99.9) mmax = np.percentile(mr_preproc, 99.9) ims = [{ 'vmin': 0, 'vmax': mmax, 'cmap': py.cm.Greys_r }, { 'vmin': 0, 'vmax': pmax }, { 'vmin': 0, 'vmax': pmax }] pv.ThreeAxisViewer([mr_preproc, pet_preproc, pred], imshow_kwargs=ims)
#---------------------------------------------------------------------------- # parse the command line parser = argparse.ArgumentParser() parser.add_argument('ct_file', help = 'CT nrrd file') parser.add_argument('seg_file', help = 'segmentation nrrd file') args = parser.parse_args() ct, ct_hdr = nrrd.read(args.ct_file) seg, seg_hdr = nrrd.read(args.seg_file) ct_aff = get_lps_affine_from_hdr(ct_hdr) seg_aff = get_lps_affine_from_hdr(seg_hdr) ct_voxsize = np.sqrt((ct_aff**2).sum(0))[:-1] # the segmentation array usually a crop of the original array seg_offset = np.round((seg_aff[:-1,-1] - ct_aff[:-1,-1]) / np.diag(ct_aff)[:3]).astype(int) seg2 = np.zeros(ct.shape, dtype = np.int8) seg2[seg_offset[0]:(seg_offset[0]+seg.shape[0]), seg_offset[1]:(seg_offset[1]+seg.shape[1]), seg_offset[2]:(seg_offset[2]+seg.shape[2])] = seg seg2_aff = ct_aff.copy() # reorient the images to standard LPS orientation ct, ct_aff = pi.reorient_image_and_affine(ct, ct_aff) seg2, seg2_aff = pi.reorient_image_and_affine(seg2, seg2_aff) imshow_kwargs = [{'cmap':py.cm.Greys_r, 'vmin':-300, 'vmax':300}, {}] pv.ThreeAxisViewer([ct,seg2], voxsize = ct_voxsize, imshow_kwargs = imshow_kwargs)
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)
pet_affine, mr_affine, training_voxsize, perc = 99.99, coreg = coreg_inputs, crop_mr = crop_mr) #------------------------------------------------------------------ # the actual CNN prediction x = [np.expand_dims(np.expand_dims(pet_preproc,0),-1), np.expand_dims(np.expand_dims(mr_preproc,0),-1)] pred = model.predict(x).squeeze() # undo the intensity normalization pet_preproc *= pet_max mr_preproc *= mr_max pred *= pet_max #------------------------------------------------------------------ # save the preprocessed input and output nib.save(nib.Nifti1Image(pet_preproc, o_aff), 'pet_preproc.nii') nib.save(nib.Nifti1Image(mr_preproc, o_aff), 'mr_preproc.nii') nib.save(nib.Nifti1Image(pred, o_aff), output_fname) # show the results import pymirc.viewer as pv pmax = np.percentile(pred,99.9) mmax = np.percentile(mr_preproc,99.9) ims = [{'vmin':0, 'vmax': mmax, 'cmap': py.cm.Greys_r}, {'vmin':0, 'vmax': pmax}, {'vmin':0, 'vmax': pmax}] pv.ThreeAxisViewer([np.flip(mr_preproc,(0,1)),np.flip(pet_preproc,(0,1)),np.flip(pred,(0,1))], imshow_kwargs = ims)
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) # show the aligned volume and the ROI volume (cropped versions) th = 0.3 * np.percentile(vol_aligned, 99.9) bbox = find_objects(vol_aligned > th)[0] vi = pv.ThreeAxisViewer([vol_aligned[bbox], vol_aligned[bbox]], [None, roi_vol[bbox]**0.1], voxsize=dcm.voxsize, ls='')
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()
ct_vol = ct_dcm.get_data() ct_vol[ct_vol < -1024] = -1024 # we artificially rotate and shift the CT for the regisration of the PET rp = np.array([10, -5, -20, 0.1, 0.2, -0.1]) R = pymi.kul_aff(rp, origin=np.array(ct_vol.shape) / 2) ct_vol_rot = pymi.aff_transform(ct_vol, R, ct_vol.shape, cval=ct_vol.min()) pet_coreg, coreg_aff, coreg_params = pymi.rigid_registration( pet_vol, ct_vol_rot, pet_dcm.affine, ct_dcm.affine) imshow_kwargs = [{ 'cmap': py.cm.Greys_r, 'vmin': -200, 'vmax': 200 }, { 'cmap': py.cm.Greys_r, 'vmin': -200, 'vmax': 200 }, { 'cmap': py.cm.Greys, 'vmin': 0, 'vmax': np.percentile(pet_coreg, 99.9) }] vi = pymv.ThreeAxisViewer([ct_vol_rot, ct_vol_rot, pet_coreg], ovols=[None, pet_coreg, None], imshow_kwargs=imshow_kwargs, voxsize=ct_dcm.voxsize, width=6)