def nosql_draw2(check_func, match):
            from matplotlib.backends.backend_agg import FigureCanvas

            try:
                from matplotlib.backends.backend_agg import Figure
            except ImportError:
                from matplotlib.figure import Figure

            was_interactive = mpl.is_interactive()
            if was_interactive:
                mpl.interactive(False)
            # fnum = 32
            fig = Figure()
            canvas = FigureCanvas(fig)  # NOQA
            # fig.clf()
            ax = fig.add_subplot(1, 1, 1)
            if check_func is not None and check_func():
                return
            ax, xywh1, xywh2 = match.show(ax=ax)
            if check_func is not None and check_func():
                return
            savekw = {
                # 'dpi' : 60,
                'dpi': 80,
            }
            axes_extents = pt.extract_axes_extents(fig)
            # assert len(axes_extents) == 1, 'more than one axes'
            extent = axes_extents[0]
            with io.BytesIO() as stream:
                # This call takes 23% - 15% of the time depending on settings
                fig.savefig(stream, bbox_inches=extent, **savekw)
                stream.seek(0)
                data = np.fromstring(stream.getvalue(), dtype=np.uint8)
            if check_func is not None and check_func():
                return
            pt.plt.close(fig)
            image = cv2.imdecode(data, 1)
            thumbsize = 221
            max_dsize = (thumbsize, thumbsize)
            dsize, sx, sy = vt.resized_clamped_thumb_dims(
                vt.get_size(image), max_dsize)
            if check_func is not None and check_func():
                return
            image = vt.resize(image, dsize)
            vt.imwrite(fpath, image)
            if check_func is not None and check_func():
                return
    def dump_to_disk(self, dpath, num=None, prefix='temp_img'):
        import numpy as np
        import wbia.plottool as pt

        dpath = ut.ensurepath(dpath)
        num_zeros = np.ceil(np.log10(len(self.gpath_list)))
        total = len(self.gpath_list)
        if num is None:
            num = total
        fmtstr = prefix + '_%0' + str(num_zeros) + 'd.jpg'
        fig = pt.figure(fnum=self.fnum)
        for index in ut.ProgIter(range(num), lbl='dumping images to disk'):
            fig = pt.figure(fnum=self.fnum)
            fig.clf()
            ax = self._plot_index(index, {'fnum': self.fnum})
            fig = ax.figure
            axes_extents = pt.extract_axes_extents(fig)
            assert len(axes_extents) == 1, 'more than one axes'
            extent = axes_extents[0]
            fpath = ut.unixjoin(dpath, fmtstr % (index))
            fig.savefig(fpath, bbox_inches=extent)
        pt.plt.close(fig)
    def nosql_draw(check_func, rchip1_fpath, rchip2_fpath, kpts1, kpts2):
        # This gets executed in the child thread and does drawing async style
        # from matplotlib.backends.backend_pdf import FigureCanvasPdf as FigureCanvas
        # from matplotlib.backends.backend_pdf import Figure
        # from matplotlib.backends.backend_svg import FigureCanvas
        # from matplotlib.backends.backend_svg import Figure
        from matplotlib.backends.backend_agg import FigureCanvas

        try:
            from matplotlib.backends.backend_agg import Figure
        except ImportError:
            from matplotlib.figure import Figure

        kpts1_ = vt.offset_kpts(kpts1, (0, 0), (resize_factor, resize_factor))
        kpts2_ = vt.offset_kpts(kpts2, (0, 0), (resize_factor, resize_factor))

        # from matplotlib.figure import Figure
        if check_func is not None and check_func():
            return

        rchip1 = vt.imread(rchip1_fpath)
        rchip1 = vt.resize_image_by_scale(rchip1, resize_factor)
        if check_func is not None and check_func():
            return
        rchip2 = vt.imread(rchip2_fpath)
        rchip2 = vt.resize_image_by_scale(rchip2, resize_factor)
        if check_func is not None and check_func():
            return

        try:
            idx = cm.daid2_idx[daid]
            fm = cm.fm_list[idx]
            fsv = None if cm.fsv_list is None else cm.fsv_list[idx]
            fs = None if fsv is None else fsv.prod(axis=1)
        except KeyError:
            fm = []
            fs = None
            fsv = None

        maxnum = 200
        if fs is not None and len(fs) > maxnum:
            # HACK TO ONLY SHOW TOP MATCHES
            sortx = fs.argsort()[::-1]
            fm = fm.take(sortx[:maxnum], axis=0)
            fs = fs.take(sortx[:maxnum], axis=0)

        was_interactive = mpl.is_interactive()
        if was_interactive:
            mpl.interactive(False)
        # fnum = 32
        fig = Figure()
        canvas = FigureCanvas(fig)  # NOQA
        # fig.clf()
        ax = fig.add_subplot(1, 1, 1)
        if check_func is not None and check_func():
            return
        # fig = pt.plt.figure(fnum)
        # H1 = np.eye(3)
        # H2 = np.eye(3)
        # H1[0, 0] = .5
        # H1[1, 1] = .5
        # H2[0, 0] = .5
        # H2[1, 1] = .5
        ax, xywh1, xywh2 = pt.show_chipmatch2(rchip1,
                                              rchip2,
                                              kpts1_,
                                              kpts2_,
                                              fm,
                                              fs=fs,
                                              colorbar_=False,
                                              ax=ax)
        if check_func is not None and check_func():
            return
        savekw = {
            # 'dpi' : 60,
            'dpi': 80,
        }
        axes_extents = pt.extract_axes_extents(fig)
        # assert len(axes_extents) == 1, 'more than one axes'
        extent = axes_extents[0]
        with io.BytesIO() as stream:
            # This call takes 23% - 15% of the time depending on settings
            fig.savefig(stream, bbox_inches=extent, **savekw)
            stream.seek(0)
            data = np.fromstring(stream.getvalue(), dtype=np.uint8)
        if check_func is not None and check_func():
            return
        pt.plt.close(fig)
        image = cv2.imdecode(data, 1)
        thumbsize = 221
        max_dsize = (thumbsize, thumbsize)
        dsize, sx, sy = vt.resized_clamped_thumb_dims(vt.get_size(image),
                                                      max_dsize)
        if check_func is not None and check_func():
            return
        image = vt.resize(image, dsize)
        vt.imwrite(fpath, image)
        if check_func is not None and check_func():
            return
Beispiel #4
0
def _dev_iters_until_threshold():
    """
    INTERACTIVE DEVELOPMENT FUNCTION

    How many iterations of ewma until you hit the poisson / biniomal threshold

    This establishes a principled way to choose the threshold for the refresh
    criterion in my thesis. There are paramters --- moving parts --- that we
    need to work with: `a` the patience, `s` the span, and `mu` our ewma.

    `s` is a span paramter indicating how far we look back.

    `mu` is the average number of label-changing reviews in roughly the last
    `s` manual decisions.

    These numbers are used to estimate the probability that any of the next `a`
    manual decisions will be label-chanigng. When that probability falls below
    a threshold we terminate. The goal is to choose `a`, `s`, and the threshold
    `t`, such that the probability will fall below the threshold after a maximum
    of `a` consecutive non-label-chaning reviews. IE we want to tie the patience
    paramter (how far we look ahead) to how far we actually are willing to go.
    """
    import numpy as np
    import utool as ut
    import sympy as sym

    i = sym.symbols('i', integer=True, nonnegative=True, finite=True)
    # mu_i = sym.symbols('mu_i', integer=True, nonnegative=True, finite=True)
    s = sym.symbols('s', integer=True, nonnegative=True, finite=True)  # NOQA
    thresh = sym.symbols('tau', real=True, nonnegative=True, finite=True)  # NOQA
    alpha = sym.symbols('alpha', real=True, nonnegative=True, finite=True)  # NOQA
    c_alpha = sym.symbols('c_alpha', real=True, nonnegative=True, finite=True)
    # patience
    a = sym.symbols('a', real=True, nonnegative=True, finite=True)

    available_subs = {
        a: 20,
        s: a,
        alpha: 2 / (s + 1),
        c_alpha: (1 - alpha),
    }

    def subs(expr, d=available_subs):
        """ recursive expression substitution """
        expr1 = expr.subs(d)
        if expr == expr1:
            return expr1
        else:
            return subs(expr1, d=d)

    # mu is either the support for the poisson distribution
    # or is is the p in the binomial distribution
    # It is updated at timestep i based on ewma, assuming each incoming responce is 0
    mu_0 = 1.0
    mu_i = c_alpha ** i

    # Estimate probability that any event will happen in the next `a` reviews
    # at time `i`.
    poisson_i = 1 - sym.exp(-mu_i * a)
    binom_i = 1 - (1 - mu_i) ** a

    # Expand probabilities to be a function of i, s, and a
    part = ut.delete_dict_keys(available_subs.copy(), [a, s])
    mu_i = subs(mu_i, d=part)
    poisson_i = subs(poisson_i, d=part)
    binom_i = subs(binom_i, d=part)

    if True:
        # ewma of mu at time i if review is always not label-changing (meaningful)
        mu_1 = c_alpha * mu_0  # NOQA
        mu_2 = c_alpha * mu_1  # NOQA

    if True:
        i_vals = np.arange(0, 100)
        mu_vals = np.array([subs(mu_i).subs({i: i_}).evalf() for i_ in i_vals])  # NOQA
        binom_vals = np.array(
            [subs(binom_i).subs({i: i_}).evalf() for i_ in i_vals]
        )  # NOQA
        poisson_vals = np.array(
            [subs(poisson_i).subs({i: i_}).evalf() for i_ in i_vals]
        )  # NOQA

        # Find how many iters it actually takes my expt to terminate
        thesis_draft_thresh = np.exp(-2)
        np.where(mu_vals < thesis_draft_thresh)[0]
        np.where(binom_vals < thesis_draft_thresh)[0]
        np.where(poisson_vals < thesis_draft_thresh)[0]

    sym.pprint(sym.simplify(mu_i))
    sym.pprint(sym.simplify(binom_i))
    sym.pprint(sym.simplify(poisson_i))

    # Find the thresholds that force termination after `a` reviews have passed
    # do this by setting i=a
    poisson_thresh = poisson_i.subs({i: a})
    binom_thresh = binom_i.subs({i: a})

    logger.info('Poisson thresh')
    logger.info(sym.latex(sym.Eq(thresh, poisson_thresh)))
    logger.info(sym.latex(sym.Eq(thresh, sym.simplify(poisson_thresh))))

    poisson_thresh.subs({a: 115, s: 30}).evalf()

    sym.pprint(sym.Eq(thresh, poisson_thresh))
    sym.pprint(sym.Eq(thresh, sym.simplify(poisson_thresh)))

    logger.info('Binomial thresh')
    sym.pprint(sym.simplify(binom_thresh))

    sym.pprint(sym.simplify(poisson_thresh.subs({s: a})))

    def taud(coeff):
        return coeff * 360

    if 'poisson_cache' not in vars():
        poisson_cache = {}
        binom_cache = {}

    S, A = np.meshgrid(np.arange(1, 150, 1), np.arange(0, 150, 1))

    import wbia.plottool as pt

    SA_coords = list(zip(S.ravel(), A.ravel()))
    for sval, aval in ut.ProgIter(SA_coords):
        if (sval, aval) not in poisson_cache:
            poisson_cache[(sval, aval)] = float(
                poisson_thresh.subs({a: aval, s: sval}).evalf()
            )
    poisson_zdata = np.array(
        [poisson_cache[(sval, aval)] for sval, aval in SA_coords]
    ).reshape(A.shape)
    fig = pt.figure(fnum=1, doclf=True)
    pt.gca().set_axis_off()
    pt.plot_surface3d(
        S,
        A,
        poisson_zdata,
        xlabel='s',
        ylabel='a',
        rstride=3,
        cstride=3,
        zlabel='poisson',
        mode='wire',
        contour=True,
        title='poisson3d',
    )
    pt.gca().set_zlim(0, 1)
    pt.gca().view_init(elev=taud(1 / 16), azim=taud(5 / 8))
    fig.set_size_inches(10, 6)
    fig.savefig(
        'a-s-t-poisson3d.png',
        dpi=300,
        bbox_inches=pt.extract_axes_extents(fig, combine=True),
    )

    for sval, aval in ut.ProgIter(SA_coords):
        if (sval, aval) not in binom_cache:
            binom_cache[(sval, aval)] = float(
                binom_thresh.subs({a: aval, s: sval}).evalf()
            )
    binom_zdata = np.array(
        [binom_cache[(sval, aval)] for sval, aval in SA_coords]
    ).reshape(A.shape)
    fig = pt.figure(fnum=2, doclf=True)
    pt.gca().set_axis_off()
    pt.plot_surface3d(
        S,
        A,
        binom_zdata,
        xlabel='s',
        ylabel='a',
        rstride=3,
        cstride=3,
        zlabel='binom',
        mode='wire',
        contour=True,
        title='binom3d',
    )
    pt.gca().set_zlim(0, 1)
    pt.gca().view_init(elev=taud(1 / 16), azim=taud(5 / 8))
    fig.set_size_inches(10, 6)
    fig.savefig(
        'a-s-t-binom3d.png',
        dpi=300,
        bbox_inches=pt.extract_axes_extents(fig, combine=True),
    )

    # Find point on the surface that achieves a reasonable threshold

    # Sympy can't solve this
    # sym.solve(sym.Eq(binom_thresh.subs({s: 50}), .05))
    # sym.solve(sym.Eq(poisson_thresh.subs({s: 50}), .05))
    # Find a numerical solution
    def solve_numeric(expr, target, want, fixed, method=None, bounds=None):
        """
        Args:
            expr (Expr): symbolic expression
            target (float): numberic value
            fixed (dict): fixed values of the symbol

        expr = poisson_thresh
        expr.free_symbols
        fixed = {s: 10}

        solve_numeric(poisson_thresh, .05, {s: 30}, method=None)
        solve_numeric(poisson_thresh, .05, {s: 30}, method='Nelder-Mead')
        solve_numeric(poisson_thresh, .05, {s: 30}, method='BFGS')
        """
        import scipy.optimize

        # Find the symbol you want to solve for
        want_symbols = expr.free_symbols - set(fixed.keys())
        # TODO: can probably extend this to multiple params
        assert len(want_symbols) == 1, 'specify all but one var'
        assert want == list(want_symbols)[0]
        fixed_expr = expr.subs(fixed)

        def func(a1):
            expr_value = float(fixed_expr.subs({want: a1}).evalf())
            return (expr_value - target) ** 2

        # if method is None:
        #     method = 'Nelder-Mead'
        #     method = 'Newton-CG'
        #     method = 'BFGS'
        # Use one of the other params the startin gpoing
        a1 = list(fixed.values())[0]
        result = scipy.optimize.minimize(func, x0=a1, method=method, bounds=bounds)
        if not result.success:
            logger.info('\n')
            logger.info(result)
            logger.info('\n')
        return result

    # Numeric measurments of thie line

    thresh_vals = [0.001, 0.01, 0.05, 0.1, 0.135]
    svals = np.arange(1, 100)

    target_poisson_plots = {}
    for target in ut.ProgIter(thresh_vals, bs=False, freq=1):
        poisson_avals = []
        for sval in ut.ProgIter(svals, 'poisson', freq=1):
            expr = poisson_thresh
            fixed = {s: sval}
            want = a
            aval = solve_numeric(expr, target, want, fixed, method='Nelder-Mead').x[0]
            poisson_avals.append(aval)
        target_poisson_plots[target] = (svals, poisson_avals)

    fig = pt.figure(fnum=3)
    for target, dat in target_poisson_plots.items():
        pt.plt.plot(*dat, label='prob={}'.format(target))
    pt.gca().set_xlabel('s')
    pt.gca().set_ylabel('a')
    pt.legend()
    pt.gca().set_title('poisson')
    fig.set_size_inches(5, 3)
    fig.savefig(
        'a-vs-s-poisson.png',
        dpi=300,
        bbox_inches=pt.extract_axes_extents(fig, combine=True),
    )

    target_binom_plots = {}
    for target in ut.ProgIter(thresh_vals, bs=False, freq=1):
        binom_avals = []
        for sval in ut.ProgIter(svals, 'binom', freq=1):
            aval = solve_numeric(
                binom_thresh, target, a, {s: sval}, method='Nelder-Mead'
            ).x[0]
            binom_avals.append(aval)
        target_binom_plots[target] = (svals, binom_avals)

    fig = pt.figure(fnum=4)
    for target, dat in target_binom_plots.items():
        pt.plt.plot(*dat, label='prob={}'.format(target))
    pt.gca().set_xlabel('s')
    pt.gca().set_ylabel('a')
    pt.legend()
    pt.gca().set_title('binom')
    fig.set_size_inches(5, 3)
    fig.savefig(
        'a-vs-s-binom.png',
        dpi=300,
        bbox_inches=pt.extract_axes_extents(fig, combine=True),
    )

    # ----
    if True:

        fig = pt.figure(fnum=5, doclf=True)
        s_vals = [1, 2, 3, 10, 20, 30, 40, 50]
        for sval in s_vals:
            pp = poisson_thresh.subs({s: sval})

            a_vals = np.arange(0, 200)
            pp_vals = np.array(
                [float(pp.subs({a: aval}).evalf()) for aval in a_vals]
            )  # NOQA

            pt.plot(a_vals, pp_vals, label='s=%r' % (sval,))
        pt.legend()
        pt.gca().set_xlabel('a')
        pt.gca().set_ylabel('poisson prob after a reviews')
        fig.set_size_inches(5, 3)
        fig.savefig(
            'a-vs-thresh-poisson.png',
            dpi=300,
            bbox_inches=pt.extract_axes_extents(fig, combine=True),
        )

        fig = pt.figure(fnum=6, doclf=True)
        s_vals = [1, 2, 3, 10, 20, 30, 40, 50]
        for sval in s_vals:
            pp = binom_thresh.subs({s: sval})
            a_vals = np.arange(0, 200)
            pp_vals = np.array(
                [float(pp.subs({a: aval}).evalf()) for aval in a_vals]
            )  # NOQA
            pt.plot(a_vals, pp_vals, label='s=%r' % (sval,))
        pt.legend()
        pt.gca().set_xlabel('a')
        pt.gca().set_ylabel('binom prob after a reviews')
        fig.set_size_inches(5, 3)
        fig.savefig(
            'a-vs-thresh-binom.png',
            dpi=300,
            bbox_inches=pt.extract_axes_extents(fig, combine=True),
        )

        # -------

        fig = pt.figure(fnum=5, doclf=True)
        a_vals = [1, 2, 3, 10, 20, 30, 40, 50]
        for aval in a_vals:
            pp = poisson_thresh.subs({a: aval})
            s_vals = np.arange(1, 200)
            pp_vals = np.array(
                [float(pp.subs({s: sval}).evalf()) for sval in s_vals]
            )  # NOQA
            pt.plot(s_vals, pp_vals, label='a=%r' % (aval,))
        pt.legend()
        pt.gca().set_xlabel('s')
        pt.gca().set_ylabel('poisson prob')
        fig.set_size_inches(5, 3)
        fig.savefig(
            's-vs-thresh-poisson.png',
            dpi=300,
            bbox_inches=pt.extract_axes_extents(fig, combine=True),
        )

        fig = pt.figure(fnum=5, doclf=True)
        a_vals = [1, 2, 3, 10, 20, 30, 40, 50]
        for aval in a_vals:
            pp = binom_thresh.subs({a: aval})
            s_vals = np.arange(1, 200)
            pp_vals = np.array(
                [float(pp.subs({s: sval}).evalf()) for sval in s_vals]
            )  # NOQA
            pt.plot(s_vals, pp_vals, label='a=%r' % (aval,))
        pt.legend()
        pt.gca().set_xlabel('s')
        pt.gca().set_ylabel('binom prob')
        fig.set_size_inches(5, 3)
        fig.savefig(
            's-vs-thresh-binom.png',
            dpi=300,
            bbox_inches=pt.extract_axes_extents(fig, combine=True),
        )

    # ---------------------
    # Plot out a table

    mu_i.subs({s: 75, a: 75}).evalf()
    poisson_thresh.subs({s: 75, a: 75}).evalf()

    sval = 50
    for target, dat in target_poisson_plots.items():
        slope = np.median(np.diff(dat[1]))
        aval = int(np.ceil(sval * slope))
        thresh = float(poisson_thresh.subs({s: sval, a: aval}).evalf())
        logger.info(
            'aval={}, sval={}, thresh={}, target={}'.format(aval, sval, thresh, target)
        )

    for target, dat in target_binom_plots.items():
        slope = np.median(np.diff(dat[1]))
        aval = int(np.ceil(sval * slope))
def save_figure(
    fnum=None,
    fpath=None,
    fpath_strict=None,
    usetitle=False,
    overwrite=True,
    defaultext=None,
    verbose=1,
    dpi=None,
    figsize=None,
    saveax=None,
    fig=None,
    dpath=None,
):
    """
    Helper to save the figure image to disk. Tries to be smart about filename
    lengths, extensions, overwrites, etc...

    DEPCIATE

    Args:
        fnum (int):  figure number
        fpath (str): file path string
        fpath_strict (str): uses this exact path
        usetitle (bool): uses title as the fpath
        overwrite (bool): default=True
        defaultext (str): default extension
        verbose (int):  verbosity flag
        dpi (int): dots per inch
        figsize (tuple(int, int)): figure size
        saveax (bool or Axes): specifies if the axes should be saved instead of
            the figure

    References:
        for saving only a specific Axes
        http://stackoverflow.com/questions/4325733/save-a-subplot-in-matplotlib
        http://robotics.usc.edu/~ampereir/wordpress/?p=626
        http://stackoverflow.com/questions/1271023/resize-a-figure-automatically-in-matplotlib
    """
    if dpi is None:
        dpi = custom_constants.DPI

    if defaultext is None:
        if mpl.get_backend().lower() == 'pdf':
            defaultext = '.pdf'
        else:
            defaultext = '.jpg'
    # print('figsize = %r' % (figsize,))
    fig, fnum = prepare_figure_for_save(fnum, dpi, figsize, fig)
    if fpath_strict is None:
        fpath_clean = prepare_figure_fpath(fig, fpath, fnum, usetitle,
                                           defaultext, verbose, dpath)
    else:
        fpath_clean = fpath_strict
    savekw = {'dpi': dpi}
    if verbose > 1:
        # print('verbose = %r' % (verbose,))
        print('[pt.save_figure] saveax = %r' % (saveax, ))

    if False:
        import wbia.plottool as pt

        extent = pt.extract_axes_extents(fig)
        savekw['bbox_inches'] = extent

    if saveax is not None and saveax is not False:
        if verbose > 0:
            print('\n[pt.save_figure] SAVING ONLY EXTENT saveax=%r\n' %
                  (saveax, ))
        if saveax is True:
            saveax = plt.gca()
        # ut.embed()
        # saveax.set_aspect('auto')
        import wbia.plottool as pt
        import numpy as np

        xy, w, h = pt.get_axis_xy_width_height(saveax)
        ar = np.abs(w / h)
        if verbose == 2:
            print('[pt.save_figure] saveax xywh = %r' % ((xy, w, h), ))
            print('[pt.save_figure] saveax ar = %.2f' % (ar, ))
        saveax.set_aspect('equal')
        # extent is bbox in the form [[x0, y0], [x1, y1]]
        extent = saveax.get_window_extent().transformed(
            fig.dpi_scale_trans.inverted())
        if verbose == 2:
            print('[pt.save_figure] bbox ar = %.2f' % np.abs(
                (extent.width / extent.height, )))
        # extent = saveax.get_window_extent().transformed(fig.transFigure.inverted())
        # print('[df2] bbox ar = %.2f' % np.abs((extent.width / extent.height,)))
        savekw['bbox_inches'] = extent.expanded(1.0, 1.0)
        if verbose == 2:
            print('[pt.save_figure] savekw = ' + ut.repr2(savekw))
        # ut.embed()

    # fname_clean = split(fpath_clean)[1]
    with warnings.catch_warnings():
        warnings.filterwarnings('ignore', category=DeprecationWarning)
        if overwrite or not exists(fpath_clean):
            if verbose == 2:
                print('[pt.save_figure] save_figure() full=%r' %
                      (fpath_clean, ))
            elif verbose == 1:
                fpathndir = ut.path_ndir_split(fpath_clean, 5)
                print('[pt.save_figure] save_figure() ndir=%r' % (fpathndir))
            # fig.savefig(fpath_clean)
            if verbose > 1 or ut.VERBOSE:
                print(']pt.save_figure] fpath_clean = %s' % (fpath_clean, ))
                print('[pt.save_figure] savekw = ' + ut.repr2(savekw))
            # savekw['bbox_inches'] = 'tight'
            # print('savekw = %r' % (savekw,))
            if fpath_clean.endswith('.png'):
                savekw['transparent'] = True
                savekw['edgecolor'] = 'none'
                # savekw['axes.edgecolor'] = 'none'
            fig.savefig(fpath_clean, **savekw)
        else:
            if verbose > 0:
                print('[pt.save_figure] not overwriteing')
    return fpath_clean