setup observations using the sfr ASCII output file.  Setups
    sfr point observations using segment and reach numbers.

    Parameters
    ----------
    sft_out_file : str
        the existing SFR output file
    seg_reach : dict, list or pandas.DataFrame
        a dict, or list of SFR [segment,reach] pairs identifying observation locations.
        If dict the key value in the dict is the base observation name.
        If None, all reaches are used as individual observations. Default is None - THIS MAY SET UP A LOT OF OBS!
    model : flopy.mbase
        a flopy model.  If passed, the observation names will have the datetime of the
        observation appended to them.  If None, the observation names will have the
        stress period appended to them. Default is None.
    include_path : bool
        flag to prepend sfr_out_file path to sfr_obs.config.  Useful for setting up
        process in separate directory for where python is running.


    Returns
    -------
    df : pd.DataFrame
        dataframe of obsnme, obsval and obgnme if inschek run was successful.  Else None

    Note
    ----
    This function writes "sfr_reach_obs.config" which must be kept in the dir where
    "apply_sfr_reach_obs()" is being called during the forward run

def setup_sfr_reach_obs(sfr_out_file,seg_reach=None,ins_file=None,model=None,
                        include_path=False):
    """setup observations using the sfr ASCII output file.  Setups
    sfr point observations using segment and reach numbers.

    Parameters
    ----------
    sft_out_file : str
        the existing SFR output file
    seg_reach : dict, list or pandas.DataFrame
        a dict, or list of SFR [segment,reach] pairs identifying observation locations.
        If dict the key value in the dict is the base observation name.
        If None, all reaches are used as individual observations. Default is None - THIS MAY SET UP A LOT OF OBS!
    model : flopy.mbase
        a flopy model.  If passed, the observation names will have the datetime of the
        observation appended to them.  If None, the observation names will have the
        stress period appended to them. Default is None.
    include_path : bool
        flag to prepend sfr_out_file path to sfr_obs.config.  Useful for setting up
        process in separate directory for where python is running.


    Returns
    -------
    df : pd.DataFrame
        dataframe of obsnme, obsval and obgnme if inschek run was successful.  Else None

    Note
    ----
    This function writes "sfr_reach_obs.config" which must be kept in the dir where
    "apply_sfr_reach_obs()" is being called during the forward run

    """
    if seg_reach is None:
        warnings.warn("Obs will be set up for every reach", PyemuWarning)
        seg_reach = 'all'
    elif isinstance(seg_reach, list) or isinstance(seg_reach, np.ndarray):
        if np.ndim(seg_reach) == 1:
            seg_reach = [seg_reach]
        assert np.shape(
            seg_reach)[1] == 2, "varible seg_reach expected shape (n,2), received {0}".format(np.shape(seg_reach))
        seg_reach = pd.DataFrame(seg_reach, columns=['segment', 'reach'])
        seg_reach.index = seg_reach.apply(lambda x: "s{0:03d}r{1:03d}".format(int(x.segment), int(x.reach)), axis=1)
    elif isinstance(seg_reach, dict):
        seg_reach = pd.DataFrame.from_dict(seg_reach, orient='index', columns=['segment', 'reach'])
    else:
        assert isinstance(
            seg_reach, pd.DataFrame), "'selection needs to be pandas Dataframe. Type {} passed.".format(type(seg_reach))
        assert np.all([sr in seg_reach.columns for sr in ['segment', 'reach']]
                      ), "Either 'segment' or 'reach' not in selection columns"

    sfr_dict = load_sfr_out(sfr_out_file, selection=seg_reach)
    kpers = list(sfr_dict.keys())
    kpers.sort()

    if isinstance(seg_reach, str) and seg_reach == 'all':
        seg_reach = sfr_dict[kpers[0]][['segment', 'reach']]
        seg_reach.index = seg_reach.apply(lambda x: "s{0:03d}r{1:03d}".format(int(x.segment), int(x.reach)), axis=1)
    keys = ["sfr_out_file"]
    if include_path:
        values = [os.path.split(sfr_out_file)[-1]]
    else:
        values = [sfr_out_file]
    diff = seg_reach.loc[seg_reach.apply(lambda x: "{0:03d}_{1:03d}".format(int(x.segment), int(x.reach))
                                         not in sfr_dict[list(sfr_dict.keys())[0]].index, axis=1)]

    if len(diff) > 0:
        for ob in diff.itertuples():
            warnings.warn("segs,reach pair listed with onames {0} was not found: {1}".
                          format(ob.Index, "({},{})".format(ob.segment, ob.reach)), PyemuWarning)
    seg_reach = seg_reach.drop(diff.index)
    seg_reach['obs_base'] = seg_reach.index
    df_key = pd.DataFrame({"obs_base": keys, "segment": 0, 'reach': values})
    df_key = pd.concat([df_key, seg_reach], sort=True).reset_index(drop=True)
    if include_path:
        pth = os.path.join(*[p for p in os.path.split(sfr_out_file)[:-1]])
        config_file = os.path.join(pth,"sfr_reach_obs.config")
    else:
        config_file = "sfr_reach_obs.config"
    print("writing 'sfr_reach_obs.config' to {0}".format(config_file))
    df_key.to_csv(config_file)

    bd = '.'
    if include_path:
        bd = os.getcwd()
        os.chdir(pth)
    try:
        df = apply_sfr_reach_obs()
    except Exception as e:
        os.chdir(bd)
        raise Exception("error in apply_sfr_reach_obs(): {0}".format(str(e)))
    os.chdir(bd)
    if model is not None:
        dts = (pd.to_datetime(model.start_datetime) + pd.to_timedelta(np.cumsum(model.dis.perlen.array), unit='d')).date
        df.loc[:, "datetime"] = df.kper.apply(lambda x: dts[x])
        df.loc[:, "time_str"] = df.datetime.apply(lambda x: x.strftime("%Y%m%d"))
    else:
        df.loc[:, "time_str"] = df.kper.apply(lambda x: "{0:04d}".format(x))
    df.loc[:, "flaqx_obsnme"] = df.apply(lambda x: "{0}_{1}_{2}".format("fa", x.obs_base, x.time_str), axis=1)
    df.loc[:, "flout_obsnme"] = df.apply(lambda x: "{0}_{1}_{2}".format("fo", x.obs_base, x.time_str), axis=1)

    if ins_file is None:
        ins_file = sfr_out_file + ".reach_processed.ins"

    with open(ins_file, 'w') as f:
        f.write("pif ~\nl1\n")
        for fla, flo in zip(df.flaqx_obsnme, df.flout_obsnme):
            f.write("l1 w w !{0}! !{1}!\n".format(fla, flo))

    df = None
    pth = os.path.split(ins_file)[:-1]
    pth = os.path.join(*pth)
    if pth == '':
        pth = '.'
    bd = os.getcwd()
    os.chdir(pth)
    try:
        #df = _try_run_inschek(os.path.split(ins_file)[-1],os.path.split(sfr_out_file+".processed")[-1])
        df = try_process_ins_file(os.path.split(ins_file)[-1], os.path.split(sfr_out_file+".reach_processed")[-1])
    except Exception as e:
        pass
    os.chdir(bd)
    if df is not None:
        df.loc[:, "obsnme"] = df.index.values
        df.loc[:, "obgnme"] = df.obsnme.apply(lambda x: "flaqx" if x.startswith("fa") else "flout")
        return df