예제 #1
0
def mmrchain(
    datain,  # all input data in a dictionary
    scanner_params,  # all scanner parameters in one dictionary 
    # containing constants, transaxial and axial
    # LUTs.
    outpath='',  # output path for results
    frames=['fluid', [0, 0]],  # definition of time frames.
    mu_h=[],  # hardware mu-map.
    mu_o=[],  # object mu-map.
    tAffine=[],  # affine transformations for the mu-map for
    # each time frame separately.
    itr=4,  # number of OSEM iterations
    fwhm=0.,  # Gaussian Smoothing FWHM
    recmod=-1,  # reconstruction mode: -1: undefined, chosen
    # automatically. 3: attenuation and scatter
    # correction, 1: attenuation correction
    # only, 0: no correction (randoms only).
    histo=[],  # input histogram (from list-mode data);
    # if not given, it will be performed.
    trim=False,
    trim_scale=2,
    trim_interp=1,  # interpolation for upsampling used in PVC
    trim_memlim=True,  # reduced use of memory for machines
    # with limited memory (slow though)
    pvcroi=[],  # ROI used for PVC.  If undefined no PVC
    # is performed.
    psfkernel=[],
    pvcitr=5,
    fcomment='',  # text comment used in the file name of
    # generated image files
    ret_sinos=False,  # return prompt, scatter and randoms
    # sinograms for each reconstruction
    store_img=True,
    store_img_intrmd=False,
    store_itr=[],  # store any reconstruction iteration in
    # the list.  ignored if the list is empty.
    del_img_intrmd=False):

    logging.debug('#decompose all the scanner parameters and constants')
    Cnt = scanner_params['Cnt']
    txLUT = scanner_params['txLUT']
    axLUT = scanner_params['axLUT']

    # -------------------------------------------------------------------------
    logging.debug('# FRAMES')
    logging.debug('# check for the provided dynamic frames')
    if isinstance(frames, list):
        # Can be given in three ways:
        # * a 1D list (duration of each frame is listed)
        # * a more concise 2D list--repetition and duration lists in
        #   each entry.  Must start with the 'def' entry.
        # * a 2D list with fluid timings: must start with the string
        #   'fluid' or 'timings.  a 2D list with consecutive lists
        #   describing start and end of the time frame, [t0, t1];
        #   The number of time frames for this option is unlimited,
        #   provided the t0 and t1 are within the acquisition times.

        if  isinstance(frames[0], basestring) and (frames[0]=='fluid' or frames[0]=='timings') \
            and all([isinstance(t,list) and len(t)==2 for t in frames[1:]]):
            logging.debug("# 2D starting with entry 'fluid' or 'timings'")
            t_frms = frames[1:]

        elif isinstance(frames[0], basestring) and frames[0]=='def' \
            and all([isinstance(t,list) and len(t)==2 for t in frames[1:]]):
            logging.debug("# if 2D definitions, starting with entry 'def':")
            # get total time and list of all time frames
            dfrms = dynamic_timings(frames)
            t_frms = dfrms['timings'][1:]

        elif all([isinstance(t, integers) for t in frames]):
            logging.debug('# if 1D:')
            # get total time and list of all time frames
            dfrms = dynamic_timings(frames)
            t_frms = dfrms['timings'][1:]

        else:
            print 'e> osemdyn: frames definitions are not given in the correct list format: 1D [15,15,30,30,...] or 2D list [[2,15], [2,30], ...]'
    else:
        print 'e> osemdyn: provided dynamic frames definitions are not in either Python list or nympy array.'
        raise TypeError('Wrong data type for dynamic frames')
    logging.debug('# number of dynamic time frames->' + str(len(t_frms)))
    nfrm = len(t_frms)
    # -------------------------------------------------------------------------

    # -------------------------------------------------------------------------
    logging.debug('# create folders for results')
    if outpath == '':
        petdir = os.path.join(datain['corepath'], 'reconstructed')
        fmudir = os.path.join(datain['corepath'], 'mumap-obj')
        pvcdir = os.path.join(datain['corepath'], 'PRCL')
    else:
        petdir = os.path.join(outpath, 'PET')
        fmudir = os.path.join(outpath, 'mumap-obj')
        pvcdir = os.path.join(outpath, 'PRCL')

    logging.debug(
        '# folder for co-registered mu-maps (for motion compensation)')
    fmureg = os.path.join(fmudir, 'registered')
    logging.debug('# folder for affine transformation MR/CT->PET')
    petaff = os.path.join(petdir, 'faffine')

    logging.debug(
        '# folder for reconstructed images (dynamic or static depending on number of frames).'
    )
    if nfrm > 1:
        petimg = os.path.join(petdir, 'multiple-frames')
        pvcdir = os.path.join(pvcdir, 'multiple-frames')
    elif nfrm == 1:
        petimg = os.path.join(petdir, 'single-frame')
        pvcdir = os.path.join(pvcdir, 'single-frame')
    else:
        print 'e> confused!'
        raise TypeError('Unrecognised time frames!')
    # create now the folder
    nimpa.create_dir(petimg)
    # create folder
    nimpa.create_dir(petdir)
    # -------------------------------------------------------------------------

    # -------------------------------------------------------------------------
    logging.debug('# MU-MAPS')
    logging.debug(
        '# get the mu-maps, if given;  otherwise will use blank mu-maps.')
    if tAffine:
        muod = obtain_image(mu_o,
                            imtype='object mu-map',
                            verbose=Cnt['VERBOSE'])
    else:
        muod = obtain_image(mu_o, Cnt=Cnt, imtype='object mu-map')

    logging.debug('# hardware mu-map')
    muhd = obtain_image(mu_h, Cnt, imtype='hardware mu-map')

    logging.debug(
        '# choose the mode of reconstruction based on the provided (or not) mu-maps'
    )
    if muod['exists'] and muhd['exists'] and recmod == -1:
        recmod = 3
    elif (muod['exists'] or muhd['exists']) and recmod == -1:
        recmod = 1
        print 'w> partial mu-map:  scatter correction is switched off.'
    else:
        if recmod == -1:
            recmod = 0
            print 'w> no mu-map provided: scatter and attenuation corrections are switched off.'
    # -------------------------------------------------------------------------

    #import pdb; pdb.set_trace()

    # output dictionary
    output = {}
    output['recmod'] = recmod
    output['frames'] = t_frms
    output['#frames'] = nfrm
    logging.debug('pipe.mmrchain.output->')
    logging.debug(output)

    logging.debug(
        '# if affine transformation is given the baseline mu-map in NIfTI file or dictionary has to be given'
    )
    if not tAffine:
        if Cnt['VERBOSE']:
            print 'i> using the provided mu-map the same way for all frames.'
    else:
        if len(tAffine) != nfrm:
            print 'e> the number of affine transformations in the list has to be the same as the number of dynamic frames!'
            raise IndexError('Inconsistent number of frames.')
        elif not isinstance(tAffine, list):
            print 'e> tAffine has to be a list of either 4x4 numpy arrays of affine transformations or a list of file path strings!'
            raise IndexError('Expecting a list.')
        elif not 'fim' in muod:
            print 'e> when tAffine is given, the object mu-map has to be provided either as a dictionary or NIfTI file!'
            raise NameError('No path to object mu-map.')

        logging.debug(
            '# check if all are file path strings to the existing files')
        if all([isinstance(t, basestring) for t in tAffine]):
            if all([os.path.isfile(t) for t in tAffine]):
                # the internal list of affine transformations
                faff_frms = tAffine
                if Cnt['VERBOSE']:
                    print 'i> using provided paths to affine transformations for each dynamic frame.'
            else:
                print 'e> not all provided paths are valid!'
                raise IOError('Wrong paths.')
        # check if all are numpy arrays
        elif all([isinstance(t, (np.ndarray, np.generic)) for t in tAffine]):
            # create the folder for dynamic affine transformations
            nimpa.create_dir(petaff)
            faff_frms = []
            for i in range(nfrm):
                fout = os.path.join(petaff, 'affine_frame(' + str(i) + ').txt')
                np.savetxt(fout, tAffine[i], fmt='%3.9f')
                faff_frms.append(fout)
            if Cnt['VERBOSE']:
                print 'i> using provided numpy arrays affine transformations for each dynamic frame.'
        else:
            raise StandardError(
                'Affine transformations for each dynamic frame could not be established.'
            )

        # -------------------------------------------------------------------------------------
        logging.debug('# get ref image for mu-map resampling')
        # -------------------------------------------------------------------------------------
        if 'fmuref' in muod:
            fmuref = muod['fmuref']
            if Cnt['VERBOSE']:
                print 'i> reusing the reference mu-map from the object mu-map dictionary.'
        else:
            # create folder if doesn't exists
            nimpa.create_dir(fmudir)
            # ref file name
            fmuref = os.path.join(fmudir, 'muref.nii.gz')
            # ref affine
            B = image_affine(datain, Cnt, gantry_offset=False)
            # ref image (blank)
            im = np.zeros((Cnt['SO_IMZ'], Cnt['SO_IMY'], Cnt['SO_IMX']),
                          dtype=np.float32)
            # store ref image
            nimpa.array2nii(im, B, fmuref)
            if Cnt['VERBOSE']:
                print 'i> generated a reference mu-map in', fmuref
        # -------------------------------------------------------------------------------------

        output['fmuref'] = fmuref
        output['faffine'] = faff_frms

    logging.debug(
        '# output list of intermidiate file names for mu-maps and PET images (useful for dynamic imaging)'
    )
    if tAffine: output['fmureg'] = []
    if store_img_intrmd: output['fpeti'] = []

    logging.debug('# dynamic images in one numpy array')
    dynim = np.zeros((nfrm, Cnt['SO_IMZ'], Cnt['SO_IMY'], Cnt['SO_IMY']),
                     dtype=np.float32)
    logging.debug(
        '#if asked, output only scatter+randoms sinogram for each frame')
    if ret_sinos and itr > 1 and recmod > 2:
        dynrsn = np.zeros((nfrm, Cnt['NSN11'], Cnt['NSANGLES'], Cnt['NSBINS']),
                          dtype=np.float32)
        dynssn = np.zeros((nfrm, Cnt['NSN11'], Cnt['NSANGLES'], Cnt['NSBINS']),
                          dtype=np.float32)
        dynpsn = np.zeros((nfrm, Cnt['NSN11'], Cnt['NSANGLES'], Cnt['NSBINS']),
                          dtype=np.float32)

    # import pdb; pdb.set_trace()

    logging.debug('# starting frame index with reasonable prompt data')
    ifrmP = 0
    logging.debug('# iterate over frame index')
    for ifrm in range(nfrm):
        # start time of a current (ifrm-th) dynamic frame
        t0 = int(t_frms[ifrm][0])
        # end time of a current (ifrm-th) dynamic frame
        t1 = int(t_frms[ifrm][1])
        # --------------
        logging.debug(
            '# check if there is enough prompt data to do a reconstruction')
        # --------------
        print 'i> dynamic frame times t0, t1:', t0, t1
        if not histo:
            hst = mmrhist(datain, scanner_params, t0=t0, t1=t1)
        else:
            hst = histo
            print ''
            print 'i> using provided histogram'
            print ''
        if np.sum(hst['dhc']) > 0.99 * np.sum(hst['phc']):
            print '==============================================================================================='
            print 'w> the amount of random events is the greatest part of prompt events => omitting reconstruction'
            print '==============================================================================================='
            ifrmP = ifrm + 1
            continue
        # --------------------
        logging.debug(
            '# transform the mu-map if given the affine transformation for each frame'
        )
        if tAffine:
            # create the folder for aligned (registered for motion compensation) mu-maps
            nimpa.create_dir(fmureg)
            # the converted nii image resample to the reference size
            fmu = os.path.join(
                fmureg, 'mumap_dyn_frm' + str(ifrm) + fcomment + '.nii.gz')
            logging.debug('# command for resampling')
            if os.path.isfile(Cnt['RESPATH']):
                cmd = [
                    Cnt['RESPATH'], '-ref', fmuref, '-flo', muod['fim'],
                    '-trans', faff_frms[ifrm], '-res', fmu, '-pad', '0'
                ]
                if not Cnt['VERBOSE']: cmd.append('-voff')
                call(cmd)
            else:
                print 'e> path to the executable for resampling is incorrect!'
                raise IOError('Incorrect NiftyReg (resampling) executable.')
            # get the new mu-map from the just resampled file
            muodct = nimpa.getnii(fmu, output='all')
            muo = muodct['im']
            A = muodct['affine']
            muo[muo < 0] = 0
            output['fmureg'].append(fmu)
        else:
            muo = muod['im']
        #---------------------

        logging.debug('# output image file name')
        if nfrm > 1:
            frmno = '_frm' + str(ifrm)
        else:
            frmno = ''

        logging.debug('# run OSEM reconstruction of a single time frame')
        recimg = mmrrec.osemone(datain, [muhd['im'], muo],
                                hst,
                                scanner_params,
                                recmod=recmod,
                                itr=itr,
                                fwhm=fwhm,
                                outpath=petimg,
                                frmno=frmno,
                                fcomment=fcomment + '_i',
                                store_img=store_img_intrmd,
                                store_itr=store_itr,
                                ret_sinos=ret_sinos)
        logging.debug('# form dynamic numpy array')
        dynim[ifrm, :, :, :] = recimg.im
        if ret_sinos and itr > 1 and recmod > 2:
            dynpsn[ifrm, :, :, :] = hst['psino']
            dynssn[ifrm, :, :, :] = recimg.ssn
            dynrsn[ifrm, :, :, :] = recimg.rsn

        if store_img_intrmd: output['fpeti'].append(recimg.fpet)
        if nfrm == 1: output['tuple'] = recimg

    output['im'] = np.squeeze(dynim)
    if ret_sinos and itr > 1 and recmod > 2:
        output['sinos'] = {'psino': dynpsn, 'ssino': dynssn, 'rsino': dynrsn}

    # ----------------------------------------------------------------------
    logging.debug('# trim the PET image')
    logging.debug('# images have to be stored for PVC')
    if pvcroi: store_img_intrmd = True
    if trim:
        logging.debug('# trim; create file name')
        if 'lm_dcm' in datain:
            fnm = os.path.basename(datain['lm_dcm'])[:20]
        elif 'lm_ima' in datain:
            fnm = os.path.basename(datain['lm_ima'])[:20]
        logging.debug('# trim PET and upsample')
        petu = nimpa.trimim(dynim,
                            affine=image_affine(datain, Cnt),
                            scale=trim_scale,
                            int_order=trim_interp,
                            outpath=petimg,
                            fname=fnm,
                            fcomment=fcomment,
                            store_img_intrmd=store_img_intrmd,
                            memlim=trim_memlim,
                            verbose=Cnt['VERBOSE'])

        output.update({
            'trimmed': {
                'im': petu['im'],
                'fpet': petu['fimi'],
                'affine': petu['affine']
            }
        })
    # ----------------------------------------------------------------------

    # ----------------------------------------------------------------------
    logging.debug('#run PVC if requested and required input given')
    if pvcroi:
        if not os.path.isfile(datain['T1lbl']):
            print 'e> no label image from T1 parcellations and/or ROI definitions!'
            raise StandardError('No ROIs')
        else:
            # get the PSF kernel for PVC
            if not psfkernel:
                psfkernel = nimpa.psf_measured(scanner='mmr', scale=trim_scale)
            else:
                if isinstance(
                        psfkernel,
                    (np.ndarray, np.generic)) and psfkernel.shape != (3, 17):
                    print 'e> the PSF kernel has to be an numpy array with the shape of (3, 17)!'
                    raise IndexError('PSF: wrong shape or not a matrix')
        logging.debug('# perform PVC for each time frame')
        froi2 = []
        fpvc = []
        dynpvc = np.zeros(petu['im'].shape, dtype=np.float32)
        for i in range(ifrmP, nfrm):
            # transform the parcelations (ROIs) if given the affine transformation for each frame
            if not tAffine:
                print 'w> affine transformation are not provided: will generate for the time frame.'
                faffpvc = ''
                #raise StandardError('No affine transformation')
            else:
                faffpvc = faff_frms[i]
            # chose file name of individual PVC images
            if nfrm > 1:
                fcomment_pvc = '_frm' + str(i) + fcomment
            else:
                fcomment_pvc = fcomment
            #============================
            logging.debug('# perform PVC')
            petpvc_dic = nimpa.pvc_iyang(petu['fimi'][i],
                                         datain,
                                         Cnt,
                                         pvcroi,
                                         psfkernel,
                                         itr=pvcitr,
                                         faff=faffpvc,
                                         fcomment=fcomment_pvc,
                                         outpath=pvcdir,
                                         store_img=store_img_intrmd)
            #============================
            if nfrm > 1:
                dynpvc[i, :, :, :] = petpvc_dic['im']
            else:
                dynpvc = petpvc_dic['im']

            froi2.append(petpvc_dic['froi'])
            fpvc.append(petpvc_dic['fpet'])
        logging.debug('# update output dictionary')
        output.update({'impvc': dynpvc, 'froi': froi2, 'fpvc': fpvc})
    # ----------------------------------------------------------------------

    if store_img:
        logging.debug('# description for saving NIFTI image')
        logging.debug(
            '# attenuation number: if only bed present then it is 0.5')
        attnum = (1 * muhd['exists'] + 1 * muod['exists']) / 2.
        descrip =    'alg=osem'                     \
                    +';att='+str(attnum*(recmod>0)) \
                    +';sct='+str(1*(recmod>1))      \
                    +';spn='+str(Cnt['SPN'])        \
                    +';sub=14'                      \
                    +';itr='+str(itr)               \
                    +';fwhm='+str(fwhm)             \
                    +';nfrm='+str(nfrm)

        # squeeze the not needed dimensions
        dynim = np.squeeze(dynim)

        logging.debug(
            '# NIfTI file name for the full PET image (single or multiple frame)'
        )

        logging.debug('# save the image to NIfTI file')
        if nfrm == 1:
            t0 = hst['t0']
            t1 = hst['t1']
            if t1 == t0:
                t0 = 0
                t1 = hst['dur']
            fpet = os.path.join(
                petimg,
                os.path.basename(recimg.fpet)[:8] + '_itr-' + str(itr) +
                '_t-' + str(t0) + '-' + str(t1) + 'sec')
            fpeto = fpet + fcomment + '.nii.gz'
            nimpa.prc.array2nii(dynim[::-1, ::-1, :],
                                recimg.affine,
                                fpeto,
                                descrip=descrip)
        else:
            fpet = os.path.join(
                petimg,
                os.path.basename(recimg.fpet)[:8] + '_itr-' + str(itr) +
                '_nfrm-' + str(nfrm))
            fpeto = fpet + fcomment + '.nii.gz'
            nimpa.prc.array2nii(dynim[:, ::-1, ::-1, :],
                                recimg.affine,
                                fpeto,
                                descrip=descrip)

        logging.debug('# get output file names for trimmed/PVC images')
        if trim:
            logging.debug('# trim; folder for trimmed and dynamic')
            pettrim = os.path.join(petimg, 'trimmed')
            # make folder
            nimpa.create_dir(pettrim)
            # trimming scale added to NIfTI descritoption
            descrip_trim = descrip + ';trim_scale=' + str(trim_scale)
            # file name for saving the trimmed image
            fpetu = os.path.join(
                pettrim,
                os.path.basename(fpet) + '_trimmed-upsampled-scale-' +
                str(trim_scale))
            # in case of PVC
            if pvcroi:
                logging.debug(
                    '# itertive Yang (iY) added to NIfTI descritoption')
                descrip_pvc = descrip_trim + ';pvc=iY'
                # file name for saving the PVC NIfTI image
                fpvc = fpetu + '_PVC' + fcomment + '.nii.gz'
                output['trimmed']['fpvc'] = fpvc

            # update the trimmed image file name
            fpetu += fcomment + '.nii.gz'
            logging.debug(
                '# trim; store the file name in the output dictionary')
            output['trimmed']['fpet'] = fpetu

        output['fpet'] = fpeto

        logging.debug('# save images')
        if nfrm == 1:
            if trim:
                nimpa.prc.array2nii(petu['im'][::-1, ::-1, :],
                                    petu['affine'],
                                    fpetu,
                                    descrip=descrip_trim)
            if pvcroi:
                nimpa.prc.array2nii(dynpvc[::-1, ::-1, :],
                                    petu['affine'],
                                    fpvc,
                                    descrip=descrip_pvc)
        elif nfrm > 1:
            if trim:
                nimpa.prc.array2nii(petu['im'][:, ::-1, ::-1, :],
                                    petu['affine'],
                                    fpetu,
                                    descrip=descrip_trim)
            if pvcroi:
                nimpa.prc.array2nii(dynpvc[:, ::-1, ::-1, :],
                                    petu['affine'],
                                    fpvc,
                                    descrip=descrip_pvc)

    if del_img_intrmd:
        if pvcroi:
            for fi in fpvc:
                os.remove(fi)
        if trim:
            for fi in petu['fimi']:
                os.remove(fi)

    return output
예제 #2
0
def vsm(
    datain,
    mumaps,
    em,
    hst,
    rsinos,
    scanner_params,
    prcnt_scl=0.1,
    emmsk=False,
    return_uninterp=False,
    return_ssrb=False,
    return_mask=False,
):
    '''
    Voxel-driven scatter modelling (VSM).
    Obtain a scatter sinogram using the mu-maps (hardware and object mu-maps)
    an estimate of emission image, the prompt measured sinogram, an
    estimate of the randoms sinogram and a normalisation sinogram.
    Input:
    - datain:       Contains the data used for scatter-specific detector
                    normalisation.  May also include the non-corrected
                    emission image used for masking, when requested.
    - mumaps:       A tuple of hardware and object mu-maps (in this order).
    - em:           An estimate of the emission image.
    - hst:          Dictionary containing the histogrammed measured data into
                    sinograms.
    - rsinos:       Randoms sinogram (3D).  Needed for proper scaling of
                    scatter to the prompt data.
    - scanner_params: Scanner specific parameters.
    - prcnt_scl:    Ratio of the maximum scatter intensities below which the
                    scatter is not used for fitting it to the tails of prompt
                    data.  Default is 10%.
    - emmsk:        When 'True' it will use uncorrected emission image for
                    masking the sources (voxels) of photons to be used in the
                    scatter modelling.
    '''
    log = logging.getLogger(__name__)

    muh, muo = mumaps

    #-constants, transaxial and axial LUTs are extracted
    Cnt = scanner_params['Cnt']
    txLUT = scanner_params['txLUT']
    axLUT = scanner_params['axLUT']

    if emmsk and not os.path.isfile(datain['em_nocrr']):
        log.info(
            'reconstruction of emission data without scatter and attenuation correction for mask generation'
        )
        recnac = mmrrec.osemone(datain,
                                mumaps,
                                hst,
                                scanner_params,
                                recmod=0,
                                itr=3,
                                fwhm=2.0,
                                store_img=True)
        datain['em_nocrr'] = recnac.fpet

    #-get the normalisation components
    nrmcmp, nhdr = mmrnorm.get_components(datain, Cnt)

    #-smooth for defining the sino scatter only regions
    mu_sctonly = ndi.filters.gaussian_filter(mmrimg.convert2dev(muo, Cnt),
                                             fwhm2sig(0.42, Cnt),
                                             mode='mirror')

    if Cnt['SPN'] == 1:
        snno = Cnt['NSN1']
        snno_ = Cnt['NSN64']
        ssrlut = axLUT['sn1_ssrb']
        saxnrm = nrmcmp['sax_f1']
    elif Cnt['SPN'] == 11:
        snno = Cnt['NSN11']
        snno_ = snno
        ssrlut = axLUT['sn11_ssrb']
        saxnrm = nrmcmp['sax_f11']

    #LUTs for scatter
    sctLUT = get_sctLUT(Cnt)

    #-smooth before down-sampling mu-map and emission image
    muim = ndi.filters.gaussian_filter(muo + muh,
                                       fwhm2sig(0.42, Cnt),
                                       mode='mirror')
    muim = ndi.interpolation.zoom(muim, Cnt['SCTSCLMU'],
                                  order=3)  #(0.499, 0.5, 0.5)

    emim = ndi.filters.gaussian_filter(em, fwhm2sig(0.42, Cnt), mode='mirror')
    emim = ndi.interpolation.zoom(emim, Cnt['SCTSCLEM'],
                                  order=3)  #(0.34, 0.33, 0.33)
    #emim = ndi.interpolation.zoom( emim, (0.499, 0.5, 0.5), order=3 )

    #-smooth the mu-map for mask creation.  the mask contains voxels for which attenuation ray LUT is found.
    smomu = ndi.filters.gaussian_filter(muim,
                                        fwhm2sig(0.84, Cnt),
                                        mode='mirror')
    mumsk = np.int8(smomu > 0.003)

    #CORE SCATTER ESTIMATION
    NSCRS, NSRNG = 64, 8
    sctout = {
        'xsxu':
        np.zeros((NSCRS, NSCRS / 2),
                 dtype=np.int8),  #one when xs>xu, otherwise zero
        'bin_indx':
        np.zeros((NSCRS, NSCRS / 2), dtype=np.int32),
        'sct_val':
        np.zeros((Cnt['TOFBINN'], NSRNG, NSCRS, NSRNG, NSCRS / 2),
                 dtype=np.float32),
        'sct_3d':
        np.zeros((Cnt['TOFBINN'], snno_, NSCRS, NSCRS / 2), dtype=np.float32)
    }

    #<<+>><<+>><<+>><<+>><<+>><<+>><<+>><<+>><<+>><<+>><<+>><<+>><<+>><<+>><<+>><<+>><<+>><<+>>
    petsct.scatter(sctout, muim, mumsk, emim, sctLUT, txLUT, axLUT, Cnt)
    #<<+>><<+>><<+>><<+>><<+>><<+>><<+>><<+>><<+>><<+>><<+>><<+>><<+>><<+>><<+>><<+>><<+>><<+>>
    sct3d = sctout['sct_3d']
    sctind = sctout['bin_indx']

    log.debug('total scatter sum:%r' % np.sum(sct3d))
    if np.sum(sct3d) < 1e-04:
        sss = np.zeros((snno, Cnt['NSANGLES'], Cnt['NSBINS']),
                       dtype=np.float32)
        amsksn = np.zeros((snno, Cnt['NSANGLES'], Cnt['NSBINS']),
                          dtype=np.float32)
        sssr = np.zeros((Cnt['NSEG0'], Cnt['NSANGLES'], Cnt['NSBINS']),
                        dtype=np.float32)
        return sss, sssr, amsksn

    #> get SSR for randoms from span-1 or span-11
    rssr = np.zeros((Cnt['NSEG0'], Cnt['NSANGLES'], Cnt['NSBINS']),
                    dtype=np.float32)
    for i in range(snno):
        rssr[ssrlut[i], :, :] += rsinos[i, :, :]

    #ATTENUATION FRACTIONS for scatter only regions, and NORMALISATION for all SCATTER
    #<<+>><<+>><<+>><<+>><<+>><<+>><<+>><<+>><<+>><<+>><<+>><<+>><<+>><<+>><<+>><<+>><<+>><<+>>
    currentspan = Cnt['SPN']
    Cnt['SPN'] = 1
    atto = np.zeros((txLUT['Naw'], Cnt['NSN1']), dtype=np.float32)
    petprj.fprj(atto, mu_sctonly, txLUT, axLUT, np.array([-1], dtype=np.int32),
                Cnt, 1)
    atto = mmraux.putgaps(atto, txLUT, Cnt)
    #--------------------------------------------------------------
    #get norm components setting the geometry and axial to ones as they are accounted for differently
    nrmcmp['geo'][:] = 1
    nrmcmp['axe1'][:] = 1
    #get sino with no gaps
    nrmg = np.zeros((txLUT['Naw'], Cnt['NSN1']), dtype=np.float32)
    mmr_auxe.norm(nrmg, nrmcmp, hst['buckets'], axLUT, txLUT['aw2ali'], Cnt)
    nrm = mmraux.putgaps(nrmg, txLUT, Cnt)
    #--------------------------------------------------------------

    #get attenuation + norm in (span-11) and SSR
    attossr = np.zeros((Cnt['NSEG0'], Cnt['NSANGLES'], Cnt['NSBINS']),
                       dtype=np.float32)
    nrmsssr = np.zeros((Cnt['NSEG0'], Cnt['NSANGLES'], Cnt['NSBINS']),
                       dtype=np.float32)
    for i in range(Cnt['NSN1']):
        si = axLUT['sn1_ssrb'][i]
        attossr[si, :, :] += atto[i, :, :] / float(axLUT['sn1_ssrno'][si])
        nrmsssr[si, :, :] += nrm[i, :, :] / float(axLUT['sn1_ssrno'][si])
    if currentspan == 11:
        Cnt['SPN'] = 11
        nrmg = np.zeros((txLUT['Naw'], snno), dtype=np.float32)
        mmr_auxe.norm(nrmg, nrmcmp, hst['buckets'], axLUT, txLUT['aw2ali'],
                      Cnt)
        nrm = mmraux.putgaps(nrmg, txLUT, Cnt)
    #--------------------------------------------------------------

    #get the mask for the object from uncorrected emission image
    if emmsk and os.path.isfile(datain['em_nocrr']):
        nim = nib.load(datain['em_nocrr'])
        A = nim.get_sform()
        eim = np.float32(nim.get_data())
        eim = eim[:, ::-1, ::-1]
        eim = np.transpose(eim, (2, 1, 0))

        em_sctonly = ndi.filters.gaussian_filter(eim,
                                                 fwhm2sig(.6, Cnt),
                                                 mode='mirror')
        msk = np.float32(em_sctonly > 0.07 * np.max(em_sctonly))
        msk = ndi.filters.gaussian_filter(msk,
                                          fwhm2sig(.6, Cnt),
                                          mode='mirror')
        msk = np.float32(msk > 0.01)
        msksn = mmrprj.frwd_prj(msk, txLUT, axLUT, Cnt)

        mssr = mmraux.sino2ssr(msksn, axLUT, Cnt)
        mssr = mssr > 0
    else:
        mssr = np.zeros((Cnt['NSEG0'], Cnt['NSANGLES'], Cnt['NSBINS']),
                        dtype=np.bool)

    #<<+>><<+>><<+>><<+>><<+>><<+>><<+>><<+>><<+>><<+>><<+>><<+>><<+>><<+>><<+>><<+>><<+>><<+>>

    #--------------------------------------------------------------------------------------------
    # get scatter sinos for TOF or non-TOF
    if Cnt['TOFBINN'] > 1:
        ssn = np.zeros((Cnt['TOFBINN'], snno, Cnt['NSANGLES'], Cnt['NSBINS']),
                       dtype=np.float64)
        sssr = np.zeros(
            (Cnt['TOFBINN'], Cnt['NSEG0'], Cnt['NSANGLES'], Cnt['NSBINS']),
            dtype=np.float32)
        tmp2d = np.zeros((Cnt['NSANGLES'] * Cnt['NSBINS']), dtype=np.float64)
        log.info('interpolate each scatter sino...')
        for k in range(Cnt['TOFBINN']):
            log.info('doing TOF bin k = %d' % k)
            for i in range(snno):
                tmp2d[:] = 0
                for ti in range(len(sctind)):
                    tmp2d[sctind[ti]] += sct3d[k, i, ti]
                #interpolate estimated scatter
                ssn[k, i, :, :] = get_sctinterp(
                    np.reshape(tmp2d, (Cnt['NSANGLES'], Cnt['NSBINS'])),
                    sctind, Cnt)
                sssr[k, ssrlut[i], :, :] += ssn[k, i, :, :]
            log.info('TOF bin #%d' % k)
    elif Cnt['TOFBINN'] == 1:
        ssn = np.zeros((snno, Cnt['NSANGLES'], Cnt['NSBINS']),
                       dtype=np.float32)
        sssr = np.zeros((Cnt['NSEG0'], Cnt['NSANGLES'], Cnt['NSBINS']),
                        dtype=np.float32)
        tmp2d = np.zeros((Cnt['NSANGLES'] * Cnt['NSBINS']), dtype=np.float32)
        log.info('scatter sinogram interpolation...')
        for i in trange(snno,
                        desc="interpolating",
                        unit="sinogram",
                        leave=log.getEffectiveLevel() < logging.INFO):
            tmp2d[:] = 0
            for ti in range(len(sctind)):
                tmp2d[sctind[ti]] += sct3d[0, i, ti]
            #interpolate estimated scatter
            ssn[i, :, :] = get_sctinterp(
                np.reshape(tmp2d, (Cnt['NSANGLES'], Cnt['NSBINS'])), sctind,
                Cnt)
            sssr[ssrlut[i], :, :] += ssn[i, :, :]
    #--------------------------------------------------------------------------------------------

    #=== scale scatter for ssr and non-TOF===
    #mask
    rmsk = (txLUT['msino'] > 0).T
    rmsk.shape = (1, Cnt['NSANGLES'], Cnt['NSBINS'])
    rmsk = np.repeat(rmsk, Cnt['NSEG0'], axis=0)
    amsksn = np.logical_and(attossr >= 0.999, rmsk) * ~mssr
    #scaling factors for ssr
    scl_ssr = np.zeros((Cnt['NSEG0']), dtype=np.float32)
    for sni in range(Cnt['NSEG0']):
        # region of choice for scaling
        thrshld = prcnt_scl * np.max(sssr[sni, :, :])
        amsksn[sni, :, :] *= (sssr[sni, :, :] > thrshld)
        amsk = amsksn[sni, :, :]
        #normalised estimated scatter
        mssn = sssr[sni, :, :] * nrmsssr[sni, :, :]
        mssn[np.invert(amsk)] = 0
        #vectorised masked sino
        vssn = mssn[amsk]
        vpsn = hst['pssr'][sni, amsk] - rssr[sni, amsk]
        scl_ssr[sni] = np.sum(vpsn) / np.sum(mssn)
        #ssr output
        sssr[sni, :, :] *= nrmsssr[sni, :, :] * scl_ssr[sni]

    #=== scale scatter for the proper sino ===
    sss = np.zeros((snno, Cnt['NSANGLES'], Cnt['NSBINS']), dtype=np.float32)
    for i in range(snno):
        sss[i, :, :] = ssn[i, :, :] * scl_ssr[ssrlut[i]] * saxnrm[i] * nrm[
            i, :, :]

    out = {}

    if return_uninterp:
        out['uninterp'] = sct3d
        out['indexes'] = sctind

    if return_ssrb:
        out['ssrb'] = sssr

    if return_mask:
        out['mask'] = amsksn

    if not out:
        return sss
    else:
        out['sino'] = sss
        return out
예제 #3
0
def align_mumap(
    datain,
    scanner_params=None,
    outpath='',
    reg_tool='niftyreg',
    use_stored=False,
    hst=None,
    t0=0,
    t1=0,
    itr=2,
    faff='',
    fpet='',
    fcomment='',
    store=False,
    store_npy=False,
    petopt='ac',
    musrc='ute',         # another option is pct for mu-map source
    ute_name='UTE2',
    del_auxilary=True,
    verbose=False,
):
    '''
    Align the a pCT or MR-derived mu-map to a PET image reconstructed to chosen
    specifications (e.g., with/without attenuation and scatter corrections)

    use_sotred only works if hst or t0/t1 given but not when faff.
    '''
    if scanner_params is None:
        scanner_params = {}

    # > output folder
    if outpath == '':
        opth = os.path.join(datain['corepath'], 'mumap-obj')
    else:
        opth = os.path.join(outpath, 'mumap-obj')

    # > create the folder, if not existent
    nimpa.create_dir(opth)

    # > get the timing of PET if affine not given
    if faff == '' and hst is not None and isinstance(hst, dict) and 't0' in hst:
        t0 = hst['t0']
        t1 = hst['t1']

    # > file name for the output mu-map
    fnm = 'mumap-' + musrc.upper()

    # > output dictionary
    mu_dct = {}

    # ---------------------------------------------------------------------------
    # > used stored if requested
    if use_stored:
        fmu_stored = fnm + '-aligned-to_t'\
                     + str(t0)+'-'+str(t1)+'_'+petopt.upper()\
                     + fcomment
        fmupath = os.path.join(opth, fmu_stored + '.nii.gz')

        if os.path.isfile(fmupath):
            mudct_stored = nimpa.getnii(fmupath, output='all')
            # > create output dictionary
            mu_dct['im'] = mudct_stored['im']
            mu_dct['affine'] = mudct_stored['affine']
            # pu_dct['faff'] = faff
            return mu_dct
    # ---------------------------------------------------------------------------

    # > tmp folder for not aligned mu-maps
    tmpdir = os.path.join(opth, 'tmp')
    nimpa.create_dir(tmpdir)

    # > three ways of passing scanner constants <Cnt> are here decoded
    if 'Cnt' in scanner_params:
        Cnt = scanner_params['Cnt']
    elif 'SO_IMZ' in scanner_params:
        Cnt = scanner_params
    else:
        Cnt = rs.get_mmr_constants()

    # > if affine not provided histogram the LM data for recon and registration
    if not os.path.isfile(faff):
        from niftypet.nipet.prj import mmrrec

        # -histogram the list data if needed
        if hst is None:
            from niftypet.nipet import mmrhist
            if 'txLUT' in scanner_params:
                hst = mmrhist(datain, scanner_params, t0=t0, t1=t1)
            else:
                raise ValueError('Full scanner are parameters not provided\
                     but are required for histogramming.')

    # ========================================================
    # -get hardware mu-map
    if 'hmumap' in datain and os.path.isfile(datain['hmumap']):
        muh = np.load(datain['hmumap'], allow_pickle=True)["hmu"]
        (log.info if verbose else log.debug)('loaded hardware mu-map from file:\n{}'.format(
            datain['hmumap']))
    elif outpath != '':
        hmupath = os.path.join(outpath, "mumap-hdw", "hmumap.npz")
        if os.path.isfile(hmupath):
            muh = np.load(hmupath, allow_pickle=True)["hmu"]
            datain["hmumap"] = hmupath
        else:
            raise IOError('Invalid path to the hardware mu-map')
    else:
        log.error('the hardware mu-map is required first.')
        raise IOError('Could not find the hardware mu-map!')
    # ========================================================
    # -check if T1w image is available
    if not {'MRT1W#', 'T1nii', 'T1bc', 'T1N4'}.intersection(datain):
        log.error('no MR T1w images required for co-registration!')
        raise IOError('T1w image could not be obtained!')
    # ========================================================

    # -if the affine is not given,
    # -it will be generated by reconstructing PET image, with some or no corrections
    if not os.path.isfile(faff):
        # first recon pet to get the T1 aligned to it
        if petopt == 'qnt':
            # ---------------------------------------------
            # OPTION 1 (quantitative recon with all corrections using MR-based mu-map)
            # get UTE object mu-map (may not be in register with the PET data)
            mudic = obj_mumap(datain, Cnt, outpath=tmpdir, del_auxilary=del_auxilary)
            muo = mudic['im']
            # reconstruct PET image with UTE mu-map to which co-register T1w
            recout = mmrrec.osemone(datain, [muh, muo], hst, scanner_params, recmod=3, itr=itr,
                                    fwhm=0., fcomment=fcomment + '_QNT-UTE',
                                    outpath=os.path.join(outpath, 'PET',
                                                         'positioning'), store_img=True)
        elif petopt == 'nac':
            # ---------------------------------------------
            # OPTION 2 (recon without any corrections for scatter and attenuation)
            # reconstruct PET image with UTE mu-map to which co-register T1w
            muo = np.zeros(muh.shape, dtype=muh.dtype)
            recout = mmrrec.osemone(datain, [muh, muo], hst, scanner_params, recmod=1, itr=itr,
                                    fwhm=0., fcomment=fcomment + '_NAC',
                                    outpath=os.path.join(outpath, 'PET',
                                                         'positioning'), store_img=True)
        elif petopt == 'ac':
            # ---------------------------------------------
            # OPTION 3 (recon with attenuation correction only but no scatter)
            # reconstruct PET image with UTE mu-map to which co-register T1w
            mudic = obj_mumap(datain, Cnt, outpath=tmpdir, del_auxilary=del_auxilary)
            muo = mudic['im']

            recout = mmrrec.osemone(datain, [muh, muo], hst, scanner_params, recmod=1, itr=itr,
                                    fwhm=0., fcomment=fcomment + '_AC-UTE',
                                    outpath=os.path.join(outpath, 'PET',
                                                         'positioning'), store_img=True)

        fpet = recout.fpet
        mu_dct['fpet'] = fpet

        # ------------------------------
        if musrc == 'ute' and ute_name in datain and os.path.exists(datain[ute_name]):
            # change to NIfTI if the UTE sequence is in DICOM files (folder)
            if os.path.isdir(datain[ute_name]):
                fnew = os.path.basename(datain[ute_name])
                run([Cnt['DCM2NIIX'], '-f', fnew, datain[ute_name]])
                fute = glob.glob(os.path.join(datain[ute_name], fnew + '*nii*'))[0]
            elif os.path.isfile(datain[ute_name]):
                fute = datain[ute_name]

            # get the affine transformation
            if reg_tool == 'spm':
                regdct = nimpa.coreg_spm(fpet, fute,
                                         outpath=os.path.join(outpath, 'PET', 'positioning'))
            elif reg_tool == 'niftyreg':
                regdct = nimpa.affine_niftyreg(
                    fpet,
                    fute,
                    outpath=os.path.join(outpath, 'PET', 'positioning'),
                    executable=Cnt['REGPATH'],
                    omp=multiprocessing.cpu_count() / 2,                 # pcomment=fcomment,
                    rigOnly=True,
                    affDirect=False,
                    maxit=5,
                    speed=True,
                    pi=50,
                    pv=50,
                    smof=0,
                    smor=0,
                    rmsk=True,
                    fmsk=True,
                    rfwhm=15.,                                           # pillilitres
                    rthrsh=0.05,
                    ffwhm=15.,                                           # pillilitres
                    fthrsh=0.05,
                    verbose=verbose)
            else:
                raise ValueError('unknown registration tool requested')

            faff_mrpet = regdct['faff']

        elif musrc == 'pct':

            ft1w = nimpa.pick_t1w(datain)

            if reg_tool == 'spm':
                regdct = nimpa.coreg_spm(fpet, ft1w,
                                         outpath=os.path.join(outpath, 'PET', 'positioning'))
            elif reg_tool == 'niftyreg':
                regdct = nimpa.affine_niftyreg(
                    fpet,
                    ft1w,
                    outpath=os.path.join(outpath, 'PET', 'positioning'),
                    executable=Cnt['REGPATH'],
                    omp=multiprocessing.cpu_count() / 2,
                    rigOnly=True,
                    affDirect=False,
                    maxit=5,
                    speed=True,
                    pi=50,
                    pv=50,
                    smof=0,
                    smor=0,
                    rmsk=True,
                    fmsk=True,
                    rfwhm=15.,                                           # pillilitres
                    rthrsh=0.05,
                    ffwhm=15.,                                           # pillilitres
                    fthrsh=0.05,
                    verbose=verbose)
            else:
                raise ValueError('unknown registration tool requested')

            faff_mrpet = regdct['faff']

        else:
            raise IOError('Floating MR image not provided or is invalid.')

    else:
        faff_mrpet = faff
        regdct = {}
        if not os.path.isfile(fpet):
            raise IOError('e> the reference PET should be supplied with the affine.')

    # > output file name for the aligned mu-maps
    if musrc == 'pct':

        # > convert to mu-values before resampling to avoid artefacts with negative values
        nii = nib.load(datain['pCT'])
        img = nii.get_fdata(dtype=np.float32)
        img_mu = hu2mu(img)
        nii_mu = nib.Nifti1Image(img_mu, nii.affine)
        fflo = os.path.join(tmpdir, 'pct2mu-not-aligned.nii.gz')
        nib.save(nii_mu, fflo)

        freg = os.path.join(opth, 'pct2mu-aligned-' + fcomment + '.nii.gz')

    elif musrc == 'ute':
        freg = os.path.join(opth, 'UTE-res-tmp' + fcomment + '.nii.gz')
        if 'UTE' not in datain:
            fnii = 'converted-from-DICOM_'
            tstmp = nimpa.time_stamp(simple_ascii=True)
            # convert the DICOM mu-map images to nii
            if 'mumapDCM' not in datain:
                raise IOError('DICOM with the UTE mu-map are not given.')
            run([Cnt['DCM2NIIX'], '-f', fnii + tstmp, '-o', opth, datain['mumapDCM']])
            # piles for the T1w, pick one:
            fflo = glob.glob(os.path.join(opth, '*' + fnii + tstmp + '*.nii*'))[0]
        else:
            if os.path.isfile(datain['UTE']):
                fflo = datain['UTE']
            else:
                raise IOError('The provided NIfTI UTE path is not valid.')

    # > call the resampling routine to get the pCT/UTE in place
    if reg_tool == "spm":
        nimpa.resample_spm(fpet, fflo, faff_mrpet, fimout=freg, del_ref_uncmpr=True,
                           del_flo_uncmpr=True, del_out_uncmpr=True)
    else:
        nimpa.resample_niftyreg(fpet, fflo, faff_mrpet, fimout=freg, executable=Cnt['RESPATH'],
                                verbose=verbose)

    # -get the NIfTI of registered image
    nim = nib.load(freg)
    A = nim.affine
    imreg = nim.get_fdata(dtype=np.float32)
    imreg = imreg[:, ::-1, ::-1]
    imreg = np.transpose(imreg, (2, 1, 0))

    # -convert to mu-values; sort out the file name too.
    if musrc == 'pct':
        mu = imreg
    elif musrc == 'ute':
        mu = np.float32(imreg) / 1e4
        # -remove the converted file from DICOMs
        os.remove(fflo)
    else:
        raise NameError('Confused o_O')

    # > get rid of negatives and nans
    mu[mu < 0] = 0
    mu[np.isnan(mu)] = 0

    # > return image dictionary with the image itself and other parameters
    mu_dct['im'] = mu
    mu_dct['affine'] = A
    mu_dct['faff'] = faff_mrpet

    if store or store_npy:
        nimpa.create_dir(opth)
        if faff == '':
            fname = fnm + '-aligned-to_t'\
                    + str(t0)+'-'+str(t1)+'_'+petopt.upper()\
                    + fcomment
        else:
            fname = fnm + '-aligned-to-given-affine' + fcomment
    if store_npy:
        fnp = os.path.join(opth, fname + ".npz")
        np.savez(fnp, mu=mu, A=A)
    if store:
        # > NIfTI
        fmu = os.path.join(opth, fname + '.nii.gz')
        nimpa.array2nii(mu[::-1, ::-1, :], A, fmu)
        mu_dct['fim'] = fmu

    if del_auxilary:
        os.remove(freg)

        if musrc == 'ute' and not os.path.isfile(faff):
            os.remove(fute)
        shutil.rmtree(tmpdir)

    return mu_dct
예제 #4
0
def pct_mumap(datain, scanner_params, hst=None, t0=0, t1=0, itr=2, petopt='ac', faff='', fpet='',
              fcomment='', outpath='', store_npy=False, store=False, verbose=False):
    '''
    GET THE MU-MAP from pCT IMAGE (which is in T1w space)
    * the mu-map will be registered to PET which will be reconstructed for time frame t0-t1
    * it f0 and t1 are not given the whole LM dataset will be reconstructed
    * the reconstructed PET can be attenuation and scatter corrected or NOT using petopt
    '''
    if hst is None:
        hst = []

    # constants, transaxial and axial LUTs are extracted
    Cnt = scanner_params['Cnt']

    if not os.path.isfile(faff):
        from niftypet.nipet.prj import mmrrec

        # histogram the list data if needed
        if not hst:
            from niftypet.nipet.lm import mmrhist
            hst = mmrhist(datain, scanner_params, t0=t0, t1=t1)

    # get hardware mu-map
    if datain.get("hmumap", "").endswith(".npz") and os.path.isfile(datain["hmumap"]):
        muh = np.load(datain["hmumap"], allow_pickle=True)["hmu"]
        (log.info if verbose else log.debug)('loaded hardware mu-map from file:\n{}'.format(
            datain['hmumap']))
    elif outpath:
        hmupath = os.path.join(outpath, "mumap-hdw", "hmumap.npz")
        if os.path.isfile(hmupath):
            muh = np.load(hmupath, allow_pickle=True)["hmu"]
            datain['hmumap'] = hmupath
        else:
            raise IOError('Invalid path to the hardware mu-map')
    else:
        log.error('The hardware mu-map is required first.')
        raise IOError('Could not find the hardware mu-map!')

    if not {'MRT1W#', 'T1nii', 'T1bc'}.intersection(datain):
        log.error('no MR T1w images required for co-registration!')
        raise IOError('Missing MR data')
    # ----------------------------------

    # output dictionary
    mu_dct = {}
    if not os.path.isfile(faff):
        # first recon pet to get the T1 aligned to it
        if petopt == 'qnt':
            # ---------------------------------------------
            # OPTION 1 (quantitative recon with all corrections using MR-based mu-map)
            # get UTE object mu-map (may not be in register with the PET data)
            mudic = obj_mumap(datain, Cnt)
            muo = mudic['im']
            # reconstruct PET image with UTE mu-map to which co-register T1w
            recout = mmrrec.osemone(datain, [muh, muo], hst, scanner_params, recmod=3, itr=itr,
                                    fwhm=0., fcomment=fcomment + '_qntUTE',
                                    outpath=os.path.join(outpath, 'PET',
                                                         'positioning'), store_img=True)
        elif petopt == 'nac':
            # ---------------------------------------------
            # OPTION 2 (recon without any corrections for scatter and attenuation)
            # reconstruct PET image with UTE mu-map to which co-register T1w
            muo = np.zeros(muh.shape, dtype=muh.dtype)
            recout = mmrrec.osemone(datain, [muh, muo], hst, scanner_params, recmod=1, itr=itr,
                                    fwhm=0., fcomment=fcomment + '_NAC',
                                    outpath=os.path.join(outpath, 'PET',
                                                         'positioning'), store_img=True)
        elif petopt == 'ac':
            # ---------------------------------------------
            # OPTION 3 (recon with attenuation correction only but no scatter)
            # reconstruct PET image with UTE mu-map to which co-register T1w
            mudic = obj_mumap(datain, Cnt, outpath=outpath)
            muo = mudic['im']
            recout = mmrrec.osemone(datain, [muh, muo], hst, scanner_params, recmod=1, itr=itr,
                                    fwhm=0., fcomment=fcomment + '_AC',
                                    outpath=os.path.join(outpath, 'PET',
                                                         'positioning'), store_img=True)

        fpet = recout.fpet
        mu_dct['fpet'] = fpet

        # ------------------------------
        # get the affine transformation
        ft1w = nimpa.pick_t1w(datain)
        try:
            regdct = nimpa.coreg_spm(fpet, ft1w,
                                     outpath=os.path.join(outpath, 'PET', 'positioning'))
        except Exception:
            regdct = nimpa.affine_niftyreg(
                fpet,
                ft1w,
                outpath=os.path.join(outpath, 'PET', 'positioning'), # pcomment=fcomment,
                executable=Cnt['REGPATH'],
                omp=multiprocessing.cpu_count() / 2,
                rigOnly=True,
                affDirect=False,
                maxit=5,
                speed=True,
                pi=50,
                pv=50,
                smof=0,
                smor=0,
                rmsk=True,
                fmsk=True,
                rfwhm=15.,                                           # pillilitres
                rthrsh=0.05,
                ffwhm=15.,                                           # pillilitres
                fthrsh=0.05,
                verbose=verbose)

        faff = regdct['faff']
        # ------------------------------

    # pCT file name
    if outpath == '':
        pctdir = os.path.dirname(datain['pCT'])
    else:
        pctdir = os.path.join(outpath, 'mumap-obj')
    mmraux.create_dir(pctdir)
    fpct = os.path.join(pctdir, 'pCT_r_tmp' + fcomment + '.nii.gz')

    # > call the resampling routine to get the pCT in place
    if os.path.isfile(Cnt['RESPATH']):
        cmd = [
            Cnt['RESPATH'], '-ref', fpet, '-flo', datain['pCT'], '-trans', faff, '-res', fpct,
            '-pad', '0']
        if log.getEffectiveLevel() > logging.INFO:
            cmd.append('-voff')
        run(cmd)
    else:
        log.error('path to resampling executable is incorrect!')
        raise IOError('Incorrect path to executable!')

    # get the NIfTI of the pCT
    nim = nib.load(fpct)
    A = nim.get_sform()
    pct = nim.get_fdata(dtype=np.float32)
    pct = pct[:, ::-1, ::-1]
    pct = np.transpose(pct, (2, 1, 0))
    # convert the HU units to mu-values
    mu = hu2mu(pct)
    # get rid of negatives
    mu[mu < 0] = 0

    # return image dictionary with the image itself and other parameters
    mu_dct['im'] = mu
    mu_dct['affine'] = A
    mu_dct['faff'] = faff

    if store:
        # now save to numpy array and NIfTI in this folder
        if outpath == '':
            pctumapdir = os.path.join(datain['corepath'], 'mumap-obj')
        else:
            pctumapdir = os.path.join(outpath, 'mumap-obj')
        mmraux.create_dir(pctumapdir)
        # > Numpy
        if store_npy:
            fnp = os.path.join(pctumapdir, "mumap-pCT.npz")
            np.savez(fnp, mu=mu, A=A)

        # > NIfTI
        fmu = os.path.join(pctumapdir, 'mumap-pCT' + fcomment + '.nii.gz')
        nimpa.array2nii(mu[::-1, ::-1, :], A, fmu)
        mu_dct['fim'] = fmu
        datain['mumapCT'] = fmu

    return mu_dct
예제 #5
0
def rmumaps(datain, Cnt, t0=0, t1=0, use_stored=False):
    '''
    get the mu-maps for hardware and object and trim it axially for reduced rings case
    '''
    from niftypet.nipet.lm import mmrhist
    from niftypet.nipet.prj import mmrrec
    fcomment = '(R)'

    # get hardware mu-map
    if os.path.isfile(datain['hmumap']) and use_stored:
        muh = np.load(datain["hmumap"], allow_pickle=True)["hmu"]
        log.info('loaded hardware mu-map from file:\n{}'.format(datain['hmumap']))
    else:
        hmudic = hdw_mumap(datain, [1, 2, 4], Cnt)
        muh = hmudic['im']

    # get pCT mu-map if stored in numpy file and then exit, otherwise do all the processing
    if os.path.isfile(datain['mumapCT']) and use_stored:
        mup = np.load(datain["mumapCT"], allow_pickle=True)["mu"]
        muh = muh[2 * Cnt['RNG_STRT']:2 * Cnt['RNG_END'], :, :]
        mup = mup[2 * Cnt['RNG_STRT']:2 * Cnt['RNG_END'], :, :]
        return [muh, mup]

    # get UTE object mu-map (may be not in register with the PET data)
    if os.path.isfile(datain['mumapUTE']) and use_stored:
        muo, _ = np.load(datain['mumapUTE'], allow_pickle=True)
    else:
        mudic = obj_mumap(datain, Cnt, store=True)
        muo = mudic['im']

    if os.path.isfile(datain['pCT']):
        # reconstruct PET image with default settings to be used to alight pCT mu-map
        params = mmraux.get_mmrparams()
        Cnt_ = params['Cnt']
        txLUT_ = params['txLUT']
        axLUT_ = params['axLUT']

        # histogram for reconstruction with UTE mu-map
        hst = mmrhist.hist(datain, txLUT_, axLUT_, Cnt_, t0=t0, t1=t1)
        # reconstruct PET image with UTE mu-map to which co-register T1w
        recute = mmrrec.osemone(datain, [muh, muo], hst, params, recmod=3, itr=4, fwhm=0.,
                                store_img=True, fcomment=fcomment + '_QNT-UTE')
        # --- MR T1w
        if os.path.isfile(datain['T1nii']):
            ft1w = datain['T1nii']
        elif os.path.isfile(datain['T1bc']):
            ft1w = datain['T1bc']
        elif os.path.isdir(datain['MRT1W']):
            # create file name for the converted NIfTI image
            fnii = 'converted'
            run([Cnt['DCM2NIIX'], '-f', fnii, datain['T1nii']])
            ft1nii = glob.glob(os.path.join(datain['T1nii'], '*converted*.nii*'))
            ft1w = ft1nii[0]
        else:
            raise IOError('Disaster: no T1w image!')

        # putput for the T1w in register with PET
        ft1out = os.path.join(os.path.dirname(ft1w), 'T1w_r' + '.nii.gz')
        # pext file fo rthe affine transform T1w->PET
        faff = os.path.join(os.path.dirname(ft1w), fcomment + 'mr2pet_affine' + '.txt')
        # time.strftime('%d%b%y_%H.%M',time.gmtime())
        # > call the registration routine
        if os.path.isfile(Cnt['REGPATH']):
            cmd = [
                Cnt['REGPATH'], '-ref', recute.fpet, '-flo', ft1w, '-rigOnly', '-speeeeed', '-aff',
                faff, '-res', ft1out]
            if log.getEffectiveLevel() > logging.INFO:
                cmd.append('-voff')
            run(cmd)
        else:
            raise IOError('Path to registration executable is incorrect!')

        # pet the pCT mu-map with the above faff
        pmudic = pct_mumap(datain, txLUT_, axLUT_, Cnt, faff=faff, fpet=recute.fpet,
                           fcomment=fcomment)
        mup = pmudic['im']

        muh = muh[2 * Cnt['RNG_STRT']:2 * Cnt['RNG_END'], :, :]
        mup = mup[2 * Cnt['RNG_STRT']:2 * Cnt['RNG_END'], :, :]
        return [muh, mup]
    else:
        muh = muh[2 * Cnt['RNG_STRT']:2 * Cnt['RNG_END'], :, :]
        muo = muo[2 * Cnt['RNG_STRT']:2 * Cnt['RNG_END'], :, :]
        return [muh, muo]