[docs]
def ozone_diagnostics(adfobj):
    """
    Generates an ozone-specific diagnostics based on the previous ncl-based 
    diagnostics package.  Current package plots ozone sesasonal cycle, profiles, and 
    taylor diagrams.
    Created 19 January, 2024
    Shawn Honomichl
    Associate Scientist III
    NCAR/ACOM
    shawnh@ucar.edu
    Functions
    ---------
    ozone_diagnostics(adfobj)
        use ADF object to make maps
    define_regions(InputRegion)
    define_stations(InputStation)
    open_process_sonde_data_simone(obsdir)
    open_process_sonde_data_simone_v2(obsdir)
    Subplot_O3(ax,X0,Y0,X1,Model0,Model1,SondeMean,SondeLow,SondeHigh,PLevel,plottype,case_base,case_test,ylim,Quadrant,Compare_Obs)
    Plot_Seasonal_Cycle_Profile(X0,X01,Y0,X1,X11,Y1,X2,X22,Y2,X3,X33,Y3,SondeMean0,SondeMean1,SondeMean2,SondeMean3,SondeLow0,SondeLow1,SondeLow2,SondeLow3,SondeHigh0,SondeHigh1,SondeHigh2,SondeHigh3,Model0I,Model01I,Model1I,Model11I,Model2I,Model21I,Model3I,Model31I,ylim0,ylim1,ylim2,ylim3,PLevel0,PLevel1,PLevel2,PLevel3,case_base,case_test,Station,oFile,plottype,Compare_Obs)
    get_model_data(ClimoFile)
    process_model_seasonal_cycle(MinLon,MaxLon,MinLat,MaxLat,Model_Dat,pnew,intyp,kxtrp,Station_Lons,Station_Lats)
    process_model_profiles(Model_Dat,O3_0,PS_0,pnew,intyp,kxtrp,ILAT,ILON,lat_0,lon_0)
    taylor_plot_setup(ax,baseline)
    taylor_stats_single(casedata, refdata)
    plot_taylor_data(wks, df,k, **kwargs)
    taylor_plot_finalize(wks, casenames, casecolors, syear_cases, eyear_cases, SubZones, needs_bias_labels=False,needs_case_labels=False,needs_subzone_labels=False)
        
        Parameters
        ----------
        adfobj : AdfDiag
            The diagnostics object that contains all the configuration information
        Returns
        -------
        Does not return a value; produces plots and saves files.
        Notes
        -----
        This function imports `pandas` and `plotting_functions`
        It uses the AdfDiag object's methods to get necessary information.
        Specificially:
        adfobj.diag_var_list
            List of variables
        adfobj.get_basic_info
            Regrid data path, checks `compare_obs`, checks `redo_plot`, checks `plot_press_levels`
        adfobj.plot_location
            output plot path
        adfobj.get_cam_info
            Get `cam_case_name` and `case_nickname`
        adfobj.climo_yrs
            start and end climo years of the case(s), `syears` & `eyears`
            start and end climo years of the reference, `syear_baseline` & `eyear_baseline`
        adfobj.var_obs_dict
            reference data (conditional)
        adfobj.get_baseline_info
            get reference case, `cam_case_name`
        adfobj.variable_defaults 
            dict of variable-specific plot preferences
        adfobj.read_config_var
            dict of basic info, `diag_basic_info`
            Then use to check `plot_type`
        adfobj.compare_obs
            Used to set data path
        adfobj.debug_log
            Issues debug message
        adfobj.add_website_data
            Communicates information to the website generator
        
    """
    #Import modules:
    import numpy as np
    import xarray as xr
    import warnings  # use to warn user about missing files.
    import os
    import matplotlib.pyplot as plt
    import matplotlib as mpl
    import geocat.comp as gcomp
    from scipy.interpolate import RegularGridInterpolator
    from scipy import stats
    from matplotlib.ticker import (MultipleLocator, AutoMinorLocator)
    import os.path
    import pandas as pd
    from matplotlib.lines import Line2D
    from matplotlib.legend_handler import HandlerTuple
    from scipy.stats import pearsonr
    import sys
    def my_formatwarning(msg, *args, **kwargs):
        # ignore everything except the message
        return str(msg) + '\n'
    warnings.formatwarning = my_formatwarning
    #Notify user that script has started:
    msg = "\n  Generating ozone plots..."
    print(f"{msg}\n  {'-' * (len(msg)-3)}")
    #-----------------------------------------------------------------------------------------
    #Lookup pertinent region info
    #-----------------------------------------------------------------------------------------
    #Region_Number : ['region short name', 'region long name',region_min_lat, region_max_lat,region_min_lon,region_max_lon]
    #NOTE that the sub-region is added for V1
    def define_regions(InputRegion):
    
        Region_Lookup={0 : ['not_assigned','not assigned','not assigned',0,0,0,0],
                        1 : ['nh_polar_west','NH Polar West','High Latitudes',70.0,90.0,-135.0+360,-45.0+360],
                        2 : ['nh_polar_east','NH Polar East','High Latitudes',58.0,90.0,-45.0,45.0],
                        3 : ['canada','Canada','High Latitudes',48.0,62.0,-135.0+360,-45.0+360],
                        4 : ['west_europe','Western Europe','Mid Latitudes',43.0,57.5,0.0,25.0],
                        5 : ['eastern_us','Eastern US','Mid Latitudes',34.0,40.0,-95.0+360,-75.0+360],
                        6 : ['japan','Japan','Mid Latitudes',30.0,40.0,120.0,150.0],
                        7 : ['nh_tropic','NH Sub Tropics','Tropics',15.0,30.0,90.0,225.0],
                        8 : ['tropics1','W-Pacific/E-Indian Ocean','Tropics',-20.0,0.0,90.0,225.0],
                        9 : ['tropics2','Equatorial Americas','Tropics',-15.0,15.0,225.0,315.0],
                        10 : ['tropics3','Atlantic/Africa','Tropics',-15.0,15.0,-45.0,45.0],
                        11 : ['sh_midlat','SH Midlatitudes','Mid Latitudes',-57.5,-40.0,135.0,180.0],
                        12 : ['sh_polar','SH Polar','High Latitudes',-90.0,-58.0,0,360],
                        13 : ['Boulder','Boulder','not assigned',37.0,42.0,-110.0+360.0,-100.0+360.0],
                        }
    
    #--------------------------------------------------------------------------------------
    #try to locate the region
    #--------------------------------------------------------------------------------------
    try:
        RegionInfo=Region_Lookup[InputRegion]
    except:
        print("Input region not defined") #if region not in the lookup then auto-assign to 0
        RegionInfo=Region_Lookup[0]
        
    #--------------------------------------------------------------------------------------
    #return the region info and the total number of regions in the table.
    #--------------------------------------------------------------------------------------
    return RegionInfo,len(Region_Lookup)-1
    #-----------------------------------------------------------------------------------------
    #Lookup pertinent ozonesonde station info
    #-----------------------------------------------------------------------------------------
    def define_stations(InputStation):
        #Station : ['country',lat N,lon E]
        Station_Lookup={0 : ['Alert','Canada',82.5,-62.33],
                        1 : ['Eureka','Canada',80.05,-86.42],
                        2 : ['Ny_Alesund','Norway',78.92,11.93],
                        3 : ['Resolute','Canada',74.7,-95.0],
                        4 : ['Scoresbysund','Greenland',70.48,-21.97],
                        5 : ['Lerwick','UK',60.13,-1.1+360.0],
                        6 : ['Churchill','Canada',58.77,-94.17],
                        7 : ['Edmonton','Canada',53.53,-113.5],
                        8 : ['Goose_Bay','Canada',53.29,-60.39],
                        9 : ['Legionowo','Poland',52.4,20.97],
                        10 : ['Lindenberg','Germany',52.21,14.12],
                        11 : ['DeBilt','Netherlands',52.10,5.18],
                        12 : ['Uccle','Belgium',50.8,4.35],
                        13 : ['Praha','Czech Republic',50.01,14.45],
                        14 : ['Hohenpeissenberg','Germany',47.80,11.02],
                        15 : ['Payerne','Switzerland',46.82,6.95],
                        16 : ['Madrid','Spain',40.45,-3.72],
                        17 : ['Boulder','Colorado',39.99,-105.26],
                        18 : ['Wallops_Island','Maryland',37.94,-75.46],
                        19 : ['Trinidad Head','California',41.05,-124.15],
                        20 : ['Huntsville','Alabama',34.73,-86.64],
                        21 : ['Sapporo','Japan',43.06,141.35],
                        22 : ['Tateno','Japan',36.05,140.13],
                        23 : ['Kagoshima','Japan',31.6,130.6],
                        24 : ['Naha','Japan',26.21,127.68],
                        25 : ['Hongkong','China',22.32,114.17],
                        26 : ['Paramaribo','Suriname',5.75,55.2],
                        27 : ['Hilo','USA',19.72,155.07],
                        28 : ['San Cristobal','Galapagos',-0.87,-89.44],
                        29 : ['Nairobi','Kenya',-1.29,36.82],
                        30 : ['Natal','Brazil',-5.83,-35.2],
                        31 : ['Ascension','Atlantic',-7.95,-14.36],
                        32 : ['Watukosek','Indonesia',-7.57,112.66],
                        33 : ['Samoa','Pacific',-14.25,-170.56],
                        34 : ['Fiji','Fiji',-17.71,178.07],
                        35 : ['Reunion','Africa',-21.1,55.4],
                        36 : ['Broadmeadows','Australia',-37.68,144.92],
                        37 : ['Lauder','New Zealand',-45.04,169.68],
                        38 : ['Macquarie','Australia',-54.50,158.95],
                        39 : ['Marambio','Antarctica',-64.0,-56.0],
                        40 : ['Neumayer','Antarctica',-70.62,8.37],
                        41 : ['Syowa','Antarctica',-69.01,39.59]}
        
    #--------------------------------------------------------------------------------------
    #try to locate the station
    #--------------------------------------------------------------------------------------
    try :
        StationInfo=Station_Lookup[InputStation]
    except:
        print("Input station not defined")
        
    return StationInfo,len(Station_Lookup)-1
    
    #-----------------------------------------------------------------------------------------
    #Process ozone sonde climatology
    #-----------------------------------------------------------------------------------------
    def open_process_sonde_data_simone(obsdir):
        #Grab and package all of the data that will be used for plotting
        NRegions=define_regions(0)[1]
        
        O3_MeanC=[]
        O3_WidthC=[]
        O3_StdDevC=[]
        Region=[]
        PressureC=[]
        
        for i in range(1,NRegions+1):
            Region_Info=define_regions(i)[0]
            ifileID=Region_Info[0]
            
            Region.append(ifileID)
            
            Check_File=obsdir+'ozonesondes_'+ifileID+'1995_2011.nc'
            
            if (os.path.isfile(Check_File)):
                print("Using Ozone Climatology File: ",Check_File)
                
                ds = xr.open_dataset(Check_File)
                O3_Mean = ds.o3_mean
                O3_Width = ds.o3_width
                O3_StdDev = ds.o3_std
                
                if (ifileID == 'Boulder'):
                    Pressure = ds.press
                else:
                    Pressure = ds.levels
                    
                if (i == 1):
                    O3_MeanC=O3_Mean
                    O3_WidthC=O3_Width
                    O3_StdDevC=O3_StdDev
                    PressureC=Pressure
                else:
                    O3_MeanC = np.dstack( (O3_MeanC,O3_Mean))
                    O3_WidthC = np.dstack( (O3_WidthC,O3_Width))
                    O3_StdDevC = np.dstack( (O3_StdDevC,O3_Width))
                    PressureC=np.dstack( (PressureC,Pressure))
            else:
                print("Error opening ozonesonde file ",Check_File)
                return -1
        
        return O3_MeanC,O3_WidthC,O3_StdDevC,np.squeeze(PressureC),Region
        #-----------------------------------------------------------------------------------------
        #Process ozone sonde climatology V2
        #-----------------------------------------------------------------------------------------
        def open_process_sonde_data_simone_v2(obsdir):
            #Grab and package all of the data that will be used for plotting
            NRegions=define_regions(0)[1]
            O3_MeanC=[]
            O3_WidthC=[]
            O3_StdDevC=[]
            Region=[]
            PressureC=[]
            for i in range(1,NRegions+1):
                Region_Info=define_regions(i)[0]
                ifileID=Region_Info[0]
                Region.append(ifileID)
                Check_File=obsdir+'ozonesondes_'+ifileID+'1995_2011.nc'
                if (os.path.isfile(Check_File)):
                    print("Using Ozone Climatology File: ",Check_File)
                    
                    ds = xr.open_dataset(Check_File)
                    O3_Mean = ds.o3_mean
                    O3_Width = ds.o3_width
                    O3_StdDev = ds.o3_std
                    
                    if (ifileID == 'Boulder'):
                        Pressure = ds.press
                        O3_Mean = O3_Mean.rename({'level':'press'})
                        O3_Width=O3_Width.rename({'level':'press'})
                        O3_StdDev=O3_StdDev.rename({'level':'press'})
                        Pressure = Pressure.rename({'level':'press'})
                    else:
                        Pressure = ds.levels
                    if (i == 1):
                        O3_MeanC=O3_Mean
                        O3_WidthC=O3_Width
                        O3_StdDevC=O3_StdDev
                        PressureC=Pressure
                    else:
                        O3_MeanC = xr.concat([O3_MeanC,O3_Mean],'z')
                        O3_WidthC = xr.concat([O3_WidthC,O3_Width],'z')
                        O3_StdDevC = xr.concat([O3_StdDevC,O3_StdDev],'z')
                        PressureC=xr.concat([PressureC,Pressure],'z')
                else:
                    print("Error opening ozonesonde file ",Check_File)
                    return -1
        return O3_MeanC,O3_WidthC,O3_StdDevC,PressureC,Region
    #-----------------------------------------------------------------------------------------
    #Subroutine - plots the ozone data subplots
    #-----------------------------------------------------------------------------------------
    def Subplot_O3(ax,X0,Y0,X1,Model0,Model1,SondeMean,SondeLow,SondeHigh,PLevel,plottype,case_base,case_test,ylim,Quadrant,Compare_Obs):
        #plottype == 0 ; Seasonal Cycle
        #plottype == 1 ; Profiles
        
        #Blue is the base case  - if comparing obs will not be plotted
        #Red is the test case - should always be plotted
        Model_linewidth=0.75
        
        if plottype == 0:
            ax.plot(Y0,X1,'tab:red',linestyle='dashed',linewidth=Model_linewidth)
            ax.plot(Y0,Model1,'tab:red',linestyle='solid',linewidth=Model_linewidth)
            if Compare_Obs <= 0:
                ax.plot(Y0,X0,'tab:blue',linestyle='dashed',linewidth=Model_linewidth)
                ax.plot(Y0,Model0,'tab:blue',linestyle='solid',linewidth=Model_linewidth)
                
            ax.set_title(PLevel,loc='left')
            
            props = dict(boxstyle='round',facecolor='wheat',alpha=0.5)
            if Compare_Obs <= 0:
                ax.text(0.05,0.95,"     Mean:                                            \nAbs. Diff:\n            r:",transform=ax.transAxes,fontsize=5,verticalalignment='top',bbox=props,color='black')
            else:
                ax.text(0.05,0.95,"     Mean:                                \nAbs. Diff:\n            r:",transform=ax.transAxes,fontsize=5,verticalalignment='top',bbox=props,color='black')
            
            meanS0=np.mean(SondeMean)
            meanS0S='%.1f' % meanS0
            
            meanX01=np.mean( [X1,Model1])
            meanX01S='%.1f' % meanX01
            AbsDif1=np.absolute(meanX01-meanS0)
            AbsDif1S='%.1f' % AbsDif1
            PCorr01=stats.pearsonr(X1,SondeMean)
            PCorr01S='%.2f' % PCorr01[0]
            
            if Compare_Obs <= 0:
                ax.text(0.70,0.92,meanX01S,fontsize=5,color='tab:red',transform=ax.transAxes)
                ax.text(0.70,0.86,AbsDif1S,fontsize=5,color='tab:red',transform=ax.transAxes)
                ax.text(0.70,0.80,PCorr01S,fontsize=5,color='tab:red',transform=ax.transAxes)
            else:
                ax.text(0.50,0.92,meanX01S,fontsize=5,color='tab:red',transform=ax.transAxes)
                ax.text(0.50,0.86,AbsDif1S,fontsize=5,color='tab:red',transform=ax.transAxes)
                ax.text(0.50,0.80,PCorr01S,fontsize=5,color='tab:red',transform=ax.transAxes)
            
            ax.text(0.30,0.92,meanS0S,fontsize=5,color='black',transform=ax.transAxes)
            
            if Compare_Obs <= 0:
                
                meanX0=np.mean([X0,Model0])
                meanX0S='%.1f' % meanX0
                AbsDif0=np.absolute(meanX0-meanS0)
                AbsDif0S='%.1f' % AbsDif0
                PCorr0=stats.pearsonr(X0,SondeMean)
                PCorr0S='%.2f' % PCorr0[0]
            
                ax.text(0.50,0.92,meanX0S,fontsize=5,color='tab:blue',transform=ax.transAxes)
                ax.text(0.50,0.86,AbsDif0S,fontsize=5,color='tab:blue',transform=ax.transAxes)
                ax.text(0.50,0.80,PCorr0S,fontsize=5,color='tab:blue',transform=ax.transAxes)
            
            #legend (bottom left) side of bottom left plot
            if Quadrant == 2:
                if Compare_Obs >0:
                    ax.text(0.12,0.15,"Region Avg.",fontsize=5.0,color='tab:red',transform=ax.transAxes)
                    ax.text(0.12,0.09,case_test,fontsize=5.0,color='tab:red',transform=ax.transAxes)
                    ax.plot([0.03,0.11],[0.16,0.16],'tab:red',linestyle='dashed',transform=ax.transAxes,linewidth=0.75)
                    ax.plot([0.03,0.11],[0.10,0.10],'tab:red',linestyle='solid',transform=ax.transAxes,linewidth=0.75)
                else:
                    ax.text(0.12,0.27,"Region Avg.",fontsize=5.0,color='tab:blue',transform=ax.transAxes)
                    ax.text(0.12,0.21,case_base,fontsize=5.0,color='tab:blue',transform=ax.transAxes)
                    ax.plot([0.03,0.11],[0.28,0.28],'tab:blue',linestyle='dashed',transform=ax.transAxes,linewidth=0.75)
                    ax.plot([0.03,0.11],[0.22,0.22],'tab:blue',linestyle='solid',transform=ax.transAxes,linewidth=0.75)
                    ax.text(0.12,0.15,"Region Avg.",fontsize=5.0,color='tab:red',transform=ax.transAxes)
                    ax.text(0.12,0.09,case_test,fontsize=5.0,color='tab:red',transform=ax.transAxes)
                    ax.plot([0.03,0.11],[0.16,0.16],'tab:red',linestyle='dashed',transform=ax.transAxes,linewidth=0.75)
                    ax.plot([0.03,0.11],[0.10,0.10],'tab:red',linestyle='solid',transform=ax.transAxes,linewidth=0.75)
                    
                ax.plot([0.06],[0.04],'ko',markersize=1.25,transform=ax.transAxes)
                ax.text(0.12,0.03,"Ozonesondes",fontsize=5.0,color='black',transform=ax.transAxes)
            
            #plot the ozonesonde data for a type 0 plot
            ax.plot(Y0,SondeMean,'ko',markersize=1.25)
            for i in range(1,13):
                ax.plot([i,i],[SondeLow[i-1],SondeHigh[i-1]],color='black',linestyle='solid',linewidth=1)
                
            #set the appropriate x/y axis information
            ax.set(xlabel="Month", ylabel="Ozone (ppb)", xlim=(1,12),ylim=ylim,xscale='linear',yscale='linear')
            ax.xaxis.set_major_locator(MultipleLocator(2))
            
        else:
            ax.plot(X1,Y0,'tab:red',linestyle='dashed',linewidth=Model_linewidth)
            ax.plot(Model1,Y0,'tab:red',linestyle='solid',linewidth=Model_linewidth)
            ax.set_title(PLevel,loc='left')
            
            if Compare_Obs <= 0:
                ax.plot(X0,Y0,'tab:blue',linestyle='dashed',linewidth=Model_linewidth)
                ax.plot(Model0,Y0,'tab:blue',linestyle='solid',linewidth=Model_linewidth)
            
            if Quadrant == 2: 
                offset=0.67
                if Compare_Obs > 0:
                    ax.text(0.12,0.16+offset,"Ozonesondes",fontsize=5.0,color='black',transform=ax.transAxes)
                    ax.plot([0.06],[0.17+offset],'ko',markersize=1.25,transform=ax.transAxes)
                else:
                    ax.text(0.12,0.03+offset,"Ozonesondes",fontsize=5.0,color='black',transform=ax.transAxes)
                    ax.plot([0.06],[0.04+offset],'ko',markersize=1.25,transform=ax.transAxes)
                
                ax.text(0.12,0.27+offset,"Region Avg.",fontsize=5.0,color='tab:red',transform=ax.transAxes)
                ax.text(0.12,0.21+offset,case_test,fontsize=5.0,color='tab:red',transform=ax.transAxes)
                ax.plot([0.03,0.11],[0.28+offset,0.28+offset],'tab:red',linestyle='dashed',transform=ax.transAxes,linewidth=0.75)
                ax.plot([0.03,0.11],[0.22+offset,0.22+offset],'tab:red',linestyle='solid',transform=ax.transAxes,linewidth=0.75)
                
                
                if Compare_Obs <= 0:
                    ax.text(0.12,0.15+offset,"Region Avg.",fontsize=5.0,color='tab:blue',transform=ax.transAxes)
                    ax.text(0.12,0.09+offset,case_base,fontsize=5.0,color='tab:blue',transform=ax.transAxes)
                    ax.plot([0.03,0.11],[0.16+offset,0.16+offset],'tab:blue',linestyle='dashed',transform=ax.transAxes,linewidth=0.75)
                    ax.plot([0.03,0.11],[0.10+offset,0.10+offset],'tab:blue',linestyle='solid',transform=ax.transAxes,linewidth=0.75)
                
            #plot the ozonesonde data for a type 1 plot
            Step=1
            SondeMean=SondeMean[1::Step]
            Y00=Y0[1::Step]
            SondeLow=SondeLow[1::Step]
            SondeHigh=SondeHigh[1::Step]
            ax.plot(SondeMean,Y00,'ko',markersize=1.25)
            for i in range(0,len(Y00)):
                ax.plot([SondeLow[i],SondeHigh[i]],[Y00[i],Y00[i]],color='black',linestyle='solid',linewidth=1)
                
            #set the appropriate x/y axis information
            ax.set(xlabel="Ozone (ppb)", ylabel="Pressure (hPa)", xlim=ylim,ylim=[1000.0,0.0],xscale='linear',yscale='linear')
            
        #Set tick parameters
        ax.xaxis.set_minor_locator(AutoMinorLocator())
        ax.yaxis.set_minor_locator(AutoMinorLocator())
        ax.tick_params(top=True, bottom=True, left=True, right=True)
            
        return True
    #-----------------------------------------------------------------------------------------
    #Subroutine - plot the seasonal cycle
    #-----------------------------------------------------------------------------------------
    def Plot_Seasonal_Cycle_Profile(X0,X01,Y0,X1,X11,Y1,X2,X22,Y2,X3,X33,Y3,SondeMean0,SondeMean1,SondeMean2,SondeMean3,SondeLow0,SondeLow1,SondeLow2,SondeLow3,SondeHigh0,SondeHigh1,SondeHigh2,SondeHigh3,Model0I,Model01I,Model1I,Model11I,Model2I,Model21I,Model3I,Model31I,ylim0,ylim1,ylim2,ylim3,PLevel0,PLevel1,PLevel2,PLevel3,case_base,case_test,Station,oFile,plottype,Compare_Obs):
        
        #--------------------------------------------------------------------------------------
        #set the default font size for the plots
        #--------------------------------------------------------------------------------------
        mpl.rcParams['font.size'] = 8
        plt.rcParams['figure.figsize'] = [6.4,4.8]
        
        #--------------------------------------------------------------------------------------
        #set the subplots
        #--------------------------------------------------------------------------------------
        fig, axs = plt.subplots(nrows=2,ncols=2,sharex=False,sharey=False)
        
        #--------------------------------------------------------------------------------------
        #Primary subplots
        #--------------------------------------------------------------------------------------
        PTL = Subplot_O3(axs[0,0],X0,Y0,X01,Model0I,Model01I,SondeMean0,SondeLow0,SondeHigh0,PLevel0,plottype,case_base,case_test,ylim0,0,Compare_Obs)#top left quadrant 0
        PTR = Subplot_O3(axs[0,1],X1,Y1,X11,Model1I,Model11I,SondeMean1,SondeLow1,SondeHigh1,PLevel1,plottype,case_base,case_test,ylim1,1,Compare_Obs)#top right quadrant 1
        PBL = Subplot_O3(axs[1,0],X2,Y2,X22,Model2I,Model21I,SondeMean2,SondeLow2,SondeHigh2,PLevel2,plottype,case_base,case_test,ylim2,2,Compare_Obs)#bottom left quadrant 2
        PBR = Subplot_O3(axs[1,1],X3,Y3,X33,Model3I,Model31I,SondeMean3,SondeLow3,SondeHigh3,PLevel3,plottype,case_base,case_test,ylim3,3,Compare_Obs)#bottom right quadrant 3
        
        #--------------------------------------------------------------------------------------
        #A couple of other plotting adjustments
        #--------------------------------------------------------------------------------------
        fig.suptitle(Station)
        plt.subplots_adjust(wspace=0.4,hspace=0.4,left=0.3) #Adjusts the spacing between the plots
        plt.rcParams['font.size'] = '7'
        
        Output_IMG = oFile #set the output filename
        
        #--------------------------------------------------------------------------------------
        #Try to save the plot as an image file
        #--------------------------------------------------------------------------------------
        try:
                plt.savefig(Output_IMG, bbox_inches = 'tight', dpi = 200, format = 'png') #there will be more to add here...
                plt.clf() 
                plt.close(fig)
        except:
                print("ERROR: Could not save file ",Output_IMG,flush=True)
                
        return True
    #-----------------------------------------------------------------------------------------
    #Subroutine - retrieve model data from a netCDF file
    #-----------------------------------------------------------------------------------------
    def get_model_data(ClimoFile):
        class Model_Data:
            #Get the Reference Case data
            if (os.path.isfile(ClimoFile)):
                print("ADF Climatology File Located: ",ClimoFile)
                
                ds = xr.open_dataset(ClimoFile)
                lon = ds.lon
                lat = ds.lat
                hyam = ds.hyam
                hybm = ds.hybm
                o3 = ds.O3
                ps = ds.PS
        
                #these typically won't change with time so only select the time 0
                hyam=hyam[0,:]
                hybm=hybm[0,:]
            
        MDAT=Model_Data()
        return MDAT
        
    #-----------------------------------------------------------------------------------------
    #Subroutine - Process Model Seasonal Cycle 
    #-----------------------------------------------------------------------------------------
    def process_model_seasonal_cycle(MinLon,MaxLon,MinLat,MaxLat,Model_Dat,pnew,intyp,kxtrp,Station_Lons,Station_Lats):
        class model_dat_proc:
            #get the model data from the base and test cases for the region
            if (MinLon < 0 and MaxLon > 0): #if the region crosses the date line - do different processing
                O3_00=Model_Dat.o3.sel(lon=slice(MinLon+360.0,360.0),lat=slice(MinLat,MaxLat))
                O3_01=Model_Dat.o3.sel(lon=slice(0,MaxLon),lat=slice(MinLat,MaxLat))
                O3_0 = xr.concat([O3_00, O3_01], dim='lon') #Changed for using geocat.comp.interpolation function
                PS_00=Model_Dat.ps.sel(lon=slice(MinLon+360.0,360.0),lat=slice(MinLat,MaxLat))
                PS_01=Model_Dat.ps.sel(lon=slice(0,MaxLon),lat=slice(MinLat,MaxLat))
                PS_0 = xr.concat([PS_00, PS_01], dim='lon') #Changed for using geocat.comp.interpolation function
                lon_00=Model_Dat.lon.sel(lon=slice(MinLon+360.0,360.0))
                lon_01=Model_Dat.lon.sel(lon=slice(0,MaxLon))
                lon_0 = xr.concat([lon_00, lon_01], dim='lon') #Changed for using geocat.comp.interpolation function
            
                #resort the arrays as needed
                lon_sort=np.concatenate((lon_00,lon_01)).argsort() #Changed for using geocat.comp.interpolation function
                O3_0 = O3_0.isel(lon=lon_sort)
                PS_0 = PS_0.isel(lon=lon_sort)
                lon_0 = lon_0.isel(lon=lon_sort)
            
                O3_sfc=O3_0.isel(lev=[-1]).squeeze()*1.0e9 #get the lowest model surface level data
            
            else: #if the region does not cross the date line
            
                O3_0=Model_Dat.o3.sel(lon=slice(MinLon,MaxLon),lat=slice(MinLat,MaxLat))
                PS_0=Model_Dat.ps.sel(lon=slice(MinLon,MaxLon),lat=slice(MinLat,MaxLat))
                lon_0=Model_Dat.lon.sel(lon=slice(MinLon,MaxLon))
                O3_sfc=O3_0.isel(lev=[-1]).squeeze()*1.0e9
            lat_0=Model_Dat.lat.sel(lat=slice(MinLat,MaxLat))
            O3_sfc_04=np.mean(O3_sfc,axis=(1,2))
            
            O3_0I = gcomp.interpolation.interp_hybrid_to_pressure(O3_0,PS_0,Model_Dat.hyam,Model_Dat.hybm,new_levels=np.array(pnew),method='linear',p0=100000.0,extrapolate=False)*1.0e9
        
            #Get the seasonal cycle of the base case at each needed pressure level
            #and average over the region.
            O3_01=np.mean(O3_0I[:,0,:,:],axis=(1,2))
            O3_02=np.mean(O3_0I[:,1,:,:],axis=(1,2))
            O3_03=np.mean(O3_0I[:,2,:,:],axis=(1,2))
            O3_04=np.mean(O3_0I[:,3,:,:],axis=(1,2))
        
            #Interpolate the model at each ozonesonde location and extract the needed information for the plot
            Station_Find = np.where( (Station_Lons >= MinLon) & (Station_Lons <= MaxLon) & (Station_Lats >= MinLat) & (Station_Lats <= MaxLat))
            ILAT=Station_Lats[Station_Find]
            ILON=Station_Lons[Station_Find]
        
            #Set the points to interpolate to
            for i in range(1,13):
                for j in range(0,len(ILAT)):
                    if (i == 1 and j == 0):
                        O3_Pt=[i,float(ILAT[j]),float(ILON[j])]
                    else:
                        O3_Pt=np.vstack( (O3_Pt,[i,float(ILAT[j]),float(ILON[j])] ))
        
            months=[1,2,3,4,5,6,7,8,9,10,11,12]
        
            #set up the regular grid interpolator for each case and level
            
            O3_0I=O3_0I.values
            lat_0=lat_0.values
            lon_0=lon_0.values
            O3_sfc=O3_sfc.values
            
            interp_0 = RegularGridInterpolator((months,lat_0,lon_0), np.squeeze(O3_0I[:,0,:,:]))
            interp_1 = RegularGridInterpolator((months,lat_0,lon_0), np.squeeze(O3_0I[:,1,:,:]))
            interp_2 = RegularGridInterpolator((months,lat_0,lon_0), np.squeeze(O3_0I[:,2,:,:]))
            interp_3 = RegularGridInterpolator((months,lat_0,lon_0), O3_sfc)
        
            #interpolate the model data at each case and pressure level
            O3_station_0 = interp_0(O3_Pt)
            O3_station_1 = interp_1(O3_Pt)
            O3_station_2 = interp_2(O3_Pt)
            O3_station_3 = interp_3(O3_Pt)
        
            #loop through each station interpolation and average like months
            O3M=np.squeeze(O3_Pt[:,0])
        
            O3_station_00=[]
            O3_station_10=[]
            O3_station_20=[]
            O3_station_30=[]
        
            for i in range(1,13):
                Elms = np.where(O3M == i)
                O3_station_00.append(np.mean(O3_station_0[Elms]))
                O3_station_10.append(np.mean(O3_station_1[Elms]))
                O3_station_20.append(np.mean(O3_station_2[Elms]))
                O3_station_30.append(np.mean(O3_station_3[Elms]))
        
            #For consistency convert the O3_station variables into numpy arrays
            O3_station_00=np.array(O3_station_00)
            O3_station_10=np.array(O3_station_10)
            O3_station_20=np.array(O3_station_20)
            O3_station_30=np.array(O3_station_30)
        
        Processed_Model_Dat=model_dat_proc()
        
        return Processed_Model_Dat
        
    #-----------------------------------------------------------------------------------------
    #Subroutine - Process Model Profiles
    #-----------------------------------------------------------------------------------------
    def process_model_profiles(Model_Dat,O3_0,PS_0,pnew,intyp,kxtrp,ILAT,ILON,lat_0,lon_0):
        class model_dat_proc:
        
            O3_0I1 = gcomp.interpolation.interp_hybrid_to_pressure(data=O3_0,hyam=Model_Dat.hyam,hybm=Model_Dat.hybm,new_levels=np.array(pnew*100.0),ps=PS_0,method='linear',p0=100000.0,extrapolate=False)*1.0e9
            O3_0I1=O3_0I1.values
            
            Locate_Bad=np.where(O3_0I1 > 10000.0)
            if len(Locate_Bad) > 0:
                O3_0I1[Locate_Bad]=np.nan
            
            #Get the monthly of the case at each needed pressure level
            #and average over the region.
            O3_011=np.nanmean(O3_0I1[0,:,:,:],axis=(1,2))
            O3_021=np.nanmean(O3_0I1[3,:,:,:],axis=(1,2))
            O3_031=np.nanmean(O3_0I1[6,:,:,:],axis=(1,2))
            O3_041=np.nanmean(O3_0I1[9,:,:,:],axis=(1,2))
            
            #Set the points to interpolate to
            for i in range(0,len(pnew)):
                for j in range(0,len(ILAT)):
                    if (i == 0 and j == 0):
                        O3_Pt=[pnew[i],float(ILAT[j]),float(ILON[j])]
                    else:
                        O3_Pt=np.vstack( (O3_Pt,[pnew[i],float(ILAT[j]),float(ILON[j])] ))
            
            #set up the regular grid interpolator for each case and level
            interp_01 = RegularGridInterpolator((pnew,lat_0,lon_0), np.squeeze(O3_0I1[0,:,:,:]))
            interp_11 = RegularGridInterpolator((pnew,lat_0,lon_0), np.squeeze(O3_0I1[3,:,:,:]))
            interp_21 = RegularGridInterpolator((pnew,lat_0,lon_0), np.squeeze(O3_0I1[6,:,:,:]))
            interp_31 = RegularGridInterpolator((pnew,lat_0,lon_0), np.squeeze(O3_0I1[9,:,:,:]))
            
            #interpolate the model data at each case and pressure level
            O3_station_01 = interp_01(O3_Pt)
            O3_station_11 = interp_11(O3_Pt)
            O3_station_21 = interp_21(O3_Pt)
            O3_station_31 = interp_31(O3_Pt)
            
            #loop through each station interpolation and average like plevs
            O3M=np.squeeze(O3_Pt[:,0])
            
            O3_station_001=[]
            O3_station_101=[]
            O3_station_201=[]
            O3_station_301=[]
            
            for i in range(0,len(pnew)):
                Elms = np.where(O3M == pnew[i])
                O3_station_001.append(np.nanmean(O3_station_01[Elms]))
                O3_station_101.append(np.nanmean(O3_station_11[Elms]))
                O3_station_201.append(np.nanmean(O3_station_21[Elms]))
                O3_station_301.append(np.nanmean(O3_station_31[Elms]))
            #For consistency convert the O3_station variables into numpy arrays
            O3_station_001=np.array(O3_station_001)
            O3_station_101=np.array(O3_station_101)
            O3_station_201=np.array(O3_station_201)
            O3_station_301=np.array(O3_station_301)
            
            Processed_Model_Dat=model_dat_proc()
        
        return Processed_Model_Dat
        
    #-----------------------------------------------------------------------------------------
    #Sets up the Taylor Diagram Plot
    #-----------------------------------------------------------------------------------------
    def taylor_plot_setup(ax,baseline):
        """Constructs Figure and Axes objects for basic Taylor Diagram."""
        corr_labels = np.array([0.0, .1, .2, .3, .4, .5, .6, .7, .8, .9, .95, .99, 1.])
        corr_locations = np.pi/2 - np.arccos((corr_labels))  # azim. ticks in radians.
        ax.set_thetamin(0)
        ax.set_thetamax(90)
        ax.set_ylim([0, 1.6])  # Works better than set_rmin / set_rmax
        ax.set_theta_zero_location("N") # zero at top,
        ax.set_theta_direction(-1)  # angle increases clockwise
        thetalines, thetalabels = ax.set_thetagrids(np.degrees(corr_locations), corr_labels)
        ax.grid(axis='x', linewidth=0)  # turn off radial grid
        ax.set_rgrids(np.arange(-0.5, 2.75, .50))
        ax.set_ylabel("Difference from Mean [Normalized]")
        # Add tick marks along azimuth
        tick = [ax.get_rmax(),ax.get_rmax()*0.97]
        for t in corr_locations:
            ax.plot([t,t], tick, lw=0.72, color="k")
        ax.text(np.radians(50), ax.get_rmax()*1.1, "Correlation", ha='center', rotation=-50, fontsize=8)
        ax.text(np.radians(95), 1.0, "REF", ha='center')
        ax.set_title(baseline, fontsize=8,pad=5)
        
        return True
    #-----------------------------------------------------------------------------------------
    #Calculate the correlations, sigmas, and bias
    #-----------------------------------------------------------------------------------------
    def taylor_stats_single(casedata, refdata):
        """This replicates the basic functionality of 'taylor_stats' from NCL.
        input:
            casedata : input data, DataArray
            refdata  : reference case data, DataArray
            w        : if true use cos(latitude) as spatial weight, if false assume uniform weight
        returns:
            pattern_correlation, ratio of standard deviation (case/ref), bias
        """
        correlation = pearsonr(casedata.values,refdata.values).statistic**2 #this pearson correlation seems to work the best
        a_sigma = casedata.std()
        b_sigma = refdata.std()
        mean_case=casedata.mean()
        mean_ref=refdata.mean()
        
        mean_diff = 1.0 + (mean_case - mean_ref) / mean_ref
        
        bias = (100*((mean_case - mean_ref)/mean_ref)).item()
        
        ratio = a_sigma/b_sigma
        if ratio > 2.5:
            ratio=2.5
        
        return correlation, ratio, bias, mean_diff
    #-----------------------------------------------------------------------------------------
    #Plot the Taylor Data
    #-----------------------------------------------------------------------------------------
    def plot_taylor_data(wks, df,k, **kwargs):
        """Apply data on top of the Taylor Diagram Axes.
            wks -> Axes object, probably from taylor_plot_setup
            df  -> DataFrame holding the Taylor stats.
            kwargs -> optional arguments
            look for 'use_bias'
            look for 'case_color'
        """
        # option is whether to stylize the markers by the bias:
        use_bias = False
        if 'use_bias' in kwargs:
            if kwargs['use_bias']:
                use_bias = True
                df['bias_digi'] = np.digitize(df['bias'].values, [-20, -10, -5, -1, 1, 5, 10, 20])
                marker_list = ["v", "v", "v", "v", "o", "^", "^", "^", "^"]
                marker_size = [8, 4, 2, 1, 1, 1, 2, 4, 8]
        # option: has color been specified as case_color?
        # --> expect the case labeling to be done external to this function
        if 'case_color' in kwargs:
            color = kwargs['case_color']
            if isinstance(color, int):
                # assume we should use this as an index
                color = mpl.cm.tab20(color) # convert to RGBA
                # TODO: allow colormap to be specified.
        
        #base case
        annos = []  # list will hold strings for legend
        #k = 1
        for ndx, row in df.iterrows():
            # NOTE: ndx will be the DataFrame index, and we expect that to be the variable name
            theta = np.pi/2 - np.arccos(row['corr'])  # Transform DATA
            if use_bias:
                mk = marker_list[row['bias_digi']]
                mksz = marker_size[row['bias_digi']]
                wks.plot(theta, row['mean_diff'], marker=mk, markersize=mksz, color=color)
            else:
                wks.plot(theta, row['mean_diff'], marker='o', markersize=6, color=color)
            annos.append(f"{k} - {ndx.replace('_','')}")
            wks.annotate(str(k), (theta, row['mean_diff']), ha='center', va='bottom',xytext=(0,5), textcoords='offset points', fontsize='x-large',color=color)
            k += 1  # increment the annotation number (THIS REQUIRES CASES TO HAVE SAME ORDER IN DataFrame)
        
        return wks
    #-----------------------------------------------------------------------------------------
    #Plot out the final components of the Taylor Diagrams
    #-----------------------------------------------------------------------------------------
    def taylor_plot_finalize(wks, casenames, casecolors, syear_cases, eyear_cases, SubZones, needs_bias_labels=False,needs_case_labels=False,needs_subzone_labels=False):
        """Apply final formatting to a Taylor diagram.
            wks -> Axes object that has passed through taylor_plot_setup and plot_taylor_data
            casenames -> list of case names for the legend
            casecolors -> list of colors for the cases
            needs_bias_labels -> Bool, if T make the legend for the bias-sized markers.
        """
        # CASE LEGEND -- Color-coded
        bottom_of_text = 0.05
        height_of_lines = 0.08
        
        n = 0
        
        #CASE LEGEND
        if needs_case_labels:
            for case_idx, (s, c) in enumerate(zip(casenames, casecolors)):
                text = wks.text(0.052, bottom_of_text + n*height_of_lines, s,
                color=c, ha='left', va='bottom', transform=wks.transAxes, fontsize=8)
                n += 1
                
        # BIAS LEGEND
        if needs_bias_labels:
            # produce an info-box showing the markers/sizes based on bias
            bias_legend_elements = [(Line2D([0], [0], marker="v", color='k', label="> 20%", markersize=8, fillstyle='none', linewidth=0), Line2D([0], [0], marker="^", color='k', label="> 20%", markersize=8, fillstyle='none', linewidth=0)),
                                    (Line2D([0], [0], marker="v", color='k', label="10-20%", markersize=4, linewidth=0), Line2D([0], [0], marker="^", color='k', label="10-20%", markersize=4, linewidth=0)),
                                    (Line2D([0], [0], marker="v", color='k', label="5-10%", markersize=2, linewidth=0), Line2D([0], [0], marker="^", color='k', label="5-10%", markersize=2, linewidth=0)),
                                    (Line2D([0], [0], marker="v", color='k', label=">1-5%", markersize=1, linewidth=0), Line2D([0], [0], marker="^", color='k', label=">1-5%", markersize=1, linewidth=0)),
                                    Line2D([0], [0], marker="o", color='k', label="< 1%", markersize=1, linewidth=0),
                                    ]
            bias_legend_labels = ["> 20%", "10-20%", "5-10%", "1-5%", "< 1%"]
            wks.legend(handles=bias_legend_elements, labels=bias_legend_labels, loc='lower left', handler_map={tuple: HandlerTuple(ndivide=None, pad=1.)}, labelspacing=1, handletextpad=1, frameon=False, title="-/+ Bias",
                        title_fontsize=10)
        
        #SubZone Legend
        n = 0
        if needs_subzone_labels:
            for k in range(0,len(SubZones)):
                text=wks.text(0.052,bottom_of_text + n*height_of_lines,str(k+1)+' '+SubZones[k],
                ha='left', va='bottom', transform=wks.transAxes, fontsize=8)
                n += 1
        
        return wks
    #-----------------------------------------------------------------------------------------
    #Primary Ozone Diagnostics Routine
    #-----------------------------------------------------------------------------------------
    def ozone_diagnostics (adfobj):
    
        #----------------------------------------------------------------------------------
        #Extract relevant info from the ADF
        #----------------------------------------------------------------------------------
        obsdir='/glade/campaign/acom/acom-climate/tilmes/obs_data/amwg/amwg_data/obs_data/cam-chem/' #location of the ozonesonde data files
        cam_climo_loc = adfobj.get_cam_info('cam_climo_loc',required=True)[0]
        baseline_climo_loc = adfobj.get_baseline_info('cam_climo_loc',required=True)
        
        #check the number of cases.  For the O3 diagnostics there needs to be 2.
        #If there is more than 2, then only the first two will be used.
        case_test = adfobj.get_cam_info('cam_case_name', required=True)[0]
        case_base = adfobj.get_baseline_info('cam_case_name',required=True)
        
        #Grab all case nickname(s)
        test_nicknames = adfobj.case_nicknames["test_nicknames"]
        base_nickname = adfobj.case_nicknames["base_nickname"]
        
        plot_locations = adfobj.plot_location[0]
        
        climo_base=baseline_climo_loc+'/'+case_base+"_O3_climo.nc"
        if os.path.isfile(climo_base):
            print(climo_base," located")
        else:
            print(climo_base," could not be located.  Exiting O3 diagnostics.")
            return
        
        climo_test=cam_climo_loc+'/'+case_test+"_O3_climo.nc"
        if os.path.isfile(climo_test):
            print(climo_test," located")
        else:
            print(climo_test," could not be located. Exiting O3 diagnostics.")
            return
            
    #check if existing plots need to be redone
    redo_plot = adfobj.get_basic_info('redo_plot')
    print(f"\t NOTE: redo_plot is set to {redo_plot}")
                
    #Compare_Obs=0 #initially assumes that user is comparing two models
    Compare_Obs=0 #initially assumes that user is comparing two models
    if adfobj.compare_obs:
        Compare_Obs=1 #if comparing a model with observations then don't need to include the base case in the plots
    Compare_Obs=adfobj.compare_obs
    print("-------------------------------")
    print("Processing Ozone Diagnostics...")
    print("-------------------------------")
    
    #--------------------------------------------------------------------------------------
    #Check and make sure that if the O3 Diagnostics are selected in the .yaml file that
    #ozone (O3) is listed in the diag_var_list.
    #--------------------------------------------------------------------------------------
    if not ('O3' in adfobj.diag_var_list):
        msg = "No ozone ('O3') variable present"
        msg += " in 'diag_var_list', so O3 diagnostic plots will"
        msg += " be skipped."
        print(msg)
        return
    
    #--------------------------------------------------------------------------------------
    #Get the ozone sonde data from all of the stations
    #--------------------------------------------------------------------------------------
    Data = open_process_sonde_data_simone(obsdir)
    Obs_Mean=Data[0] #mean values
    Obs_Width=Data[1] #width of the data
    Obs_StdDev=Data[2] #Standard deviation of the data
    Obs_Pressure=Data[3] #Pressure of the obs data
    Regions=Data[4] #regions
    
    #--------------------------------------------------------------------------------------
    #Get model data from get_model_data()
    #--------------------------------------------------------------------------------------
    Test_Data=get_model_data(climo_test) #Grab test model data
    if Compare_Obs <= 0:
        Base_Data=get_model_data(climo_base) #Grab base model data
    
    #--------------------------------------------------------------------------------------   
    #Loop through each station and get the station coordinates
    #--------------------------------------------------------------------------------------
    Station_Lats=[]
    Station_Lons=[]
    Station_SNames=[]
    
    NStations=define_stations(0)[1]
    for i in range(0,NStations+1):
        Station_Info=define_stations(i)[0]
        if (Station_Info[3] < 0.0): 
            Station_Lons=np.append(Station_Lons,float(Station_Info[3])+360.0)
        else:
            Station_Lons=np.append(Station_Lons,float(Station_Info[3]))
        Station_Lats=np.append(Station_Lats,float(Station_Info[2]))
        Station_SNames=np.append(Station_SNames,Station_Info[0])
    #--------------------------------------------------------------------------------------
    #set the parameters for interpolation to pressure levels
    #--------------------------------------------------------------------------------------
    pnew = [50,250,500,900]
    pnew_Pa = np.array(pnew)*100.0
    intyp = 2     # 1=linear, 2=log, 3=log-log
    kxtrp = False # True=extrapolate (when the output pressure level is outside of the range of psrf)
    months=[1,2,3,4,5,6,7,8,9,10,11,12]
    #--------------------------------------------------------------------------------------
    #loop through each region and plot the Data
    #--------------------------------------------------------------------------------------
    for i in range(1,len(Regions)+1):
        
        #-----------------------------------------------------------------------------------
        #Grab the info for the current region of interest
        #-----------------------------------------------------------------------------------
        Region_Info=define_regions(i)[0] #Get information on the current region of interest
        SName=Region_Info[0]
        LName=Region_Info[1]
        SubRegion=Region_Info[2]
        MinLat=Region_Info[3]
        MaxLat=Region_Info[4]
        MinLon=Region_Info[5]
        MaxLon=Region_Info[6]
        
        oFile_Seasonal = plot_locations+'/'+SName.replace("_","")+'_SeasonalCycle_ANN_Special_Mean.png'
        oFile_Profile = plot_locations+'/'+SName.replace("_","")+'_Profile_ANN_Special_Mean.png'
        
        #-----------------------------------------------------------------------------------
        #Check if redo_plot set and if not and plots exist already then
        #add them to the website (if enabled) and then exit the routine
        #-----------------------------------------------------------------------------------
        if (not(redo_plot)) and (os.path.isfile(oFile_Seasonal)) and (os.path.isfile(oFile_Profile)):
            print(SName,' region plots exist and redo_plot is false.  Adding to website and Skipping plot.')
            adfobj.add_website_data(oFile_Seasonal,SName.replace("_","")+"_SeasonalCycle", None, season="ANN",multi_case=True,category="O3_DIAGNOSTICS")
            adfobj.add_website_data(oFile_Profile,SName.replace("_","")+"_Profile", None, season="ANN", multi_case=True,category="O3_DIAGNOSTICS")
            continue
        else:
            print("Plotting Region ",LName)
        #-----------------------------------------------------------------------------------
        #Process the model data
        #-----------------------------------------------------------------------------------
        Processed_Seasonal_Cycle_Test_Data = process_model_seasonal_cycle(MinLon,MaxLon,MinLat,MaxLat,Test_Data,pnew_Pa,intyp,kxtrp,Station_Lons,Station_Lats)
        if Compare_Obs <= 0:
            Processed_Seasonal_Cycle_Base_Data = process_model_seasonal_cycle(MinLon,MaxLon,MinLat,MaxLat,Base_Data,pnew_Pa,intyp,kxtrp,Station_Lons,Station_Lats)
        else:
            Processed_Seasonal_Cycle_Base_Data=Processed_Seasonal_Cycle_Test_Data #allows the plotting routine to run without having to call it different ways.
        
        #-----------------------------------------------------------------------------------
        #Filter to the appropriate ozone sonde regional data.
        #-----------------------------------------------------------------------------------
        Obs_Mean_0=Obs_Mean[:,:,i-1]
        Obs_Width_0=Obs_Width[:,:,i-1]
        Obs_StdDev_0=Obs_StdDev[:,:,i-1]
        Obs_Pressure_0=Obs_Pressure[:,i-1]
        
        #-----------------------------------------------------------------------------------
        #Get the pressure level index for each of the 4 levels to 
        #filter the ozone sonde data to
        #-----------------------------------------------------------------------------------
        Locate_P0=np.where(Obs_Pressure_0 == pnew[0])[0]
        Locate_P1=np.where(Obs_Pressure_0 == pnew[1])[0]
        Locate_P2=np.where(Obs_Pressure_0 == pnew[2])[0]
        Locate_P3=np.where(Obs_Pressure_0 == np.max(Obs_Pressure_0))[0]
        
        #-----------------------------------------------------------------------------------
        #For Boulder specifically, the Index should be 2.
        #-----------------------------------------------------------------------------------
        if LName == 'Boulder':
            Locate_P3=2
        
        #-----------------------------------------------------------------------------------
        #Trim the ozonesonde data to the appropriate pressure levels
        #-----------------------------------------------------------------------------------
        Obs_Mean_00=np.squeeze(Obs_Mean_0[Locate_P0,:])
        Obs_Mean_10=np.squeeze(Obs_Mean_0[Locate_P1,:])
        Obs_Mean_20=np.squeeze(Obs_Mean_0[Locate_P2,:])
        Obs_Mean_30=np.squeeze(Obs_Mean_0[Locate_P3,:])
        
        #Currently not being utilized
        #Obs_Width_00=np.squeeze(Obs_Width_0[Locate_P0,:])
        #Obs_Width_10=np.squeeze(Obs_Width_0[Locate_P1,:])
        #Obs_Width_20=np.squeeze(Obs_Width_0[Locate_P2,:])
        #Obs_Width_30=np.squeeze(Obs_Width_0[Locate_P3,:])
        
        Obs_StdDev_00=np.squeeze(Obs_StdDev_0[Locate_P0,:])
        Obs_StdDev_10=np.squeeze(Obs_StdDev_0[Locate_P1,:])
        Obs_StdDev_20=np.squeeze(Obs_StdDev_0[Locate_P2,:])
        Obs_StdDev_30=np.squeeze(Obs_StdDev_0[Locate_P3,:])
        
        Obs_Low_00=Obs_Mean_00-Obs_StdDev_00
        Obs_Low_10=Obs_Mean_10-Obs_StdDev_10
        Obs_Low_20=Obs_Mean_20-Obs_StdDev_20
        Obs_Low_30=Obs_Mean_30-Obs_StdDev_30
        
        Obs_High_00=Obs_Mean_00+Obs_StdDev_00
        Obs_High_10=Obs_Mean_10+Obs_StdDev_10
        Obs_High_20=Obs_Mean_20+Obs_StdDev_20
        Obs_High_30=Obs_Mean_30+Obs_StdDev_30
            
        #-----------------------------------------------------------------------------------   
        #Assign the output seasonal cycle filename and Plot the Seasonal Cycle Plot
        #-----------------------------------------------------------------------------------
        Plot_Seasonal_Cycle_Profile(Processed_Seasonal_Cycle_Base_Data.O3_01,Processed_Seasonal_Cycle_Test_Data.O3_01,months,Processed_Seasonal_Cycle_Base_Data.O3_02,Processed_Seasonal_Cycle_Test_Data.O3_02,months,Processed_Seasonal_Cycle_Base_Data.O3_03,Processed_Seasonal_Cycle_Test_Data.O3_03,months,Processed_Seasonal_Cycle_Base_Data.O3_sfc_04,Processed_Seasonal_Cycle_Test_Data.O3_sfc_04,months,Obs_Mean_00,Obs_Mean_10,Obs_Mean_20,Obs_Mean_30,Obs_Low_00,Obs_Low_10,Obs_Low_20,Obs_Low_30,Obs_High_00,Obs_High_10,Obs_High_20,Obs_High_30,Processed_Seasonal_Cycle_Base_Data.O3_station_00,Processed_Seasonal_Cycle_Test_Data.O3_station_00,Processed_Seasonal_Cycle_Base_Data.O3_station_10,Processed_Seasonal_Cycle_Test_Data.O3_station_10,Processed_Seasonal_Cycle_Base_Data.O3_station_20,Processed_Seasonal_Cycle_Test_Data.O3_station_20,Processed_Seasonal_Cycle_Base_Data.O3_station_30,Processed_Seasonal_Cycle_Test_Data.O3_station_30,[0,6000],[0,750],[0,120],[0,75],'50 hPa','250 hPa','500 hPa','Sfc',base_nickname,test_nicknames,LName,oFile_Seasonal,0,Compare_Obs)
        #-----------------------------------------------------------------------------------
        #Set up for the monthly profile plots - I am calculating my own pressure level data here to ensure a tight match with the ozone sonde climatology
        #-----------------------------------------------------------------------------------
        pnew1=[0.01,0.05,0.1,0.15,0.25,0.5,0.75,1.0,5.0,10.0,20.0,30.0,40.0,50.0,60.0,70.0,80.0,90.0,100.0,125.0,150.0,175.0,200.0,225.0,250.0,275.0,300.0,350.0,400.0,450.0,500.0,550.0,600.0,650.0,700.0,750.0,800.0,850.0,900.0,950.0,1000.0]
        pnew1=np.flip(Obs_Pressure_0)
        
        #-----------------------------------------------------------------------------------
        #Process the model data 
        #-----------------------------------------------------------------------------------
        Processed_Profiles_Test = process_model_profiles(Test_Data,Processed_Seasonal_Cycle_Test_Data.O3_0,Processed_Seasonal_Cycle_Test_Data.PS_0,pnew1,intyp,kxtrp,Processed_Seasonal_Cycle_Test_Data.ILAT,Processed_Seasonal_Cycle_Test_Data.ILON,Processed_Seasonal_Cycle_Test_Data.lat_0,Processed_Seasonal_Cycle_Test_Data.lon_0)
        if Compare_Obs<=0:
            Processed_Profiles_Base = process_model_profiles(Base_Data,Processed_Seasonal_Cycle_Base_Data.O3_0,Processed_Seasonal_Cycle_Base_Data.PS_0,pnew1,intyp,kxtrp,Processed_Seasonal_Cycle_Base_Data.ILAT,Processed_Seasonal_Cycle_Base_Data.ILON,Processed_Seasonal_Cycle_Base_Data.lat_0,Processed_Seasonal_Cycle_Base_Data.lon_0)      
        else:
            Processed_Profiles_Base=Processed_Profiles_Test
        
        #-----------------------------------------------------------------------------------
        #Trim the ozonesonde data to the appropriate pressure levels
        #-----------------------------------------------------------------------------------
        Obs_Mean_001=np.squeeze(Obs_Mean_0[:,0])
        Obs_Mean_101=np.squeeze(Obs_Mean_0[:,3])
        Obs_Mean_201=np.squeeze(Obs_Mean_0[:,6])
        Obs_Mean_301=np.squeeze(Obs_Mean_0[:,9])
        
        #Currently not being utilized
        #Obs_Width_001=np.squeeze(Obs_Width_0[:,0])
        #Obs_Width_101=np.squeeze(Obs_Width_0[:,3])
        #Obs_Width_201=np.squeeze(Obs_Width_0[:,6])
        #Obs_Width_301=np.squeeze(Obs_Width_0[:,9])
        
        Obs_StdDev_001=np.squeeze(Obs_StdDev_0[:,0])
        Obs_StdDev_101=np.squeeze(Obs_StdDev_0[:,3])
        Obs_StdDev_201=np.squeeze(Obs_StdDev_0[:,6])
        Obs_StdDev_301=np.squeeze(Obs_StdDev_0[:,9])
        
        Obs_Low_001=Obs_Mean_001-Obs_StdDev_001
        Obs_Low_101=Obs_Mean_101-Obs_StdDev_101
        Obs_Low_201=Obs_Mean_201-Obs_StdDev_201
        Obs_Low_301=Obs_Mean_301-Obs_StdDev_301
        
        Obs_High_001=Obs_Mean_001+Obs_StdDev_001
        Obs_High_101=Obs_Mean_101+Obs_StdDev_101
        Obs_High_201=Obs_Mean_201+Obs_StdDev_201
        Obs_High_301=Obs_Mean_301+Obs_StdDev_301
        #-----------------------------------------------------------------------------------
        #Assign the output monthly profile filename and Plot the Seasonal Cycle Plot
        #-----------------------------------------------------------------------------------
        Plot_Seasonal_Cycle_Profile(Processed_Profiles_Base.O3_011,Processed_Profiles_Test.O3_011,pnew1,Processed_Profiles_Base.O3_021,Processed_Profiles_Test.O3_021,pnew1,Processed_Profiles_Base.O3_031,Processed_Profiles_Test.O3_031,pnew1,Processed_Profiles_Base.O3_041,Processed_Profiles_Test.O3_041,pnew1,np.flip(Obs_Mean_001),np.flip(Obs_Mean_101),np.flip(Obs_Mean_201),np.flip(Obs_Mean_301),np.flip(Obs_Low_001),np.flip(Obs_Low_101),np.flip(Obs_Low_201),np.flip(Obs_Low_301),np.flip(Obs_High_001),np.flip(Obs_High_101),np.flip(Obs_High_201),np.flip(Obs_High_301),Processed_Profiles_Base.O3_station_001,Processed_Profiles_Test.O3_station_001,Processed_Profiles_Base.O3_station_101,Processed_Profiles_Test.O3_station_101,Processed_Profiles_Base.O3_station_201,Processed_Profiles_Test.O3_station_201,Processed_Profiles_Base.O3_station_301,Processed_Profiles_Test.O3_station_301,[0,125],[0,125],[0,125],[0,125],'Jan', 'Apr','Jul','Oct',base_nickname,test_nicknames,LName,oFile_Profile,1,Compare_Obs)
        #-----------------------------------------------------------------------------------
        #Once the plots have successfully run, add the web page entries (if enabled).
        #-----------------------------------------------------------------------------------
        adfobj.add_website_data(oFile_Seasonal,SName.replace("_","")+"_SeasonalCycle", None, season="ANN",multi_case=True,category="Ozone Diagnostics - Seasonal Cycle")
        adfobj.add_website_data(oFile_Profile,SName.replace("_","")+"_Profile", None, season="ANN", multi_case=True,category="Ozone Diagnostics - Profiles")
        
    #--------------------------------------------------------------------------------------
    #--------------------------------------------------------------------------------------
    #Plot out Taylor Diagrams
    #
    # Plot a taylor diagram at each pressure level and zone.
    #
    # For this taylor diagram we are trying to compare ozonesonde data to model outputs.
    #
    # The ozonesonde files located at /glade/campaign/acom/acom-climate/tilmes/obs_data/amwg/amwg_data/obs_data/cam-chem/
    # condaint the mean sonde profiles (press,months).  These need to get compared with
    # model data which is (months,press,lat,lon).  Specific pressure levels are selected.
    #--------------------------------------------------------------------------------------
    #--------------------------------------------------------------------------------------
    
    #--------------------------------------------------------------------------------------
    #Set plotting level and zone information
    #--------------------------------------------------------------------------------------
    PLEVS=[900,500,250,50] #pressure levels to plot
    
    Zones=['Tropics','Mid Latitudes','High Latitudes'] #zones to plot
    
    SubZones= {'Tropics': ['NH SubTropic','W-Pacific/E-Indian Ocean','equatorial Americas','Atlantic/Africa'],
                'Mid Latitudes': ['Western Europe','Eastern US','Japan','SH Midlatitudes'],
                'High Latitudes': ['NH Polar West','NH Polar East','Canada','SH Polar']}
                        
    SubZoneIndexes= {'Tropics': [6,7,8,9],
                        'Mid Latitudes': [3,4,5,10],
                        'High Latitudes': [0,1,2,11]}                
    #Define the output filename and location of the image file
    Output_IMG=plot_locations+"/OzoneSonde_TaylorDiagram_ANN_Special_Mean.png"
    
    #-----------------------------------------------------------------------------------
    #Check if redo_plot set and if not and plots exist already then
    #add them to the website (if enabled) and then exit the routine
    #-----------------------------------------------------------------------------------
    if (not(redo_plot)) and (os.path.isfile(Output_IMG)):
        print(SName,' Ozone Taylor plot exists and redo_plot is false.  Adding to website and Skipping plot.')
        adfobj.add_website_data(Output_IMG, "OzoneSonde_TaylorDiagram", None,season="ANN", multi_case=True)
        sys.exit(0)
    else:
        print("Plotting Ozone Taylor Diagrams")
    
    #--------------------------------------------------------------------------------------
    #Get the ozone sonde and related data
    #--------------------------------------------------------------------------------------
    SData,SData_Width,SData_Dev,SDataPrs,SData_Region = open_process_sonde_data_simone_v2(obsdir)
    SDataPrs=SDataPrs[0,:]
    
    #initialize subplots
    fig, axs = plt.subplots(nrows=len(PLEVS),ncols=len(Zones),sharex=False,sharey=False,subplot_kw={'projection':'polar'},figsize=(12,12))
    
    #loop through each pressure level (900, 500, 250, 50)
    for i in range(0,len(PLEVS)):
    
        CLEV=float(PLEVS[i]) #set the current pressure level
        
        #loop through each ZONES (i.e. Tropics, Mid Latitudes, and High Latitudes)
        for j in range(0,len(Zones)):
            #set up the basic plot
            TayX=taylor_plot_setup(axs[i,j],Zones[j]+" "+str(PLEVS[i])+" hPa")
            
            #loop through each subzone and average the ozonesonde data
            ListSubZones=SubZones[Zones[j]]
            ListSubZonel=SubZoneIndexes[Zones[j]]
            
            base_x = SData[ListSubZonel,:,:] #limit to only the subregions within the main regions
            
            Find_Index=np.where(SDataPrs == CLEV) #find the right pressure index
            base_x=base_x[:,Find_Index[0],:] #limit to the required pressure level - AT THIS POINT BASE_X HAS SIZE (NSONDE,1,12)
            SubZoneIndex0=SubZoneIndexes[Zones[j]]
            
            #loop through each sub zone and plot on the relavent pressure level and primary zone plot
            for k in range(0,len(SubZoneIndex0)):
                
                #----------------------------------------------------------------------------
                #determine the latitude and longitude ranges to average model data for
                #----------------------------------------------------------------------------
                Region_Info=define_regions(SubZoneIndex0[k]+1)[0]
                SName=Region_Info[0]
                LName=Region_Info[1]
                SubRegion=Region_Info[2]
                MinLat=float(Region_Info[3])
                MaxLat=float(Region_Info[4])
                MinLon=float(Region_Info[5])
                MaxLon=float(Region_Info[6])
                if (MinLon < 0 and MaxLon > 0): #if the region crosses the date line - do different processing
                    O3_00=Base_Data.o3.sel(lon=slice(MinLon+360.0,360.0),lat=slice(MinLat,MaxLat))
                    O3_01=Base_Data.o3.sel(lon=slice(0,MaxLon),lat=slice(MinLat,MaxLat))
                    O3_0 = xr.concat([O3_00,O3_01],'lon')
                    O3_0 = O3_0.sel(lev=CLEV,method='nearest')
                    O3_10=Test_Data.o3.sel(lon=slice(MinLon+360.0,360.0),lat=slice(MinLat,MaxLat))
                    O3_11=Test_Data.o3.sel(lon=slice(0,MaxLon),lat=slice(MinLat,MaxLat))
                    O3_1 = xr.concat([O3_10,O3_11],'lon')
                    O3_1 = O3_1.sel(lev=CLEV,method='nearest')
                else: #if the region does not cross the date line
                    O3_0=Base_Data.o3.sel(lon=slice(MinLon,MaxLon),lat=slice(MinLat,MaxLat))
                    O3_0=O3_0.sel(lev=CLEV,method='nearest')
                    O3_1=Test_Data.o3.sel(lon=slice(MinLon,MaxLon),lat=slice(MinLat,MaxLat))
                    O3_1=O3_1.sel(lev=CLEV,method='nearest')
                lat_0=Base_Data.lat.sel(lat=slice(MinLat,MaxLat))
                lat_0=lat_0.mean(dim='lat')
                lat_1=Test_Data.lat.sel(lat=slice(MinLat,MaxLat))
                lat_1=lat_1.mean(dim='lat')
                
                base_x0=base_x[k,:,:]
                base_x0=base_x0.squeeze('press')
                
                test_x_base=O3_0*1.0e9 #convert mol/mol to ppbv
                test_x_test=O3_1*1.0e9 #convert mol/mol to ppbv
                #average the regions down to monthly average values that can be compared with the monthly average sonde values.
                test_x_base=test_x_base.mean(dim='lat')
                test_x_test=test_x_test.mean(dim='lat')
                test_x_base=test_x_base.mean(dim='lon')
                test_x_test=test_x_test.mean(dim='lon')
            
                #set up the data templates to pass to the taylor stats calculator
                df_template = pd.DataFrame(index=['O3'], columns=['corr', 'ratio', 'bias','mean_diff'])
                result_by_case = {cname: df_template.copy() for cname in ['base','test']}
                #calculate the taylor stats for the base and test case
                result_by_case['base'].loc['O3'] = taylor_stats_single(test_x_base, base_x0)
                result_by_case['test'].loc['O3'] = taylor_stats_single(test_x_test, base_x0)
                case_colors = [mpl.cm.tab20(0),mpl.cm.tab20(6)] #hard code to blue and red to match the original ncl version
                
                #Set plotting flags that determine which panels to plot things like legends.
                if (i == 0 and j == 0 and k == 0):
                    Plot_Bias_Legend=False
                else:
                    Plot_Bias_Legend=False
                    
                if (i == 0 and j == 1 and k == 0):
                    Plot_Case_Legend=True
                else:
                    Plot_Case_Legend=False
                    
                if (i == 3):
                    Plot_Zone_Legend=True
                else:
                    Plot_Zone_Legend=False
                
                #plot the taylor data for each case
                axs0 = plot_taylor_data(axs[i,j],result_by_case['base'],k+1, use_bias=False,case_color=case_colors[0])
                axs1 = plot_taylor_data(axs[i,j],result_by_case['test'],k+1, use_bias=False,case_color=case_colors[1])
                #finalize the data plots with legends, labels, etc.
                syear_cases = adfobj.climo_yrs["syears"]
                eyear_cases = adfobj.climo_yrs["eyears"]
                taylor_plot_finalize(axs[i,j],[case_base,case_test],case_colors, syear_cases, eyear_cases, ListSubZones, needs_bias_labels=Plot_Bias_Legend,needs_case_labels=Plot_Case_Legend,needs_subzone_labels=Plot_Zone_Legend)
    plt.subplots_adjust(wspace=-0.3,hspace=0.3) #Adjusts the spacing between the plots
    st = fig.suptitle("Comparison to Ozonesondes", fontsize=16)
    st.set_y(0.95)
    plt.savefig(Output_IMG, bbox_inches = 'tight', dpi = 150, format = 'png') #there will be more to add here...
    
    #Add plot to website (if enabled):
    adfobj.add_website_data(Output_IMG, "OzoneSonde_TaylorDiagram",None, season="ANN",multi_case=True,category="Ozone Diagnostics - Taylor Diagrams")
    print("Ozone Diagnostics Generated Successfully!")