http://scikit-image.org/docs/dev/api/skimage.restoration.html?highlight=denoise

def updateframe(t, raw, wavelen, cam, ax, fg):
    showcb = False

    ttxt = f'Cam {cam.name}: '

    if raw.ndim == 3:
        frame = raw[t, ...]
    elif raw.ndim == 2:
        frame = raw
    elif raw.ndim == 1:  # GeoData
        frame = raw.reshape((sqrt(raw.size), -1))
    else:
        raise ValueError('ndim==3 or 2')
# %% filtering (optional) # FIXME provide noise estimate
    """
    http://scikit-image.org/docs/dev/api/skimage.restoration.html?highlight=denoise
    """
    if 'wiener' in cam.cp:
        # psf = ones((cam.wiener, cam.wiener)) / cam.wiener**2
        frame = wiener(frame, cam.cp['wiener'])

    if 'medfilt2d' in cam.cp:
        frame = medfilt2d(frame.astype(float), cam.cp['medfilt2d'])

    if 'denoise_bilateral' in cam.cp:
        frame = skres.denoise_bilateral(((frame - cam.clim[0]) / (cam.clim[1] - cam.clim[0])).clip(0., 1.),
                                        sigma_color=0.05, sigma_spatial=15, multichannel=False)
    if 'denoise_tv_chambolle' in cam.cp:
        frame = skres.denoise_tv_chambolle(((frame - cam.clim[0]) / (cam.clim[1] - cam.clim[0])).clip(0., 1.),
                                           )
# %% plotting raw uint16 data
    if False:
        v = vis.HistEqStretch(frame)
        NORM = ImageNormalize(stretch=v)

    NORM = None
    #  NORM = LogNorm()
    # NORM = ImageNormalize(stretch=vis.LogStretch())

    hi = ax.imshow(frame,
                   origin='lower', interpolation='none',
                   # aspect='equal',
                   # extent=(0,C.superx,0,C.supery),
                   vmin=cam.clim[0], vmax=cam.clim[1],
                   cmap='gray', norm=NORM)

    # autoscale(False) for case where we put plots on top of image
    # yet still reduces blank space between subplots
    ax.autoscale(False)

    if showcb:  # showing the colorbar makes the plotting go 5-10x more slowly
        hc = fg.colorbar(hi, ax=ax)  # not cax!
        hc.set_label(f'{raw.dtype} data numbers')

    dtframe = datetime.utcfromtimestamp(cam.tKeo[t])

    if cam.name == 'asi':
        dtstr = datetime.strftime(dtframe, '%H:%M:%S')
        if int(wavelen[t]) == 428:
            tcolor = 'blue'
        elif int(wavelen[t]) == 557:
            tcolor = 'limegreen'
        elif int(wavelen[t]) == 630:
            tcolor = 'red'
        else:
            tcolor = 'limegreen'
        ttxt += f'{dtstr} $\lambda$ {wavelen[t]:.1f}'
    else:
        dtstr = datetime.strftime(dtframe, '%H:%M:%S.%f')[:-3]  # millisecond
        tcolor = 'limegreen'
        ttxt += f'{dtstr}'

    ax.set_title(ttxt, color=tcolor)

    ax.set_axis_off()  # no ticks

    if False:
        ax.set_xlabel('x-pixel')
        if cam.name == 0:
            ax.set_ylabel('y-pixel')
# %% plotting 1D cut line
    try:
        ax.plot(cam.cutcol[cam.Lcind], cam.cutrow[cam.Lcind],
                marker='.', linestyle='none', color='blue', markersize=1, alpha=0.5)
        # plot magnetic zenith
        ax.scatter(x=cam.cutcol[cam.angleMagzenind],
                   y=cam.cutrow[cam.angleMagzenind],
                   marker='o', facecolors='none', color='red', s=500, linewidth=2, alpha=0.5)
    except AttributeError:  # asi
        pass
# %% plot cleanup
    ax.grid(False)  # in case Seaborn is used
    return dtframe