Exemple #1
0
def frwd_prj(im,
             scanner_params,
             isub=np.array([-1], dtype=np.int32),
             dev_out=False,
             attenuation=False):
    ''' Calculate forward projection (a set of sinograms) for the provided input image.
        Arguments:
        im -- input image (can be emission or mu-map image).
        scanner_params -- dictionary of all scanner parameters, containing scanner constants,
            transaxial and axial look up tables (LUT).
        isub -- array of transaxial indices of all sinograms (angles x bins) used for subsets.
            when the first element is negative, all transaxial bins are used (as in pure EM-ML).
        dev_out -- if True, output sinogram is in the device form, i.e., with two dimensions
            (# bins/angles, # sinograms) instead of default three (# sinograms, # bins, # angles).
        attenuation -- controls whether emission or LOR attenuation probability sinogram
            is calculated; the default is False, meaning emission sinogram; for attenuation
            calculations (attenuation=True), the exponential of the negative of the integrated
            mu-values along LOR path is taken at the end.
    '''
    log = logging.getLogger(__name__)

    # Get particular scanner parameters: Constants, transaxial and axial LUTs
    Cnt = scanner_params['Cnt']
    txLUT = scanner_params['txLUT']
    axLUT = scanner_params['axLUT']

    #>choose between attenuation forward projection (mu-map is the input)
    #>or the default for emission image forward projection
    if attenuation:
        att = 1
    else:
        att = 0

    if Cnt['SPN'] == 1:
        # number of rings calculated for the given ring range (optionally we can use only part of the axial FOV)
        NRNG_c = Cnt['RNG_END'] - Cnt['RNG_STRT']
        # number of sinos in span-1
        nsinos = NRNG_c**2
        # correct for the max. ring difference in the full axial extent (don't use ring range (1,63) as for this case no correction)
        if NRNG_c == 64:
            nsinos -= 12
    elif Cnt['SPN'] == 11:
        nsinos = Cnt['NSN11']
    elif Cnt['SPN'] == 0:
        nsinos = Cnt['NSEG0']

    if im.shape[0] == Cnt['SO_IMZ'] and im.shape[1] == Cnt[
            'SO_IMY'] and im.shape[2] == Cnt['SO_IMX']:
        ims = mmrimg.convert2dev(im, Cnt)
    elif im.shape[0] == Cnt['SZ_IMX'] and im.shape[1] == Cnt[
            'SZ_IMY'] and im.shape[2] == Cnt['SZ_IMZ']:
        ims = im
    elif im.shape[0] == Cnt['rSO_IMZ'] and im.shape[1] == Cnt[
            'SO_IMY'] and im.shape[2] == Cnt['SO_IMX']:
        ims = mmrimg.convert2dev(im, Cnt)
    elif im.shape[0] == Cnt['SZ_IMX'] and im.shape[1] == Cnt[
            'SZ_IMY'] and im.shape[2] == Cnt['rSZ_IMZ']:
        ims = im
    else:
        log.error(
            'wrong image size;  it has to be one of these: (z,y,x) = (127,344,344) or (y,x,z) = (320,320,128)'
        )

    log.debug('number of sinos:%d' % nsinos)

    #predefine the sinogram.  if subsets are used then only preallocate those bins which will be used.
    if isub[0] < 0:
        sinog = np.zeros((txLUT['Naw'], nsinos), dtype=np.float32)
    else:
        sinog = np.zeros((len(isub), nsinos), dtype=np.float32)

    # --------------------
    petprj.fprj(sinog, ims, txLUT, axLUT, isub, Cnt, att)
    # --------------------
    # get the sinogram bins in a proper sinogram
    sino = np.zeros((txLUT['Naw'], nsinos), dtype=np.float32)
    if isub[0] >= 0: sino[isub, :] = sinog
    else: sino = sinog

    # put the gaps back to form displayable sinogram
    if not dev_out:
        sino = mmraux.putgaps(sino, txLUT, Cnt)

    return sino
Exemple #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
Exemple #3
0
def osemone(datain,
            mumaps,
            hst,
            scanner_params,
            recmod=3,
            itr=4,
            fwhm=0.,
            mask_radius=29.,
            sctsino=np.array([]),
            outpath='',
            store_img=False,
            frmno='',
            fcomment='',
            store_itr=[],
            emmskS=False,
            ret_sinos=False,
            attnsino=None,
            randsino=None,
            normcomp=None):

    #---------- sort out OUTPUT ------------
    #-output file name for the reconstructed image, initially assume n/a
    fout = 'n/a'
    if store_img or store_itr:
        if outpath == '':
            opth = os.path.join(datain['corepath'], 'reconstructed')
        else:
            opth = outpath
        mmraux.create_dir(opth)

    if ret_sinos:
        return_ssrb = True
        return_mask = True
    else:
        return_ssrb = False
        return_mask = False

    #----------

    # Get particular scanner parameters: Constants, transaxial and axial LUTs
    Cnt = scanner_params['Cnt']
    txLUT = scanner_params['txLUT']
    axLUT = scanner_params['axLUT']

    import time
    from niftypet import nipet
    # from niftypet.nipet.sct import mmrsct
    # from niftypet.nipet.prj import mmrhist

    if Cnt['VERBOSE']: print 'i> reconstruction in mode', recmod

    # get object and hardware mu-maps
    muh, muo = mumaps

    # get the GPU version of the image dims
    mus = mmrimg.convert2dev(muo + muh, Cnt)

    if Cnt['SPN'] == 1:
        snno = Cnt['NSN1']
    elif Cnt['SPN'] == 11:
        snno = Cnt['NSN11']

    # remove gaps from the prompt sino
    psng = mmraux.remgaps(hst['psino'], txLUT, Cnt)

    #=========================================================================
    # GET NORM
    #-------------------------------------------------------------------------
    if normcomp == None:
        ncmp, _ = mmrnorm.get_components(datain, Cnt)
    else:
        ncmp = normcomp
        print 'w> using user-defined normalisation components'
    nsng = mmrnorm.get_sinog(datain, hst, axLUT, txLUT, Cnt, normcomp=ncmp)
    #=========================================================================

    #=========================================================================
    # ATTENUATION FACTORS FOR COMBINED OBJECT AND BED MU-MAP
    #-------------------------------------------------------------------------
    #> combine attenuation and norm together depending on reconstruction mode
    if recmod == 0:
        asng = np.ones(psng.shape, dtype=np.float32)
    else:
        #> check if the attenuation sino is given as an array
        if isinstance(attnsino, np.ndarray) \
                and attnsino.shape==(Cnt['NSN11'], Cnt['NSANGLES'], Cnt['NSBINS']):
            asng = mmraux.remgaps(attnsino, txLUT, Cnt)
            print 'i> using provided attenuation factor sinogram'
        elif isinstance(attnsino, np.ndarray) \
                and attnsino.shape==(Cnt['Naw'], Cnt['NSN11']):
            asng = attnsino
            print 'i> using provided attenuation factor sinogram'
        else:
            asng = np.zeros(psng.shape, dtype=np.float32)
            petprj.fprj(asng, mus, txLUT, axLUT,
                        np.array([-1], dtype=np.int32), Cnt, 1)
    #> combine attenuation and normalisation
    ansng = asng * nsng
    #=========================================================================

    #=========================================================================
    # Randoms
    #-------------------------------------------------------------------------
    if isinstance(randsino, np.ndarray):
        rsino = randsino
        rsng = mmraux.remgaps(randsino, txLUT, Cnt)
    else:
        rsino, snglmap = nipet.randoms(hst, scanner_params)
        rsng = mmraux.remgaps(rsino, txLUT, Cnt)
    #=========================================================================

    #=========================================================================
    # SCAT
    #-------------------------------------------------------------------------
    if recmod == 2:
        if sctsino.size > 0:
            ssng = mmraux.remgaps(sctsino, txLUT, Cnt)
        elif sctsino.size == 0 and os.path.isfile(datain['em_crr']):
            emd = nimpa.getnii(datain['em_crr'])
            ssn = nipet.vsm(datain,
                            mumaps,
                            emd['im'],
                            hst,
                            rsino,
                            scanner_params,
                            prcnt_scl=0.1,
                            emmsk=False)
            ssng = mmraux.remgaps(ssn, txLUT, Cnt)
        else:
            print 'e> no emission image available for scatter estimation!  check if it' 's present or the path is correct.'
            sys.exit()
    else:
        ssng = np.zeros(rsng.shape, dtype=rsng.dtype)
    #=========================================================================

    if Cnt['VERBOSE']:
        print '\n>------ OSEM (', itr, ') -------\n'
    #------------------------------------
    Sn = 14  # number of subsets
    #-get one subset to get number of projection bins in a subset
    Sprj, s = get_subsets14(0, scanner_params)
    Nprj = len(Sprj)
    #-init subset array and sensitivity image for a given subset
    sinoTIdx = np.zeros((Sn, Nprj + 1), dtype=np.int32)
    #-init sensitivity images for each subset
    imgsens = np.zeros((Sn, Cnt['SZ_IMY'], Cnt['SZ_IMX'], Cnt['SZ_IMZ']),
                       dtype=np.float32)
    for n in range(Sn):
        sinoTIdx[n, 0] = Nprj  #first number of projection for the given subset
        sinoTIdx[n, 1:], s = get_subsets14(n, scanner_params)
        # sensitivity image
        petprj.bprj(imgsens[n, :, :, :], ansng[sinoTIdx[n, 1:], :], txLUT,
                    axLUT, sinoTIdx[n, 1:], Cnt)
    #-------------------------------------

    #-mask for reconstructed image.  anything outside it is set to zero
    msk = mmrimg.get_cylinder(
        Cnt, rad=mask_radius, xo=0, yo=0, unival=1, gpu_dim=True) > 0.9

    #-init image
    img = np.ones((Cnt['SZ_IMY'], Cnt['SZ_IMX'], Cnt['SZ_IMZ']),
                  dtype=np.float32)

    #-decay correction
    lmbd = np.log(2) / resources.riLUT[Cnt['ISOTOPE']]['thalf']
    if Cnt['DCYCRR'] and 't0' in hst and 'dur' in hst:
        dcycrr = np.exp(lmbd * hst['t0']) * lmbd * hst['dur'] / (
            1 - np.exp(-lmbd * hst['dur']))
        # apply quantitative correction to the image
        qf = ncmp['qf'] / resources.riLUT[Cnt['ISOTOPE']]['BF'] / float(
            hst['dur'])
        qf_loc = ncmp['qf_loc']
    elif not Cnt['DCYCRR'] and 't0' in hst and 'dur' in hst:
        dcycrr = 1.
        # apply quantitative correction to the image
        qf = ncmp['qf'] / resources.riLUT[Cnt['ISOTOPE']]['BF'] / float(
            hst['dur'])
        qf_loc = ncmp['qf_loc']
    else:
        dcycrr = 1.
        qf = 1.
        qf_loc = 1.

    #-affine matrix for the reconstructed images
    B = mmrimg.image_affine(datain, Cnt)

    #-time it
    stime = time.time()

    # import pdb; pdb.set_trace()

    #=========================================================================
    # OSEM RECONSTRUCTION
    #-------------------------------------------------------------------------
    for k in trange(itr, disable=not Cnt['VERBOSE'], desc="OSEM"):
        petprj.osem(img, msk, psng, rsng, ssng, nsng, asng, imgsens, txLUT,
                    axLUT, sinoTIdx, Cnt)
        if np.nansum(img) < 0.1:
            print '---------------------------------------------------------------------'
            print 'w> it seems there is not enough true data to render reasonable image.'
            print '---------------------------------------------------------------------'
            #img[:]=0
            itr = k
            break
        if recmod >= 3 and (((k < itr - 1) and (itr > 1))):  # or (itr==1)
            sct_time = time.time()

            sct = nipet.vsm(datain,
                            mumaps,
                            mmrimg.convert2e7(img, Cnt),
                            hst,
                            rsino,
                            scanner_params,
                            emmsk=emmskS,
                            return_ssrb=return_ssrb,
                            return_mask=return_mask)

            if isinstance(sct, dict):
                ssn = sct['sino']
            else:
                ssn = sct

            ssng = mmraux.remgaps(ssn, txLUT, Cnt)

            if Cnt['VERBOSE']:
                print 'i> scatter time:', (time.time() - sct_time)

        # save images during reconstruction if requested
        if store_itr and k in store_itr:
            im = mmrimg.convert2e7(img * (dcycrr * qf * qf_loc), Cnt)
            fout =  os.path.join(opth, os.path.basename(datain['lm_bf'])[:8] \
                + frmno +'_t'+str(hst['t0'])+'-'+str(hst['t1'])+'sec' \
                +'_itr'+str(k)+fcomment+'_inrecon.nii.gz')
            nimpa.array2nii(im[::-1, ::-1, :], B, fout)

    if Cnt['VERBOSE']: print 'i> recon time:', (time.time() - stime)
    #=========================================================================

    if Cnt['VERBOSE']:
        print 'i> applying decay correction of', dcycrr
        print 'i> applying quantification factor', qf, 'to the whole image for the frame duration of :', hst[
            'dur']

    img *= dcycrr * qf * qf_loc  #additional factor for making it quantitative in absolute terms (derived from measurements)

    #---- save images -----
    #-first convert to standard mMR image size
    im = mmrimg.convert2e7(img, Cnt)

    #-description text to NIfTI
    #-attenuation number: if only bed present then it is 0.5
    attnum = (1 * (np.sum(muh) > 0.5) + 1 * (np.sum(muo) > 0.5)) / 2.
    descrip =   'alg=osem'+ \
                ';sub=14'+ \
                ';att='+str(attnum*(recmod>0))+ \
                ';sct='+str(1*(recmod>1))+ \
                ';spn='+str(Cnt['SPN'])+ \
                ';itr='+str(itr) +\
                ';fwhm='+str(fwhm) +\
                ';t0='+str(hst['t0']) +\
                ';t1='+str(hst['t1']) +\
                ';dur='+str(hst['dur']) +\
                ';qf='+str(qf)

    if fwhm > 0:
        im = ndi.filters.gaussian_filter(im,
                                         fwhm2sig(fwhm, Cnt),
                                         mode='mirror')
    if store_img:
        fout =  os.path.join(opth, os.path.basename(datain['lm_bf'])[:8] \
                + frmno +'_t'+str(hst['t0'])+'-'+str(hst['t1'])+'sec' \
                +'_itr'+str(itr)+fcomment+'.nii.gz')
        if Cnt['VERBOSE']: print 'i> saving image to: ', fout
        nimpa.array2nii(im[::-1, ::-1, :], B, fout, descrip=descrip)

    # returning:
    # (0) E7 image [can be smoothed];
    # (1) file name of saved E7 image
    # (2) [optional] scatter sino
    # (3) [optional] single slice rebinned scatter
    # (4) [optional] mask for scatter scaling based on attenuation data
    # (5) [optional] random sino
    # if ret_sinos and recmod>=3:
    #     recout = namedtuple('recout', 'im, fpet, ssn, sssr, amsk, rsn')
    #     recout.im   = im
    #     recout.fpet = fout
    #     recout.ssn  = ssn
    #     recout.sssr = sssr
    #     recout.amsk = amsk
    #     recout.rsn  = rsino
    # else:
    #     recout = namedtuple('recout', 'im, fpet')
    #     recout.im   = im
    #     recout.fpet = fout
    if ret_sinos and recmod >= 3 and itr > 1:
        RecOut = namedtuple('RecOut', 'im, fpet, affine, ssn, sssr, amsk, rsn')
        recout = RecOut(im, fout, B, ssn, sct['ssrb'], sct['mask'], rsino)
    else:
        RecOut = namedtuple('RecOut', 'im, fpet, affine')
        recout = RecOut(im, fout, B)

    return recout