#!/usr/bin/env python

import glob
import shutil
import os
from os.path import join as pjoin
from os.path import exists, split, splitext
import time

import matplotlib
import seaborn
seaborn.set_style("dark")
# matplotlib.use('Agg', warn=False)
import numpy as np
import matplotlib.pyplot as plt
import shutil
import nibabel as nib

from mako.lookup import TemplateLookup
makolookup = TemplateLookup(directories=['./tpl'])

import logging
logging.basicConfig(format='%(levelname)s:%(message)s', level=logging.DEBUG)
logging.getLogger('matplotlib.font_manager').disabled = True
logging.getLogger('matplotlib').setLevel(logging.WARNING)
import stabilityfuncs as sf
import studyinfo
import spike as spk
import shimmingcalc as shm
import PIL
from PIL import Image as img

import pdfkit

def stabilitycalc(dirname, dicompath, starttime, sliceshift, shimmingfilename=None, noshimmingfilename=None, initxcenter=None, initycenter=None, initzcenter=None):

    '''
        stabilitycalc
        =============
        To use this function:
            1) Create an input folder and a "nii" subfolder. Copy one dicom acquisition file inside input folder and copy nifti files inside "nii" subfolder
            2) Check nifti files' names:
                - acquisition file should contain "acquisition" in its name
                - shimming file (if used) should contain "shimming" in its name
                - no shimming file (if used) should contain "no_shimming" in its name
            3) Launch command:
                ex. -> stabilitycalc("outputpath", "dicompath", 1, 0, "shimmingfile.nii", "noshimmingfile.nii", ...)

        A folder will be create inside output path selected (dirname) where result of computation will be stored:
            - images (.png)
            - analysissummary.txt
            - dataquality.txt
            - output.html
            - output.pdf
        ----------------------------
        REQUIREMENT INPUTS:
            - dirname: output path to store results (path)
            - dicompath: path to find dicom file/s (path)
            - starttime: start time point to begin analysis (int)
            - sliceshift: shift from center axial slice (int)

        OPTIONAL INPUTS:
            - shimmingfilename (optional): name of shimming file nii (string)
            - noshimmingfilename (optional): name of no-shimming file nii (string)
            - initxcenter (optional): xcenter (int)
            - initycenter (optional): ycenter (int)
            - initzcenter (optional): zcenter (int)

    '''

    logging.debug("Preparing inputs...")

    if (not os.path.exists(dirname)):
        os.mkdir(dirname)

    dicomfilenames, filenames, shimmingfilename, noshimmingfilename = sf.prepareinput(dicompath)

    if len(filenames) == 0: raise Exception("Error while converting files in nii format")

    for filename, dicomfilename in zip(filenames, dicomfilenames):
        """create the stability report for a scan"""

        logging.debug('dirname: {}\n'
                    'filename: {}\n'
                    'dicomfilename: {}\n'
                    'starttime: {}\n'
                    'sliceshift: {}\n'
                    'centers: {}, {}, {}'.format(dirname, filename, dicomfilename, starttime, sliceshift, initxcenter, initycenter, initzcenter))

        isindividualcoil = False

        if initxcenter is not None:
            initxcenter, initycenter, initzcenter = [float(x) for x in (initxcenter, initycenter, initzcenter)]
            isindividualcoil = True

        nim = nib.load(pjoin(dirname, filename))
        nim_hdr = nim.get_header()
        xdim, ydim, slicethickness,tr = nim_hdr['pixdim'][1:5]   # per gli fBirn
        #xdim, ydim, slicethickness, tr = nim_hdr['pixdim'][0:4]    # per gli ACR
        xsize, ysize, numslices = nim_hdr['dim'][1:4]

        dims = nim.get_data().shape
        if len(dims) == 4:
            # normal case
            selecteddata = nim.get_data()[:, :, :, starttime:].transpose(3, 2, 1, 0)
        elif len(dims) == 3:
            # single slice
            selecteddata = nim.get_data()[:, :, starttime:]
            sdshape2 = selecteddata.shape[2]
            if sdshape2 > 100 :
                # nifti loses the z dimension; we must reconstitute it
                sdshape = selecteddata.shape
                selecteddata = selecteddata.reshape(sdshape[0], sdshape[1], 1, sdshape[2]).transpose(3, 2, 1, 0)
            elif sdshape2 < 100:
                raise ValueError("Not Enough Data for Stability Assessment")

        numtimepoints = selecteddata.shape[0]

        info = studyinfo.studyinfo_from_dicom(dicomfilename)

        if info['ElementName'] == '':
            info['ElementName'] = 'unknown'

        if info['Coil'] != '':
            # TODO use a real datetime
            #TR = info['TR']
            year = info['StudyDate'][0:4]
            month = info['StudyDate'][4:6]
            day = info['StudyDate'][6:8]
            hour = info['StudyTime'][0:2]
            minute = info['StudyTime'][2:4]
            second = info['StudyTime'][4:6]
            datetime = info['StudyDate'] + "T" + hour + minute + second
            formatteddate = month + "/" + day + "/" + year
            formattedtime = hour + ":" + minute + ":" + second

            if tr == 0:
                tr = info['RepetitionTime']
                tr = tr/1000

        #respath = os.path.dirname(os.path.abspath(filename))
        splitfile = os.path.split(filename)
        # resultname = dicompath.split("/")[-1] if dicompath.split("/")[-1] != "" else dicompath.split("/")[-2]
        # resultname = splitfile[0]

        # make results directory
        data = day + "_" + month + "_" + year
        procresult_name = "procresults_" + info["IRCCS"].replace(" ", "_") + "_" + data + sf.directedfrom(splitfile[1])
        #procresult_name = 'procresults_' + splitfile[1] + "_" + os.path.splitext(os.path.split(filename)[1])[0]

        shutil.rmtree(pjoin(dirname, procresult_name ), ignore_errors=True)
        if not exists(pjoin(dirname, procresult_name)):
            os.mkdir(pjoin(dirname, procresult_name))

        thisdate = time.strftime("%m/%d/%Y %H:%M:%S", time.localtime())

        #Copy the OPBG logo
        folderpath = os.getcwd()
        pngfile = folderpath + '/opbglogo1.png'
        destination = dirname + '/' + procresult_name
        print (pngfile)
        shutil.copy(pngfile, destination)

        #############################
        #
        #  Calculate various statistical images
        #
        # calculate the mean, stddev, variance and ptp images
        logging.debug("calculating mean, stddev, and variance...")
        meanslice = np.mean(selecteddata, 0)
        stddevslice = np.std(selecteddata, 0)
        varslice = np.var(selecteddata, 0)
        ppslice = np.ptp(selecteddata, 0)

        # calculate a mask from meanimage and find its center
        (threshguess, threshfrac) = sf.findsepval(meanslice)
        initmask = sf.makemask(meanslice, threshfrac, 0)
        threshmean = sf.getnzfracval(initmask * meanslice, 0.02)
        if not isindividualcoil:
            objectmask = sf.makemask(meanslice, threshmean, 1)
        else:
            objectmask = sf.makemask(meanslice, 0.01 * threshmean, 1)

        logging.debug("calculating normalized standard deviation and sfnr...")
        with np.errstate(invalid='ignore'):
            normstdslice = objectmask * np.nan_to_num(100.0 * stddevslice / meanslice)
            minstddev = sf.nzrobust(objectmask * meanslice)[1] / 5000.0
            sfnrslice = np.where(objectmask * stddevslice > minstddev, meanslice / stddevslice, 0.0)

        # Now determine where the object is and how big it is
        slicecenter = sf.findCOM(objectmask)
        zcenterf = slicecenter[2]
        zcenter = int(round(zcenterf))

        slicecenter = sf.findCOM(objectmask[zcenter, :, :])
        xcenterf = slicecenter[0]
        ycenterf = slicecenter[1]
        xcenter, ycenter, zcenter = [int(round(x)) for x in (xcenterf, ycenterf, zcenterf)]
        zcenter = zcenter + sliceshift
        xvec = objectmask[zcenter, ycenter, :]
        yvec = objectmask[zcenter, :, xcenter]
        # noinspection PyUnresolvedReferences
        xmin, xmax = np.nonzero(xvec)[0][[0, -1]]
        xcenterf = (xmin + xmax) / 2
        objectradiusx = (xmax - xmin + 1.0) / 2.0
        objectradiusx_mm = xdim * objectradiusx
        # noinspection PyUnresolvedReferences
        ymin, ymax = np.nonzero(yvec)[0][[0, -1]]
        ycenterf = (ymin + ymax) / 2
        objectradiusy = (ymax - ymin + 1.0) / 2.0
        objectradiusy_mm = ydim * objectradiusy
        xcenter, ycenter = [int(round(x)) for x in (xcenterf, ycenterf)]
        origslicecenter = (xcenterf, ycenterf, zcenterf)

        if isindividualcoil:
            # reset everything to assumed values
            # TODO get this from config
            objectmask[:, :, :] = 0.0
            objectradiusx_mm = 85.0
            objectradiusy_mm = 85.0
            objectradiusx, objectradiusy = objectradiusx_mm / xdim, objectradiusy_mm / ydim
            xcenterf, ycenterf, zcenterf = initxcenter, initycenter, initzcenter
            for i in range(xsize):
                ival = (float(i) - xcenterf) * xdim
                isq = ival * ival
                for j in range(ysize):
                    jval = (float(j) - xcenterf) * ydim
                    jsq = jval * jval
                    for k in range(numslices):
                        kval = (float(k) - zcenterf) * slicethickness
                        ksq = kval * kval
                        if np.sqrt(isq + jsq + ksq) <= objectradiusx_mm:
                            objectmask[k, j, i] = 1.0
            xcenter, ycenter, zcenter = [int(round(x)) for x in (xcenterf, ycenterf, zcenterf)]

        logging.debug('coil: {}\n'.format(info['Coil']))
        # define the canonical limits
        #limits = sf.getlimits(info['Coil'])
        limits = sf.getlimits('32Ch_Head')
        #limits = sf.getlimits('Body')

        # Try to figure out what we're looking at
        objectname = "Unknown"

        object_radius_mm = np.sqrt(objectradiusx_mm * objectradiusy_mm)
        object_shape = objectradiusy / objectradiusx

        birn_phantom_radiuscheck = sf.limitcheck(object_radius_mm, limits['BIRNphantom_rad'])
        birn_phantom_shapecheck = sf.limitcheck(object_shape, limits['BIRNphantom_shape'])
        if (birn_phantom_radiuscheck < 2) and (birn_phantom_shapecheck < 2):
            objectname = "BIRN phantom"
            logging.debug("setting objectname to BIRN phantom")

        head_radiuscheck = sf.limitcheck(object_radius_mm, limits['head_rad'])
        head_shapecheck = sf.limitcheck(object_shape, limits['head_shape'])
        if (head_radiuscheck < 2) or (head_shapecheck < 2):
            objectname = "Head"
            logging.debug("setting objectname to Head")

        is_birn_sequence = True
        is_birn_protocol = True

        if (xsize != 64) or (ysize != 64) or (numslices != 28) or (tr != 2.0):
            is_birn_sequence = False
        if is_birn_sequence and (objectname == 'BIRN phantom'):
            logging.debug("Assuming this is a BIRN protocol")

            is_birn_protocol = True
            protocolname = "fBIRN"
        else:
            logging.debug("Assuming this is NOT a BIRN protocol")

            protocolname = "Unknown"

        #############################
        #
        #       Odd-even SNR - Modified to match BIRN
        #
        logging.debug("calculating even/odd snr...")

        evenims = selecteddata[0::2]
        oddims = selecteddata[1::2]
        evenlength = evenims.shape[0]
        oddlength = oddims.shape[0]
        if oddlength < evenlength:
            evenims = evenims[:evenlength - 1]
        eodiffimage = np.sum(oddims, 0, dtype=np.int64) - np.sum(evenims, 0, dtype=np.int64)

        with np.errstate(invalid='ignore', over='ignore', divide='ignore'):
            eodiffpcimage = 100.0 * np.nan_to_num(eodiffimage / (objectmask * meanslice))

        #############################
        #
        #       Weisskoff analysis - Modified to match BIRN
        #
        logging.debug("Weisskoff analysis...")

        numrois = 21
        roisizes = range(1, numrois + 1)
        timepoints = np.arange(0.0, tr * numtimepoints, tr)

        # Axial - Z axis
        [axialroiareas, axialweissstddevs, axialprojstddevs, axialweissrdc, axialprojcvs, axialweisscvs, timecourse] = sf.evalweisskoff(numrois, roisizes, timepoints, xcenter, ycenter, zcenter, selecteddata, "a")

        # Coronal - Y axis
        selecteddataweisk = selecteddata.copy().transpose(0, 3, 2, 1) # data[:, y, x, z]
        [coronalroiareas, coronalweissstddevs, coronalprojstddevs, coronalweissrdc, coronalprojcvs, coronalweisscvs, _] = sf.evalweisskoff(numrois, roisizes, timepoints, xcenter, ycenter, zcenter, selecteddataweisk, "c")

        # Sagittal - X axis
        selecteddataweisk = selecteddataweisk.transpose(0, 2, 1, 3) # data[:, x, y, z]
        [sagittalroiareas, sagittalweissstddevs, sagittalprojstddevs, sagittalweissrdc, sagittalprojcvs, sagittalweisscvs, _] = sf.evalweisskoff(numrois, roisizes, timepoints, xcenter, ycenter, zcenter, selecteddataweisk, "s")

        #Weiskoff 3D
        [cuberoiareas, cubeweissstddevs, cubeprojstddevs, cubeweissrdc, cubeprojcvs, cubeweisscvs, _] = sf.evalweisskoff(numrois, roisizes, timepoints, xcenter, ycenter, zcenter, selecteddata, "cube")

        #############################
        #
        #       Image analysis
        #

        logging.debug("Image analysis...")
        try:
            with np.errstate(invalid='ignore'):
                meanstats = sf.nzstats(meanslice * objectmask)
                stddevstats = sf.nzstats(stddevslice * objectmask)
                varstats = sf.nzstats(varslice * objectmask)
                sfnrstats = sf.nzstats(np.nan_to_num(sfnrslice * objectmask))
                normstdstats = sf.nzstats(normstdslice * objectmask)
                eodiffstats = sf.nzstats(eodiffimage * objectmask)
                eodiffpcstats = sf.nzstats(np.nan_to_num(eodiffpcimage * objectmask))
                ppstats = sf.nzstats(ppslice * objectmask)

                objectmax = np.max(objectmask)
                objectmin = np.min(objectmask)
                rawmeanmax = sf.completerobust(meanslice)[1]
                [meanmin, meanmax] = sf.nzrobust(meanslice * objectmask)
                [stddevmin, stddevmax] = sf.nzrobust(stddevslice * objectmask)
                [varmin, varmax] = sf.nzrobust(varslice * objectmask)
                [sfnrmin, sfnrmax] = sf.nzrobust(np.nan_to_num(sfnrslice * objectmask))
                [normstdmin, normstdmax] = sf.nzrobust(normstdslice * objectmask)
                [eodiffmin, eodiffmax] = sf.nzrobust(eodiffimage * objectmask)
                [eodiffpcmin, eodiffpcmax] = sf.nzrobust(np.nan_to_num(eodiffpcimage * objectmask))
                [ppmin, ppmax] = sf.nzrobust(ppslice * objectmask)
        except: continue

        #############################
        #
        #       Corner (noise region) analysis
        #
        roislice = 0.5 * meanslice
        cornerroisize = 5
        cornerxpos = int(int(cornerroisize / 2.0) + 1)
        cornerypos = int(int(cornerroisize / 2.0) + 1)
        cornerroi = sf.setroilims(cornerxpos, cornerypos, cornerroisize)
        if not isindividualcoil:
            sf.markroi(cornerroi, zcenter, roislice, 0.91 * rawmeanmax)
        cornertc = sf.getroistdtc(selecteddata, cornerroi, zcenter)

        #############################
        #
        #       Central ROI analysis
        #
        logging.debug("Analyzing central ROI...")

        try:
            centralroisize = 10
            centralroi = sf.setroilims(xcenter, ycenter, centralroisize)
            if not isindividualcoil:
                sf.markroi(centralroi, zcenter, roislice, 0.92 * rawmeanmax)
            centtc = sf.getroimeantc(selecteddata, centralroi, zcenter)

            ###new SNR eval - chiara
            centval = sf.getroival(meanslice, centralroi, zcenter) #valore medio ROI
            selecteddatadiff = selecteddata.copy() #crea copia di selecteddata
            diffslice = np.mean(selecteddatadiff, 0)
            diffslice [zcenter, :, :] = diffslice[zcenter, :, :] - diffslice[zcenter-1, :, :] #slice centrale - slice precedente
            sigmad = sf.getroistd(diffslice, centralroi, zcenter) #deviazione standard
            centsnrnew = 1.41 * (centval / sigmad) #new SNR

            cornertc_new = sf.check_zeros_corner(cornertc)
            cornertc = cornertc_new
            centsnrvec = centtc / cornertc

            centsnr = np.mean(centsnrvec)
            centsfnr = sf.getroival(sfnrslice, centralroi, zcenter)
            timepoints = np.arange(0.0, tr * numtimepoints, tr)
            centfitcoffs = np.polyfit(timepoints, timecourse, 4)
            fittc = sf.trendgen(timepoints, centfitcoffs)
            detrendedcenttc = centtc - fittc

            centmean = np.mean(centtc)
            centdrift = 100.0 * (np.max(fittc) - np.min(fittc)) / centmean
            centstddev = np.std(centtc)
            centmin = np.min(centtc)
            centmax = np.max(centtc)
            centpp = np.ptp(centtc)

            def qualitypercent(n, basis, lim):
                percent = n / basis * 100.0
                quality = sf.limitcheck(percent, limits[lim])
                return sf.qualitytag('(%4.4f%%)', quality) % percent

            centstddev_qualitytag = qualitypercent(centstddev, centmean, 'central_roi_raw_std%')
            centpp_qualitytag = qualitypercent(centpp, centmean, 'central_roi_raw_p-p%')

            centmean_dt = np.mean(detrendedcenttc)
            centstddev_dt = np.std(detrendedcenttc)
            centmin_dt = np.min(detrendedcenttc)
            centmax_dt = np.max(detrendedcenttc)
            centpp_dt = np.ptp(detrendedcenttc)

            centstddev_dt_qualitytag = qualitypercent(centstddev_dt, centmean_dt, 'central_roi_detrended_std%')
            centpp_dt_qualitytag = qualitypercent(centpp_dt, centmean_dt, 'central_roi_detrended_p-p%')

            #percental signal change
            psc = 100 * (sf.getroistd(meanslice, centralroi, zcenter)/sf.getroival(meanslice, centralroi, zcenter))

        except: pass

        #############################
        #
        #       Maximum value ROI analysis
        #
        logging.debug("Finding and analyzing maximum signal ROI...")

        try:
            maxlocroisize = 5
            maxlocradfrac = 0.7

            # find the maximum region
            if isindividualcoil:
                elementmask = sf.makemask(meanslice, threshmean, 1)
                elementcenter = sf.findCOM(elementmask)
                elementdirvec = (elementcenter[0] - origslicecenter[0],
                                elementcenter[1] - origslicecenter[1],
                                elementcenter[2] - origslicecenter[2])
                elementdirnormfac = sf.vecnorm(elementdirvec)
                maxlocoffsetscl = maxlocradfrac * objectradiusx / elementdirnormfac
                elementmaxpos = (origslicecenter[0] + maxlocoffsetscl * elementdirvec[0],
                                origslicecenter[1] + maxlocoffsetscl * elementdirvec[1],
                                origslicecenter[2] + maxlocoffsetscl * elementdirvec[2])
                if elementmaxpos[2] > numslices - 1:
                    newmaxlocoffsetscl = (float(numslices - 1) - origslicecenter[2]) / elementdirvec[2]
                    elementmaxpos = (origslicecenter[0] + newmaxlocoffsetscl * elementdirvec[0],
                                    origslicecenter[1] + newmaxlocoffsetscl * elementdirvec[1],
                                    origslicecenter[2] + newmaxlocoffsetscl * elementdirvec[2])
                    logging.debug("maxpos adjusted to fall within valid image region")

                if elementmaxpos[2] < 0:
                    newmaxlocoffsetscl = -origslicecenter[2] / elementdirvec[2]
                    elementmaxpos = (origslicecenter[0] + newmaxlocoffsetscl * elementdirvec[0],
                                    origslicecenter[1] + newmaxlocoffsetscl * elementdirvec[1],
                                    origslicecenter[2] + newmaxlocoffsetscl * elementdirvec[2])
                    logging.debug("maxpos adjusted to fall within valid image region")

                maxloccenterx, maxloccentery, maxloccenterz = [int(round(x)) for x in elementmaxpos[:3]]
                maxlocroi = sf.setroilims(maxloccenterx, maxloccentery, maxlocroisize)
                sf.markroi(maxlocroi, maxloccenterz, roislice, 0.92 * rawmeanmax)
                maxloctc = sf.getroimeantc(selecteddata, maxlocroi, zcenter)
                maxlocsnrvec = maxloctc / cornertc
                maxlocsnr = np.mean(maxlocsnrvec)
                maxlocsfnr = sf.getroival(sfnrslice, maxlocroi, zcenter)
                timepoints = np.arange(0.0, tr * numtimepoints, tr)

                maxlocfitcoffs = np.polyfit(timepoints, timecourse, 2)
                fittc = sf.trendgen(timepoints, maxlocfitcoffs)
                detrendedmaxloctc = maxloctc - fittc

                maxlocmean = np.mean(maxloctc)
                maxlocstddev = np.std(maxloctc)
                maxlocpp = np.ptp(maxloctc)

                maxloc_qualitytag = qualitypercent(maxlocstddev, maxlocmean, 'maxlocroi_rawstddev')
                maxloc_pp_qualitytag = qualitypercent(maxlocpp, maxlocmean, 'maxlocroi_rawpp')

                maxlocmean_dt = np.mean(detrendedmaxloctc)
                maxlocstddev_dt = np.std(detrendedmaxloctc)
                maxlocmin_dt = np.min(detrendedmaxloctc)
                maxlocmax_dt = np.max(detrendedmaxloctc)
                maxlocpp_dt = np.ptp(detrendedmaxloctc)

                maxloc_dt_qualitytag = qualitypercent(maxlocstddev_dt, maxlocmean_dt, 'maxlocroi_dtstddev')
                maxlocpp_dt_qualitytag = qualitypercent(maxlocpp_dt, maxlocmean_dt, 'maxlocroi_dtpp')

        except: pass

        #############################
        #
        # Individual coil assessment ROIs
        #
        logging.debug("Analyzing phased array ROIs...")

        try:
            isphasedarray = False

            coildata = sf.getphasedarraydata(info['Coil'])
            if coildata and numslices > 1:
                isphasedarray = True
                numphasedarray = len(coildata)

            if isphasedarray:
                paindices = np.arange(0.0, numphasedarray, 1.0)
                phasedarraysize = 5

                phasedarrayroimeans = np.zeros(numphasedarray)
                phasedarrayroistddevs = np.zeros(numphasedarray)
                phasedarrayroimins = np.zeros(numphasedarray)
                phasedarrayroimaxs = np.zeros(numphasedarray)
                phasedarrayroipps = np.zeros(numphasedarray)
                phasedarrayroimeans_dt = np.zeros(numphasedarray)
                phasedarrayroistddevs_dt = np.zeros(numphasedarray)
                phasedarrayroimins_dt = np.zeros(numphasedarray)
                phasedarrayroimaxs_dt = np.zeros(numphasedarray)
                phasedarrayroipps_dt = np.zeros(numphasedarray)
                phasedarrayroisfnrs = np.zeros(numphasedarray)
                phasedarrayroisnrs = np.zeros(numphasedarray)
                phasedarraytcs = np.zeros((numphasedarray, len(timepoints)), dtype=float)
                phasedarrayfittcs = np.zeros((numphasedarray, len(timepoints)), dtype=float)
                phasedarraydttcs = np.zeros((numphasedarray, len(timepoints)), dtype=float)
                phasedarraydttcs_demeaned = np.zeros((numphasedarray, len(timepoints)), dtype=float)
                phasedarraytc_summary = []
                phasedarraytc_dt_summary = []
                for i, ele in enumerate(coildata):
                    if selecteddata.shape[1] == 1 and coildata[ele]['zloc'] != 0:
                        # single slice
                        logging.debug('single slice data, changing zloc from {} to 0'.format(coildata[ele]['zloc']))

                        coildata[ele]['zloc'] = 0
                    roi = sf.setroilims(round(coildata[ele]['xloc']), round(coildata[ele]['yloc']), phasedarraysize)
                    if not isindividualcoil:
                        sf.markroi(roi, round(coildata[ele]['zloc']), roislice, 0.95 * rawmeanmax)
                    timecourse = sf.getroimeantc(selecteddata, roi, zcenter)
                    phasedarraytcs[i, :] = timecourse[:]
                    snrvec = sf.getroisnr(selecteddata, roi, round(coildata[ele]['zloc']))
                    phasedarrayroisnrs[i] = np.mean(snrvec)
                    phasedarrayroisfnrs[i] = sf.getroival(sfnrslice, roi, round(coildata[ele]['zloc']))
                    phasedarrayroimeans[i] = np.mean(timecourse)
                    phasedarrayroipps[i] = np.ptp(timecourse)
                    phasedarrayfitcoffs = np.polyfit(timepoints, timecourse, 2)
                    phasedarrayfittcs[i, :] = sf.trendgen(timepoints, phasedarrayfitcoffs)
                    phasedarraydttcs[i, :] = phasedarraytcs[i, :] - phasedarrayfittcs[i, :]
                    phasedarrayroimeans_dt[i] = np.mean(phasedarraydttcs[i, :])
                    phasedarraydttcs_demeaned[i, :] = phasedarraydttcs[i, :] - phasedarrayroimeans_dt[i]
                    phasedarrayroipps_dt[i] = np.ptp(phasedarraydttcs[i, :])

                phasedarrayroipps_percent = 100.0 * phasedarrayroipps / phasedarrayroimeans
                phasedarrayroipps_dt_percent = 100.0 * phasedarrayroipps_dt / phasedarrayroimeans_dt

                # finally calculate the correlation between the timeseries
                coilccmatrix = np.corrcoef(phasedarraydttcs_demeaned)
        except: pass

        #############################
        #
        # Peripheral ROIs
        #
        # TODO get these from config file

        try:
            peripheralroisize = 3
            peripheralradfrac = 0.8
            logging.debug("Analyzing peripheral ROIs...")

            voxperroi = peripheralroisize * peripheralroisize
            peripheralradiusx = peripheralradfrac * objectradiusx
            peripheralradiusy = peripheralradfrac * objectradiusy
            numperiph = 32

            periphindex = np.arange(numperiph)
            periphangles = (np.pi * 2 * periphindex) / numperiph
            periphanglesd = (360.0 * periphindex) / numperiph
            xlocs = xcenterf + peripheralradiusx * np.sin(periphangles)
            ylocs = ycenterf + peripheralradiusy * np.cos(periphangles)
            periphangmeans = np.zeros(numperiph)
            periphangsfnrs = np.zeros(numperiph)
            avgperiphtc = centtc * 0.0
            avgperiphmat = np.zeros((numperiph, selecteddata.shape[0]))
            fittingangle = np.zeros((numperiph, selecteddata.shape[0]))
            avgperiphsnrvec = centtc * 0.0
            periphvoxels = np.zeros((selecteddata.shape[0], voxperroi * numperiph))
            periphsnrnew=np.ones(periphindex.shape[0])

            for i in periphindex:
                roi = sf.setroilims(round(xlocs[i]), round(ylocs[i]), peripheralroisize)

                ###new SNR for each peripheral ROI
                periphval = sf.getroival(meanslice, roi, zcenter)
                sigmad = sf.getroistd(diffslice, roi, zcenter)
                periphsnrnew[i]= 1.41 * (periphval / sigmad) #new SNR

                if not isindividualcoil:
                    sf.markroi(roi, zcenter, roislice, 0.95 * rawmeanmax)
                timecourse = sf.getroimeantc(selecteddata, roi, zcenter)
                newvoxels = sf.getroivoxels(selecteddata, roi, zcenter)
                for j in range(voxperroi):
                    periphvoxels[:, i * voxperroi + j] = newvoxels[:, j]

                snrvec = sf.getroisnr(selecteddata, roi, zcenter)
                snr = np.mean(snrvec)
                avgperiphtc += timecourse / (1.0 * numperiph)
                periphfitanglecoffs = np.polyfit(timepoints, timecourse, 4) # da salvare in caso servano per il drift
                periphfitangletc = sf.trendgen(timepoints, periphfitanglecoffs)
                avgperiphmat[i,:] = timecourse #matrice 200x32
                fittingangle[i,:] = periphfitangletc
                avgperiphsnrvec += snrvec / (1.0 * numperiph)
                sfnrval = sf.getroival(sfnrslice, roi, zcenter)
                periphangmeans[i] = 100.0 * np.mean(timecourse) / centmean
                periphangsfnrs[i] = sfnrval

            avgperiphtc2 = np.mean(periphvoxels, 1) #media su tutti i pixel di tutti i voxel

            #new SNR
            peripheralsnrnew = np.mean(periphsnrnew)

            avgperiphsnrvec = avgperiphtc2 / cornertc
            detrendedperiphangle = avgperiphmat - fittingangle
            periphanglemean = np.mean(detrendedperiphangle, axis=1)

            raw_periphanglemean = np.zeros((numperiph, 1))
            raw_periphanglestd = np.zeros((numperiph, 1))
            raw_periphanglepp = np.zeros((numperiph, 1))
            raw_periphangledrift = np.zeros((numperiph, 1))
            detrended_periphanglemean = np.zeros((numperiph, 1))
            detrended_periphanglestd = np.zeros((numperiph, 1))
            detrended_periphanglepp = np.zeros((numperiph, 1))

            for i in periphindex:
                raw_periphanglemean[i] = np.mean(avgperiphmat[i,:])
                raw_periphanglestd[i] = np.std(avgperiphmat[i,:])
                raw_periphanglepp[i] = np.ptp(avgperiphmat[i,:])
                raw_periphangledrift[i] = 100.0 * (np.max(fittingangle[i,:]) - np.min(fittingangle[i,:])) / raw_periphanglemean[i]
                detrended_periphanglemean[i]  = np.mean(detrendedperiphangle[i,:])
                detrended_periphanglestd[i]  = np.std(detrendedperiphangle[i,:])
                detrended_periphanglepp[i]  = np.ptp(detrendedperiphangle[i,:])

            # do average timecourse calculations
            periphfitcoffs = np.polyfit(timepoints, avgperiphtc, 4)
            periphfittc = sf.trendgen(timepoints, periphfitcoffs)
            detrendedperiphtc = avgperiphtc - periphfittc
            periphmean = np.mean(avgperiphtc)
            periphdrift = 100.0 * (np.max(periphfittc) - np.min(periphfittc)) / periphmean
            periphstddev = np.std(avgperiphtc)
            periphmin = np.min(avgperiphtc)
            periphmax = np.max(avgperiphtc)
            periphpp = np.ptp(avgperiphtc)
            periph_qualitytag = qualitypercent(periphstddev, periphmean, 'peripheral_roi_raw_std%')
            periphpp_qualitytag = qualitypercent(periphpp, periphmean, 'peripheral_roi_raw_p-p%')

            periphmean_dt = np.mean(detrendedperiphtc)
            periphstddev_dt = np.std(detrendedperiphtc)
            periphmin_dt = np.min(detrendedperiphtc)
            periphmax_dt = np.max(detrendedperiphtc)
            periphpp_dt = np.ptp(detrendedperiphtc)
            periph_dt_qualitytag = qualitypercent(periphstddev_dt, periphmean_dt, 'peripheral_roi_detrended_std%')
            periphpp_dt_qualitytag = qualitypercent(periphpp_dt, periphmean_dt, 'peripheral_roi_detrended_p-p%')

            # do calculations regarding angular dependance
            meanangperiphval = np.mean(periphangmeans)
            meanangperiphsfnr = np.mean(periphangsfnrs)
            meanangperiphsnr = np.mean(avgperiphsnrvec)

            ptpangperiphval = np.ptp(periphangmeans)
            periphangintensity_qualitytag = qualitypercent(ptpangperiphval, meanangperiphval, 'peripheral_angle_p-p%')

            periphang_sfnr_ptp = np.ptp(periphangsfnrs)
            periphang_sfnr_pp_qualitytag = qualitypercent(periphang_sfnr_ptp, meanangperiphsfnr, 'peripheral_angle_SFNR_p-p%')

        except: pass

        #############################
        #
        #       Ghost ROI analysis
        #
        logging.debug("Analyzing ghost ROI...")

        try:
            ghostroisize = 4
            ghostevenxpos = int(int(xsize / 2) + 1)
            ghostoddxpos = int(xcenterf - objectradiusx + ghostroisize - 1)
            ghostypos = int(1 + ghostroisize / 2.0)
            evenghostroi = sf.setroilims(ghostevenxpos, ghostypos, ghostroisize)
            oddghostroi = sf.setroilims(ghostoddxpos, ghostypos, ghostroisize)
            if not isindividualcoil:
                sf.markroi(evenghostroi, zcenter, roislice, 0.97 * rawmeanmax)
                sf.markroi(oddghostroi, zcenter, roislice, 0.97 * rawmeanmax)

            evenghosttc = sf.getroimeantc(selecteddata, evenghostroi, zcenter)
            oddghosttc = sf.getroimeantc(selecteddata, oddghostroi, zcenter)

            relevenghosttc = 100.0 * evenghosttc / centtc
            reloddghosttc = 100.0 * oddghosttc / centtc

            oddghostmean = np.mean(reloddghosttc)
            oddghoststddev = np.std(reloddghosttc)
            oddghostmin = np.min(reloddghosttc)
            oddghostmax = np.max(reloddghosttc)
            oddghostpp = np.ptp(reloddghosttc)
            evenghostmean = np.mean(relevenghosttc)
            evenghoststddev = np.std(relevenghosttc)
            evenghostmin = np.min(relevenghosttc)
            evenghostmax = np.max(relevenghosttc)
            evenghostpp = np.ptp(relevenghosttc)

        except: pass

        #############################
        #
        # Spikes Analysis (01/08/2018)
        #
        logging.debug("Analyzing spikes...")
        spikeok = True

        try:
            isspike = False
            indexslice = np.arange(selecteddata.shape[1])
            numslice = indexslice + 1
            (spikemeants, peaks_ts, peaks_nspk, peaks_slices) = spk.SpikeDetection(selecteddata)

            if (peaks_nspk.sum() > 0): #se c'è almeno uno spike
                isspike = True
                truepeaksslices = peaks_slices[peaks_nspk != 0]

        except: spikeok = False

        #############################
        #
        # Shimming Analysis (01/08/2018)
        #
        logging.debug("Analyzing shimming...")
        isshimming = 0

        try:
            if shimmingfilename is not None:
                isshimming = 1
                (PixDiffShimming, numPixDiffShimming) = shm.Shimming(dirname, shimmingfilename, 0, 10)
                (PixDiffNOShimming, numPixDiffNOShimming) = shm.Shimming(dirname, noshimmingfilename, 0, 10)
                ShimmingRatio = float(numPixDiffShimming)/float(numPixDiffNOShimming)
        except: isshimming = False

        #############################
        #
        # Output
        #

        # statistical images section

        # noinspection PyShadowingNames
        def slicepic(inputslice, caption, minp, maxp, dirname, outputname, colormap):
            sf.showslice2(inputslice, caption, minp, maxp, colormap)
            plt.savefig(pjoin(dirname, procresult_name, outputname + '.png'), format='png')
            plt.close()

        try:
            roimin = np.min(roislice)
            roimax = np.max(roislice)

            with np.errstate(invalid='ignore'):
                slicepic(roislice, "ROI locations", roimin, roimax, dirname, 'roiimage', 1)
                slicepic(normstdslice, "Normalized stddev % image", normstdmin, normstdmax, dirname, 'normstdimage', 0)
                slicepic(objectmask, "Object mask", objectmin, objectmax, dirname, 'objectmaskimage', 0)
                slicepic(varslice, "Variance image", varmin, varmax, dirname, 'varimage', 0)
                slicepic(stddevslice, "Stddev image", stddevmin, stddevmax, dirname, 'stddevimage', 0)
                slicepic(meanslice, "Mean image", meanmin, meanmax, dirname, 'meanimage', 0)
                slicepic(sfnrslice, "SFNR image", sfnrmin, sfnrmax, dirname, 'sfnrimage', 0)
                slicepic(eodiffimage, "Even odd diff image", eodiffmin, eodiffmax, dirname, 'eodiffimage', 0)
                slicepic(np.nan_to_num(objectmask * eodiffpcimage), "Even odd diff percent image", eodiffpcmin, eodiffpcmax,
                        dirname, 'eodiffpcimage', 0)
                slicepic(ppslice, "Peak to peak image", ppmin, ppmax, dirname, 'ppimage', 0)

            def makefig(figk):
                """make a figure using the figure dictionary"""
                # noinspection PyCallingNonCallable
                figs[figk]['fn'](*figs[figk]['args'])
                plt.savefig(pjoin(dirname, procresult_name, figk + '.png'), format='png')
                plt.close()

            # @formatter:off
            figs = {
                'axial_weisskoffplot': {'fn': sf.showweisskoff2, 'args': (axialroiareas, axialweissstddevs, axialprojstddevs, "Axial Weisskoff plot", "Axial")},
                'coronal_weisskoffplot': {'fn': sf.showweisskoff2, 'args': (coronalroiareas, coronalweissstddevs, coronalprojstddevs, "Coronal Weisskoff plot", "Coronal")},
                'sagittal_weisskoffplot': {'fn': sf.showweisskoff2, 'args': (sagittalroiareas, sagittalweissstddevs, sagittalprojstddevs, "Sagittal Weisskoff plot", "Sagittal")},
                'cube_weisskoffplot': {'fn': sf.showweisskoff2, 'args': (cuberoiareas, cubeweissstddevs, cubeprojstddevs, "Cube Weisskoff plot", "Cube")},
                'oddghostroiplot': {'fn': sf.showtc2, 'args': (timepoints, reloddghosttc, 0.0 * timepoints + oddghostmean, "Relative odd ghost ROI amplitude plot (%)")},
                'evenghostroiplot': {'fn': sf.showtc2, 'args': (timepoints, relevenghosttc, 0.0 * timepoints + evenghostmean, "Relative even ghost ROI amplitude plot (%)")},
                'PeripheralAngle': {'fn': sf.showimage3, 'args': (avgperiphmat, periphanglesd, "Peirpheral Intensity at different angles" )}}

            if spikeok:
                if not isspike:
                    figs.update({
                        'Spike_Detection': {'fn': sf.showimage, 'args': (spikemeants, "Background Timeseries for Spike detection" , "#Repetition", "Mean Signal in Background ROI", "Spike Detection (%)")}})
                else:
                    figs.update({
                        'Spike_Detection': {'fn': sf.showimage_mod, 'args': (spikemeants, "#Repetition", "Mean Signal in Background ROI", "Spike Detection (%)", truepeaksslices)}})

            if isshimming:
                figs.update({
                    'ShimmingMask': {'fn': sf.showimage2, 'args': (PixDiffShimming, "Circular Mask - Central Slice" ,  "Shimming Mask")},
                    'NOShimmingMask': {'fn': sf.showimage2, 'args': (PixDiffNOShimming, "Circular Mask - Central Slice", "NO Shimming Mask")}})

            if isphasedarray:
                figs.update({
                    'coilccmatrix': {'fn': sf.showslice3, 'args': (coilccmatrix, "Phased array element correlation matrix", 0.0, 1.0, 0)},
                    'phasedarrayroisnrplot': {'fn': sf.showtc, 'args': (paindices, phasedarrayroisnrs, "Phased array SNR by element")},
                    'phasedarrayroisfnrplot': {'fn': sf.showtc, 'args': (paindices, phasedarrayroisfnrs, "Phased array SFNR by element")},
                    'phasedarrayroippplot': {'fn': sf.showtc, 'args': (paindices, phasedarrayroipps_percent, "Phased array p-p% variation by element")},
                    'phasedarrayroippdtplot': {'fn': sf.showtc, 'args': (paindices, phasedarrayroipps_dt_percent, "Phased array p-p% variation by element (after detrending)")}})

            if isindividualcoil:
                figs.update({
                    'maxlocroiplot': {'fn': sf.showtc2, 'args': (timepoints, maxloctc, maxlocmean_dt + fittc, "Max sensitivity ROI plot (%)")},
                    'maxlocroisnrplot': {'fn': sf.showtc2, 'args': (timepoints, maxlocsnrvec, 0.0 * maxlocsnrvec + maxlocsnr, "Max sensitivity ROI SNR over time")},
                    'maxlocroidtplot': {'fn': sf.showtc, 'args': (timepoints, maxloctc - fittc, "Detrended max sensitivity ROI plot (%)")}})
            else:
                figs.update({
                    'centroiplot': {'fn': sf.showtc2, 'args': (timepoints, centtc, centmean_dt + fittc, "Central ROI plot (%)")},
                    'centroisnrplot': {'fn': sf.showtc2, 'args': (timepoints, centsnrvec, 0.0 * centsnrvec + centsnr, "Central ROI SNR over time")},
                    'centroidtplot': {'fn': sf.showtc, 'args': (timepoints, centtc - fittc, "Detrended central ROI plot (%)")},
                    'periphroiplot': {'fn': sf.showtc2, 'args': (periphanglesd, periphangmeans, 0.0 * periphanglesd + meanangperiphval, "Relative peripheral image intensity (%)", "Angle")},
                    'periphroisfnrplot': {'fn': sf.showtc2, 'args': (periphanglesd, periphangsfnrs, 0.0 * periphanglesd + meanangperiphsfnr, "Absolute peripheral SFNR", "Angle")},
                    'periphroitcplot': {'fn': sf.showtc2, 'args': (timepoints, avgperiphtc, periphmean_dt + periphfittc, "Peripheral ROI plot (%)")},
                    'periphroisnrplot': {'fn': sf.showtc2, 'args': (timepoints, avgperiphsnrvec, 0.0 * timepoints + meanangperiphsnr, "Peripheral ROI SNR over time")},
                    'periphroidttcplot': {'fn': sf.showtc, 'args': (timepoints, avgperiphtc - periphfittc, "Detrended peripheral ROI plot (%)")},
                    'periphangleplot1': {'fn': sf.showimageangle, 'args': (timepoints, periphanglesd, avgperiphmat, periphanglemean, fittingangle , 0)},
                    'periphangleplot2': {'fn': sf.showimageangle, 'args': (timepoints, periphanglesd, avgperiphmat, periphanglemean, fittingangle ,1)},
                    'periphangleplot3': {'fn': sf.showimageangle, 'args': (timepoints, periphanglesd, avgperiphmat, periphanglemean, fittingangle ,2)},
                    'periphangleplot4': {'fn': sf.showimageangle, 'args': (timepoints, periphanglesd, avgperiphmat, periphanglemean, fittingangle ,3)},
                    'periphangleplot5': {'fn': sf.showimageangle, 'args': (timepoints, periphanglesd, avgperiphmat, periphanglemean, fittingangle ,4)},
                    'periphangleplot6': {'fn': sf.showimageangle, 'args': (timepoints, periphanglesd, avgperiphmat, periphanglemean, fittingangle ,5)},
                    'periphangleplot7': {'fn': sf.showimageangle, 'args': (timepoints, periphanglesd, avgperiphmat, periphanglemean, fittingangle ,6)},
                    'periphangleplot8': {'fn': sf.showimageangle, 'args': (timepoints, periphanglesd, avgperiphmat, periphanglemean, fittingangle ,7)}})

            # @formatter:on

            for k in figs:
                makefig(k)

        except:pass

        try:
            # data quality report
            datadict = {'Coil': info['Coil'],
                        'Date': formatteddate,
                        'Time': formattedtime,
                        'DateTime': datetime,
                        'Object': objectname,
                        'Protocol': protocolname,
                        'Element': info['ElementName'],
                        'processed_as_individual': isindividualcoil,
                        'object_radius_mm': object_radius_mm,
                        'object_shape': object_shape,
                        'center_of_mass_x': xcenterf,
                        'center_of_mass_y': ycenterf,
                        'center_of_mass_z': zcenterf}

            if not isindividualcoil:
                datadict.update({'central_roi_raw_mean': centmean,
                                'central_roi_raw_std': centstddev,
                                'central_roi_raw_std%': 100.0 * centstddev / centmean,
                                'central_roi_raw_min': centmin,
                                'central_roi_raw_max': centmax,
                                'central_roi_raw_p-p': centpp,
                                'central_roi_raw_p-p%': 100.0 * centpp / centmean,
                                'central_roi_detrended_mean': centmean_dt,
                                'central_roi_detrended_std': centstddev_dt,
                                'central_roi_detrended_std%': 100.0 * centstddev_dt / centmean_dt,
                                'central_roi_detrended_min': centmin_dt,
                                'central_roi_detrended_max': centmax_dt,
                                'central_roi_detrended_p-p': centpp_dt,
                                'central_roi_detrended_p-p%': 100.0 * centpp_dt / centmean_dt,
                                'central_roi_SNR': centsnr,
                                'central_roi_SNR_new_method': centsnrnew,
                                'central_roi_SFNR': centsfnr,
                                'central_roi_PSC%': psc,
                                'central_roi_polyfit_lin': 100.0 * centfitcoffs[1] / centfitcoffs[2],
                                'central_roi_polyfit_quad': 100.0 * centfitcoffs[0] / centfitcoffs[2],
                                'peripheral_roi_raw_mean': periphmean,
                                'peripheral_roi_raw_std': periphstddev,
                                'peripheral_roi_raw_std%': 100.0 * periphstddev / periphmean,
                                'peripheral_roi_raw_min': periphmin,
                                'peripheral_roi_raw_max': periphmax,
                                'peripheral_roi_raw_p-p': periphpp,
                                'peripheral_roi_raw_p-p%': 100.0 * periphpp / periphmean,
                                'peripheral_roi_detrended_mean': periphmean_dt,
                                'peripheral_roi_detrended_std': periphstddev_dt,
                                'peripheral_roi_detrended_std%': 100.0 * periphstddev_dt / periphmean_dt,
                                'peripheral_roi_detrended_min': periphmin_dt,
                                'peripheral_roi_detrended_max': periphmax_dt,
                                'peripheral_roi_detrended_p-p': periphpp_dt,
                                'peripheral_roi_detrended_p-p%': 100.0 * periphpp_dt / periphmean_dt,
                                'peripheral_roi_SNR': meanangperiphsnr,
                                'peripheral_roi_SNR_new_method': peripheralsnrnew,
                                'peripheral_roi_SFNR': meanangperiphsfnr,
                                'peripheral_roi_polyfit_lin': 100.0 * periphfitcoffs[1] / periphfitcoffs[2],
                                'peripheral_roi_polyfit_quad': 100.0 * periphfitcoffs[0] / periphfitcoffs[2]})
            else:
                datadict.update({'maxloc_roi_x': elementmaxpos[0],
                                'maxloc_roi_y': elementmaxpos[1],
                                'maxloc_roi_z': elementmaxpos[2],
                                'maxloc_roi_dirvec_x': elementdirvec[0] / elementdirnormfac,
                                'maxloc_roi_dirvec_y': elementdirvec[1] / elementdirnormfac,
                                'maxloc_roi_dirvec_z': elementdirvec[2] / elementdirnormfac,
                                'maxloc_roi_mean': maxlocmean_dt,
                                'maxloc_roi_std': maxlocstddev_dt,
                                'maxloc_roi_std%': 100.0 * maxlocstddev_dt / maxlocmean_dt,
                                'maxloc_roi_min': maxlocmin_dt,
                                'maxloc_roi_max': maxlocmax_dt,
                                'maxloc_roi_p-p': maxlocpp_dt,
                                'maxloc_roi_p-p%': 100.0 * maxlocpp_dt / maxlocmean_dt,
                                'maxloc_roi_SNR': maxlocsnr,
                                'maxloc_roi_SFNR': maxlocsfnr,
                                'maxloc_roi_polyfit_lin': 100.0 * maxlocfitcoffs[1] / maxlocfitcoffs[2],
                                'maxloc_roi_polyfit_quad': 100.0 * maxlocfitcoffs[0] / maxlocfitcoffs[2]})
            datadict.update({'odd_ghost_mean': oddghostmean,
                            'odd_ghost_std': oddghoststddev,
                            'odd_ghost_min': oddghostmin,
                            'odd_ghost_max': oddghostmax,
                            'odd_ghost_p-p': oddghostpp,
                            'odd_ghost_p-p%': 100.0 * oddghostpp / oddghostmean,
                            'even_ghost_mean': evenghostmean,
                            'even_ghost_std': evenghoststddev,
                            'even_ghost_min': evenghostmin,
                            'even_ghost_max': evenghostmax,
                            'even_ghost_p-p': evenghostpp,
                            'even_ghost_p-p%': 100.0 * evenghostpp / evenghostmean,
                            '3D_weissrdc': cubeweissrdc,
                            'axial_weissrdc': axialweissrdc,
                            'coronal_weissrdc': coronalweissrdc,
                            'sagittal_weissrdc': sagittalweissrdc,
                            'central_roi_drift%': centdrift,
                            'peripheral_roi_drift%': periphdrift})

            afp = open(pjoin(dirname, procresult_name, 'dataquality.txt'), "w")

            for k in datadict:
                try:
                    entrydesc = sf.formatlimits(limits[k])
                    entryval = datadict[k]
                    entryquality = sf.limitcheck(entryval, limits[k])
                    afp.writelines(','.join((entrydesc, str(entryval), {0: 'Pass', 1: 'Warn', 2: 'Fail'}[entryquality])) + "\n")
                except KeyError:
                    pass

        except:pass #raise Exception("Error generating dataquality file")

        ########################################################
        #
        # Write summary text file and report

        try:
            tpl = makolookup.get_template('analysissummary_mod.txt')
            summaryfile = pjoin(dirname, procresult_name, 'analysissummary.txt')
            with open(summaryfile, 'w') as fp:
                fp.write(tpl.render(**locals()))

            tpl = makolookup.get_template('stability_mod.html')
            with open(pjoin(dirname, procresult_name, 'output.html'), 'w') as fp:
                fp.write(tpl.render(**locals()))
        except:pass #raise Exception("Error generating analysis summary or stability file")

        ########################################################
        #
        # Conversion to PDF

        try:
            dirhtml = pjoin(dirname, procresult_name) + "/output.html"
            dirpdf = pjoin(dirname, procresult_name) + "/output.pdf"
            config = pdfkit.configuration(wkhtmltopdf='C:\\Program Files\\wkhtmltopdf\\bin\\wkhtmltopdf.exe')
            wkhtmltopdf_options = {"enable-local-file-access": None}
            pdfkit.from_url(dirhtml, dirpdf, options = wkhtmltopdf_options, configuration=config)
        except:raise Exception("Error extracting PDF")

if __name__ == '__main__':
    import argparse

    parser = argparse.ArgumentParser(description='Calculate stability values and create the output PDF and web page.')
    parser.add_argument('dirname', help='the output directory')
    parser.add_argument('dicomfilename', help='the directory where DICOM files are stored')
    parser.add_argument('starttime', help='the number of tr periods to skip at the beginning of the file')
    parser.add_argument('sliceshift', help='number of slice to shift for center slice analysis without artefacts by BottiLuc')

    com = parser.add_argument_group('use this location as the center of mass of the phantom')
    com.add_argument('shimmingfilename', nargs='?', metavar='shimmingfilename')
    com.add_argument('noshimmingfilename', nargs='?', metavar='noshimmingfilename')
    com.add_argument('initxcenter', nargs='?', metavar='xcenter')
    com.add_argument('initycenter', nargs='?', metavar='ycenter')
    com.add_argument('initzcenter', nargs='?', metavar='zcenter')

    args = parser.parse_args()
    if None in (args.initxcenter, args.initycenter,
                args.initzcenter) and not args.initxcenter == args.initycenter == args.initzcenter:
        parser.error('If you set one center of mass parameter, you must set all three.')

    stabilitycalc(args.dirname, args.dicomfilename, int(args.starttime), int(args.sliceshift), args.shimmingfilename, args.noshimmingfilename, args.initxcenter, args.initycenter,
                args.initzcenter)


#!/usr/bin/env python
# -*- coding: utf-8 -*-

################################################################################
#
#   MEASURE - Master Equation Automatic Solver for Unimolecular REactions
#
#   Copyright (c) 2010 by Joshua W. Allen (jwallen@mit.edu)
#
#   Permission is hereby granted, free of charge, to any person obtaining a
#   copy of this software and associated documentation files (the 'Software'),
#   to deal in the Software without restriction, including without limitation
#   the rights to use, copy, modify, merge, publish, distribute, sublicense,
#   and/or sell copies of the Software, and to permit persons to whom the
#   Software is furnished to do so, subject to the following conditions:
#
#   The above copyright notice and this permission notice shall be included in
#   all copies or substantial portions of the Software.
#
#   THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
#   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
#   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
#   AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
#   LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
#   FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
#   DEALINGS IN THE SOFTWARE.
#
################################################################################

"""
This module contains the main :meth:`execute()` function for MEASURE.
"""

import logging
import time
import os.path
import numpy

from rmgpy.quantity import Quantity, constants
from rmgpy.kinetics import Chebyshev, PDepArrhenius
from rmgpy.reaction import Reaction

################################################################################

class MEASURE:
    """
    A representation of a Master Equation Automatic Solver for Unimolecular
    REactions (MEASURE) job. The attributes are:

    =================== ======================= ================================
    Attribute           Type                    Description
    =================== ======================= ================================
    `inputFile`         ``str``                 The path to the input file
    `logFile`           ``str``                 The path to the log file
    `outputFile`        ``str``                 The path to the output file
    `drawFile`          ``str``                 The path to the PES drawing file (PNG, SVG, PDF, or PS)
    ------------------- ----------------------- --------------------------------
    `Tmin`              :class:`Quantity`       The minimum temperature at which to compute :math:`k(T,P)` values
    `Tmax`              :class:`Quantity`       The maximum temperature at which to compute :math:`k(T,P)` values
    `Tcount`            ``int``                 The number of temperatures at which to compute :math:`k(T,P)` values
    `Pmin`              :class:`Quantity`       The minimum pressure at which to compute :math:`k(T,P)` values
    `Pmax`              :class:`Quantity`       The maximum pressure at which to compute :math:`k(T,P)` values
    `Pcount`            ``int``                 The number of pressures at which to compute :math:`k(T,P)` values
    `Emin`              :class:`Quantity`       The minimum energy to use to compute :math:`k(T,P)` values
    `Emax`              :class:`Quantity`       The maximum energy to use to compute :math:`k(T,P)` values
    `grainSize`         :class:`Quantity`       The maximum energy grain size to use to compute :math:`k(T,P)` values
    `grainCount`        ``int``                 The minimum number of energy grains to use to compute :math:`k(T,P)` values
    `method`            ``str``                 The method to use to reduce the master equation to :math:`k(T,P)` values
    `model`             ``str``                 The interpolation model to fit to the computed :math:`k(T,P)` values
    ------------------- ----------------------- --------------------------------
    `network`           :class:`Network`        The unimolecular reaction network
    `Tlist`             :class:`Quantity`       An array of temperatures at which to compute :math:`k(T,P)` values
    `Plist`             :class:`Quantity`       An array of pressures at which to compute :math:`k(T,P)` values
    `Elist`             :class:`Quantity`       An array of energies to use to compute :math:`k(T,P)` values
    =================== ======================= ================================

    """

    def __init__(self, inputFile=None, outputFile=None, logFile=None, drawFile=None):
        self.inputFile = inputFile
        self.logFile = logFile
        self.outputFile = outputFile
        self.drawFile = drawFile
        self.clear()

    def clear(self):
        """
        Clear all loaded information about the job (except the file paths).
        """
        self.Tmin = None
        self.Tmax = None
        self.Tcount = None
        self.Pmin = None
        self.Pmax = None
        self.Pcount = None
        self.Emin = None
        self.Emax = None
        self.grainSize = None
        self.grainCount = None

        self.method = None
        self.model = None

        self.network = None
        self.Tlist = None
        self.Plist = None
        self.Elist = None

    def copy(self):
        """
        Return a copy of the current MEASURE job.
        """
        measure = MEASURE()

        measure.inputFile = self.inputFile
        measure.logFile = self.logFile
        measure.outputFile = self.outputFile
        measure.drawFile = self.drawFile

        if self.Tmin is not None: measure.Tmin = Quantity(self.Tmin)
        if self.Tmax is not None: measure.Tmax = Quantity(self.Tmax)
        measure.Tcount = self.Tcount
        if self.Pmin is not None: measure.Pmin = Quantity(self.Pmin)
        if self.Pmax is not None: measure.Pmax = Quantity(self.Pmax)
        measure.Pcount = self.Pcount
        if self.Emin is not None: measure.Emin = Quantity(self.Emin)
        if self.Emax is not None: measure.Emax = Quantity(self.Emax)
        if self.grainSize is not None: measure.grainSize = Quantity(self.grainSize)
        measure.grainCount = self.grainCount

        measure.method = self.method
        measure.model = self.model

        measure.network = self.network
        measure.Tlist = self.Tlist
        measure.Plist = self.Plist
        measure.Elist = self.Elist

        return measure

    def loadInput(self, inputFile=None):
        """
        Load a MEASURE job from the input file located at `inputFile`, or
        from the `inputFile` attribute if not given as a parameter.
        """
        from input import readFile

        # If an input file is specified, then it overrides the inputFile attribute
        if inputFile is not None:
            self.inputFile = inputFile
        # No matter where we got the input filename, make sure that it exists
        if not os.path.exists(self.inputFile):
            raise PDepError('Input file "{0}" does not exist.'.format(self.inputFile))

        # Set locations of log and output files to be in same folder as input file
        # (unless already set previously)
        inputDirectory = os.path.dirname(os.path.relpath(self.inputFile))
        if not self.outputFile:
            self.outputFile = os.path.join(inputDirectory, 'output.py')
        if not self.logFile:
            self.logFile = os.path.join(inputDirectory, 'MEASURE.log')

        # Load the data from the input file
        readFile(self.inputFile, self)

    def loadOutput(self, outputFile=None):
        """
        Load a MEASURE job from the output file located at `outputFile`, or
        from the `outputFile` attribute if not given as a parameter.
        """
        from input import readFile

        # If an input file is specified, then it overrides the inputFile attribute
        if outputFile is not None:
            self.outputFile = outputFile
        # No matter where we got the input filename, make sure that it exists
        if not os.path.exists(self.outputFile):
            raise PDepError('Output file "{0}" does not exist.'.format(self.outputFile))

        # Load the data from the output file
        readFile(self.outputFile, self)

    def saveInput(self, inputFile=None):
        """
        Save a MEASURE job to the output file located at `outputFile`, or
        from the `outputFile` attribute if not given as a parameter.
        """
        from output import writeFile

        # If an input file is specified, then it overrides the inputFile attribute
        if inputFile is not None:
            self.inputFile = inputFile

        writeFile(self.inputFile, self)

    def saveOutput(self, outputFile=None):
        """
        Save a MEASURE job to the output file located at `outputFile`, or
        from the `outputFile` attribute if not given as a parameter.
        """
        from output import writeFile

        # If an output file is specified, then it overrides the outputFile attribute
        if outputFile is not None:
            self.outputFile = outputFile

        writeFile(self.outputFile, self)

    def draw(self):
        """
        Draw the potential energy surface corresponding to the loaded MEASURE
        calculation.
        """
        logging.info('Drawing potential energy surface...')
        self.network.drawPotentialEnergySurface(self.drawFile)

    def compute(self):
        """
        Compute the pressure-dependent rate coefficients :math:`k(T,P)` for
        the loaded MEASURE calculation.
        """

        # Only proceed if the input network is valid
        if self.network is None or self.network.errorString != '':
            raise PDepError('Attempted to run MEASURE calculation with invalid input.')

        Nisom = len(self.network.isomers)
        Nreac = len(self.network.reactants)
        Nprod = len(self.network.products)

        network = self.network
        Tmin = self.Tmin.value
        Tmax = self.Tmax.value
        Tlist = self.Tlist.values
        Pmin = self.Pmin.value
        Pmax = self.Pmax.value
        Plist = self.Plist.values
        method = self.method
        model = self.model

        # Calculate the rate coefficients
        K = network.calculateRateCoefficients(Tlist, Plist, method, grainCount=self.grainCount, grainSize=self.grainSize.value)

        # Fit interpolation model
        from rmgpy.reaction import Reaction
        from rmgpy.measure.reaction import fitInterpolationModel
        if model[0] != '':
            logging.info('Fitting {0} interpolation models...'.format(model[0]))
        configurations = []
        configurations.extend([[isom] for isom in network.isomers])
        configurations.extend([reactants for reactants in network.reactants])
        configurations.extend([products for products in network.products])
        for i in range(Nisom+Nreac+Nprod):
            for j in range(Nisom+Nreac):
                if i != j:
                    # Check that we have nonzero k(T,P) values
                    if (numpy.any(K[:,:,i,j]) and not numpy.all(K[:,:,i,j])):
                        raise NetworkError('Zero rate coefficient encountered while updating network {0}.'.format(network))

                    # Make a new net reaction
                    netReaction = Reaction(
                        reactants=configurations[j],
                        products=configurations[i],
                        kinetics=None,
                        reversible=(i<Nisom+Nreac),
                    )
                    network.netReactions.append(netReaction)

                    # Set/update the net reaction kinetics using interpolation model
                    netReaction.kinetics = fitInterpolationModel(netReaction, Tlist, Plist,
                        K[:,:,i,j],
                        model, Tmin, Tmax, Pmin, Pmax, errorCheck=True)
        logging.info('')

    def loadFAMEInput(self, path, moleculeDict=None):
        """
        Load the contents of a FAME input file into the MEASURE object. FAME
        is an early version of MEASURE written in Fortran and used by RMG-Java.
        This script enables importing FAME input files into MEASURE so we can
        use the additional functionality that MEASURE provides. Note that it
        is mostly designed to load the FAME input files generated automatically
        by RMG-Java, and may not load hand-crafted FAME input files. If you
        specify a `moleculeDict`, then this script will use it to associate
        the species with their structures.
        """

        from network import Network
        from collision import SingleExponentialDown
        from rmgpy.species import Species, TransitionState
        from rmgpy.reaction import Reaction
        from rmgpy.species import LennardJones
        from rmgpy.statmech import HarmonicOscillator, HinderedRotor, StatesModel
        from rmgpy.thermo import ThermoData
        from rmgpy.kinetics import Arrhenius

        def readMeaningfulLine(f):
            line = f.readline()
            while line != '':
                line = line.strip()
                if len(line) > 0 and line[0] != '#':
                    return line
                else:
                    line = f.readline()
            return ''

        moleculeDict = moleculeDict or {}

        logging.info('Loading file "{0}"...'.format(path))
        f = open(path)

        # Read method
        method = readMeaningfulLine(f).lower()
        if method == 'modifiedstrongcollision':
            self.method = 'modified strong collision'
        elif method == 'reservoirstate':
            self.method = 'reservoir state'

        # Read temperatures
        Tcount, Tunits, Tmin, Tmax = readMeaningfulLine(f).split()
        self.Tmin = Quantity(float(Tmin), Tunits)
        self.Tmax = Quantity(float(Tmax), Tunits)
        self.Tcount = int(Tcount)
        Tlist = []
        for i in range(int(Tcount)):
            Tlist.append(float(readMeaningfulLine(f)))
        self.Tlist = Quantity(Tlist, Tunits)

        # Read pressures
        Pcount, Punits, Pmin, Pmax = readMeaningfulLine(f).split()
        self.Pmin = Quantity(float(Pmin), Punits)
        self.Pmax = Quantity(float(Pmax), Punits)
        self.Pcount = int(Pcount)
        Plist = []
        for i in range(int(Pcount)):
            Plist.append(float(readMeaningfulLine(f)))
        self.Plist = Quantity(Plist, Punits)

        # Read interpolation model
        model = readMeaningfulLine(f).split()
        if model[0].lower() == 'chebyshev':
            self.model = ['chebyshev', int(model[1]), int(model[2])]
        elif model[0].lower() == 'pdeparrhenius':
            self.model = ['pdeparrhenius']

        # Read grain size or number of grains
        self.grainCount = 0
        self.grainSize = Quantity(0.0, "J/mol")
        for i in range(2):
            data = readMeaningfulLine(f).split()
            if data[0].lower() == 'numgrains':
                self.grainCount = int(data[1])
            elif data[0].lower() == 'grainsize':
                self.grainSize = Quantity(float(data[2]), data[1])

        # Create the Network
        self.network = Network()

        # Read collision model
        data = readMeaningfulLine(f)
        assert data.lower() == 'singleexpdown'
        alpha0units, alpha0 = readMeaningfulLine(f).split()
        T0units, T0 = readMeaningfulLine(f).split()
        n = readMeaningfulLine(f)
        collisionModel = SingleExponentialDown(
            alpha0 = Quantity(float(alpha0), alpha0units),
            T0 = Quantity(float(T0), T0units),
            n = Quantity(float(n)),
        )

        speciesDict = {}

        # Read bath gas parameters
        bathGas = Species(label='bath_gas', collisionModel=collisionModel)
        molWtunits, molWt = readMeaningfulLine(f).split()
        if molWtunits == 'u': molWtunits = 'g/mol'
        bathGas.molecularWeight = Quantity(float(molWt), molWtunits)
        sigmaLJunits, sigmaLJ = readMeaningfulLine(f).split()
        epsilonLJunits, epsilonLJ = readMeaningfulLine(f).split()
        bathGas.lennardJones = LennardJones(
            sigma = Quantity(float(sigmaLJ), sigmaLJunits),
            epsilon = Quantity(float(epsilonLJ), epsilonLJunits),
        )
        self.network.bathGas = {bathGas: 1.0}

        # Read species data
        Nspec = int(readMeaningfulLine(f))
        for i in range(Nspec):
            species = Species()

            # Read species label
            species.label = readMeaningfulLine(f)
            speciesDict[species.label] = species
            if species.label in moleculeDict:
                species.molecule = [moleculeDict[species.label]]

            # Read species E0
            E0units, E0 = readMeaningfulLine(f).split()
            species.E0 = Quantity(float(E0), E0units)

            # Read species thermo data
            H298units, H298 = readMeaningfulLine(f).split()
            S298units, S298 = readMeaningfulLine(f).split()
            Cpcount, Cpunits = readMeaningfulLine(f).split()
            Cpdata = []
            for i in range(int(Cpcount)):
                Cpdata.append(float(readMeaningfulLine(f)))
            species.thermo = ThermoData(
                H298 = Quantity(float(H298), H298units),
                S298 = Quantity(float(S298), S298units),
                Tdata = Quantity([300,400,500,600,800,1000,1500], "K"),
                Cpdata = Quantity(Cpdata, Cpunits),
            )

            # Read species collision parameters
            molWtunits, molWt = readMeaningfulLine(f).split()
            if molWtunits == 'u': molWtunits = 'g/mol'
            species.molecularWeight = Quantity(float(molWt), molWtunits)
            sigmaLJunits, sigmaLJ = readMeaningfulLine(f).split()
            epsilonLJunits, epsilonLJ = readMeaningfulLine(f).split()
            species.lennardJones = LennardJones(
                sigma = Quantity(float(sigmaLJ), sigmaLJunits),
                epsilon = Quantity(float(epsilonLJ), epsilonLJunits),
            )

            species.states = StatesModel()

            # Read species vibrational frequencies
            freqCount, freqUnits = readMeaningfulLine(f).split()
            frequencies = []
            for j in range(int(freqCount)):
                frequencies.append(float(readMeaningfulLine(f)))
            species.states.modes.append(HarmonicOscillator(
                frequencies = Quantity(frequencies, freqUnits),
            ))

            # Read species external rotors
            rotCount, rotUnits = readMeaningfulLine(f).split()
            if int(rotCount) > 0:
                raise NotImplementedError('Cannot handle external rotational modes in FAME input.')

            # Read species internal rotors
            freqCount, freqUnits = readMeaningfulLine(f).split()
            frequencies = []
            for j in range(int(freqCount)):
                frequencies.append(float(readMeaningfulLine(f)))
            barrCount, barrUnits = readMeaningfulLine(f).split()
            barriers = []
            for j in range(int(barrCount)):
                barriers.append(float(readMeaningfulLine(f)))
            if barrUnits == 'cm^-1':
                barrUnits = 'J/mol'
                barriers = [barr * constants.h * constants.c * constants.Na * 100. for barr in barriers]
            elif barrUnits in ['Hz', 's^-1']:
                barrUnits = 'J/mol'
                barriers = [barr * constants.h * constants.Na for barr in barriers]
            elif barrUnits != 'J/mol':
                raise Exception('Unexpected units "{0}" for hindered rotor barrier height.'.format(barrUnits))
            inertia = [V0 / 2.0 / (nu * constants.c * 100.)**2 / constants.Na for nu, V0 in zip(frequencies, barriers)]
            for I, V0 in zip(inertia, barriers):
                species.states.modes.append(HinderedRotor(
                    inertia = Quantity(I,"kg*m^2"),
                    barrier = Quantity(V0,barrUnits),
                    symmetry = 1,
                ))

            # Read overall symmetry number
            species.states.spinMultiplicity = int(readMeaningfulLine(f))

        # Read isomer, reactant channel, and product channel data
        Nisom = int(readMeaningfulLine(f))
        Nreac = int(readMeaningfulLine(f))
        Nprod = int(readMeaningfulLine(f))
        for i in range(Nisom):
            data = readMeaningfulLine(f).split()
            assert data[0] == '1'
            self.network.isomers.append(speciesDict[data[1]])
        for i in range(Nreac):
            data = readMeaningfulLine(f).split()
            assert data[0] == '2'
            self.network.reactants.append([speciesDict[data[1]], speciesDict[data[2]]])
        for i in range(Nprod):
            data = readMeaningfulLine(f).split()
            if data[0] == '1':
                self.network.products.append([speciesDict[data[1]]])
            elif data[0] == '2':
                self.network.products.append([speciesDict[data[1]], speciesDict[data[2]]])

        # Read path reactions
        Nrxn = int(readMeaningfulLine(f))
        for i in range(Nrxn):

            # Read and ignore reaction equation
            equation = readMeaningfulLine(f)
            reaction = Reaction(transitionState=TransitionState(), reversible=True)
            self.network.pathReactions.append(reaction)

            # Read reactant and product indices
            data = readMeaningfulLine(f).split()
            reac = int(data[0]) - 1
            prod = int(data[1]) - 1
            if reac < Nisom:
                reaction.reactants = [self.network.isomers[reac]]
            elif reac < Nisom+Nreac:
                reaction.reactants = self.network.reactants[reac-Nisom]
            else:
                reaction.reactants = self.network.products[reac-Nisom-Nreac]
            if prod < Nisom:
                reaction.products = [self.network.isomers[prod]]
            elif prod < Nisom+Nreac:
                reaction.products = self.network.reactants[prod-Nisom]
            else:
                reaction.products = self.network.products[prod-Nisom-Nreac]

            # Read reaction E0
            E0units, E0 = readMeaningfulLine(f).split()
            reaction.transitionState.E0 = Quantity(float(E0), E0units)

            # Read high-pressure limit kinetics
            data = readMeaningfulLine(f)
            assert data.lower() == 'arrhenius'
            Aunits, A = readMeaningfulLine(f).split()
            if '/' in Aunits:
                index = Aunits.find('/')
                Aunits = '{0}/({1})'.format(Aunits[0:index], Aunits[index+1:])
            Eaunits, Ea = readMeaningfulLine(f).split()
            n = readMeaningfulLine(f)
            reaction.kinetics = Arrhenius(
                A = Quantity(float(A), Aunits),
                Ea = Quantity(float(Ea), Eaunits),
                n = Quantity(float(n)),
            )

        f.close()

    def loadFAMEOutput(self, path):
        """
        Load the contents of a FAME ourput file into the MEASURE object. This
        method assumes that you have already loaded the corresponding input
        file via :meth:`loadFAMEInput()`.
        """

        def readMeaningfulLine(f):
            line = f.readline()
            while line != '':
                line = line.strip()
                if len(line) > 0 and line[0] != '#':
                    return line
                else:
                    line = f.readline()
            return ''

        with open(path, 'r') as f:

            method = readMeaningfulLine(f).strip()
            Tlist = numpy.array([float(d) for d in readMeaningfulLine(f).strip().split()[2:]])
            Plist = numpy.array([float(d) for d in readMeaningfulLine(f).strip().split()[2:]])
            model = readMeaningfulLine(f).strip().split()

            Nspec = int(readMeaningfulLine(f).strip())
            Nisom = int(readMeaningfulLine(f).strip())
            Nreac = int(readMeaningfulLine(f).strip())
            Nprod = int(readMeaningfulLine(f).strip())
            Npath = int(readMeaningfulLine(f).strip())
            Nnet = int(readMeaningfulLine(f).strip())

            assert Nisom == len(self.network.isomers)
            assert Nreac == len(self.network.reactants)
            assert Nprod == len(self.network.products)
            assert Npath == len(self.network.pathReactions)

            for n in range(Nnet):
                reac, prod = readMeaningfulLine(f).strip().split()
                reac = int(reac) - 1; prod = int(prod) - 1

                if reac < Nisom:
                    reactants = [self.network.isomers[reac]]
                elif reac < Nisom + Nreac:
                    reactants = self.network.reactants[reac-Nisom]
                elif reac < Nisom + Nreac + Nprod:
                    reactants = self.network.products[reac-Nisom-Nreac]
                else:
                    reactants = []

                if prod < Nisom:
                    products = [self.network.isomers[prod]]
                elif prod < Nisom + Nreac:
                    products = self.network.reactants[prod-Nisom]
                elif prod < Nisom + Nreac + Nprod:
                    products = self.network.products[prod-Nisom-Nreac]
                else:
                    products = []

                readMeaningfulLine(f)

                K = numpy.zeros((len(Tlist), len(Plist)), numpy.float64)
                for t in range(len(Tlist)):
                    K[t,:] = [float(d) for d in readMeaningfulLine(f).strip().split()[1:]]

                if len(reactants) > 1:
                    # FAME returns k(T,P) values in cm^3/mol*s and s^-1, when
                    # we want m^3/mol*s and s^-1
                    K /= 1e6
                    kunits = 'm^3/(mol*s)'
                else:
                    kunits = 's^-1'

                if model[0].lower() == 'chebyshev':
                    degreeT = int(model[1]); degreeP = int(model[2])
                    coeffs = numpy.zeros((degreeT, degreeP), numpy.float64)
                    for t in range(degreeT):
                        coeffs[t,:] = [float(d) for d in readMeaningfulLine(f).strip().split()]
                    if kunits == 'm^3/(mol*s)':
                        coeffs[0,0] -= 6.0
                    kinetics = Chebyshev(coeffs=coeffs, kunits=kunits, Tmin=self.Tmin, Tmax=self.Tmax, Pmin=self.Pmin, Pmax=self.Pmax)
                elif model[0].lower() == 'pdeparrhenius':
                    pressures = []
                    arrhenius = []
                    for p in range(len(Plist)):
                        P, A, n, Ea = [float(d) for d in readMeaningfulLine(f).strip().split()]
                        if kunits == 'm^3/(mol*s)':
                            A /= 1e6
                        pressures.append(P)
                        arrhenius.append(Arrhenius(
                            A = (A,kunits), n = n, Ea = (Ea,"J/mol"), T0=(1,"K"),
                            Tmin=self.Tmin, Tmax=self.Tmax
                        ))
                    kinetics = PDepArrhenius(pressures=(pressures,"Pa"), arrhenius=arrhenius, Tmin=self.Tmin, Tmax=self.Tmax, Pmin=self.Pmin, Pmax=self.Pmax)

                netReaction = Reaction(
                    reactants = reactants,
                    products = products,
                    kinetics = kinetics
                )

                self.network.netReactions.append(netReaction)

################################################################################

def initializeLogging(level, logFile=None):
    """
    Initialize the logging system. The level of information printed is
    determined by looking at the ``args.quiet`` and ``args.verbose`` attributes
    to see if either of the corresponding flags were set. The parameter `args`
    is an object returned by the ``argparse`` module.
    """

    # Reassign the level names so that they look better on printing
    logging.addLevelName(logging.CRITICAL, 'CRITICAL: ')
    logging.addLevelName(logging.ERROR, 'ERROR: ')
    logging.addLevelName(logging.WARNING, 'Warning: ')
    logging.addLevelName(logging.INFO, '')
    logging.addLevelName(logging.DEBUG, '')

    # Create logger
    logger = logging.getLogger()
    logger.setLevel(level)

    # Remove any old handlers that might exist
    while logger.handlers:
        logger.removeHandler(logger.handlers[0])

    # Create formatter
    formatter = logging.Formatter('%(levelname)s%(message)s')

    # Create console handler and set level to debug
    # Also send everything to stdout rather than stderr
    import sys
    ch = logging.StreamHandler(sys.stdout)
    ch.setLevel(level)
    ch.setFormatter(formatter)
    logger.addHandler(ch)

    # create file handler
    if logFile is not None:
        fh = logging.FileHandler(filename=logFile, mode='w')
        fh.setLevel(min(logging.DEBUG,level))
        fh.setFormatter(formatter)
        logger.addHandler(fh)

################################################################################

def logHeader(level=logging.INFO):
    """
    Output a header containing identifying information about RMG to the log.
    """

    logging.log(level, '###############################################################')
    logging.log(level, '# Master Equation Automatic Solver for Unimolecular REactions #')
    logging.log(level, '# (MEASURE)                                                   #')
    logging.log(level, '# Release: 0.1.0 (7 July 2010)                                #')
    logging.log(level, '# Author: Joshua W. Allen (jwallen@mit.edu)                   #')
    logging.log(level, '# Website: http://jwallen.github.com/MEASURE                  #')
    logging.log(level, '###############################################################\n')

################################################################################

def execute(inputFile, outputFile=None, drawFile=None, logFile=None, quiet=False, verbose=False):
    """
    Execute a MEASURE job using the file located at `inputFile` as the input
    file.
    """
    # We will save our output files to the directory containing the input file,
    # NOT the current working directory
    outputDirectory = os.path.dirname(os.path.relpath(inputFile))

    # Determine output level for logging system
    if quiet:
        level = logging.WARNING
    elif verbose:
        level = logging.DEBUG
    else:
        level = logging.INFO

    # Initialize the logging system
    if logFile is not None:
        logFile = os.path.abspath(logFile)
    else:
        logFile = os.path.join(outputDirectory, 'MEASURE.log')
    initializeLogging(level, logFile)

    # Log start timestamp
    logging.info('MEASURE execution initiated at ' + time.asctime() + '\n')

    # Log header
    logHeader()

    # Initialize the MEASURE job
    measure = MEASURE(inputFile=inputFile, outputFile=outputFile, logFile=logFile, drawFile=drawFile)

    # Load input file
    measure.loadInput()

    # Proceed with the desired job
    if measure.network is not None and measure.network.errorString == '':
        if drawFile is not None:
            # Draw the potential energy surface
            measure.draw()
        else:
            # Compute the k(T,P) values
            measure.compute()
            # Save results to output file
            measure.saveOutput()

    # Log end timestamp
    logging.info('')
    logging.info('MEASURE execution terminated at ' + time.asctime())


import torch, pdb
import torch.nn
from IPython import embed
import torch.nn.functional as F
import pdb
import random
import numpy as np
import time
import functools

from .utils import nms, add_box_img, nms_worker
from torch.multiprocessing import Pool, Manager

def rpn_cross_entropy(input, target):
    r"""
    :param input: (15x15x5,2)
    :param target: (15x15x5,)
    :return:
    """
    mask_ignore = target == -1
    mask_calcu = 1 - mask_ignore
    loss = F.cross_entropy(input=input[mask_calcu], target=target[mask_calcu])
    return loss

def rpn_cross_entropy_balance(input, target, num_pos, num_neg, anchors, ohem_pos=None, ohem_neg=None):
    r"""
    :param input: (N,1125,2)
    :param target: (15x15x5,)
    :return:
    """
    # if ohem:
    #     final_loss = rpn_cross_entropy_balance_parallel(input, target, num_pos, num_neg, anchors, ohem=True,
    #                                                     num_threads=4)
    # else:
    loss_all = []
    for batch_id in range(target.shape[0]):
        min_pos = min(len(np.where(target[batch_id].cpu() == 1)[0]), num_pos)
        min_neg = int(min(len(np.where(target[batch_id].cpu() == 1)[0]) * num_neg / num_pos, num_neg))
        pos_index = np.where(target[batch_id].cpu() == 1)[0].tolist()
        neg_index = np.where(target[batch_id].cpu() == 0)[0].tolist()

        if ohem_pos:
            if len(pos_index) > 0:
                pos_loss_bid = F.cross_entropy(input=input[batch_id][pos_index],
                                               target=target[batch_id][pos_index], reduction='none')
                selected_pos_index = nms(anchors[pos_index], pos_loss_bid.cpu().detach().numpy(), min_pos)
                pos_loss_bid_final = pos_loss_bid[selected_pos_index]
            else:
                pos_loss_bid = torch.FloatTensor([0]).cuda()
                pos_loss_bid_final = pos_loss_bid
        else:
            pos_index_random = random.sample(pos_index, min_pos)
            if len(pos_index) > 0:
                pos_loss_bid_final = F.cross_entropy(input=input[batch_id][pos_index_random],
                                                     target=target[batch_id][pos_index_random], reduction='none')
            else:
                pos_loss_bid_final = torch.FloatTensor([0]).cuda()

        if ohem_neg:
            if len(pos_index) > 0:
                neg_loss_bid = F.cross_entropy(input=input[batch_id][neg_index],
                                               target=target[batch_id][neg_index], reduction='none')
                selected_neg_index = nms(anchors[neg_index], neg_loss_bid.cpu().detach().numpy(), min_neg)
                neg_loss_bid_final = neg_loss_bid[selected_neg_index]
            else:
                neg_loss_bid = F.cross_entropy(input=input[batch_id][neg_index],
                                               target=target[batch_id][neg_index], reduction='none')
                selected_neg_index = nms(anchors[neg_index], neg_loss_bid.cpu().detach().numpy(), num_neg)
                neg_loss_bid_final = neg_loss_bid[selected_neg_index]
        else:
            if len(pos_index) > 0:
                neg_index_random = random.sample(np.where(target[batch_id].cpu() == 0)[0].tolist(), min_neg)
                neg_loss_bid_final = F.cross_entropy(input=input[batch_id][neg_index_random],
                                                     target=target[batch_id][neg_index_random], reduction='none')
            else:
                neg_index_random = random.sample(np.where(target[batch_id].cpu() == 0)[0].tolist(), num_neg)
                neg_loss_bid_final = F.cross_entropy(input=input[batch_id][neg_index_random],
                                                     target=target[batch_id][neg_index_random], reduction='none')
        loss_bid = (pos_loss_bid_final.mean() + neg_loss_bid_final.mean()) / 2
        loss_all.append(loss_bid)
    final_loss = torch.stack(loss_all).mean()
    return final_loss

def rpn_smoothL1(input, target, label, num_pos=16, ohem=None):
    r'''
    :param input: torch.Size([1, 1125, 4])
    :param target: torch.Size([1, 1125, 4])
            label: (torch.Size([1, 1125]) pos neg or ignore
    :return:
    '''
    loss_all = []
    for batch_id in range(target.shape[0]):
        min_pos = min(len(np.where(label[batch_id].cpu() == 1)[0]), num_pos)
        if ohem:
            pos_index = np.where(label[batch_id].cpu() == 1)[0]
            if len(pos_index) > 0:
                loss_bid = F.smooth_l1_loss(input[batch_id][pos_index], target[batch_id][pos_index], reduction='none')
                sort_index = torch.argsort(loss_bid.mean(1))
                loss_bid_ohem = loss_bid[sort_index[-num_pos:]]
            else:
                loss_bid_ohem = torch.FloatTensor([0]).cuda()[0]
            loss_all.append(loss_bid_ohem.mean())
        else:
            pos_index = np.where(label[batch_id].cpu() == 1)[0]
            pos_index = random.sample(pos_index.tolist(), min_pos)
            if len(pos_index) > 0:
                loss_bid = F.smooth_l1_loss(input[batch_id][pos_index], target[batch_id][pos_index])
            else:
                loss_bid = torch.FloatTensor([0]).cuda()[0]
            loss_all.append(loss_bid.mean())
    final_loss = torch.stack(loss_all).mean()
    return final_loss