Beispiel #1
0
def ATL11_to_ATL15(xy0, Wxy=4e4, ATL11_index=None, E_RMS={}, \
            t_span=[2019.25, 2020.5], \
            spacing={'z0':2.5e2, 'dz':5.e2, 'dt':0.25},  \
            sigma_geo=6.5,\
            dzdt_lags=[1, 4],\
            hemisphere=1, reference_epoch=None,\
            region=None, reread_dirs=None, \
            data_file=None, \
            max_iterations=5, \
            N_subset=8,  \
            W_edit=None, \
            out_name=None, \
            compute_E=False, \
            mask_file=None, \
            tide_mask_file=None,\
            tide_directory=None,\
            tide_adjustment=False,\
            tide_adjustment_file=None,\
            tide_model=None,\
            avg_scales=None,\
            edge_pad=None,\
            error_res_scale=None,\
            calc_error_file=None,\
            verbose=False,\
            write_data_only=False):
    '''
    Function to generate DEMs and height-change maps based on ATL11 surface height data.

    Inputs:
        xy0: 2 elements specifying the center of the domain
        Wxy: (float) Width of the domain
        ATL11_index: (string) Index file (from pointCollection.geoIndex) for ATL11 data
        E_RMS: (dict) dictionary specifying constraints on derivatives of the ice-sheet surface.
        t_span: (2-element list of floats) starting and ending times for the output grids (in years CE)
        spacing: (dict) dictionary specifying the grid spacing for z0, dz, and dt
        dzdt_lags: (list) lags over which elevation change rates and errors will be calculated
        hemisphere: (int) the hemisphere in which the grids will be generated. 1 for northern hemisphere, -1 for southern
        reference_epoch: (int) The time slice (counting from zero, in steps of spacing['dt']) corresponding to the DEM
        reread_dirs: (string) directory containing output files from which data will be read (if None, data will be read from the index file)
        data_file: (string) output file from which to reread data (alternative to reread_dirs)
        max_iterations: (int) maximum number of iterations in three-sigma edit of the solution
        N_subset: (int) If specified, the domain is subdivided into this number of divisions in x and y, and a three-sigma edit is calculated for each
        W_edit: (float) Only data within this distance of the grid center can be editied in the iterative step
        out_name: (string) Name of the output file
        compute_E: (bool) If true, errors will be calculated
        mask_file: (string) File specifying areas for which data should be used and strong constraints should be applied
        tide_mask_file: (string)  File specifying the areas for which the tide model should be calculated
        tide_directory: (string)  Directory containing the tide model data
        tide_adjustment: (bool)  Use bounded least-squares fit to adjust tides for extrapolated points
        tide_adjustment_file: (string)  File specifying the areas for which the tide model should be empirically adjusted
        tide_model: (string)  Name of the tide model to use for a given domain
        avg_scales: (list of floats) scales over which the output grids will be averaged and errors will be calculated
        error_res_scale: (float) If errors are calculated, the grid resolution will be coarsened by this factor
        calc_error_file: (string) Output file for which errors will be calculated.
        verbose: (bool) Print progress of processing run
    '''
    SRS_proj4, EPSG=get_SRS_info(hemisphere)

    E_RMS0={'d2z0_dx2':200000./3000/3000, 'd3z_dx2dt':3000./3000/3000, 'd2z_dxdt':3000/3000, 'd2z_dt2':5000}
    E_RMS0.update(E_RMS)

    W={'x':Wxy, 'y':Wxy,'t':np.diff(t_span)}
    ctr={'x':xy0[0], 'y':xy0[1], 't':np.mean(t_span)}

    # initialize file_list to empty in case we're rereading the data
    file_list=[]
    
    # figure out where to get the data
    if data_file is not None:
        data=pc.data().from_h5(data_file, group='data')
    elif calc_error_file is not None:
        data=pc.data().from_h5(calc_error_file, group='data')
        max_iterations=0
        compute_E=True
        N_subset=None
    elif reread_dirs is not None:
        data = reread_data_from_fits(xy0, Wxy, reread_dirs, template='E%d_N%d.h5')
    else:
        data, file_list = read_ATL11(xy0, Wxy, ATL11_index, SRS_proj4, sigma_geo=sigma_geo)
        N0=data.size
        decimate_data(data, 1.2e6, Wxy, 5000, xy0[0], xy0[1] )
        print(f'decimated {N0} to {data.size}')

    if data is None:
        print("No data present for region, returning.")
        return None

    # if any manual edits are needed, make them here:
    manual_edits(data)

    if data is None or data.size < 10:
        print("Fewer than 10 data points, returning")
        return None

    if data.time.max()-data.time.min() < 80./365.:
        print("time span too short, returning.")
        return None

    if edge_pad is not None:
        ctr_dist = np.max(np.abs(data.x-xy0[0]), np.abs(data.y-xy0[1]))
        in_ctr = ctr_dist < Wxy/2 - edge_pad
        if np.sum(in_ctr) < 50:
            return None

    if W_edit is not None:
        # this is used for data that we are rereading from a set of other files.
        # data that are far from the center of this file cannot be eliminated by
        # the three-sigma edit
        data.assign({'editable':  (np.abs(data.x-xy0[0])<=W_edit/2) & (np.abs(data.y-xy0[1])<=W_edit/2)})


    # work out which mask to use based on the region
    tide_mask_data=None
    mask_data=None
    if region is not None:
        if region=='AA':
            mask_data=pc.grid.data().from_geotif(mask_file, bounds=[xy0[0]+np.array([-1.2, 1.2])*Wxy/2, xy0[1]+np.array([-1.2, 1.2])*Wxy/2])
            import scipy.ndimage as snd
            mask_data.z=snd.binary_erosion(snd.binary_erosion(mask_data.z, np.ones([1,3])), np.ones([3,1]))
            mask_file=None
        if region=='GL' and mask_file.endswith('.nc'):
            mask_data, tide_mask_data = read_bedmachine_greenland(mask_file, xy0, Wxy)


    # apply the tides if a directory has been provided
    # NEW 2/19/2021: apply the tides only if we have not read the data from first-round fits.
    if (tide_mask_file is not None or tide_mask_data is not None) and reread_dirs is None and calc_error_file is None and data_file is None:
        apply_tides(data, xy0, Wxy,
                    tide_mask_file=tide_mask_file,
                    tide_mask_data=tide_mask_data,
                    tide_directory=tide_directory,
                    tide_model=tide_model,
                    tide_adjustment=tide_adjustment,
                    tide_adjustment_file=tide_adjustment_file,
                    EPSG=EPSG, verbose=verbose)

    if write_data_only:
        return {'data':data}

    # call smooth_xytb_fitting
    S=smooth_xytb_fit(data=data, ctr=ctr, W=W, spacing=spacing, E_RMS=E_RMS0,
                     reference_epoch=reference_epoch, N_subset=N_subset, compute_E=compute_E,
                     bias_params=['rgt','cycle'],  max_iterations=max_iterations,
                     srs_proj4=SRS_proj4, VERBOSE=True, dzdt_lags=dzdt_lags,
                     mask_file=mask_file, mask_data=mask_data, mask_scale={0:10, 1:1},
                     error_res_scale=error_res_scale,
                     avg_scales=avg_scales)
    S['file_list'] = file_list
    return S
Beispiel #2
0
def fit_CS2(xy0, Wxy=4e4, E_RMS={}, t_span=[2003, 2020], spacing={'z0':2.5e2, 'dz':5.e2, 'dt':0.5},  \
            hemisphere=1, reference_epoch=None, reread_dirs=None, max_iterations=5, N_subset=None, Edit_only=False, \
            avg_scales=None, dzdt_lags=None,\
            sensor_dict={}, out_name=None, replace=False, DOPLOT=False, spring_only=False, \
            geoid_file=None, mask_file=None, calc_tide=False,\
            bias_nsigma_edit=None, bias_nsigma_iteration=3,\
            calc_error_file=None, data_file=None, restart_TSE=False,\
            DEM_file=None, DEM_tol=None):
    """
        Wrapper for smooth_xytb_fit that can find data and set the appropriate parameters
    """
    print("fit_CS2: working on %s" % out_name, flush=True)
    baseline_code = {'C': 2, 'D': 3}
    # temporary:
    if hemisphere == -1:
        srs_proj4 = '+proj=stere +lat_0=-90 +lat_ts=-71 +lon_0=0 +x_0=0 +y_0=0 +datum=WGS84 +units=m +no_defs'
        #index_files={ 'D':{'POCA': glob.glob('/Volumes/ice3/ben/CS2_data/AA/retrack/2*/*/index_POCA.h5'),
        #                  'swath':glob.glob('/Volumes/ice3/ben/CS2_data/AA/retrack/2*/*/index_SW.h5')}}
        index_files = {
            'D': {
                'POCA':
                glob.glob(
                    '/Volumes/ice3/ben/CS2_data/AA/tiles/2*/*/POCA/GeoIndex.h5'
                ),
                'swath':
                glob.glob(
                    '/Volumes/ice3/ben/CS2_data/AA/tiles/2*/*/sw/GeoIndex.h5')
            }
        }

    if hemisphere == 1:
        srs_proj4 = '+proj=stere +lat_0=90 +lat_ts=70 +lon_0=-45 +x_0=0 +y_0=0 +datum=WGS84 +units=m +no_defs'
        index_files = {
            'C': {
                'POCA':
                glob.glob(
                    '/Volumes/ice2/ben/CS2_data/GL/retrack_BC/2*/index_POCA.h5'
                ),
                'swath':
                glob.glob(
                    '/Volumes/ice2/ben/CS2_data/GL/retrack_BC/2*/index_SW.h5')
            },
            'D': {
                'POCA':
                glob.glob(
                    '/Volumes/ice2/ben/CS2_data/GL/retrack/2*/index_POCA.h5'),
                'swath':
                glob.glob(
                    '/Volumes/ice2/ben/CS2_data/GL/retrack/2*/index_SW.h5')
            }
        }

    compute_E = False
    # set defaults for E_RMS, then update with input parameters
    E_RMS0 = {
        'd2z0_dx2': 200000. / 3000 / 3000,
        'd3z_dx2dt': 1000. / 3000 / 3000,
        'd2z_dxdt': 6000 / 3000,
        'd2z_dt2': 5000
    }
    E_RMS0.update(E_RMS)

    W = {'x': Wxy, 'y': Wxy, 't': np.diff(t_span)}
    ctr = {'x': xy0[0], 'y': xy0[1], 't': np.mean(t_span)}
    if out_name is not None:
        try:
            out_name = out_name % (xy0[0] / 1000, xy0[1] / 1000)
        except:
            pass

    if calc_error_file is not None:
        # get xy0 from the filename
        re_match = re.compile('E(.*)_N(.*).h5').search(calc_error_file)
        xy0 = [float(re_match.group(ii)) * 1000 for ii in [1, 2]]
        data, sensor_dict = reread_data_from_fits(
            xy0, Wxy, [os.path.dirname(calc_error_file)])
        compute_E = True
        max_iterations = 0
        N_subset = None
    elif data_file is not None:
        data = pc.data().from_h5(data_file, group='data')
        ###testing
        data.sigma_corr = 0.05 + 2 * (data.swath > 0.5)
    elif reread_dirs is None:
        data = []
        for baseline in index_files.keys():
            data += [
                read_CS2_data(xy0,
                              Wxy,
                              index_files[baseline],
                              apply_filters=True,
                              DEM_file=DEM_file,
                              dem_tol=50)
            ]
            #'sigma':np.sqrt(0.2**2+0.5*(data.swath>0.5))
            ###testing
            data[-1].assign({'z':data[-1].h, \
                  'sigma_corr': 0.05 + 2*(data[-1].swath>0.5),
                  'baseline': baseline_code[baseline]+np.zeros_like(data[-1].swath)})
        if len(data) > 1:
            data[0].index(
                ~np.in1d(data[0].abs_orbit, np.unique(data[1].abs_orbit)))
            data = pc.data().from_list(data)
        else:
            data = data[0]
        D_PC = data[data.swath == 0]
        D_sw = data[data.swath == 1]

        orb_dict = pc.bin_rows(np.c_[D_sw.abs_orbit])
        D_sw_sub = pc.data().from_list(
            [D_sw[orb_dict[key]].blockmedian(400) for key in orb_dict])
        data = pc.data().from_list([D_PC, D_sw_sub])

        data.index((data.time > t_span[0]) & (data.time < t_span[1]))
        if calc_tide:
            if hemisphere == -1:
                # note: compute_tide_correction returns a masked array.
                # for now, we're going to convert it to an array by stripping the mask
                temp=compute_tide_corrections(\
                    data.x,data.y,(data.time-2000)*365.25*24*3600,
                    DIRECTORY='/Volumes/ice3/tyler/tide_models',MODEL='CATS2008',
                    EPOCH=(2000,1,1,0,0,0), TYPE='drift', TIME='utc')
                temp[temp.mask] = np.NaN
                data.assign({'tide': np.array(temp)})
                data.z[np.isfinite(data.tide)] -= data.tide[np.isfinite(
                    data.tide)]
    else:
        data, sensor_dict = reread_data_from_fits(xy0,
                                                  Wxy,
                                                  reread_dirs,
                                                  template='E%d_N%d.h5')
        N_subset = None

    if restart_TSE:
        if 'three_sigma_edit' in data.fields:
            data.three_sigma_edit = np.ones_like(data.x, dtype=bool)
        else:
            data.assign({'three_sigma_edit': np.ones_like(data.x, dtype=bool)})

    if reference_epoch is None:
        reference_epoch = np.ceil(
            len(np.arange(t_span[0], t_span[1], spacing['dt'])) /
            2).astype(int)

    for field in data.fields:
        setattr(data, field, getattr(data, field).astype(np.float64))

    if DEM_file is not None:
        DEM=pc.grid.data().from_geotif(DEM_file, \
              bounds=[[xy0[0]-W['x']/1.8, xy0[0]+W['x']/1.8],\
                      [xy0[1]-W['y']/1.8, xy0[1]+W['y']/1.8]])
        data.assign({'DEM': DEM.interp(data.x, data.y)})

    # want less than 900K points
    if data.size > 9e5:
        D_sw = data[data.swath == 1]
        D_poca = data[data.swath == 0]
        D_sw.index(
            np.arange(0, D_sw.size,
                      D_sw.size / (9e5 - D_poca.size)).astype(int))
        data = pc.data().from_list([D_sw, D_poca])

    # run the fit
    KWargs = {
        'data': data,
        'W': W,
        'ctr': ctr,
        'spacing': spacing,
        'srs_proj4': srs_proj4,
        'mask_file': mask_file,
        'E_RMS': E_RMS0,
        'E_RMS_d2x_PS_bias': 1.e-7,
        'E_RMS_PS_bias': 1,
        'compute_E': compute_E,
        'verbose': True,
        'mask_scale': {
            0: 10,
            1: 1
        },
        'max_iterations': max_iterations,
        'N_subset': N_subset,
        'bias_params': ['abs_orbit', 'swath'],
        'DEM_tol': DEM_tol,
        'avg_scales': avg_scales,
        'bias_filter': lambda D: D[D.swath > 0.5],
        'bias_nsigma_edit': bias_nsigma_edit,
        'bias_nsigma_iteration': bias_nsigma_iteration
    }
    ### testing
    KWargs['bias_filter'] = None
    S = smooth_xytb_fit(**KWargs)

    if out_name is not None:
        if calc_error_file is None:
            save_fit_to_file(S,
                             out_name,
                             sensor_dict,
                             dzdt_lags=S['dzdt_lags'])
        else:
            save_errors_to_file(S, out_name, sensor_dict)
    print("fit_CS2: done with %s" % out_name, flush=True)
    return S, data, sensor_dict
Beispiel #3
0
def fit_CS2(xy0, Wxy=4e4, E_RMS={}, t_span=[2003, 2020], spacing={'z0':2.5e2, 'dz':5.e2, 'dt':0.5},  \
            hemisphere=1, reference_epoch=None, reread_dirs=None, max_iterations=5, N_subset=None, Edit_only=False, \
            sensor_dict={}, out_name=None, replace=False, DOPLOT=False, spring_only=False, \
            geoid_file=None, mask_file=None, \
            calc_error_file=None, DEM_file=None, DEM_tol=None):
    """
        Wrapper for smooth_xytb_fit that can find data and set the appropriate parameters
    """
    print("fit_CS2: working on %s" % out_name)
    baseline_code = {'C': 2, 'D': 3}
    # temporary:
    if hemisphere == -1:
        index_files={'C':{'POCA':['/Volumes/insar6/ben/Cryosat/POCA_h5_C/AA_REMA_v1_sigma4_Jan2020/GeoIndex.h5', \
                     '/Volumes/insar6/ben/Cryosat/POCA_h5_C/AA_REMA_v1_sigma4/GeoIndex.h5'], \
                     'swath':['/Volumes/insar6/ben/Cryosat/SW_h5_C/AA_REMA_v1_sigma4_Jan2020/GeoIndex.h5', \
                              '/Volumes/insar6/ben/Cryosat/SW_h5_C/AA_REMA_v1_sigma4/GeoIndex.h5'] },
                     'D':{'POCA': glob.glob('/Volumes/ice2/ben/CS2_data/AA/retrack/2*/index_POCA.h5'),
                          'swath':[glob.glob('/Volumes/ice2/ben/CS2_data/AA/retrack/2*/index_sw.h5')]}
                     }
    if hemisphere == 1:
        index_files = {
            'C': {
                'POCA': [
                    glob.glob(
                        '/Volumes/ice2/ben/CS2_data/GL/retrack_BC/2*/index_POCA.h5'
                    )
                ],
                'swath': [
                    glob.glob(
                        '/Volumes/ice2/ben/CS2_data/GLretrack_BC/2*/index_SW.h5'
                    )
                ]
            },
            'D': {
                'POCA': [
                    glob.glob(
                        '/Volumes/ice2/ben/CS2_data/GL/retrack/2*/index_POCA.h5'
                    )
                ],
                'swath': [
                    glob.glob(
                        '/Volumes/ice2/ben/CS2_data/GLretrack/2*/index_SW.h5')
                ]
            }
        }

    compute_E = False
    # set defaults for E_RMS, then update with input parameters
    E_RMS0 = {
        'd2z0_dx2': 200000. / 3000 / 3000,
        'd3z_dx2dt': 1000. / 3000 / 3000,
        'd2z_dxdt': 6000 / 3000,
        'd2z_dt2': 5000
    }
    E_RMS0.update(E_RMS)

    W = {'x': Wxy, 'y': Wxy, 't': np.diff(t_span)}
    ctr = {'x': xy0[0], 'y': xy0[1], 't': np.mean(t_span)}
    if out_name is not None:
        try:
            out_name = out_name % (xy0[0] / 1000, xy0[1] / 1000)
        except:
            pass

    if calc_error_file is not None:
        # get xy0 from the filename
        re_match = re.compile('E(.*)_N(.*).h5').search(calc_error_file)
        xy0 = [float(re_match.group(ii)) * 1000 for ii in [1, 2]]
        data, sensor_dict = reread_data_from_fits(
            xy0, Wxy, [os.path.dirname(calc_error_file)])
        compute_E = True
        max_iterations = 0
        N_subset = None
    elif reread_dirs is None:
        data = []
        for baseline in ['C', 'D']:

            data += read_cs2_data(xy0,
                                  Wxy,
                                  index_files[baseline],
                                  apply_filters=True,
                                  DEM_file=DEM_file,
                                  dem_tol=50)
            #'sigma':np.sqrt(0.2**2+0.5*(data.swath>0.5))
            data[-1].assign({'z':data[-1].h, \
                  'sigma_corr': 2*(data.swath[-1]>0.5),
                  'baseline': baseline_code[baseline]+np.zeros_like(data[-1].swath)})
        if len(data) > 1:
            data[0].index(~np.in1d(data[0].abs_orbit),
                          np.unique(data[1].abs_orbit))
            data = pc.data().from_list(data)
        else:
            data = data[0]
        D_PC = data[data.swath == 0]
        D_sw = data[data.swath == 1]

        orb_dict = pc.bin_rows(np.c_[D_sw.abs_orbit])
        D_sw_sub = pc.data().from_list(
            [D_sw[orb_dict[key]].blockmedian(400) for key in orb_dict])
        data = pc.data().from_list([D_PC, D_sw_sub])

        data.index((data.time > t_span[0]) & (data.time < t_span[1]))
    else:
        data, sensor_dict = reread_data_from_fits(xy0,
                                                  Wxy,
                                                  reread_dirs,
                                                  template='E%d_N%d.h5')
        N_subset = None
    if reference_epoch is None:
        reference_epoch = np.ceil(
            len(np.arange(t_span[0], t_span[1], spacing['dt'])) /
            2).astype(int)

    for field in data.fields:
        setattr(data, field, getattr(data, field).astype(np.float64))

    if DEM_file is not None:
        DEM=pc.grid.data().from_geotif(DEM_file, \
              bounds=[[xy0[0]-W['x']/1.8, xy0[0]+W['x']/1.8],\
                      [xy0[1]-W['y']/1.8, xy0[1]+W['y']/1.8]])
        data.assign({'DEM': DEM.interp(data.x, data.y)})

    data.index((data.swath == 0) | (np.mod(np.arange(0, data.size), 2) == 0))

    # run the fit
    KWargs = {
        'data': data,
        'W': W,
        'ctr': ctr,
        'spacing': spacing,
        'E_RMS': E_RMS0,
        'E_RMS_d2x_PS_bias': 1.e-7,
        'E_RMS_PS_bias': 1,
        'compute_E': compute_E,
        'verbose': True,
        'max_iterations': max_iterations,
        'N_subset': N_subset,
        'bias_params': ['abs_orbit', 'swath'],
        'DEM_tol': DEM_tol,
        'bias_filter': lambda D: D[D.swath > 0.5]
    }
    #print("WARNING WARNING WARNING::::SETTING BIAS_PARAMS AND PS_BIAS_RMS TO NONE!!!!!")
    #KWargs['bias_params']=None
    #KWargs['E_RMS_d2x_PS_bias']=None
    S = smooth_xytb_fit(**KWargs)

    if out_name is not None:
        if calc_error_file is None:
            save_fit_to_file(S,
                             out_name,
                             sensor_dict,
                             dzdt_lags=S['dzdt_lags'])
        else:
            save_errors_to_file(S, out_name, sensor_dict)
    print("fit_CS2: done with %s" % out_name)
    return S, data, sensor_dict
Beispiel #4
0
def ATL11_to_ATL15(xy0, Wxy=4e4, ATL11_index=None, E_RMS={}, \
            t_span=[2019.25, 2020.5], \
            spacing={'z0':2.5e2, 'dz':5.e2, 'dt':0.25},  \
            dzdt_lags=[1, 4],\
            hemisphere=1, reference_epoch=None, reread_dirs=None, \
            data_file=None, \
            max_iterations=5, \
            N_subset=8,  \
            W_edit=None, \
            out_name=None, \
            compute_E=False, \
            mask_file=None, \
            tide_mask_file=None,\
            tide_directory=None,\
            avg_scales=None,\
            error_res_scale=None,\
            calc_error_file=None):
    '''
    Function to generate DEMs and height-change maps based on ATL11 surface height data.
    
    Inputs:
        xy0: 2 elements specifying the center of the domain
        Wxy: (float) Width of the domain
        ATL11_index: (string) Index file (from pointCollection.geoIndex) for ATL11 data
        E_RMS: (dict) dictionary specifying constraints on derivatives of the ice-sheet surface.  
        t_span: (2-element list of floats) starting and ending times for the output grids (in years CE)
        spacing: (dict) dictionary specifying the grid spacing for z0, dz, and dt
        dzdt_lags: (list) lags over which elevation change rates and errors will be calculated
        hemisphere: (int) the hemisphere in which the grids will be generated. 1 for northern hemisphere, -1 for southern
        reference_epoch: (int) The time slice (counting from zero, in steps of spacing['dt']) corresponding to the DEM
        reread_dirs: (string) directory containing output files from which data will be read (if None, data will be read from the index file)
        data_file: (string) output file from which to reread data (alternative to reread_dirs)
        max_iterations: (int) maximum number of iterations in three-sigma edit of the solution
        N_subset: (int) If specified, the domain is subdivided into this number of divisions in x and y, and a three-sigma edit is calculated for each
        W_edit: (float) Only data within this distance of the grid center can be editied in the iterative step
        out_name: (string) Name of the output file
        compute_E: (bool) If true, errors will be calculated
        mask_file: (string) File specifying areas for which data should be used and strong constraints should be applied
        tide_mask_file: (string)  File specifying the areas for which the tide model should be calculated
        tide_directory: (string)  Directory containing the tide model data
        avg_scales: (list of floats) scales over which the output grids will be averaged and errors will be calculated
        error_res_scale: (float) If errors are calculated, the grid resolution will be coarsened by this factor
        calc_error_file: (string) Output file for which errors will be calculated.
    '''
    SRS_proj4 = get_SRS_proj4(hemisphere)

    E_RMS0 = {
        'd2z0_dx2': 200000. / 3000 / 3000,
        'd3z_dx2dt': 3000. / 3000 / 3000,
        'd2z_dxdt': 3000 / 3000,
        'd2z_dt2': 5000
    }
    E_RMS0.update(E_RMS)

    W = {'x': Wxy, 'y': Wxy, 't': np.diff(t_span)}
    ctr = {'x': xy0[0], 'y': xy0[1], 't': np.mean(t_span)}

    # figure out where to get the data
    if data_file is not None:
        data = pc.data().from_h5(data_file, group='/')
    elif calc_error_file is not None:
        data = pc.data().from_h5(calc_error_file, group='data')
        max_iterations = 0
        compute_E = True
        N_subset = None
    elif reread_dirs is not None:
        data = reread_data_from_fits(xy0,
                                     Wxy,
                                     reread_dirs,
                                     template='E%d_N%d.h5')
    else:
        data = read_ATL11(xy0, Wxy, ATL11_index, SRS_proj4)
        N0 = data.size
        decimate_data(data, 1.2e6, Wxy, 5000, xy0[0], xy0[1])
        print(f'decimated {N0} to {data.size}')

    # apply the tides if a directory has been provided
    if tide_mask_file is not None:
        apply_tides(data, xy0, Wxy, tide_mask_file, tide_directory)

    if W_edit is not None:
        # this is used for data that we are rereading from a set of other files.
        # data that are far from the center of this file cannot be eliminated by
        # the three-sigma edit
        data.assign({
            'editable': (np.abs(data.x - xy0[0]) <= W_edit / 2) &
            (np.abs(data.y - xy0[1]) <= W_edit / 2)
        })

    # call smooth_xytb_fit
    S = smooth_xytb_fit(data=data,
                        ctr=ctr,
                        W=W,
                        spacing=spacing,
                        E_RMS=E_RMS0,
                        reference_epoch=reference_epoch,
                        N_subset=N_subset,
                        compute_E=compute_E,
                        bias_params=['rgt', 'cycle'],
                        max_iterations=max_iterations,
                        srs_proj4=SRS_proj4,
                        VERBOSE=True,
                        dzdt_lags=dzdt_lags,
                        mask_file=mask_file,
                        mask_scale={
                            0: 10,
                            1: 1
                        },
                        error_res_scale=error_res_scale,
                        avg_scales=avg_scales)
    return S