'''
Tools to calculate thermal simulations for TUS experiments

ABOUT:
     author        - Samuel Pichardo
     date          - June 23, 2021
     last update   - May 22, 2022

'''

import numpy as np
import matplotlib.pyplot as plt
from  scipy.io import loadmat,savemat
from BabelViscoFDTD.tools.RayleighAndBHTE import BHTE,BHTEMultiplePressureFields
from BabelViscoFDTD.H5pySimple import SaveToH5py,ReadFromH5py
from scipy.io import loadmat,savemat
from platform import platform
from os.path import isfile
from BabelViscoFDTD.tools.RayleighAndBHTE import  InitOpenCL, InitCuda, InitMetal, InitMLX
from multiprocessing import Process,Queue
import sys
import time
import gc
from scipy.ndimage import median_filter

class InOutputWrapper(object):
    '''
    Wrapper class to redirect stdout or stderr to a multiprocessing queue.
    '''
    def __init__(self, queue, stdout=True):
        self.queue=queue
        if stdout:
            self._stream = sys.stdout
            sys.stdout = self
        else:
            self._stream = sys.stderr
            sys.stderr = self
        self._stdout = stdout

    def write(self, text):
        self.queue.put(text)

    def __getattr__(self, name):
        return getattr(self._stream, name)

    def __del__(self):
        try:
            if self._stdout:
                sys.stdout = self._stream
            else:
                sys.stderr = self._stream
        except AttributeError:
            pass

def GetThermalOutName(InputPData,DurationUS,DurationOff,DutyCycle,Isppa,PRF,Repetitions):
    '''
    Generate a standardized output filename for thermal simulation results.

    Parameters
    ----------
    InputPData : str
        Path to the input pressure data file.
    DurationUS : float
        Sonication duration in microseconds.
    DurationOff : float
        Off duration in microseconds.
    DutyCycle : float
        Duty cycle (0-1).
    Isppa : float
        Spatial-peak pulse-average intensity (W/cm^2).
    PRF : int
        Pulse repetition frequency (Hz).
    Repetitions : int
        Number of repetitions.

    Returns
    -------
    str
        Output filename for thermal simulation results.
    '''
    if DurationUS>=1 and DurationOff>=1:
        suffix = '-ThermalField-Duration-%i-DurationOff-%i-DC-%i-Isppa-%2.1fW-PRF-%iHz' % (DurationUS,DurationOff,DutyCycle*1000,Isppa,PRF)
    else:
        suffix = '-ThermalField-Duration-%3.2f-DurationOff-%3.2f-DC-%i-Isppa-%2.1fW-PRF-%iHz' % (DurationUS,DurationOff,DutyCycle*1000,Isppa,PRF)
    if Repetitions >1:
        suffix+='-%iReps' % (Repetitions)
    if '__Steer_X' in InputPData:
        #we check if this a case for multifocal delivery
        return InputPData.split('__Steer_X')[0]+'_DataForSim'+suffix
    else:
        return InputPData.split('.h5')[0]+suffix

def AnalyzeLosses(pAmp,MaterialMap,LocIJK,Input,
                  MaterialList,pAmpWater,Isppa,
                  xf,yf,zf,SelBrain,
                  bForceHomogenousMedium,
                  bSegmentedBrain,
                  TxSystem,
                  IdRegionBenchmark=[],
                  FixedAcousticPower=0.0):
    '''
    Analyze acoustic energy losses between water and tissue and compute pressure adjustment.

    Parameters
    ----------
    pAmp : np.ndarray
        Pressure amplitude map in tissue.
    MaterialMap : np.ndarray
        Material map of the simulation domain.
    LocIJK : array-like
        Target location indices.
    Input : dict
        Input simulation data.
    MaterialList : dict
        Dictionary of material properties.
    pAmpWater : np.ndarray
        Pressure amplitude map in water.
    Isppa : float
        Spatial-peak pulse-average intensity (W/cm^2).
    xf, yf, zf : np.ndarray
        Spatial coordinate vectors.
    SelBrain : np.ndarray
        Boolean mask for brain region.
    bForceHomogenousMedium : bool
        If True, use homogenous medium.
    bSegmentedBrain : bool
        If True, segmented brain is used.
    IdRegionBenchmark : list, optional
        List of region IDs for benchmarking.
    FixedAcousticPower: float, optional
        If passed (>0.0), it will adjust the power based on the water power, this is useful for benchmarking tests

    Returns
    -------
    tuple
        PressureRatio : float
            Ratio to adjust pressure for target intensity.
        RatioLosses : float
            Ratio of acoustic energy losses.
    '''
    pAmpBrain=pAmp.copy()

    SoSMap=MaterialList['SoS'][MaterialMap]
    DensityMap=MaterialList['Density'][MaterialMap]

    pAmpBrain[SelBrain==False]=0.0
    
    cz=LocIJK[2]
    
    PlanAtMaximum=pAmpBrain[:,:,cz]
    AcousticEnergy=(PlanAtMaximum**2/2/DensityMap[:,:,cz]/ SoSMap[:,:,cz]*((xf[1]-xf[0])**2)).sum()
    print('Acoustic Energy at maximum plane',AcousticEnergy)
    

    xfr=Input['x_vec']
    yfr=Input['y_vec']
    zfr=Input['z_vec'].copy()
    zfr-=zfr.min()
    
    
    PlanAtMaximumWater=pAmpWater[:,:,2] 
    AcousticEnergyWater=(PlanAtMaximumWater**2/2/MaterialList['Density'][0]/ MaterialList['SoS'][0]*((xf[1]-xf[0])**2)).sum()
    print('Water Acoustic Energy entering',AcousticEnergyWater)
    if bForceHomogenousMedium:
        selregion = MaterialMap ==0
    elif  len(IdRegionBenchmark)>0:
        selregion= np.isin(MaterialMap,IdRegionBenchmark)
    else:
        if bSegmentedBrain:
            if 'MaterialMapCT' in Input:
                selregion = np.isin(MaterialMap,[2,3,4,5])==False
            else:
                selregion = np.isin(MaterialMap,[0,1,2,3])
        else:
            if 'MaterialMapCT' in Input:
                selregion=MaterialMap!=2
            else:
                selregion=MaterialMap!=4
        
    pAmpWater[selregion]=0.0
    cxw,cyw,czw=np.where(pAmpWater==pAmpWater.max())
    cxw=cxw[0]
    cyw=cyw[0]
    czw=czw[0]
    print('Location Max Pessure Water',cxw,cyw,czw,'\n',
            xf[cxw],yf[cyw],zf[czw]-zf[0],pAmpWater.max()/1e6)
    
    pAmpTissue=pAmp.copy()#np.ascontiguousarray(np.flip(Input['p_amp'],axis=2))
    pAmpTissue[SelBrain==False]=0.0

    cxr,cyr,czr=np.where(pAmpTissue==pAmpTissue.max())
    cxr=cxr[0]
    cyr=cyr[0]
    czr=czr[0]
    print('Location Max Pressure Tissue',cxr,cyr,czr,'\n',
            xfr[cxr],yfr[cyr],zfr[czr],pAmpTissue.max()/1e6)
    
    if TxSystem in ['DomeTx']: #for Dome Txs, we use peak intensity as there is no easy way to do the integral of  "planewave" conditions
        RatioLosses=(pAmpTissue.max()/pAmpWater.max())**2
        print('Total losses ratio using single punctual measurement for dome Tx',RatioLosses,np.log10(RatioLosses)*10)
    else:    
        PlanAtMaximumWaterMaxLoc=pAmpWater[:,:,czw]
        AcousticEnergyWaterMaxLocWat=(PlanAtMaximumWaterMaxLoc**2/2/MaterialList['Density'][0]/ MaterialList['SoS'][0]*((xf[1]-xf[0])**2)).sum()
        print('Water Acoustic Energy at maximum plane water max loc',AcousticEnergyWaterMaxLocWat) #must be very close to AcousticEnergyWater

        PlanAtMaximumTissue=pAmpTissue[:,:,czw] 
        AcousticEnergyTissueMaxLocWat=(PlanAtMaximumTissue**2/2/DensityMap[:,:,czw]/SoSMap[:,:,czw]*((xf[1]-xf[0])**2)).sum()
        print('Tissue Acoustic Energy at maximum plane water max loc',AcousticEnergyTissueMaxLocWat)
        
        PlanAtMaximumWaterMaxLoc=pAmpWater[:,:,czr]
        AcousticEnergyWaterMaxLoc=(PlanAtMaximumWaterMaxLoc**2/2/MaterialList['Density'][0]/ MaterialList['SoS'][0]*((xf[1]-xf[0])**2)).sum()
        print('Water Acoustic Energy at maximum plane tissue max loc',AcousticEnergyWaterMaxLoc) #must be very close to AcousticEnergyWater
        
        PlanAtMaximumTissue=pAmpTissue[:,:,czr] 
        AcousticEnergyTissue=(PlanAtMaximumTissue**2/2/DensityMap[:,:,czr]/ SoSMap[:,:,czr]*((xf[1]-xf[0])**2)).sum()
        print('Tissue Acoustic Energy at maximum plane tissue',AcousticEnergyTissue)
        
        RatioLossesLoc=AcousticEnergyTissueMaxLocWat/AcousticEnergyWaterMaxLocWat
        print('Total losses ratio using Water loc and in dB',RatioLossesLoc,np.log10(RatioLossesLoc)*10)

        RatioLosses=AcousticEnergyTissue/AcousticEnergyWaterMaxLoc
        print('Total losses ratio and in dB',RatioLosses,np.log10(RatioLosses)*10)

        RatioLossesPeak=(pAmpTissue.max()/pAmpWater.max())**2
        print('Total losses ratio using single punctual measurement',RatioLossesPeak,np.log10(RatioLossesPeak)*10)



        if RatioLosses > (RatioLossesLoc+0.2):
            print('Warning: RatioLossesLoc is bigger than RatioLosses by more than 20%\nUsing water loc for ratio losses')
            RatioLosses=RatioLossesLoc

        if FixedAcousticPower >0.0:
            print('OVERWRITTING LOSSES with Fixed Acoustic Power',FixedAcousticPower)
            RatioLosses = FixedAcousticPower/AcousticEnergyWaterMaxLoc
            print('RatioLosses with Fixed Acoustic Power',RatioLosses)


    if bSegmentedBrain or len(IdRegionBenchmark)>0:
        SoSTarget = SoSMap[cxr,cyr,czr]
        DensityTarget = DensityMap[cxr,cyr,czr]
    else:
        SoSTarget = SoSMap[LocIJK[0],LocIJK[1],LocIJK[2]]
        DensityTarget = DensityMap[LocIJK[0],LocIJK[1],LocIJK[2]]

    
    if FixedAcousticPower >0.0:
        PressureRatio=np.sqrt(RatioLosses)
        print('PressureRatio with FixedAcousticPower ',PressureRatio)
    else:
        PressureAdjust=np.sqrt(Isppa*1e4*2.0*SoSTarget*DensityTarget)
        PressureRatio=PressureAdjust/pAmpTissue.max()
        print('PressureRatio',PressureRatio)

    return PressureRatio,RatioLosses


def RunBHTECycles(nCurrent,
                    Repetitions,
                    TotalIterations,
                    TotalDurationBetweenGroups,
                    TotalDurationStepsOff,
                    LimitBHTEIterationsPerProcess,
                    InputPData,
                    PMaps,
                    MaterialMap,
                    MaterialList,
                    dx,
                    TotalDurationSteps,
                    nStepsOn,
                    cy,
                    nFactorMonitoring,
                    dt,
                    DutyCycle,
                    Backend,
                    MonitoringPointsMap,
                    stableTemp,
                    TemperaturePoints,
                    FinalTemp,
                    FinalDose,
                    PreviousData,
                    bRunInSubProcess=False,
                    ):
    '''
    Run multiple cycles of the Bio-Heat Transfer Equation (BHTE) simulation.

    Parameters
    ----------
    nCurrent : int
        Current iteration index.
    Repetitions : int
        Number of repetitions per group.
    TotalIterations : int
        Total number of iterations.
    TotalDurationBetweenGroups : int
        Duration between groups (steps).
    TotalDurationStepsOff : int
        Duration of off period (steps).
    LimitBHTEIterationsPerProcess : int
        Maximum BHTE iterations per process.
    InputPData : str or np.ndarray
        Input pressure data or filename.
    PMaps : np.ndarray
        Pressure maps.
    MaterialMap : np.ndarray
        Material map.
    MaterialList : dict
        Material properties.
    dx : float
        Spatial step size.
    TotalDurationSteps : int
        Total duration in steps.
    nStepsOn : int or np.ndarray
        Number of steps with sonication on.
    cy : int
        Central y-index.
    nFactorMonitoring : int
        Monitoring interval (steps).
    dt : float
        Time step size.
    DutyCycle : float
        Duty cycle (0-1).
    Backend : str
        Backend type ('CUDA', 'OpenCL', 'Metal', 'MLX').
    MonitoringPointsMap : np.ndarray
        Map of monitoring points.
    stableTemp : float
        Baseline temperature.
    TemperaturePoints : np.ndarray
        Array to store temperature points.
    FinalTemp : np.ndarray
        Final temperature array.
    FinalDose : np.ndarray
        Final dose array.
    PreviousData: dict or None
        Previous data used to concatenate results
    bRunInSubProcess : bool, optional
        If True, run in subprocess.
    Returns
    -------
    tuple
        ResTempMax, ResDose, FinalTemp, FinalDose, TemperaturePoints, nCurrent
    '''
    if type(InputPData) is str:
        p0=PMaps*0
    else:
        p0=PMaps[0,:,:,:]*0
    for nCurrent in range(nCurrent,TotalIterations):
        if nCurrent >0 :
            initT0=FinalTemp
            initDose=FinalDose
        else:
            if PreviousData is not None:
                print('Starting thermal simulation with previous results')
                initT0=PreviousData['FinalTemp']
                initDose=PreviousData['FinalDose']
                print('MaxT0',initT0.max())
            else:
                initT0=None
                initDose=None
            
        if type(InputPData) is str:
            ResTemp,ResDose,MonitorSlice,Qarr,TemperaturePointsOn=BHTE(PMaps,
                                                            MaterialMap,
                                                            MaterialList,
                                                            dx,
                                                            TotalDurationSteps,
                                                            nStepsOn,
                                                            cy,
                                                            nFactorMonitoring=nFactorMonitoring,
                                                            dt=dt,
                                                            DutyCycle=DutyCycle,
                                                            Backend=Backend,
                                                            initT0=initT0,
                                                            initDose=initDose,
                                                            MonitoringPointsMap=MonitoringPointsMap,
                                                            stableTemp=stableTemp)
        else:
            ResTemp,ResDose,MonitorSlice,Qarr,TemperaturePointsOn=BHTEMultiplePressureFields(PMaps,
                                                            MaterialMap,
                                                            MaterialList,
                                                            dx,
                                                            TotalDurationSteps,
                                                            nStepsOn,
                                                            cy,
                                                            nFactorMonitoring=nFactorMonitoring,
                                                            dt=dt,
                                                            Backend=Backend,
                                                            initT0=initT0,
                                                            initDose=initDose,
                                                            MonitoringPointsMap=MonitoringPointsMap,
                                                            stableTemp=stableTemp)

        gc.collect(1)

        if nCurrent==0:
            ResTempMax=ResTemp
        else:
            ResTempMax=np.maximum(ResTempMax,ResTemp)
        
        #for cooling off, we do not need to do steering, just running with no energy
        if TotalDurationStepsOff>0:
            FinalTemp,FinalDose,MonitorSliceOff,dum,TemperaturePointsOff=BHTE(p0,
                                                            MaterialMap,
                                                            MaterialList,
                                                            dx,
                                                            TotalDurationStepsOff,
                                                            0,
                                                            cy,
                                                            nFactorMonitoring=nFactorMonitoring,
                                                            dt=dt,
                                                            DutyCycle=DutyCycle,
                                                            Backend=Backend,
                                                            initT0=ResTemp,
                                                            initDose=ResDose,
                                                            MonitoringPointsMap=MonitoringPointsMap,
                                                            stableTemp=stableTemp) 
            if nCurrent==0:
                TemperaturePoints=np.hstack((TemperaturePointsOn,TemperaturePointsOff))
            else:
                TemperaturePoints=np.hstack((TemperaturePoints,TemperaturePointsOn,TemperaturePointsOff))
            gc.collect(1)
        else:
            FinalTemp=ResTemp
            FinalDose=ResDose
            if nCurrent==0:
                TemperaturePoints=TemperaturePointsOn
            else:
                TemperaturePoints=np.hstack((TemperaturePoints,TemperaturePointsOn))
    
        print('nCurrent,TotalIterations',nCurrent,TotalIterations)

        if (nCurrent+1)%Repetitions == 0 and TotalDurationBetweenGroups>0.0:
            #we ran the extra time off pause
            FinalTemp,FinalDose,MonitorSliceOff,dum,TemperaturePointsOff=BHTE(p0,
                                                            MaterialMap,
                                                            MaterialList,
                                                            dx,
                                                            TotalDurationBetweenGroups,
                                                            0,
                                                            cy,
                                                            nFactorMonitoring=nFactorMonitoring,
                                                            dt=dt,
                                                            DutyCycle=DutyCycle,
                                                            Backend=Backend,
                                                            initT0=FinalTemp,
                                                            initDose=FinalDose,
                                                            MonitoringPointsMap=MonitoringPointsMap,
                                                            stableTemp=stableTemp)
            TemperaturePoints=np.hstack((TemperaturePoints,TemperaturePointsOff))
            gc.collect(1)
        if (bRunInSubProcess) and\
            ((nCurrent+1)% LimitBHTEIterationsPerProcess==0 or (nCurrent+1)==TotalIterations):
            print('Finishing sub process')
            break
    return ResTempMax,ResDose,FinalTemp,FinalDose,TemperaturePoints,nCurrent+1

def RunInProcess(queueResult,Backend,deviceName,queueMsg,
                 LimitBHTEIterationsPerProcess,nCurrent,
                 NumberGroupedSonications,Repetitions,
                 InputPData,PMaps,MaterialMap,
                 MaterialList,dx,TotalDurationSteps,
                 TotalDurationStepsOff,
                 nStepsOn,cy,nFactorMonitoring,dt,
                 DutyCycle,MonitoringPointsMap,
                 TemperaturePoints,stableTemp,
                 FinalTemp,FinalDose,PreviousData,
                 TotalDurationBetweenGroups):
    '''
    Run BHTE simulation cycles in a separate process for parallel computation.

    Parameters
    ----------
    queueResult : multiprocessing.Queue
        Queue to store results.
    Backend : str
        Backend type ('CUDA', 'OpenCL', 'Metal', 'MLX').
    deviceName : str
        Name of the device to use.
    queueMsg : multiprocessing.Queue
        Queue for output messages.
    LimitBHTEIterationsPerProcess : int
        Maximum BHTE iterations per process.
    nCurrent : int
        Current iteration index.
    NumberGroupedSonications : int
        Number of grouped sonications.
    Repetitions : int
        Number of repetitions per group.
    InputPData : str or np.ndarray
        Input pressure data or filename.
    PMaps : np.ndarray
        Pressure maps.
    MaterialMap : np.ndarray
        Material map.
    MaterialList : dict
        Material properties.
    dx : float
        Spatial step size.
    TotalDurationSteps : int
        Total duration in steps.
    TotalDurationStepsOff : int
        Duration of off period (steps).
    nStepsOn : int or np.ndarray
        Number of steps with sonication on.
    cy : int
        Central y-index.
    nFactorMonitoring : int
        Monitoring interval (steps).
    dt : float
        Time step size.
    DutyCycle : float
        Duty cycle (0-1).
    MonitoringPointsMap : np.ndarray
        Map of monitoring points.
    TemperaturePoints : np.ndarray
        Array to store temperature points.
    stableTemp : float
        Baseline temperature.
    FinalTemp : np.ndarray
        Final temperature array.
    FinalDose : np.ndarray
        Final dose array.
    PreviousData: dict or None
        Previous data used to concatenate results
    TotalDurationBetweenGroups : int
        Duration between groups (steps).
    '''
    stdout = InOutputWrapper(queueMsg,True)
    
    if Backend=='CUDA':
        InitCuda(deviceName)
    elif Backend=='OpenCL':
        InitOpenCL(deviceName)
    elif Backend=='Metal':
        InitMetal(deviceName)
    elif Backend=='MLX':
        InitMLX(deviceName)

    TotalIterations=NumberGroupedSonications*Repetitions

    Res=RunBHTECycles(nCurrent,
                    Repetitions,
                    TotalIterations,
                    TotalDurationBetweenGroups,
                    TotalDurationStepsOff,
                    LimitBHTEIterationsPerProcess,
                    InputPData,
                    PMaps,
                    MaterialMap,
                    MaterialList,
                    dx,
                    TotalDurationSteps,
                    nStepsOn,
                    cy,
                    nFactorMonitoring,
                    dt,
                    DutyCycle,
                    Backend,
                    MonitoringPointsMap,
                    stableTemp,
                    TemperaturePoints,
                    FinalTemp,
                    FinalDose,
                    PreviousData,
                    bRunInSubProcess=True,
                    )
    queueResult.put(Res)
    

def CalculateTemperatureEffects(InputPData,
                                deviceName,
                                queueMsg,
                                DutyCycle=0.3,
                                Isppa=5,
                                sel_p='p_amp',
                                PRF=1500,
                                DurationUS=40,
                                DurationOff=40,
                                Repetitions=1,
                                bForceRecalc=False,
                                BaselineTemperature=37,
                                bGlobalDCMultipoint=False,
                                Frequency=7e5,
                                NumberGroupedSonications=1,
                                PauseBetweenGroupedSonications=0.0,
                                Backend='CUDA',
                                LimitBHTEIterationsPerProcess=100,
                                bForceHomogenousMedium=False,
                                bForceNoAbsorptionSkullScalp=False,
                                HomogenousMediumValues={'ThermalConductivity':0.5,  
                                    'SpecificHeat':3583.0,
                                    'Perfusion':55.0,
                                    'Absorption':0.85, #m/s
                                    'InitTemperature':37.0}, #Np/m
                                BenchmarkTestFile='',
                                bApplyMedianPressure=False,
                                TxSystem='',
                                prevSimulationResultsFile=''
                                ):

    '''
    Main function to calculate temperature effects for TUS experiments using BHTE.

    Parameters
    ----------
    InputPData : str or list
        Path(s) to input pressure data file(s).
    deviceName : str
        Name of the device to use for computation.
    queueMsg : multiprocessing.Queue
        Queue for output messages.
    DutyCycle : float, optional
        Duty cycle (default is 0.3).
    Isppa : float, optional
        Spatial-peak pulse-average intensity (W/cm^2, default is 5).
    sel_p : str, optional
        Key for pressure amplitude in input data (default is 'p_amp').
    PRF : int, optional
        Pulse repetition frequency (Hz, default is 1500).
    DurationUS : float, optional
        Sonication duration in microseconds (default is 40).
    DurationOff : float, optional
        Off duration in microseconds (default is 40).
    Repetitions : int, optional
        Number of repetitions (default is 1).
    bForceRecalc : bool, optional
        If True, force recalculation even if output exists (default is False).
    BaselineTemperature : float, optional
        Baseline temperature (default is 37).
    bGlobalDCMultipoint : bool, optional
        If True, use global duty cycle for multipoint (default is False).
    Frequency : float, optional
        Operating frequency (Hz, default is 7e5).
    NumberGroupedSonications : int, optional
        Number of grouped sonications (default is 1).
    PauseBetweenGroupedSonications : float, optional
        Pause duration between grouped sonications (default is 0.0).
    Backend : str, optional
        Backend type ('CUDA', 'OpenCL', 'Metal', 'MLX', default is 'CUDA').
    LimitBHTEIterationsPerProcess : int, optional
        Max BHTE iterations per process (default is 100).
    bForceHomogenousMedium : bool, optional
        If True, use homogenous medium (default is False).
    bForceNoAbsorptionSkullScalp : bool, optional
        If True, set skull/scalp absorption to zero (default is False).
    HomogenousMediumValues : dict, optional
        Properties for homogenous medium.
    BenchmarkTestFile : str, optional
        Path to benchmark test file.
    prevSimulationResultsFile: str, optional
        Path to previous thermal simulation. If specified, it is assumed we want to concatenate simulations.
    Returns
    -------
    str
        Output filename for saved results.
    '''
    if type(InputPData) is str:    
        outfname=GetThermalOutName(InputPData,DurationUS,DurationOff,DutyCycle,Isppa,PRF,Repetitions)
    else:
        outfname=GetThermalOutName(InputPData[0],DurationUS,DurationOff,DutyCycle,Isppa,PRF,Repetitions)
        if len(InputPData)==1:
            #we simplify if we only have a single file in the list
            InputPData=InputPData[0]
    print('InputPData',InputPData)
    print(outfname)

    print('Thermal sim with Backend',Backend)
    print('Operating Frequency',Frequency)
    print('Baseline Temperature',BaselineTemperature)
    if bForceRecalc==False:
        if isfile(outfname+'.h5'):
            print('skipping', outfname)
            return outfname
    dt=0.01
    
    AirMask = None

    if type(InputPData) is str:   
        Input=ReadFromH5py(InputPData)
        WaterInputPData=InputPData.replace('DataForSim.h5','Water_DataForSim.h5')
        print('Load water',WaterInputPData)
        InputWater=ReadFromH5py(WaterInputPData)
        
        pAmp=np.ascontiguousarray(np.flip(Input[sel_p],axis=2))
        pAmpWater=np.ascontiguousarray(np.flip(InputWater['p_amp'],axis=2))

        if 'AirMask' in Input:
            AirMask=np.ascontiguousarray(np.flip(Input['AirMask'],axis=2))
        
    else:
        ALL_ACFIELDSKULL=[]
        Input=ReadFromH5py(InputPData[0])
        if 'AirMask' in Input:
            AirMask=np.ascontiguousarray(np.flip(Input['AirMask'],axis=2))
        
        AllInputs=np.zeros((len(InputPData),Input[sel_p].shape[0],Input[sel_p].shape[1],
                            Input[sel_p].shape[2]),Input[sel_p].dtype)
        AllInputsWater=np.zeros((len(InputPData),Input[sel_p].shape[0],Input[sel_p].shape[1],
                            Input[sel_p].shape[2]),Input[sel_p].dtype)
        for n in range(len(InputPData)):
            ALL_ACFIELDSKULL.append(ReadFromH5py(InputPData[n]))
            AllInputs[n,:,:,:]=np.ascontiguousarray(np.flip(ALL_ACFIELDSKULL[-1][sel_p],axis=2))
            fwater=InputPData[n].replace('DataForSim.h5','Water_DataForSim.h5')
            AllInputsWater[n,:,:,:]=np.ascontiguousarray(np.flip(ReadFromH5py(fwater)['p_amp'],axis=2))
        
        if DurationUS>len(InputPData)*2 and bGlobalDCMultipoint: 
        #ad-hoc rule, if sonication last at least 2x seconds the number of focal spots, we  approximate the heating as each point would take 1 second (with DC indicating how much percentage will  be on), this is valid for long sonications
            DurationCalculations=1.0 
        else:
            DurationCalculations=0.1 # for short sonications, we approximate in chunks of 0.1 seconds
        if bGlobalDCMultipoint:
            while(True): #we need to add a fix for low duty cycle
                NCyclesOn=int(DutyCycle*DurationCalculations/dt/len(InputPData))
                if NCyclesOn>0:
                    break
                DurationCalculations+=0.1
            NCyclesOff=int(DurationCalculations/dt/len(InputPData))-NCyclesOn
        else:
            while(True): #we need to add a fix for low duty cycle
                NCyclesOn=int(DutyCycle*DurationCalculations/dt)
                if NCyclesOn>0:
                    break
                DurationCalculations+=0.1
            NCyclesOff=int(DurationCalculations/dt)-NCyclesOn
        assert(NCyclesOn>0)
        assert(NCyclesOff>0 )
        
        nStepsOnOffList=np.zeros((len(InputPData),2),np.int32)
        #all points have the same duration
        nStepsOnOffList[:,0]=NCyclesOn
        nStepsOnOffList[:,1]=NCyclesOff
        
    if 'MaterialMapCT' in Input:
        MaterialMap=np.ascontiguousarray(np.flip(Input['MaterialMapCT'],axis=2))
        OrigMaterialMap=np.ascontiguousarray(np.flip(Input['MaterialMap'],axis=2))
    else:
        MaterialMap=np.ascontiguousarray(np.flip(Input['MaterialMap'],axis=2))
        OrigMaterialMap=MaterialMap

    bSegmentedBrain = np.any(OrigMaterialMap>5)

    FixedAcousticPower=0.0 
    
    MaterialList={}
    MaterialList['Density']=Input['Material'][:,0]
    MaterialList['SoS']=Input['Material'][:,1]
    MaterialList['Attenuation']=Input['Material'][:,3]
    if bForceNoAbsorptionSkullScalp:
        print('Forcing no absorption in Skull and Scalp')
    if bForceHomogenousMedium:
        print('Running BHTE with homogenous medium with ',HomogenousMediumValues)
        MaterialList['SpecificHeat']=np.array([4178.0,HomogenousMediumValues['SpecificHeat']]) #(J/kg/°C)
        MaterialList['Conductivity']=np.array([0.6,HomogenousMediumValues['ThermalConductivity']]) # (W/m/°C)
        MaterialList['Perfusion']=np.array([0.0,HomogenousMediumValues['Perfusion']])
        MaterialList['Absorption']=np.array([0,HomogenousMediumValues['Absorption']])
        MaterialList['InitTemperature']=np.array([HomogenousMediumValues['InitTemperature'],HomogenousMediumValues['InitTemperature']])
        BaselineTemperature=HomogenousMediumValues['InitTemperature']
    elif len(BenchmarkTestFile)>0:
        print('Running BHTE with Benchmark Test File',BenchmarkTestFile)
        BenchmarkInput=ReadFromH5py(BenchmarkTestFile)
        for k in ['SpecificHeat','Conductivity','Perfusion','Absorption','InitTemperature']:
            MaterialList[k]=np.zeros(len(BenchmarkInput['Materials']))
            
        MaterialList['InitTemperature'][:]=BaselineTemperature
        for n,entry in enumerate(BenchmarkInput['Materials']):
            for k in ['SpecificHeat','Conductivity','Perfusion','Absorption']:
                MaterialList[k][n]=entry[k]

        if 'FixedAcousticPower' in BenchmarkInput:
            FixedAcousticPower=BenchmarkInput['FixedAcousticPower']
    elif 'MaterialMapCT' not in Input:
        #Water, Skin, Cortical, Trabecular, Brain

        #https://itis.swiss/virtual-population/tissue-properties/database/heat-capacity/
        MaterialList['SpecificHeat']=[4178.0,3391.0,1313.0,2274.0,3630.0] #(J/kg/°C)
        #https://itis.swiss/virtual-population/tissue-properties/database/thermal-conductivity/
        MaterialList['Conductivity']=[0.6,0.37,0.32,0.31,0.51] # (W/m/°C)
        #https://itis.swiss/virtual-population/tissue-properties/database/heat-transfer-rate/
        MaterialList['Perfusion']=[0.0,106.0,10.0,30.0,559.0]
        MaterialList['Absorption']=[0,0.85,0.16,0.15,0.85]
        if bForceNoAbsorptionSkullScalp:
            MaterialList['Absorption'][1]=0.0
            MaterialList['Absorption'][2]=0.0
            MaterialList['Absorption'][3]=0.0
            
        MaterialList['InitTemperature']=[BaselineTemperature,BaselineTemperature,
                                         BaselineTemperature,BaselineTemperature,BaselineTemperature]
        if bSegmentedBrain:
            #we add white matter, gray matter and CSF
            MaterialList['Conductivity']+=[0.48, 0.55, 0.57]
            MaterialList['SpecificHeat']+=[3583.0, 3696.0, 4096.0]
            MaterialList['Perfusion']+=[212.0,764.0, 0.0]
            MaterialList['Absorption']+=[0.85, 0.85, 0.0]
            MaterialList['InitTemperature']+=[BaselineTemperature,BaselineTemperature,BaselineTemperature]

        for k in ['SpecificHeat','Conductivity','Perfusion','Absorption','InitTemperature']:
            MaterialList[k]=np.array(MaterialList[k])

    else:
        #Water, Skin, Brain and skull material
        MaterialList['SpecificHeat']=np.zeros_like(MaterialList['SoS'])
        MaterialList['SpecificHeat'][0:3]=[4178,3391,3630]
        if bSegmentedBrain:
             #we add white matter, gray matter and CSF
            MaterialList['SpecificHeat'][3:6]=np.array([3583.0, 3696.0, 4096.0])
            MaterialList['SpecificHeat'][6:]=(1313.0+2274.0)/2
        else:
            MaterialList['SpecificHeat'][3:]=(1313.0+2274.0)/2

        MaterialList['Conductivity']=np.zeros_like(MaterialList['SoS'])
        MaterialList['Conductivity'][0:3]=[0.6,0.37,0.51]
        if bSegmentedBrain:
            MaterialList['Conductivity'][3:6]=np.array([0.48,0.55, 0.57])
            MaterialList['Conductivity'][6:]=(0.31+0.32)/2
        else:
            MaterialList['Conductivity'][3:]=(0.31+0.32)/2

        MaterialList['Perfusion']=np.zeros_like(MaterialList['SoS'])
        MaterialList['Perfusion'][0:3]=[0,106,559]
        if bSegmentedBrain:
            MaterialList['Perfusion'][3:6]=np.array([212.0, 764.0, 0.0])
            MaterialList['Perfusion'][6:]=(10.0+30.0)/2
        else:
            MaterialList['Perfusion'][3:]=(10.0+30.0)/2

        MaterialList['Absorption']=np.zeros_like(MaterialList['SoS'])
        MaterialList['Absorption'][0:3]=[0,0.85,0.85]
        if bSegmentedBrain:
            MaterialList['Absorption'][3:5]=0.85
            if not bForceNoAbsorptionSkullScalp:
                MaterialList['Absorption'][6:]=(0.16+0.15)/2
        else:
            if not bForceNoAbsorptionSkullScalp:
                MaterialList['Absorption'][3:]=(0.16+0.15)/2

        MaterialList['InitTemperature']=np.ones_like(MaterialList['SoS'])*BaselineTemperature


    SaveDict={}
    SaveDict['MaterialList']=MaterialList
    
    nFactorMonitoring=int(50e-3/dt) # we just track every 50 ms
    if nFactorMonitoring==0:
        nFactorMonitoring=1
    TotalDurationSteps=int((DurationUS+.001)/dt)
    nStepsOn=int(DurationUS/dt) 
    if nFactorMonitoring > TotalDurationSteps:
        nFactorMonitoring=1 #in the weird case TotalDurationSteps is less than 50 ms
    TotalDurationStepsOff=int((DurationOff+.001)/dt)
    TotalDurationBetweenGroups=int(PauseBetweenGroupedSonications/dt)

    xf=Input['x_vec']
    yf=Input['y_vec']
    zf=Input['z_vec']

    LocIJK=Input['TargetLocation'].flatten()
    IdRegionBenchmark=[]
    bNormalOperation=True
    if bForceHomogenousMedium:
        bNormalOperation=False
        SelSkull = MaterialMap >0 # we select all material 
        BrainID =[1]
    elif len(BenchmarkTestFile)>0:
        bNormalOperation=False
        BrainID=[len(BenchmarkInput['Materials'])-1]
        if BenchmarkInput['TestType']==1:
            #this is a test for the skull, we select all materials
            SelSkull = MaterialMap >0
            IdRegionBenchmark=[0]
        elif BenchmarkInput['TestType']==2:
            SelSkull = MaterialMap ==1 
            IdRegionBenchmark=[0,1]
        else:
            assert(BenchmarkInput['TestType']==3)
            maxMaterial= MaterialMap.max()
            SelSkull = (MaterialMap> 1) & (MaterialMap <= maxMaterial-2)
            IdRegionBenchmark=[maxMaterial-2,maxMaterial-2-1]
            
        
    elif 'MaterialMapCT' in Input:
        if bSegmentedBrain:
            BrainID=[2,3,4,5]
            SelSkull =MaterialMap>=6
        else:
            BrainID=[2]
            SelSkull = MaterialMap >2
    else:
        if bSegmentedBrain:
            BrainID=[4,5,6,7]
        else:
            BrainID=[4]

        SelSkull =(MaterialMap>1) &\
            (MaterialMap<4)

    cx=LocIJK[0]
    cy=LocIJK[1]
    cz=LocIJK[2]

    SelBrain=np.isin(MaterialMap,BrainID)
    SelSkin=MaterialMap==1

    if bNormalOperation and bApplyMedianPressure:
        #we apply median filter to skull region 
        if type(InputPData) is str: 
            pAmpSk=pAmp.copy()
            pAmpSk=median_filter(pAmpSk,3)
            pAmp[SelSkull]=pAmpSk[SelSkull]
        else:
            for n in range(len(InputPData)):
                pAmpSk=AllInputsWater[n,:,:,:].copy()
                pAmpSk=median_filter(pAmpSk,3)
                AllInputsWater[n,:,:,:][SelSkull]=pAmpSk[SelSkull]

    if type(InputPData) is str:   
        PressureRatio,RatioLosses=AnalyzeLosses(pAmp,MaterialMap,LocIJK,Input,
                                                MaterialList,pAmpWater,Isppa,
                                                xf,yf,zf,SelBrain,
                                                bForceHomogenousMedium,
                                                bSegmentedBrain,
                                                TxSystem,
                                                IdRegionBenchmark,
                                                FixedAcousticPower=FixedAcousticPower)
    else:
        PressureRatio=np.zeros(len(InputPData),dtype=AllInputs.dtype)
        RatioLosses=np.zeros(len(InputPData),dtype=AllInputs.dtype)
        for n in range(len(InputPData)):
            pAmp=AllInputs[n,:,:,:]
            pAmpWater=AllInputsWater[n,:,:,:]
            print('*'*40)
            print('Calculating losses for spot ',n)
            PressureRatio[n],RatioLosses[n]=AnalyzeLosses(pAmp,MaterialMap,LocIJK,Input,
                                                          MaterialList,pAmpWater,Isppa,
                                                          xf,yf,zf,SelBrain,
                                                          bForceHomogenousMedium,
                                                          bSegmentedBrain,
                                                          TxSystem,
                                                          IdRegionBenchmark,
                                                          FixedAcousticPower=FixedAcousticPower)
            print('*'*40)
        print('Average (std) of pressure ratio and losses = %f(%f) , %f(%f)' % (np.mean(PressureRatio),np.std(PressureRatio),np.mean(RatioLosses),np.std(RatioLosses)))
            
    if len(prevSimulationResultsFile)>0:
        print('Reading from previous file to concatenate',prevSimulationResultsFile)
        PreviousData=ReadFromH5py(prevSimulationResultsFile)
        initT0=PreviousData['FinalTemp']
        initDose=PreviousData['FinalDose']
    else:
        PreviousData=None
        initT0=None
        initDose=None

    if type(InputPData) is str:
        ResTemp,ResDose,MonitorSlice,Qarr=BHTE(pAmp*PressureRatio,
                                                        MaterialMap,
                                                        MaterialList,
                                                        (Input['x_vec'][1]-Input['x_vec'][0]),
                                                        TotalDurationSteps,
                                                        nStepsOn,
                                                        -1, #disabling slice monitor for memory saving
                                                        nFactorMonitoring=nFactorMonitoring,
                                                        dt=dt,
                                                        DutyCycle=DutyCycle,
                                                        Backend=Backend,
                                                        stableTemp=BaselineTemperature,
                                                        initT0=initT0,
                                                        initDose=initDose)
    else:
        InputsBHTE=AllInputs.copy()
        for n in range(len(InputPData)):
            InputsBHTE[n,:,:,:]*=PressureRatio[n]
        ResTemp,ResDose,MonitorSlice,Qarr=BHTEMultiplePressureFields(InputsBHTE,
                                                      MaterialMap,
                                                      MaterialList,
                                                      (Input['x_vec'][1]-Input['x_vec'][0]),
                                                      TotalDurationSteps,
                                                      nStepsOnOffList,
                                                      -1, #disabling slice monitor for memory saving
                                                      nFactorMonitoring=nFactorMonitoring,
                                                      dt=dt,
                                                      Backend=Backend,
                                                      stableTemp=BaselineTemperature,
                                                      initT0=initT0,
                                                      initDose=initDose)
    gc.collect(1)

    if len(prevSimulationResultsFile)>0:
        ResTemp=np.maximum(ResTemp,PreviousData['TempEndFUS'])

    ResTempSkin=ResTemp * SelSkin.astype(np.float32)
    ResTempBrain=ResTemp * SelBrain.astype(np.float32)
    ResTempSkull=ResTemp * SelSkull.astype(np.float32)
    mxSkin,mySkin,mzSkin=np.unravel_index(np.argmax(ResTempSkin, axis=None), ResTempSkin.shape)
    mxBrain,myBrain,mzBrain=np.unravel_index(np.argmax(ResTempBrain, axis=None), ResTempBrain.shape)
    mxSkull,mySkull,mzSkull=np.unravel_index(np.argmax(ResTempSkull, axis=None), ResTempSkull.shape)

    MonitoringPointsMap=np.zeros(MaterialMap.shape,np.uint32)
    if bForceHomogenousMedium==1:
        MonitoringPointsMap[mxBrain,myBrain,mzBrain]=1
    elif len(BenchmarkTestFile)>0:
        if BenchmarkInput['TestType']==1:
            MonitoringPointsMap[mxBrain,myBrain,mzBrain]=1
        elif BenchmarkInput['TestType']==2:
            MonitoringPointsMap[mxBrain,myBrain,mzBrain]=1
            MonitoringPointsMap[mxSkull,mySkull,mzSkull]=2
        else:
            assert(BenchmarkInput['TestType']==3)
            MonitoringPointsMap[mxBrain,myBrain,mzBrain]=1
            MonitoringPointsMap[mxSkull,mySkull,mzSkull]=2

            
    else:
        MonitoringPointsMap[mxSkin,mySkin,mzSkin]=1
        MonitoringPointsMap[mxBrain,myBrain,mzBrain]=2
        MonitoringPointsMap[mxSkull,mySkull,mzSkull]=3
        if not(cx==mxBrain and cy==myBrain and cz==mzBrain):
            MonitoringPointsMap[cx,cy,cz]=4
    print('Total # of grouped sonications :',NumberGroupedSonications)
    print('Total # of repetitions in a single group:',Repetitions)
    TOTAL_Iterations = NumberGroupedSonications * Repetitions

    if type(InputPData) is str:
        PMaps=pAmp*PressureRatio
        nStepsOnIn=nStepsOn
    else:
        PMaps=InputsBHTE
        nStepsOnIn=nStepsOnOffList

    FinalTemp=None
    FinalDose=None
    TemperaturePoints=None

    
    if TOTAL_Iterations <= LimitBHTEIterationsPerProcess:
        nCurrent=0
        ResTemp,ResDose,FinalTemp,FinalDose,TemperaturePoints,nCurrent=RunBHTECycles(nCurrent,
                    Repetitions,
                    TOTAL_Iterations,
                    TotalDurationBetweenGroups,
                    TotalDurationStepsOff,
                    LimitBHTEIterationsPerProcess,
                    InputPData,
                    PMaps,
                    MaterialMap,
                    MaterialList,
                     (Input['x_vec'][1]-Input['x_vec'][0]),
                    TotalDurationSteps,
                    nStepsOnIn,
                    -1,
                    nFactorMonitoring,
                    dt,
                    DutyCycle,
                    Backend,
                    MonitoringPointsMap,
                    BaselineTemperature,
                    TemperaturePoints,
                    FinalTemp,
                    FinalDose,
                    PreviousData)

    else:
        queueResult=Queue()
        nCurrent=0
        while(nCurrent<TOTAL_Iterations):
            fieldWorkerProcess = Process(target=RunInProcess, 
                                        args=(queueResult,Backend,deviceName,queueMsg,
                                                LimitBHTEIterationsPerProcess,nCurrent,
                                                NumberGroupedSonications,
                                                Repetitions,
                                                InputPData,
                                                PMaps,
                                                MaterialMap,
                                                MaterialList,
                                                (Input['x_vec'][1]-Input['x_vec'][0]),
                                                TotalDurationSteps,
                                                TotalDurationStepsOff,
                                                nStepsOnIn,-1,nFactorMonitoring,dt,
                                                DutyCycle,MonitoringPointsMap,
                                                TemperaturePoints,BaselineTemperature,
                                                FinalTemp,FinalDose,PreviousData,
                                                TotalDurationBetweenGroups))

            fieldWorkerProcess.start()
            while(True):
                time.sleep(0.1)
                if not queueResult.empty():
                    break
            ProcResults=queueResult.get()
            if nCurrent==0:
                ResTemp=ProcResults[0]
            else:
                ResTemp=np.maximum(ResTemp,ProcResults[0])
            ResDose=ProcResults[1]
            FinalTemp=ProcResults[2]
            FinalDose=ProcResults[3]
            TemperaturePoints=ProcResults[4]
            nCurrent=ProcResults[5]
            fieldWorkerProcess.terminate()
            print('process terminated')

    if len(prevSimulationResultsFile)>0:
        ResTemp=np.maximum(ResTemp,PreviousData['TempEndFUS'])

    SaveDict['mSkin']=np.array([mxSkin,mySkin,mzSkin]).astype(int)
    SaveDict['mBrain']=np.array([mxBrain,myBrain,mzBrain]).astype(int)
    SaveDict['mSkull']=np.array([mxSkull,mySkull,mzSkull]).astype(int)
    SaveDict['dt']=dt
    if type(InputPData) is str:
        SaveDict['p_map']=pAmp*PressureRatio
        SaveDict['p_map_central']=pAmp[:,cy,:]*PressureRatio
    else:
        SaveDict['p_map']=InputsBHTE.max(axis=0)
        SaveDict['p_map_central']=InputsBHTE.max(axis=0)[:,cy,:]
    SaveDict['MaterialMap_central']=MaterialMap[:,cy,:]
    SaveDict['MaterialMap']=MaterialMap
    SaveDict['PressureRatio']=PressureRatio
    if AirMask is not None:
        SaveDict['AirMask']=AirMask

    TI=ResTemp[SelBrain].max()
    
    TIS=ResTemp[SelSkin].max()

    TIC=ResTemp[SelSkull].max()
    
    print('Max. Temp. Brain, Max Temp. Skin, Max Temp. Skull',TI,TIS,TIC);

    CEMBrain=FinalDose[SelBrain].max()/60 # in min
    
    CEMSkin=FinalDose[SelSkin].max()/60 # in min

    CEMSkull=FinalDose[SelSkull].max()/60 # in min
    
    print('CEMBrain,CEMSkin,CEMSkull',CEMBrain,CEMSkin,CEMSkull)

    maxPressureLocation=SaveDict['p_map'][SelBrain].argmax()

    MaxBrainPressure = SaveDict['p_map'][SelBrain][maxPressureLocation]
        
    MI=MaxBrainPressure/1e6/np.sqrt(Frequency/1e6)
    MaxIsppa=MaxBrainPressure**2/(2.0*SaveDict['MaterialList']['SoS'][BrainID]*SaveDict['MaterialList']['Density'][BrainID])
    MaxIsppa=MaxIsppa/1e4
    MaxIspta=DutyCycle*MaxIsppa

    Ispta =DutyCycle*Isppa

    SaveDict['MaxBrainPressure']=MaxBrainPressure
    if bForceHomogenousMedium:
        IndTarget=0
    elif len(BenchmarkTestFile)>0:
        if BenchmarkInput['TestType']==1:
            IndTarget=0
        else:
            IndTarget=1
    elif (cx==mxBrain and cy==myBrain and cz==mzBrain):
        IndTarget=2
    else:
        IndTarget=3
    if PreviousData is None:
        SaveDict['TimeProfileTarget']=np.arange(TemperaturePoints.shape[1])*dt
        SaveDict['TempProfileTarget']=TemperaturePoints[IndTarget,:]
        SaveDict['TemperaturePoints']=TemperaturePoints #these are max points in skin, brain, skull and target
    else:
        SaveDict['TimeProfileTarget']=np.hstack((PreviousData['TimeProfileTarget'],PreviousData['TimeProfileTarget'][-1]+dt+np.arange(TemperaturePoints.shape[1])*dt))
        SaveDict['TempProfileTarget']=np.hstack((PreviousData['TempProfileTarget'],TemperaturePoints[IndTarget,:]))
        SaveDict['TemperaturePoints']=np.hstack((PreviousData['TemperaturePoints'],TemperaturePoints))
        
    SaveDict['MI']=MI
    SaveDict['x_vec']=xf*1e3
    SaveDict['y_vec']=yf*1e3
    SaveDict['z_vec']=zf*1e3
    SaveDict['TI']=TI-BaselineTemperature
    SaveDict['TIC']=TIC-BaselineTemperature
    SaveDict['TIS']=TIS-BaselineTemperature
    SaveDict['CEMBrain']=CEMBrain
    SaveDict['CEMSkin']=CEMSkin
    SaveDict['CEMSkull']=CEMSkull
    SaveDict['MaxIsppa']=MaxIsppa
    SaveDict['MaxIspta']=MaxIspta
    SaveDict['Isppa']=Isppa
    SaveDict['Ispta']=Ispta
    SaveDict['TempEndFUS']=ResTemp
    SaveDict['DoseEndFUS']=ResDose
    SaveDict['FinalTemp']=FinalTemp
    SaveDict['FinalDose']=FinalDose
    #we carry over these params to simplify analysis later
    PhaseDataKeys={'BasePhasedArrayProgramming':'PhaseData',\
                  'BasePhasedArrayProgrammingRefocusing':'PhaseDataRefocusing'} #Matlab can handled long variable names
    if type(InputPData) is str: 
        for k in ['XSteering','YSteering','ZSteering']:
            if k in Input:
                SaveDict[k]=Input[k]
        for k in PhaseDataKeys:
            if k in Input:
                SaveDict[PhaseDataKeys[k]]=Input[k]
    else:
        for k in ['XSteering','YSteering','ZSteering']:
            if k in Input:
                steering =np.ones(len(ALL_ACFIELDSKULL))
                for n,entry in enumerate(ALL_ACFIELDSKULL):
                    steering[n]=entry[k]
                SaveDict[k]=steering
        for k in PhaseDataKeys:
            if k in Input:
                phasedata=[]
                for n,entry in enumerate(ALL_ACFIELDSKULL):
                    phasedata.append(entry[k])
                SaveDict[PhaseDataKeys[k]]=phasedata
    SaveDict['AdjustmentInRAS']=Input['AdjustmentInRAS']
    SaveDict['DistanceFromSkin']=Input['DistanceFromSkin']
    SaveDict['TxMechanicalAdjustmentZ']=Input['TxMechanicalAdjustmentZ']
    SaveDict['TargetLocation']=Input['TargetLocation']
    SaveDict['ZIntoSkinPixels']=Input['ZIntoSkinPixels']
    SaveDict['Frequency']=Frequency
    SaveDict['TxSystem']=TxSystem
    SaveDict['RatioLosses']=RatioLosses
    SaveDict['DurationUS']=DurationUS
    SaveDict['DurationOff']=DurationOff
    SaveDict['DutyCycle']=DutyCycle
    SaveDict['PRF']=PRF
    SaveDict['BaselineTemperature']=BaselineTemperature
    SaveDict['Repetitions']=Repetitions
    SaveDict['NumberGroupedSonications']=NumberGroupedSonications
    SaveDict['PauseBetweenGroupedSonications']=PauseBetweenGroupedSonications
    
    SaveToH5py(SaveDict,outfname+'.h5')
    savemat(outfname+'.mat',SaveDict)
    
    return outfname
        
