Ejemplo n.º 1
0
def align_nema_2008_small_animal_iq_phantom(vol, voxsize, ftol = 1e-2, xtol = 1e-2, maxiter = 10, maxfev = 500, version = 'standard'):
  """ align a reconstruction of the NEMA small animal PET IQ phantom to its digital version

  Parameters
  ----------
  vol : 3D numpy float array
    containing the image

  voxsize : 3 element 1D numpy array
    containing the voxel size

  ftol, xtol, maxiter, maxfev : float / int
    parameter for the optimizer used to minimze the cost function

  version : string
    phantom version ('standard' or 'mini')

  Returns
  -------
    a 3D numpy array

  Note
  ----
  This routine can be useful to make sure that the rods in the NEMA scan are
  parallel to the axial direction.
  """
  phantom  = nema_2008_small_animal_iq_phantom(voxsize, vol.shape, version = version)
  phantom *= vol[vol>0.5*vol.max()].mean()

  reg_params = np.zeros(6)

  # registration of down sampled volumes
  dsf    = 3
  ds_aff = np.diag([dsf,dsf,dsf,1.])
  
  phantom_ds = aff_transform(phantom, ds_aff, np.ceil(np.array(phantom.shape)/dsf).astype(int))

  res = minimize(pymr.regis_cost_func, reg_params, 
                 args = (phantom_ds, vol, True, True, lambda x,y: ((x-y)**2).mean(), ds_aff), 
                 method = 'Powell', 
                 options = {'ftol':ftol, 'xtol':xtol, 'disp':True, 'maxiter':maxiter, 'maxfev':maxfev})

  reg_params = res.x.copy()
  # we have to scale the translations by the down sample factor since they are in voxels
  reg_params[:3] *= dsf

  res = minimize(pymr.regis_cost_func, reg_params, 
                 args = (phantom, vol, True, True, lambda x,y: ((x-y)**2).mean()), 
                 method = 'Powell', 
                 options = {'ftol':ftol, 'xtol':xtol, 'disp':True, 'maxiter':maxiter, 'maxfev':maxfev})
  
  regis_aff   = kul_aff(res.x, origin = np.array(vol.shape)/2)
  vol_aligned = aff_transform(vol, regis_aff, vol.shape) 
  
  return vol_aligned
Ejemplo n.º 2
0
def affine_deformation(I,
                       affine,
                       output_shape=None,
                       order=1,
                       cval=0,
                       oversampling_factors=(1, 1, 1)):
    return aff_transform(I,
                         np.round(affine, 5),
                         output_shape=output_shape or I.shape,
                         trilin=order,
                         cval=cval,
                         os0=oversampling_factors[0],
                         os1=oversampling_factors[1],
                         os2=oversampling_factors[2])
  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)
Ejemplo n.º 4
0
def preprocess_volumes(pet_vol,
                       mr_vol,
                       pet_affine,
                       mr_affine,
                       training_voxsize,
                       perc=99.99,
                       coreg=True,
                       crop_mr=True,
                       mr_ps_fwhm_mm=None,
                       verbose=False):

    # get voxel sizes from affine matrices
    pet_voxsize = np.sqrt((pet_affine**2).sum(axis=0))[:-1]
    mr_voxsize = np.sqrt((mr_affine**2).sum(axis=0))[:-1]

    # crop the MR if needed
    if crop_mr:
        bbox = find_objects(mr_vol > 0.1 * mr_vol.max(), max_label=1)[0]
        mr_vol = mr_vol[bbox]
        crop_origin = np.array([x.start for x in bbox] + [1])
        mr_affine[:-1, -1] = (mr_affine @ crop_origin)[:-1]

    # post-smooth MR if needed
    if mr_ps_fwhm_mm is not None:
        print(f'post-smoothing MR with {mr_ps_fwhm_mm} mm')
        mr_vol = gaussian_filter(mr_vol, mr_ps_fwhm_mm / (2.35 * mr_voxsize))

    # regis_aff is the affine transformation that maps from the PET to the MR grid
    # if coreg is False, it is simply deduced from the affine transformation
    # otherwise, rigid registration with mutual information is used
    if coreg:
        _, regis_aff, _ = rigid_registration(pet_vol, mr_vol, pet_affine,
                                             mr_affine)
    else:
        regis_aff = np.linalg.inv(pet_affine) @ mr_affine

    # interpolate both volumes to the voxel size used during training
    zoomfacs = mr_voxsize / training_voxsize
    if not np.all(np.isclose(zoomfacs, np.ones(3))):
        if verbose:
            print('interpolationg input volumes to training voxel size')
        mr_vol_interpolated = zoom3d(mr_vol, zoomfacs)
        mr_affine = mr_affine @ np.diag(np.concatenate((1. / zoomfacs, [1])))
    else:
        mr_vol_interpolated = mr_vol.copy()

    # this is the final affine that maps from the PET grid to interpolated MR grid
    # using the small voxels used during training
    pet_mr_interp_aff = regis_aff @ np.diag(
        np.concatenate((1. / zoomfacs, [1])))

    if not np.all(np.isclose(pet_mr_interp_aff, np.eye(4))):
        pet_vol_mr_grid_interpolated = aff_transform(pet_vol,
                                                     pet_mr_interp_aff,
                                                     mr_vol_interpolated.shape,
                                                     cval=pet_vol.min())
    else:
        pet_vol_mr_grid_interpolated = pet_vol.copy()

    # convert the input volumes to float32
    if not mr_vol_interpolated.dtype == np.float32:
        mr_vol_interpolated = mr_vol_interpolated.astype(np.float32)
    if not pet_vol_mr_grid_interpolated.dtype == np.float32:
        pet_vol_mr_grid_interpolated = pet_vol_mr_grid_interpolated.astype(
            np.float32)

    # normalize the data: we divide the images by the specified percentile (more stable than the max)
    if verbose: print('\nnormalizing the input images')
    mr_max = np.percentile(mr_vol_interpolated, perc)
    mr_vol_interpolated /= mr_max

    pet_max = np.percentile(pet_vol_mr_grid_interpolated, perc)
    pet_vol_mr_grid_interpolated /= pet_max

    return pet_vol_mr_grid_interpolated, mr_vol_interpolated, mr_affine, pet_max, mr_max
Ejemplo n.º 5
0
def rigid_registration(vol_float,
                       vol_fixed,
                       aff_float,
                       aff_fixed,
                       downsample_facs=[4],
                       metric=neg_mutual_information,
                       opts={
                           'ftol': 1e-2,
                           'xtol': 1e-2,
                           'disp': True,
                           'maxiter': 20,
                           'maxfev': 5000
                       },
                       method='Powell',
                       metric_kwargs={}):
    """ rigidly coregister a 3D floating volume to a fixed (reference) volume

  Parameters
  ----------
  vol_float : 3D numpy array
    the floating volume

  vol_fixed : 3D numpy array
    the fixed (reference) volume

  aff_float : 2D 4x4 numpy array
    affine transformation matrix that maps from pixel to world coordinates for floating volume

  aff_fixed : 2D 4x4 numpy array
    affine transformation matrix that maps from pixel to world coordinates for fixed volume

  downsample_facs : None of array_like
    perform registrations on downsampled grids before registering original volumes
    (multi-resolution approach)

  metric : function(x,y, **kwargs)
    metric that compares transformed floating and fixed volume
 
  metric_kwargs : dict
    keyword arguments passed to the metric function

  opts : dictionary
    passed to scipy.optimize.minimize as options

  method : string
    passed to scipy.optimize.minimize as method (which optimizer to use)

  Returns
  -------
  tuple of 3
  - the transformed (coregistered) floating volume
  - the registration affine transformation matrix 
  - the 6 registration parameters (3 translations, 3 rotations) from which the
    affine matrix was derived

  Note
  ----

  To apply the registration affine transformation use:
  new_vol = aff_transform(vol, reg_aff, vol_fixed.shape, cval = vol.min())
  
  """
    # define the affine transformation that maps the floating to the fixed voxel grid
    # we need this to avoid the additional interpolation in case the voxel sizes are not the same
    pre_affine = np.linalg.inv(aff_float) @ aff_fixed

    reg_params = np.zeros(6)

    # (1) initial registration with downsampled arrays
    if downsample_facs is not None:
        for dsf in downsample_facs:
            ds_aff = np.diag([dsf, dsf, dsf, 1.])

            # down sample fixed volume
            vol_fixed_ds = aff_transform(
                vol_fixed, ds_aff,
                np.ceil(np.array(vol_fixed.shape) / dsf).astype(int))

            res = minimize(regis_cost_func,
                           reg_params,
                           method=method,
                           options=opts,
                           args=(vol_fixed_ds, vol_float, True, True, metric,
                                 pre_affine @ ds_aff, metric_kwargs))

            reg_params = res.x.copy()
            # we have to scale the translations by the down sample factor since they are in voxels
            reg_params[:3] *= dsf

    # (2) registration with full arrays
    res = minimize(regis_cost_func,
                   reg_params,
                   method=method,
                   options=opts,
                   args=(vol_fixed, vol_float, True, True, metric, pre_affine,
                         metric_kwargs))
    reg_params = res.x.copy()

    # define the final affine transformation that maps from the PET grid to the rotated CT grid
    reg_aff = pre_affine @ kul_aff(reg_params,
                                   origin=np.array(vol_fixed.shape) / 2)

    # transform the floating volume
    vol_float_coreg = aff_transform(vol_float,
                                    reg_aff,
                                    vol_fixed.shape,
                                    cval=vol_float.min())

    return vol_float_coreg, reg_aff, reg_params
Ejemplo n.º 6
0
def regis_cost_func(params,
                    img_fix,
                    img_float,
                    verbose=False,
                    rotate=True,
                    metric=neg_mutual_information,
                    pre_affine=None,
                    metric_kwargs={}):
    """Generic cost function for rigid registration

  Parameters
  ----------

  params : 3 or 6 element numpy array
    If rotate is False it contains the 3 translations
    If rotate is True  it contains the 3 translations and the 3 rotation angles

  img_fix : 3D numpy array
     containg the fixed (reference) volume

  img_float : 3D numpy array
     containg the floating (moving) volume

  verbose : bool, optional
    print verbose output (default False)

  rotate : bool, optional
    rotate volume as well on top of translations (6 degrees of freedom)

  metric : function(img_fix, aff_transform(img_float, ...)) -> R, optional
    metric used to compare fixed and floating volume (default neg_mutual_information)

  pre_affine : 2D 4x4 numpy array
    affine transformation applied before doing the rotation and shifting

  metric_kwargs : dictionary
    key word arguments passed to the metric function

  Returns
  -------
  float
    the metric between the fixed and floating volume

  See Also
  --------
  kul_aff()
  """
    if rotate:
        p = params.copy()
    else:
        p = np.concatenate((params, np.zeros(3)))

    if pre_affine is not None:
        af = pre_affine @ kul_aff(p, origin=np.array(img_fix.shape) / 2)
    else:
        af = kul_aff(params, origin=np.array(img_fix.shape) / 2)

    m = metric(
        img_fix,
        aff_transform(img_float, af, img_fix.shape, cval=img_float.min()),
        **metric_kwargs)

    if verbose:
        print(params)
        print(m)
        print('')

    return m
Ejemplo n.º 7
0
def predict(pet_input,
            mr_input,
            model_name,
            input_format='dicom',
            odir=None,
            model_dir=os.path.join('..', 'trained_models'),
            perc=99.99,
            verbose=False,
            clip_neg=True,
            ReconstructionMethod='CNN Bowsher',
            coreg=True,
            seriesdesc=None,
            affine=None,
            crop_mr=False,
            patchsize=(128, 128, 128),
            overlap=8,
            output_on_pet_grid=False,
            mr_ps_fwhm_mm=None,
            model_custom_objects=None,
            debug_mode=False):

    if seriesdesc is None:
        SeriesDescription = 'CNN Bowsher beta = 10 ' + model_name.replace(
            '.h5', '')
    else:
        SeriesDescription = seriesdesc

    if affine is None:
        if input_format == 'dicom':
            if isinstance(pet_input, list):
                regis_affine_file = os.path.dirname(
                    pet_input[0]) + '_coreg_affine.txt'
            else:
                regis_affine_file = os.path.dirname(
                    pet_input) + '_coreg_affine.txt'
        else:
            regis_affine_file = pet_input + '_coreg_affine.txt'

    else:
        regis_affine_file = affine

    # generate the output directory
    if odir is None:
        if input_format == 'dicom':
            if isinstance(pet_input, list):
                odir = os.path.join(
                    os.path.dirname(os.path.dirname(pet_input[0])),
                    'cnn_bow_' + model_name.replace('.h5', ''))
            else:
                odir = os.path.join(
                    os.path.dirname(os.path.dirname(pet_input)),
                    'cnn_bow_' + model_name.replace('.h5', ''))
        else:
            odir = os.path.join(os.path.dirname(pet_input),
                                'cnn_bow_' + model_name.replace('.h5', ''))

    # check if output directory already exists, if so add a counter to prevent overwriting
    o_suf = 1
    if os.path.isdir(odir):
        while os.path.isdir(odir + '_' + str(o_suf)):
            o_suf += 1
        odir = odir + '_' + str(o_suf)

    # load the model
    model = load_model(os.path.join(model_dir, model_name),
                       custom_objects=model_custom_objects)

    # read the input data
    if input_format == 'dicom':
        # read the MR dicoms
        if verbose: print('\nreading MR dicoms')
        if isinstance(mr_input, list):
            mr_files = deepcopy(mr_input)
        else:
            mr_files = glob(mr_input)
        mr_dcm = DicomVolume(mr_files)
        mr_vol = mr_dcm.get_data()
        mr_affine = mr_dcm.affine

        # read the PET dicoms
        if verbose: print('\nreading PET dicoms')
        if isinstance(pet_input, list):
            pet_files = deepcopy(pet_input)
        else:
            pet_files = glob(pet_input)
        pet_dcm = DicomVolume(pet_files)
        pet_vol = pet_dcm.get_data()
        pet_affine = pet_dcm.affine

    elif input_format == 'nifti':
        if verbose: print('\nreading MR nifti')
        mr_nii = nib.load(mr_input)
        mr_nii = nib.as_closest_canonical(mr_nii)
        mr_vol_ras = mr_nii.get_data()
        mr_affine_ras = mr_nii.affine
        # the closest canonical orientation of nifti is RAS
        # we have to convert that into LPS (dicom standard)
        mr_vol = np.flip(np.flip(mr_vol_ras, 0), 1)
        mr_affine = mr_affine_ras.copy()
        mr_affine[0, -1] = (
            -1 * mr_nii.affine @ np.array([mr_vol.shape[0] - 1, 0, 0, 1]))[0]
        mr_affine[1, -1] = (
            -1 * mr_nii.affine @ np.array([0, mr_vol.shape[1] - 1, 0, 1]))[1]

        if verbose: print('\nreading PET nifti')
        pet_nii = nib.load(pet_input)
        pet_nii = nib.as_closest_canonical(pet_nii)
        pet_vol_ras = pet_nii.get_data()

        pet_affine_ras = pet_nii.affine
        # the closest canonical orientation of nifti is RAS
        # we have to convert that into LPS (dicom standard)
        pet_vol = np.flip(np.flip(pet_vol_ras, 0), 1)
        pet_affine = pet_affine_ras.copy()
        pet_affine[0, -1] = (
            -1 * pet_nii.affine @ np.array([pet_vol.shape[0] - 1, 0, 0, 1]))[0]
        pet_affine[1, -1] = (
            -1 * pet_nii.affine @ np.array([0, pet_vol.shape[1] - 1, 0, 1]))[1]
    else:
        raise TypeError('Unsupported input data format')

    # read the internal voxel size that was used during training from the model header
    if os.path.isdir(os.path.join(model_dir, model_name)):
        with open(os.path.join(model_dir, model_name, 'config.json')) as f:
            cfg = json.load(f)
            training_voxsize = cfg['internal_voxsize'] * np.ones(3)
    else:
        model_data = h5py.File(os.path.join(model_dir, model_name))

        if 'header/internal_voxsize' in model_data:
            training_voxsize = model_data['header/internal_voxsize'][:]
        else:
            # in the old models the training (internal) voxel size is not in the header
            # but it was always 1x1x1 mm^3
            training_voxsize = np.ones(3)

    ################################################################
    ############ data preprocessing ################################
    ################################################################

    pet_vol_mr_grid_interpolated, mr_vol_interpolated, mr_affine, pet_max, mr_max = \
      preprocess_volumes(pet_vol, mr_vol, pet_affine, mr_affine, training_voxsize,
                         perc = perc, coreg = coreg, crop_mr = crop_mr,
                         mr_ps_fwhm_mm = mr_ps_fwhm_mm, verbose = verbose)

    ################################################################
    ############# make the actual prediction #######################
    ################################################################

    if verbose: print('\npredicting the bowsher')

    if patchsize is None:
        # case of predicting the whole volume in one big chunk
        # bring the input volumes in the correct shape for the model
        x = [
            np.expand_dims(np.expand_dims(pet_vol_mr_grid_interpolated, 0),
                           -1),
            np.expand_dims(np.expand_dims(mr_vol_interpolated, 0), -1)
        ]
        predicted_bow = model.predict(x).squeeze()
    else:
        # case of doing the prediction in multiple smaller 3D chunks (patches)
        predicted_bow = np.zeros(pet_vol_mr_grid_interpolated.shape,
                                 dtype=np.float32)

        for i in range(pet_vol_mr_grid_interpolated.shape[0] // patchsize[0] +
                       1):
            for j in range(pet_vol_mr_grid_interpolated.shape[1] //
                           patchsize[1] + 1):
                for k in range(pet_vol_mr_grid_interpolated.shape[2] //
                               patchsize[2] + 1):
                    istart = max(i * patchsize[0] - overlap, 0)
                    jstart = max(j * patchsize[1] - overlap, 0)
                    kstart = max(k * patchsize[2] - overlap, 0)

                    ioffset = i * patchsize[0] - istart
                    joffset = j * patchsize[1] - jstart
                    koffset = k * patchsize[2] - kstart

                    iend = min(((i + 1) * patchsize[0] + overlap),
                               pet_vol_mr_grid_interpolated.shape[0])
                    jend = min(((j + 1) * patchsize[1] + overlap),
                               pet_vol_mr_grid_interpolated.shape[1])
                    kend = min(((k + 1) * patchsize[2] + overlap),
                               pet_vol_mr_grid_interpolated.shape[2])

                    pet_patch = pet_vol_mr_grid_interpolated[istart:iend,
                                                             jstart:jend,
                                                             kstart:kend]
                    mr_patch = mr_vol_interpolated[istart:iend, jstart:jend,
                                                   kstart:kend]

                    # make the prediction
                    x = [
                        np.expand_dims(np.expand_dims(pet_patch, 0), -1),
                        np.expand_dims(np.expand_dims(mr_patch, 0), -1)
                    ]
                    tmp = model.predict(x).squeeze()

                    ntmp0 = min(
                        (i + 1) * patchsize[0], pet_vol_mr_grid_interpolated.
                        shape[0]) - i * patchsize[0]
                    ntmp1 = min(
                        (j + 1) * patchsize[1], pet_vol_mr_grid_interpolated.
                        shape[1]) - j * patchsize[1]
                    ntmp2 = min(
                        (k + 1) * patchsize[2], pet_vol_mr_grid_interpolated.
                        shape[2]) - k * patchsize[2]

                    predicted_bow[i * patchsize[0]:(i * patchsize[0] + ntmp0),
                                  j * patchsize[1]:(j * patchsize[1] + ntmp1),
                                  k * patchsize[2]:(
                                      k * patchsize[2] +
                                      ntmp2)] = tmp[ioffset:(ioffset + ntmp0),
                                                    joffset:(joffset + ntmp1),
                                                    koffset:(koffset + ntmp2)]

    if clip_neg: np.clip(predicted_bow, 0, None, predicted_bow)

    # unnormalize the data
    if verbose: print('\nunnormalizing the images')
    mr_vol_interpolated *= mr_max
    pet_vol_mr_grid_interpolated *= pet_max
    predicted_bow *= pet_max

    print('\n\n------------------------------------------')
    print('------------------------------------------')
    print('\nCNN prediction finished')

    ##############################################################
    ########## write the output as nifti, png, dcm ###############
    ##############################################################

    # write output pngs
    pmax = np.percentile(pet_vol_mr_grid_interpolated, 99.99)
    mmax = np.percentile(mr_vol_interpolated, 99.99)
    imshow_kwargs = [{
        'cmap': py.cm.Greys_r,
        'vmin': 0,
        'vmax': mmax
    }, {
        'cmap': py.cm.Greys,
        'vmin': 0,
        'vmax': pmax
    }, {
        'cmap': py.cm.Greys,
        'vmin': 0,
        'vmax': pmax
    }]

    vi = ThreeAxisViewer(
        [mr_vol_interpolated, pet_vol_mr_grid_interpolated, predicted_bow],
        imshow_kwargs=imshow_kwargs,
        ls='')
    vi.fig.savefig(odir + '.png')
    py.close(vi.fig)
    py.close(vi.fig_cb)
    py.close(vi.fig_sl)

    #---------------------------------------------------------------
    # generate the output affines
    if output_on_pet_grid:
        output_affine = pet_affine.copy()
        predicted_bow = aff_transform(predicted_bow,
                                      np.linalg.inv(pet_mr_interp_aff),
                                      pet_vol.shape,
                                      cval=pet_vol.min())
    else:
        output_affine = mr_affine.copy()
        for i in range(3):
            output_affine[i, :-1] *= training_voxsize[i] / np.sqrt(
                (output_affine[i, :-1]**2).sum())

    # create the output affine in RAS orientation to save niftis
    output_affine_ras = output_affine.copy()
    output_affine_ras[0, -1] = (
        -1 *
        output_affine @ np.array([predicted_bow.shape[0] - 1, 0, 0, 1]))[0]
    output_affine_ras[1, -1] = (
        -1 *
        output_affine @ np.array([0, predicted_bow.shape[1] - 1, 0, 1]))[1]

    # safe the input volumes in case of debug mode
    if debug_mode:
        nib.save(
            nib.Nifti1Image(np.flip(np.flip(mr_vol_interpolated, 0), 1),
                            output_affine_ras), odir + '_debug_mr.nii')
        nib.save(
            nib.Nifti1Image(
                np.flip(np.flip(pet_vol_mr_grid_interpolated, 0), 1),
                output_affine_ras), odir + '_debug_pet.nii')

    #------------------------------------------------------------
    # write a simple nifti as fall back in case the dicoms are not working
    # keep in mind that nifti used RAS internally
    nib.save(
        nib.Nifti1Image(np.flip(np.flip(predicted_bow, 0), 1),
                        output_affine_ras), odir + '.nii')
    print('\nWrote nifti:')
    print(odir + '.nii\n')

    #------------------------------------------------------------
    # write the dicoms
    if input_format == 'dicom':
        # read the reference PET dicom file to copy some header tags
        refdcm = pydicom.read_file(pet_files[0])
        dcm_kwargs = {}
        # copy the following tags if present in the reference dicom
        pet_keys_to_copy = [
            'AcquisitionDate', 'AcquisitionTime', 'ActualFrameDuration',
            'AccessionNumber', 'DecayCorrection', 'DecayCorrectionDateTime',
            'DecayFactor', 'DoseCalibrationFactor', 'FrameOfReferenceUID',
            'FrameReferenceTime', 'InstitutionName', 'ManufacturerModelName',
            'OtherPatientIDs', 'PatientAge', 'PatientBirthDate', 'PatientID',
            'PatientName', 'PatientPosition', 'PatientSex', 'PatientWeight',
            'ProtocolName', 'RadiopharmaceuticalInformationSequence',
            'RescaleType', 'SeriesDate', 'SeriesTime', 'StudyDate',
            'StudyDescription', 'StudyID', 'StudyInstanceUID', 'StudyTime',
            'Units'
        ]

        for key in pet_keys_to_copy:
            try:
                dcm_kwargs[key] = getattr(refdcm, key)
            except AttributeError:
                warn('Cannot copy tag ' + key)

        # write the dicom volume
        write_3d_static_dicom(predicted_bow,
                              odir,
                              affine=output_affine,
                              ReconstructionMethod=ReconstructionMethod,
                              SeriesDescription=SeriesDescription,
                              **dcm_kwargs)
        print('\nWrote dicom folder:')
        print(odir, '\n')

    print('------------------------------------------')
    print('------------------------------------------')
Ejemplo n.º 8
0
data_dir = os.path.join('..', '..', 'data', 'nema_petct')

# read PET/CT nema phantom dicom data sets
# the image data in those two data sets are aligned but
# on different grids
pet_dcm = pymf.DicomVolume(os.path.join(data_dir, 'PT', '*.dcm'))
ct_dcm = pymf.DicomVolume(os.path.join(data_dir, 'CT', '*.dcm'))

pet_vol = pet_dcm.get_data()
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,
Ejemplo n.º 9
0
def petmr_brain_data_augmentation(orig_vols, 
                                  rand_contrast_ch = 1, 
                                  ps_ch            = 0, 
                                  ps_fwhms         = [0,3.,4.],
                                  rand_misalign_ch = None, 
                                  shift_amp        = 2, 
                                  rot_amp          = 5):
  """
  function for data augmentation of input volumes


  Inputs
  ------

  orig_vols ... list of input volumes to be augmented / changed

  Keyword arguments
  -----------------

  rand_contrast_ch ... (int or None) channel where contrast is randomly flipped / quadratically changed
                       default: 1

  rand_ps_ch       ... (int or None) channel which is randomly post smooted 
                       default: 0

  ps_fwhms         ... (float) list of post smoothing fwhms (voxel units)
                       
  rand_misalign_ch ... (int or None) channel which is randomly mislaligned
                       default: None

  shift_amp        ... (float) maximal shift for misalignment in pixels - default: 2

  rot_amp          ... (float) maximal rotation angle for misalignment in degrees - default: 5


  Returns
  -------
 
  a list of augmented input volumes
  """

  n_ch  = len(orig_vols)

  augmented_vols = []

  for ps_fwhm in ps_fwhms:
    vols  = deepcopy(orig_vols)

    if ps_fwhm > 0:
      vols[ps_ch] = gaussian_filter(vols[ps_ch], ps_fwhm/2.35)

    # randomly misalign one of the input channels
    if rand_misalign_ch is not None:
      costheta = 2*np.random.rand(2) - 1
      sintheta = np.sqrt(1 - costheta**2)
      phi      = 2*np.pi*np.random.rand(2) 
      rshift   = shift_amp*np.random.rand()

      # random translation
      offset = np.array([rshift*np.cos(phi[0])*sintheta[0],
                         rshift*np.sin(phi[0])*sintheta[0],rshift*costheta[0]])

      # random rotation axis
      uv        = np.array([np.cos(phi[1])*sintheta[1],np.sin(phi[1])*sintheta[1],costheta[1]])
      rot_angle = rot_amp*np.pi*np.random.rand()/180.

      bp_center = np.array(vols[rand_misalign_ch].shape[:-1])/2 - 0.5
      aff = affine_center_rotation(uv, rot_angle, uv_origin = bp_center, offset = offset)
     
      # transform the image
      vols[rand_misalign_ch][...,0] = aff_transform(vols[rand_misalign_ch][...,0], aff, 
                                                             cval = vols[rand_misalign_ch].min())

    # randomize the contrast of the second input channel
    if rand_contrast_ch is not None:
      r1 = 0.4*np.random.random() + 0.8
      vols[rand_contrast_ch] = vols[rand_contrast_ch]**r1

      # randomly invert contrast
      if np.random.random() >= 0.5:
        vols[rand_contrast_ch] = vols[rand_contrast_ch].max() - vols[rand_contrast_ch]

    augmented_vols.append(vols)

  return augmented_vols