import numpy as np
from scipy.interpolate import interp1d
import matplotlib as mpl
from datetime import datetime, timezone
from matplotlib.colors import LinearSegmentedColormap
import warnings
import pyspedas
import logging
from copy import copy

def get_bin_boundaries(bin_centers:np.ndarray, ylog:bool = False):
    """ Calculate a list of bin boundaries from a 1-D array of bin center values.

    Parameters
    ----------
    bin_centers: np.ndarray
    Array of Y bin center values

    ylog: bool
    If True, compute the bin boundaries in log space

    Returns
    -------
    tuple
    bin_boundaries: np.ndarray[float]
        Floating point array of bin boundaries computed from the bin centers.  Output array will be one element longer than the input array.
    direction: int
        Flag increasing or decreasing bin order: +1 increasing, -1 decreasing, 0 indeterminate

    """

    nbins = len(bin_centers)
    # Bin boundaries need to be floating point, even if the original bin values are
    # integers.  Initialize to all nans. Since the outputs are boundaries, not centers,
    # there is an extra element in the output array.
    outbins = np.zeros(nbins+1,dtype=np.float64)
    outbins[:] = np.nan

    # If we're working in log space, do the transform before filtering for finite values.
    # THEMIS ESA can have 0.0 bin centers with log scaling!


    if ylog:
        # There might be bin centers equal to 0.0 (e.g. THEMIS ESA).  Replace them with half the next larger
        # bin center.  Any bin centers less than zero will get turned to NaNs when we take logs
        # (and the corresponding data bins effectively removed).
        clean_bins = copy(bin_centers)
        zero_idx = np.where(bin_centers == 0.0)
        if len(zero_idx[0]) > 0:
            clean_bins[zero_idx] = np.nan
            clean_bins[zero_idx] = np.nanmin(clean_bins)/2.0
        working_bins = np.log10(clean_bins)
    else:
        working_bins = bin_centers

    # Check for all nans, or only one finite value
    idx_finite = np.where(np.isfinite(working_bins))

    if type(idx_finite) is tuple:
        idx_finite = idx_finite[0]

    if len(idx_finite) == 0:
        # Return all nans, indeterminate direction
        return outbins, 0
    elif len(idx_finite) == 1:
        idx = idx_finite[0]
        # Only a single bin, so we have to make up some bin boundaries
        if ylog:
            outbins[idx] = bin_centers[idx]/2.0
            outbins[idx+1] = bin_centers[idx]*2.0
        else:
            outbins[idx] = bin_centers[idx] - 1.0
            outbins[idx+1] = bin_centers[idx] + 1.0
        logging.warning("get_bin_boundaries: only one finite bin detected at index %d", idx)
        logging.warning("bin center: %f   bin boundaries: [%f, %f]",bin_centers[idx],outbins[idx], outbins[idx+1])
        # Return boundaries around the single finite bin, direction is increasing
        return outbins, 1

    # The usual case: we have at least two finite bins
    # Are they in increasing or decreasing order?

    if working_bins[idx_finite[0]] > working_bins[idx_finite[-1]]:
        direction = -1
    elif working_bins[idx_finite[0]] < working_bins[idx_finite[-1]]:
        direction = 1
    else:
        # All finite bins are the same?  Or nonmonotonic?  I guess it could happen.
        direction = 0
        logging.warning("get_bin_boundaries: First and last finite bin values are identical, may be nonmonotonic or all the same?")


    # We need to make sure that no NaNs are sandwiched between finite values
    good_bin_count = len(idx_finite)
    leading_nan_count = idx_finite[0]
    trailing_nan_count = nbins - idx_finite[-1] - 1

    if good_bin_count + leading_nan_count + trailing_nan_count != nbins:
        logging.warning("get_bin_boundaries: may contain nans between finite values. Total bin count: %d,  leading nans: %d, trailing_nans: %d, finite vals: %d",
                        nbins, leading_nan_count, trailing_nan_count, good_bin_count)

    # Now compute bin boundaries from all the finite bin centers
    finite_bins = np.copy(working_bins[idx_finite])
    edge_count = good_bin_count+1
    goodbins = np.zeros(edge_count, dtype=np.float64)

    goodbins[0] = finite_bins[0] - (finite_bins[1] - finite_bins[0]) / 2.0
    goodbins[1:edge_count-1] = (finite_bins[:-1] + finite_bins[1:]) / 2.0
    goodbins[edge_count-1] = finite_bins[-1] + (finite_bins[-1] - finite_bins[-2]) / 2.0

    # Deal with any possible leading or trailing nans
    if leading_nan_count > 0:
        outbins[0:leading_nan_count] = np.nan
    outbins[leading_nan_count:leading_nan_count+edge_count] = goodbins[:]
    if trailing_nan_count > 0:
        outbins[leading_nan_count+edge_count:nbins+2] = np.nan

    # Go back to linear space
    if ylog:
        outbins = 10.0**outbins

    return outbins, direction



def specplot_make_1d_ybins(values: np.ndarray, vdata:np.ndarray, ylog:bool, min_ratio:float = 0.001, no_regrid=False):
    """ Convert 2-D Y-bin arrays of bin center values to a 1-D array and rebin the data array

    Parameters
    ----------
    values : np.ndarray
    A 2-D array of values to be plotted as a spectrogram

    vdata: np.ndarray
    A 1-d or 2-D array of values representing center values of Y axis bins.
    If 1-d, bin centers are constant, otherwise they are assumed to be time-varying.

    ylog: bool
    If True, compute the bin boundaries in log space

    min_ratio: float
    Specifies a threshold for determining whether adjacent bins should be combined in a
    "thinning" process.  The default value of .001 represents a 1-pixel difference in
    where the bin boundaries are rendered if the Y axis is 1000 pixels high.

    no_regrid: bool
    If True, skip rebinning the data array and only return the bin boundaries.

    Returns
    -------
    tuple
    regridded_zdata:np.ndarray
        The result of regridding the values array with the new, potentially differwnt bin boundaries
    bin_boundaries_1d:np.ndarray
        The new bin boundaries

    Notes
    -----

    We find the union of all the bin boundaries for time-varying bins, and use that
    instead of an arbitrary high-resolution grid as the resampling target y-values.
    Any NaN values found in the input bin centers (1D or 2D) are dealt with.

    Allows for monotonically increasing or decreasing bin values, and bins that change over time.
    2D bin center arrays with some times having ascending values and other times having descending values
    are allowed.

    """
    #logging.info("Starting 1D vbins processing")
    ntimes = values.shape[0]
    bins_1d = False

    # Determine bin boundaries at each time step (or for all time, with 1-d bin center arrays),
    # weeding out NaNs in the bin center values. Form the union of all the individual bin
    # boundary sets (keeping the time-specific boundary sets, to use when rebinning later
    # Also determine the direction of increase at each time step, and flag any time steps
    # where a direction cannot be determined.

    if len(vdata.shape) == 1:
        #logging.info("Starting 1D vbins boundary processing")
        bins_1d = True
        result = get_bin_boundaries(vdata, ylog=ylog)
        vdata_bins = result[0]
        vdata_direction = result[1]
        bin_boundaries_set = set(vdata_bins)
        input_bin_center_count = len(vdata)
    else:  # 2-d V
        #logging.info("Starting 2D vbins boundary processing")
        bins_1d = False
        input_bin_center_count = vdata.shape[1]
        vdata_bins = np.zeros((ntimes, input_bin_center_count + 1), dtype=np.float64)
        vdata_direction = np.zeros(ntimes, dtype=np.int64)

        # The sentinel values are inserted because arrays with nans will not compare equal,
        # even if the nans are in same places
        sentinel = 1e31
        result = get_bin_boundaries(vdata[0, :], ylog=ylog)
        vdata_bins[0,:] = result[0]
        vdatadir_thistime = result[1]
        vdata_direction[0] = vdatadir_thistime
        pbins = vdata_bins[0,:]
        bin_boundaries_set = set(pbins)
        p = np.copy(vdata[0,:])
        p[np.where(~np.isfinite(p))] = sentinel
        # Now that everything is initialized, go through each time index
        # and maintain a set of all the bin boundaries seen so far.
        for i in range(ntimes-1):
            k = i+1
            t=np.copy(vdata[k,:])
            t[np.where(~np.isfinite(t))] = sentinel
            diff=t-p
            if np.any(diff):
                # bin values have changed, recalculate boundaries and add to running set
                #print(k)
                result = get_bin_boundaries(vdata[k,:], ylog=ylog)
                u = result[0]
                vdatadir_thistime = result[1]
                pbins = u
                vdata_bins[k,:] = u
                vdata_direction[k] = vdatadir_thistime
                uset = set(u)
                bin_boundaries_set = bin_boundaries_set | uset
                p = t
            else:
                # bin values have not channged at this time step, use previously computed values
                vdata_bins[k,:] = pbins
                vdata_direction[k] = vdatadir_thistime

    if bins_1d:
        if vdata_direction == 1:
            #print("1d bins are increasing")
            pass
        elif vdata_direction == -1:
            #print("1d bins are decreasing")
            pass
        else:
            logging.warning("specplot_make_1d_ybins: Direction of increase of 1-D input bin centers are indeterminate (all-nan, all-same, or nonmonotonic)")
            pass
    else:
        inc_count = (vdata_direction == 1).sum()
        dec_count = (vdata_direction == -1).sum()
        ind_count = (vdata_direction == 0).sum()
        #print("2d bins increasing: ", str(inc_count))
        #print("2d bins decreasing: ", str(dec_count))
        #print("2d bins indeterminate: ", str(ind_count))
        if ind_count > 0:
            logging.warning("specplot_make_1d_ybins: Direction of increase of input bin centers was indeterminate (all-nan, all-same, or non-monotonic) at %d of %d time indices.", ind_count, ntimes)

    # Convert the bin boundary set back to an array
    #logging.info("Done finding bin boundaries, sorting")
    vdata_unsorted = np.array(list(bin_boundaries_set))

    # Clean nans and sentinel values (e.g. may be present in FAST y bin values)
    vdata_finite = [val for val in vdata_unsorted if np.isfinite(val)]

    # Sort in ascending order
    output_bin_boundaries = np.sort(vdata_finite)

    output_bin_boundary_len = len(output_bin_boundaries)

    # It is possible (e.g. ELFIN) that some bin boundaries are very close, but not equal
    # (less than a pixel high).  We might want to thin out any bin boundaries within
    # "epsilon" of the previous bin.

    ymax=output_bin_boundaries[output_bin_boundary_len-1]
    ymin=output_bin_boundaries[0]
    yrange = ymax-ymin
    #logging.info("Thinning bin boundaries")
    # With the default min_ratio, epsilon is about one pixel in the y direction for a typical plot size and dpi
    # If min_ratio is 0, the effect is that no bin boundaries will be discarded.

    if ylog:
        epsilon = (np.log10(ymax)-np.log10(ymin)) * min_ratio
    else:
        epsilon = (ymax-ymin)*min_ratio
    diff=output_bin_boundaries[1:]-output_bin_boundaries[:output_bin_boundary_len-1]

    # logging.info("Specplot 1-D y bins before thinning: array size %d, yrange %f, smallest difference %f, epsilon %f,  ratio %f",len(output_bin_boundaries),yrange,np.min(diff),epsilon, (ymax-ymin)/np.min(diff))

    last_val = output_bin_boundaries[0]
    thinned_list = [output_bin_boundaries[0]]
    for i in range(output_bin_boundary_len):
        val = output_bin_boundaries[i]
        if ylog:
            diff = np.log10(val) - np.log10(last_val)
        else:
            diff = val-last_val
        if abs(diff) > epsilon:
            thinned_list.append(val)
            last_val = val

    output_bin_boundaries = np.array(thinned_list)

    #logging.info("Done thinning bin boundaries")
    output_bin_boundary_len = len(output_bin_boundaries)
    # There could be NaNs (e.g. FAST)
    ymax=output_bin_boundaries[output_bin_boundary_len-1]
    ymin=output_bin_boundaries[0]
    yrange = ymax-ymin
    diff=output_bin_boundaries[1:]-output_bin_boundaries[:output_bin_boundary_len-1]

    # logging.info("Specplot 1-D y bins after thinning: array size %d, yrange %f, smallest difference %f, epsilon %f,  ratio %f",len(output_bin_boundaries),yrange,np.min(diff),epsilon, (ymax-ymin)/np.min(diff))

    if no_regrid:
        return output_bin_boundaries
    #logging.info("Started rebinning (new style)")
    if values.dtype.kind == 'f':
        fill = np.nan
    else:
        fill = 0

    # Now we rebin the input data array into the output array, using both the original
    # and combined bin boundaries.

    # The output value array should have a y dimension one less than the bin boundary count
    out_values = np.zeros((ntimes, output_bin_boundary_len - 1), dtype=values.dtype)
    out_values[:,:] = fill

    # Note that output_bin_boundaries is always monotonically increasing, but
    # vdata_bins (the original inputs) can be monotonically decreasing

    for time_index in range(ntimes):
        if len(vdata.shape) == 1:
            input_bin_boundaries = vdata_bins
            input_bin_centers = vdata
            direction = vdata_direction
        else:
            input_bin_boundaries = vdata_bins[time_index, :]
            input_bin_centers = vdata[time_index,:]
            direction = vdata_direction[time_index]

        #print("Time index: " + str(time_index))
        #print("Input bin boundaries:")
        #print(input_bin_boundaries)
        #print("Input bin centers")
        #print(input_bin_centers)
        #print("Output bin boundaries")
        #print(output_bin_boundaries)
        #print("Data values at time step:")
        #print(values[time_index,:])
        if direction == 1:
            # Increasing bin values
            lower_bound_indices = np.searchsorted(output_bin_boundaries, input_bin_boundaries[0:-1],side="left")
            upper_bound_indices = np.searchsorted(output_bin_boundaries, input_bin_boundaries[1:],side="left")
        elif direction == -1:
            lower_bound_indices = np.searchsorted(output_bin_boundaries, input_bin_boundaries[1:],side="left")
            upper_bound_indices = np.searchsorted(output_bin_boundaries, input_bin_boundaries[0:-1],side="left")
        else:
            continue
        #print("Lower bound indices:")
        #print(lower_bound_indices)
        #print("Upper bound indices")
        #print(upper_bound_indices)

        for i in range(input_bin_center_count):
            if not np.isfinite(input_bin_centers[i]):
                pass
            else:
                #print("input bin index: " + str(i))
                #print("input bin center: " + str(input_bin_centers[i]))
                #print("output lower bound index: " + str(lower_bound_indices[i]))
                #print("output upper bound index: " + str(upper_bound_indices[i]))
                #print("output bin value range: " + str(output_bin_boundaries[lower_bound_indices[i]:upper_bound_indices[i]+1]))
                out_values[time_index,lower_bound_indices[i]:upper_bound_indices[i]] = values[time_index, i]
                #print(out_values)
    #logging.info("Done making 1D Y bins")

    return out_values, output_bin_boundaries

def specplot(
    var_data,
    var_times,
    this_axis,
    yaxis_options,
    zaxis_options,
    plot_extras,
    colorbars,
    axis_font_size,
    fig,
    variable,
    time_idxs=None,
    style=None,
):
    """
    Plot a tplot variable as a spectrogram

    Parameters
    ----------
    var_data: dict
        A tplot dictionary containing the data to be plotted
    var_times: array of datetime objects
        An array of datetime objects specifying the time axis
    this_axis
        The matplotlib object for the panel currently being plotted
    yaxis_options: dict
        A dictionary containing the Y axis options to be used for this variable
    zaxis_options: dict
        A dictionary containing the Z axis (spectrogram values) options to be used for this variable
    plot_extras: dict
        A dictionary containing additional plot options for this variable
    colorbars: dict
        The data structure that contains information (for all variables) for creating colorbars.
    axis_font_size: int
        The font size in effect for this axis (used to create colorbars)
    fig: matplotlib.figure.Figure
        A matplotlib figure object to be used for this plot
    variable: str
        The name of the tplxxot variable to be plotted (used for log messages)
    time_idxs: array of int
        The indices of the subset of times to use for this plot. Defaults to None (plot all timestamps).
    style
        A matplotlib style object to be used for this plot. Defaults to None.

    Returns
    -------
        True
    """
    alpha = plot_extras.get("alpha")
    spec_options = {"shading": "auto", "alpha": alpha}
    ztitle = zaxis_options["axis_label"]

    zlog_str = zaxis_options["z_axis_type"]
    ylog_str = yaxis_options["y_axis_type"]
    # Convert zlog_str and ylog_str to bool
    ylog = False
    zlog = False
    if "log" in ylog_str.lower():
        ylog = True
    if "log" in zlog_str.lower():
        zlog = True
    #logging.info("ylog_str is " + str(ylog_str))
    #logging.info("zlog_str is " + str(zlog_str))

    yrange = yaxis_options["y_range"]
    if yrange[0] is None or not np.isfinite(yrange[0]):
        yrange[0] = None
    if yrange[1] is None or not np.isfinite(yrange[1]):
        yrange[1] = None

    if zaxis_options.get("z_range") is not None:
        zrange = zaxis_options["z_range"]
    else:
        zrange = [None, None]

    if zaxis_options.get("axis_subtitle") is not None:
        zsubtitle = zaxis_options["axis_subtitle"]
    else:
        zsubtitle = ""

    # Clean up any fill values in data array
    #set -1.e31 fill values to NaN, jmjm, 2024-02-29
    ytp = np.where(var_data.y==-1.e31,np.nan,var_data.y)
    var_data.y[:,:] = ytp[:,:]

    if zlog:
        zmin = np.nanmin(var_data.y)
        zmax = np.nanmax(var_data.y)
        # gracefully handle the case of all NaNs in the data, but log scale set
        # all 0 is also a problem, causes a crash later when creating the colorbar
        if np.isnan(var_data.y).all():
            # no need to set a log scale if all the data values are NaNs, or all zeroes
            spec_options["norm"] = None
            spec_options["vmin"] = zrange[0]
            spec_options["vmax"] = zrange[1]
            logging.info("Variable %s contains all-NaN data", variable)
        elif not np.any(var_data.y):
            # properly handle all 0s in the data
            spec_options["norm"] = None
            spec_options["vmin"] = zrange[0]
            spec_options["vmax"] = zrange[1]
            logging.info("Variable %s contains all-zero data", variable)
        else:
            spec_options["norm"] = mpl.colors.LogNorm(vmin=zrange[0], vmax=zrange[1])
    else:
        spec_options["norm"] = None
        spec_options["vmin"] = zrange[0]
        spec_options["vmax"] = zrange[1]

    cmap = None

    if plot_extras.get("colormap") is not None:
        cmap = plot_extras["colormap"][0]
    else:
        # default to the SPEDAS color map if the user doesn't have a MPL style set
        if style is None:
            cmap = "spedas"

    # kludge to add support for the 'spedas' color bar
    if cmap == "spedas":
        _colors = pyspedas.tplot_tools.spedas_colorbar
        spd_map = [
            (np.array([r, g, b])).astype(np.float64) / 256
            for r, g, b in zip(_colors.r, _colors.g, _colors.b)
        ]
        cmap = LinearSegmentedColormap.from_list("spedas", spd_map)

    spec_options["cmap"] = cmap

    input_zdata = var_data.y[time_idxs, :]
    input_times = var_data.times[time_idxs]

    # Figure out which attribute to use for Y bin centers
    #allow use of v1, v2, jmm, 2024-03-20
    if len(var_data) == 3:
        if hasattr(var_data,'v'):
            input_bin_centers = var_data.v
        elif hasattr(var_data,'v1'):
            input_bin_centers = var_data.v1
        else:
            logging.warning("Multidimensional variable %s has no v or v1 attribute",variable)
    elif len(var_data) == 4:
        if hasattr(var_data, 'v1'):
            if 'spec_dim_to_plot' in plot_extras:
                if plot_extras['spec_dim_to_plot'] == 'v1':
                    input_bin_centers = var_data.v1
        if hasattr(var_data, 'v2'):
            if 'spec_dim_to_plot' in plot_extras:
                if plot_extras['spec_dim_to_plot'] == 'v2':
                    input_bin_centers = var_data.v2
    else:
        logging.warning("Too many dimensions on the variable: " + variable)
        return

    # Clean up any fill values in bin center array
    vtp = np.where(input_bin_centers == -1.e31, np.nan, input_bin_centers)
    if len(vtp.shape) == 1:
        input_bin_centers[:] = vtp[:]
    else:
        input_bin_centers[:, :] = vtp[:, :]

    if len(input_bin_centers.shape) > 1:
        # time varying 'v', need to limit the values to those within the requested time range
        input_bin_centers = input_bin_centers[time_idxs, :]

    # This call flattens any time-varying bin boundaries into a 1-d list (out_vdata)
    # and regrids the data array to the new y bin count (regridded_zdata)

    #logging.info("Starting specplot processing")

    regridded_zdata, bin_boundaries_1d = specplot_make_1d_ybins(input_zdata, input_bin_centers, ylog)

    # At this point, bin_boundaries_1d, the array of bin boundaries, is guaranteed to be
    # 1-dimensional, in ascending order, with all finite values. It has one more element
    # than the Y dimension of regridded_zdata.  The Y dimension of regridded_zdata may have changed
    # as a result of flattening a 2-D out_vdata input.
    # If ylog==True, all values in bin_boundaries_1d will be strictly positive.

    assert(len(bin_boundaries_1d.shape) == 1) # bin boundaries are 1-D
    assert(len(regridded_zdata.shape) == 2) # output array is 2-D
    assert(bin_boundaries_1d.shape[0] == regridded_zdata.shape[1]+1) # bin boundaries have one more element in Y dimension
    assert(np.isfinite(bin_boundaries_1d.all())) # no nans in bin boundaries
    assert(bin_boundaries_1d[-1] > bin_boundaries_1d[0]) # bin boundaries in ascending order
    if ylog:
        assert(np.all(bin_boundaries_1d > 0.0)) # bin boundaries all positive if log scaling

    # Get min and max bin boundaries
    vmin = np.min(bin_boundaries_1d)
    vmax = np.max(bin_boundaries_1d)

    #could also have a fill in yrange
    #    if yrange[0] == -1e31: #This does not work sometimes?
    if yrange[0] < -0.9e31:
        yrange[0] = vmin
    if yrange[1] < -0.9e31:
        yrange[1] = vmax


    #logging.info("Starting specplot time boundary processing")
    input_unix_times = np.int64(input_times) / 1e9
    result = get_bin_boundaries(input_unix_times)
    # For pcolormesh, we also want bin boundaries (not center values) on the time axis
    time_boundaries_dbl = result[0]
    time_boundaries_ns = np.int64(time_boundaries_dbl*1e9)
    # Now back to numpy datetime64
    time_boundaries = np.array(time_boundaries_ns, dtype='datetime64[ns]')
    #logging.info("Done with specplot initial processing")
    # If the user set a yrange with the 'options' command, nothing is needed here
    # since tplot takes care of it.   If not, set it here to the min/max finite bin
    # boundaries.  If left unspecified, pcolormesh might do something weird to the y limits.
    if yaxis_options.get('y_range_user') is None:
        this_axis.set_ylim([vmin, vmax])


    # automatic interpolation options
    if yaxis_options.get("x_interp") is not None:
        x_interp = yaxis_options["x_interp"]

        # interpolate along the x-axis
        if x_interp:
            if yaxis_options.get("x_interp_points") is not None:
                nx = yaxis_options["x_interp_points"]
            else:
                fig_size = fig.get_size_inches() * fig.dpi
                nx = fig_size[0]

            if zlog:
                zdata = np.log10(regridded_zdata)
            else:
                zdata = regridded_zdata

            zdata[zdata < 0.0] = 0.0
            zdata[zdata == np.nan] = 0.0

            # convert to floats for the interpolation
            spec_unix_times = np.int64(var_data.times[time_idxs]) / 1e9

            # interpolate in the x-direction
            interp_func = interp1d(
                spec_unix_times, zdata, axis=0, bounds_error=False, kind="linear"
            )
            out_times = (
                np.arange(0, nx, dtype=np.float64)
                * (spec_unix_times[-1] - spec_unix_times[0])
                / (nx - 1)
                + spec_unix_times[0]
            )
            regridded_zdata = interp_func(out_times)

            if zlog:
                regridded_zdata = 10**regridded_zdata

            # Convert time bin centers to bin boundaries
            result = get_bin_boundaries(out_times, ylog=False)
            # Convert unix times back to np.datetime64[ns] objects
            unix_time_boundaries_int64 = np.int64(result[0]*1e9)
            time_boundaries = np.array(unix_time_boundaries_int64, dtype="datetime64[ns]")

    if yaxis_options.get("y_interp") is not None:
        y_interp = yaxis_options["y_interp"]

        if y_interp:
            if yaxis_options.get("y_interp_points") is not None:
                ny = yaxis_options["y_interp_points"]
            else:
                fig_size = fig.get_size_inches() * fig.dpi
                ny = fig_size[1]

            if zlog:
                zdata = np.log10(regridded_zdata)
            else:
                zdata = regridded_zdata

            if ylog:
                vdata = np.log10(bin_boundaries_1d)
                ycrange = np.log10(yrange)
            else:
                vdata = bin_boundaries_1d
                ycrange = yrange

            if not np.isfinite(ycrange[0]):
                ycrange = [np.min(vdata), yrange[1]]

            zdata[zdata < 0.0] = 0.0
            zdata[zdata == np.nan] = 0.0  # does not work

            # vdata was calculated from 1-D bin boundaries, not bin centers.
            # We need to go to bin centers for interpolation
            uppers = vdata[1:]
            lowers = vdata[0:-1]
            centers = (uppers+lowers)/2.0

            interp_func = interp1d(centers, zdata, axis=1, bounds_error=False)
            out_vdata_centers = (
                np.arange(0, ny, dtype=np.float64)
                * (ycrange[1] - ycrange[0])
                / (ny - 1)
                + ycrange[0]
            )
            regridded_zdata = interp_func(out_vdata_centers)

            # Now we'll convert from bin centers back to bin boundaries for pcolormesh
            # We're still in linear space at this point
            result = get_bin_boundaries(out_vdata_centers, ylog=False)
            rebinned_boundaries = result[0]

            if zlog:
                regridded_zdata = 10**regridded_zdata

            if ylog:
                bin_boundaries_1d = 10**rebinned_boundaries

    # check for negatives if zlog is requested
    if zlog:
        regridded_zdata[regridded_zdata < 0.0] = 0.0

    ylim_before = this_axis.get_ylim()
    # create the spectrogram (ignoring warnings)
    with warnings.catch_warnings():
        warnings.simplefilter("ignore")
        #logging.info("Starting pcolormesh")
        im = this_axis.pcolormesh(time_boundaries, bin_boundaries_1d.T, regridded_zdata.T, **spec_options)
        #logging.info("Done with pcolormesh")
    ylim_after = this_axis.get_ylim()

    #logging.info("ylim before pcolormesh: %s ",str(ylim_before))
    #logging.info("ylim after pcolormesh: %s", str(ylim_after))

    # store everything needed to create the colorbars
    colorbars[variable] = {}
    colorbars[variable]["im"] = im
    colorbars[variable]["axis_font_size"] = axis_font_size
    colorbars[variable]["ztitle"] = ztitle
    colorbars[variable]["zsubtitle"] = zsubtitle
    return True
