Ejemplo n.º 1
0
def hapiplot(*args, **kwargs):
    """Plot response from HAPI server.

    Demos
    -----
    <https://github.com/hapi-server/client-python/blob/master/hapiclient/plot/hapiplot_test.py>


    Usage
    -----
            data, meta = hapiplot(server, dataset, params, start, stop, **kwargs)
        or    
            meta = hapiplot(data, meta, **kwargs)
        where data and meta are return values from `hapi()`.

        All parameters are plotted. If a parameter has a bins attribute,
        it is plotted using `heatmap()`. Otherwise, it is plotted using
        `timeseries()`.

    Returns
    -------
        `data` is the same as that returned from `hapi()`.
        `meta` is the same as that returned from `hapi()` with the additon of

        meta['parameters'][i]['hapiplot']['figure'] is a reference to the
            figure (e.g., plt.gcf()). Usage example:

            >>> fig = meta['parameters'][i]['hapiplot']['figure']
            >>> fig.set_facecolor('blue')
            >>> fig.axes[0].set_ylabel('new y-label')
            >>> fig.axes[0].set_title('new title\\nsubtitle\\nsubtitle')
            >>> fig.tight_layout()

        meta['parameters'][i]['hapiplot']['colorbar'] is a reference to the
            colorbar on the figure (if parameter plotted as a heatmap)

        meta['parameters'][i]['hapiplot']['image'] is PNG, PDF, or SVG data
            and is included only if `returnimage=True`. Usage example:

            >>> img = meta['parameters'][i]['hapiplot']['image']
            >>> Image.open(io.BytesIO(img)).show()
            >>> # or
            >>> f = open('/tmp/a.png', 'wb')
            >>> f.write(img)
            >>> f.close()

    See Also
    ---------
        hapi: Get data from a HAPI server
        timeseries: Used by `hapiplot()` to HAPI parameters with no `bins`
        heatmap: Used by `hapiplot()` to HAPI parameters with `bins`

        <https://github.com/hapi-server/client-python-notebooks>

    kwargs
    ------
        * logging: [False] Display console messages
        * usecache: [True] Use cached data
        * tsopts: {} kwargs for the `timeseries()` function
        * hmopts: {} kwargs for the `heatmap()` function

    Other kwargs
    ------------
        * returnimage: [False] If True, `hapiplot()` returns binary image data
        * returnformat: [png], svg, or pdf
        * cachedir: Directory to store images. Default is hapiclient.hapi.cachedir()
        * useimagecache: [True] Used cached image (when returnimage=True)
        * saveimage: [False] Save image to `cachedir`
        * saveformat: [png], svg, or pdf

    Example
    --------
        >>> server  = 'http://hapi-server.org/servers/TestData/hapi'
        >>> dataset = 'dataset1'
        >>> start   = '1970-01-01T00:00:00'
        >>> stop    = '1970-01-02T00:00:00'
        >>> params  = 'scalar,vector'
        >>> opts    = {'logging': True}
        >>>
        >>> from hapiclient import hapiplot
        >>> hapiplot(server, dataset, params, start, stop, **opts)
        >>>
        >>> # or
        >>>
        >>> from hapiclient import hapi, hapiplot
        >>> data, meta = hapi(server, dataset, params, start, stop, **opts)
        >>> hapiplot(data, meta, **opts)

    """

    if len(args) == 5:
        # For consistency with gallery and autoplot functions, allow useage of
        # hapiplot(server, dataset, parameters, start, stop, **kwargs)
        from hapiclient.hapi import hapiopts
        from hapiclient.hapi import hapi
        kwargs_allowed = hapiopts()
        kwargs_reduced = {}
        # Extract hapi() options from kwargs
        for key, value in kwargs.items():
            if key in kwargs_allowed:
                kwargs_reduced[key] = value
        data, meta = hapi(args[0], args[1], args[2], args[3], args[4],
                          **kwargs_reduced)
        meta = hapiplot(data, meta, **kwargs)
        return data, meta
    else:
        data = args[0]
        meta = args[1]

    # Default options
    opts = {
        'logging': False,
        'saveimage': False,
        'returnimage': False,
        'usecache': True,
        'useimagecache': True,
        'cachedir': cachedir(),
        'backend': 'default',
        'style': 'fast',
        'title': '',
        'ztitle': '',
        'xlabel': '',
        'ylabel': '',
        'zlabel': '',
        'logx': False,
        'logy': False,
        'logz': False,
        'tsopts': {},
        'hmopts': {},
        'backend': 'default',
        'rcParams': {
            'savefig.dpi': 144,
            'savefig.format': 'png',
            'savefig.bbox': 'tight',
            'savefig.transparent': False,
            'figure.max_open_warning': 50,
            'figure.figsize': (7, 3),
            'figure.dpi': 144,
            'axes.titlesize': 10,
            "font.family": "serif",
            "font.serif": rcParams['font.serif'],
            "font.weight": "normal"
        },
        '_rcParams': {
            'figure.bbox': 'standard'
        }
    }

    # Override defaults
    opts = setopts(opts, kwargs)

    from hapiclient import __version__
    log('Running hapi.py version %s' % __version__, opts)

    # _rcParams are not actually rcParams:
    #'figure.bbox': 'standard',
    # Set to 'tight' to have fig.tight_layout() called before figure shown.

    if opts["saveimage"]:
        # Create cache directory
        dir = cachedir(opts['cachedir'], meta['x_server'])
        if not os.path.exists(dir): os.makedirs(dir)

    # Convert from NumPy array of byte literals to NumPy array of
    # datetime objects.
    timename = meta['parameters'][0]['name']
    Time = hapitime2datetime(data[timename])

    if len(meta["parameters"]) == 1:
        a = 0  # Time is only parameter
    else:
        a = 1  # Time plus another parameter

    for i in range(a, len(meta["parameters"])):

        meta["parameters"][i]['hapiplot'] = {}

        name = meta["parameters"][i]["name"]

        # Return cached image (case where we are returning binary image data)
        # imagepath() options. Only need filename under these conditions.
        if opts['saveimage'] or (opts['returnimage']
                                 and opts['useimagecache']):
            # Will use given rc style parameters and style name to generate file name.
            # Assumes rc parameters of style and hapiplot defaults never change.
            styleParams = {}
            fmt = opts['rcParams']['savefig.format']
            if 'rcParams' in kwargs:
                styleParams = kwargs['rcParams']
                if 'savefig.format' in kwargs['rcParams']:
                    kwargs['rcParams']['savefig.format']

            fnameimg = imagepath(meta, i, opts['cachedir'], styleParams, fmt)

        if opts['useimagecache'] and opts['returnimage'] and os.path.isfile(
                fnameimg):
            log('Returning cached binary image data in ' + fnameimg, opts)

            meta["parameters"][i]['hapiplot']['imagefile'] = fnameimg
            with open(fnameimg, "rb") as f:
                meta["parameters"][i]['hapiplot']['image'] = f.read()
            continue

        name = meta["parameters"][i]["name"]
        log("Plotting parameter '%s'" % name, opts)

        if len(data[name].shape) > 3:
            # TODO: Implement more than 2 dimensions?
            warning(
                'Parameter ' + name +
                ' has size with more than 2 dimensions. Plotting first two only.'
            )
            continue

        # If parameter has a size with two elements, e.g., [N1, N2]
        # create N2 plots.
        if len(data[name].shape) == 3:  # shape = (Time, N1, N2)

            nplts = data[name].shape[1]
            if opts['returnimage']:
                warning(
                    'Only returning first image for parameter with size[1] > 1.'
                )
                nplts = 1
            for j in range(nplts):
                timename = meta['parameters'][0]['name']

                # Name to indicate what is plotted
                name_new = name + "[:," + str(j) + "]"
                # Reduced data ND Array
                datar = np.ndarray(shape=(data[name].shape[0]),
                                   dtype=[(timename, data.dtype[timename]),
                                          (name_new, data[name].dtype.str,
                                           data.dtype[name].shape[1])])

                datar[timename] = data[timename]
                datar[name_new] = data[name][:, j]
                # Copy metadata to create a reduced metadata object
                metar = meta.copy()  # Shallow copy
                metar["parameters"] = []
                # Create parameters array with elements of Time parameter ...
                metar["parameters"].append(meta["parameters"][0])
                # .... and this parameter
                metar["parameters"].append(meta["parameters"][i].copy())
                # Give new name to indicate it is a subset of full parameter
                metar["parameters"][1]['name'] = name_new
                metar["parameters"][1]['name_orig'] = name
                # New size is N1
                metar["parameters"][1]['size'] = [
                    meta["parameters"][i]['size'][1]
                ]

                if 'units' in metar["parameters"][1]:
                    if type(meta["parameters"][i]['units']
                            ) == str or meta["parameters"][i]['units'] == None:
                        # Same units applies to all dimensions
                        metar["parameters"][1]["units"] = meta["parameters"][
                            i]['units']
                    else:
                        metar["parameters"][1]["units"] = meta["parameters"][
                            i]['units'][j]

                if 'label' in metar["parameters"][1]:
                    if type(meta["parameters"][i]['label']) == str:
                        # Same label applies to all dimensions
                        metar["parameters"][1]["label"] = meta["parameters"][
                            i]['label']
                    else:
                        metar["parameters"][1]["label"] = meta["parameters"][
                            i]['label'][j]

                # Extract bins corresponding to jth column of data[name]
                if 'bins' in metar["parameters"][1]:
                    metar["parameters"][1]['bins'] = []
                    metar["parameters"][1]['bins'].append(
                        meta["parameters"][i]['bins'][j])

                # rcParams is modified by setopts to have all rcParams.
                # reset to original passed rcParams so that imagepath
                # computes file name based on rcParams passed to hapiplot.
                if 'rcParams' in kwargs:
                    opts['rcParams'] = kwargs['rcParams']

                metar = hapiplot(datar, metar, **opts)
                meta["parameters"][i]['hapiplot'] = metar["parameters"][i][
                    'hapiplot']
            return meta

        if 'name_orig' in meta["parameters"][i]:
            title = meta["x_server"] + "\n" + meta["x_dataset"] + " | " + meta[
                "parameters"][i]['name_orig']
        else:
            title = meta["x_server"] + "\n" + meta["x_dataset"] + " | " + name

        as_heatmap = False
        if 'size' in meta['parameters'][
                i] and meta['parameters'][i]['size'][0] > 10:
            as_heatmap = True

        if 'bins' in meta['parameters'][i]:
            as_heatmap = True

        if 'units' in meta["parameters"][i] and type(
                meta["parameters"][i]["units"]) == list:
            if as_heatmap:
                warning(
                    "Not plotting %s as heatmap because components have different units."
                    % meta["parameters"][i]["name"])
            as_heatmap = False

        if as_heatmap:
            # Plot as heatmap

            hmopts = {
                'returnimage': opts['returnimage'],
                'transparent': opts['rcParams']['savefig.transparent']
            }

            if meta["parameters"][i]["type"] == "string":
                warning(
                    "Plots for only types double, integer, and isotime implemented. Not plotting %s."
                    % meta["parameters"][i]["name"])
                continue

            z = np.asarray(data[name])

            if 'fill' in meta["parameters"][i] and meta["parameters"][i][
                    'fill']:
                if meta["parameters"][i]["type"] == 'integer':
                    z = z.astype('<f8', copy=False)
                z = fill2nan(z, meta["parameters"][i]['fill'])

            if 'bins' in meta['parameters'][i]:
                ylabel = meta["parameters"][i]['bins'][0]["name"] + " [" + meta[
                    "parameters"][i]['bins'][0]["units"] + "]"
            else:
                ylabel = "col %d" % i

            units = meta["parameters"][i]["units"]
            nl = ""
            if len(name) + len(units) > 30:
                nl = "\n"

            zlabel = name + nl + " [" + units + "]"

            if 'bins' in meta['parameters'][i]:
                if 'ranges' in meta["parameters"][i]['bins'][0]:
                    bins = np.array(meta["parameters"][i]['bins'][0]["ranges"])
                else:
                    bins = np.array(
                        meta["parameters"][i]['bins'][0]["centers"])
            else:
                bins = np.arange(meta['parameters'][i]['size'][0])

            dt = np.diff(Time)
            dtu = np.unique(dt)
            if len(dtu) > 1:
                #warning('Time values are not uniformly spaced. Bin width for '
                #        'time will be based on time separation of consecutive time values.')
                if False and 'cadence' in meta:

                    # Cadence != time bin width in general, so don't do this.
                    # See https://github.com/hapi-server/data-specification/issues/75
                    # Kept for future reference when Parameter.bin.window or
                    # Parameter.bin.windowWidth is added to spec.
                    import isodate
                    dt = isodate.parse_duration(meta['cadence'])
                    if 'timeStampLocation' in meta:
                        if meta['timeStampLocation'].lower() == "begin":
                            Time = np.vstack((Time, Time + dt))
                        if meta['timeStampLocation'].lower() == "end":
                            Time = np.vstack((Time - dt, Time))
                        if meta['timeStampLocation'].lower() == "center":
                            Time = np.vstack((Time - dt / 2, Time + dt / 2))
                    else:
                        # Default is center
                        Time = np.vstack((Time - dt / 2, Time + dt / 2))

                    Time = np.transpose(Time)
            elif 'timeStampLocation' in meta:
                if meta['timeStampLocation'].lower() == "begin":
                    Time = np.append(Time, Time[-1] + dtu[0])
                if meta['timeStampLocation'].lower() == "end":
                    Time = Time - dtu[0]
                    Time = np.append(Time, Time[-1] + dtu[0])

            if opts['xlabel'] != '' and 'xlabel' not in opts['hmopts']:
                hmopts['xlabel'] = opts['xlabel']

            opts['hmopts']['ylabel'] = ylabel
            if opts['ylabel'] != '' and 'ylabel' not in opts['hmopts']:
                hmopts['ylabel'] = opts['ylabel']

            opts['hmopts']['title'] = title
            if opts['title'] != '' and 'title' not in opts['hmopts']:
                hmopts['title'] = opts['title']

            opts['hmopts']['zlabel'] = zlabel
            if opts['zlabel'] != '' and 'zlabel' not in opts['hmopts']:
                hmopts['zlabel'] = opts['zlabel']

            if False:
                opts['hmopts']['ztitle'] = ztitle
                if opts['ztitle'] != '' and 'ztitle' not in opts['hmopts']:
                    hmopts['ztitle'] = opts['ztitle']

            if opts['logx'] is not False:
                hmopts['logx'] = True
            if opts['logy'] is not False:
                hmopts['logy'] = True
            if opts['logz'] is not False:
                hmopts['logz'] = True

            for key, value in opts['hmopts'].items():
                hmopts[key] = value

            with rc_context(rc=opts['rcParams']):
                fig, cb = heatmap(Time, bins, np.transpose(z), **hmopts)

            meta["parameters"][i]['hapiplot']['figure'] = fig
            meta["parameters"][i]['hapiplot']['colorbar'] = cb

        else:

            tsopts = {
                'logging': opts['logging'],
                'returnimage': opts['returnimage'],
                'transparent': opts['rcParams']['savefig.transparent']
            }

            ptype = meta["parameters"][i]["type"]
            if ptype == "isotime":
                y = hapitime2datetime(data[name])
            elif ptype == 'string':
                y = data[name].astype('U')
            else:
                y = np.asarray(data[name])

            if 'fill' in meta["parameters"][i] and meta["parameters"][i][
                    'fill']:
                if ptype == 'isotime' or ptype == 'string':
                    Igood = y != meta["parameters"][i]['fill']
                    # Note that json reader returns fill to U not b.
                    Nremoved = data[name].size - Igood.size
                    if Nremoved > 0:
                        # TODO: Implement masking so connected line plots will
                        # show gaps as they do for NaN values.
                        warning('Parameter ' + name + ' is of type ' + ptype +
                                ' and has ' + str(Nremoved) +
                                ' fill value(s). Masking is not implemented, '
                                'so removing fill elements before plotting.')
                        Time = Time[Igood]
                        y = y[Igood]
                if ptype == 'integer':
                    y = y.astype('<f8', copy=False)
                if ptype == 'integer' or ptype == 'double':
                    y = fill2nan(y, meta["parameters"][i]['fill'])

            units = None
            if 'units' in meta["parameters"][i] and meta["parameters"][i][
                    'units']:
                units = meta["parameters"][i]["units"]

            nl = ""
            if type(units) == str:
                if len(name) + len(units) > 30:
                    nl = "\n"  # TODO: Automatically figure out when this is needed.

            ylabel = name
            if units is not None and type(units) is not list:
                ylabel = name + nl + " [" + units + "]"

            if type(units) == list:
                ylabel = name

            if not 'legendlabels' in opts['tsopts']:
                legendlabels = []
                if 'size' in meta['parameters'][i]:
                    for l in range(0, meta['parameters'][i]['size'][0]):
                        bin_label = ''
                        bin_name = ''
                        col_name = ''
                        if 'bins' in meta['parameters'][i]:
                            bin_name = meta['parameters'][i]['bins'][0]['name']
                            if 'label' in meta['parameters'][i]['bins'][0]:
                                if type(meta['parameters'][i]['bins'][0]
                                        ['label']) == str:
                                    bin_name = meta['parameters'][i]['bins'][
                                        0]['label']
                                else:
                                    bin_name = meta['parameters'][i]['bins'][
                                        0]['label'][l]
                            sep = ''
                            if 'centers' in meta['parameters'][i]['bins'][
                                    0] and 'ranges' in meta['parameters'][i][
                                        'bins'][0]:
                                bin_name = bin_name + ' bin with'
                                sep = ';'

                            bin_label = ''

                            if 'units' in meta['parameters'][i]['bins'][0]:
                                bin_units = meta['parameters'][i]['bins'][0][
                                    'units']
                                if type(bin_units) == list:
                                    if type(bin_units[l]) == str:
                                        bin_units = ' [' + bin_units[l] + ']'
                                    elif bin_units[l] == None:
                                        bin_units = ' []'
                                    else:
                                        bin_units = ''
                                else:
                                    if type(bin_units) == str:
                                        bin_units = ' [' + bin_units + ']'
                                    else:
                                        bin_units = ''
                            if 'centers' in meta['parameters'][i]['bins'][0]:
                                if meta['parameters'][i]['bins'][0]['centers'][
                                        l] is not None:
                                    bin_label = bin_label + ' center = ' + str(
                                        meta['parameters'][i]['bins'][0]
                                        ['centers'][l]) + bin_units
                                else:
                                    bin_label = bin_label + ' center = None'

                            if 'ranges' in meta['parameters'][i]['bins'][0]:
                                if type(meta['parameters'][i]['bins'][0]
                                        ['ranges'][l]) == list:
                                    bin_label = bin_label + sep + ' range = [' + str(
                                        meta['parameters'][i]['bins'][0]
                                        ['ranges'][l][0]) + ', ' + str(
                                            meta['parameters'][i]['bins'][0]
                                            ['ranges'][l][1]) + ']' + bin_units
                                else:
                                    bin_label = bin_label + sep + ' range = [None]'

                            if bin_label != '':
                                bin_label = 'bin:' + bin_label
                                col_name = bin_name + '#%d' % l

                        if col_name == '':
                            col_name = 'col #%d' % l

                        if 'label' in meta['parameters'][i]:
                            #print(meta)
                            #print(meta['parameters'][i]['label'])
                            if type(meta['parameters'][i]['label']) == list:
                                col_name = meta['parameters'][i]['label'][l]

                        if type(units) == list:
                            if len(units) == 1:
                                legendlabels.append(col_name + ' [' +
                                                    units[0] + '] ' +
                                                    bin_label)
                            elif type(units[l]) == str:
                                legendlabels.append(col_name + ' [' +
                                                    units[l] + '] ' +
                                                    bin_label)
                            elif units[l] == None:
                                legendlabels.append(col_name + ' [] ' +
                                                    bin_label)
                            else:
                                legendlabels.append(col_name + ' ' + bin_label)
                        else:
                            # Units are on y label
                            legendlabels.append(col_name + ' ' + bin_label)
                    tsopts['legendlabels'] = legendlabels

            # If xlabel in opts and opts['tsopts'], warn?
            if opts['xlabel'] != '' and 'xlabel' not in opts['tsopts']:
                tsopts['xlabel'] = opts['xlabel']

            tsopts['ylabel'] = ylabel
            if opts['ylabel'] != '' and 'ylabel' not in opts['tsopts']:
                tsopts['ylabel'] = opts['ylabel']

            tsopts['title'] = title
            if opts['title'] != '' and 'title' not in opts['tsopts']:
                tsopts['title'] = opts['title']

            if opts['logx'] is not False and 'logx' not in opts['tsopts']:
                tsopts['logx'] = True
            if opts['logy'] is not False and 'logy' not in opts['tsopts']:
                tsopts['logy'] = True

            # Apply tsopts
            for key, value in opts['tsopts'].items():
                tsopts[key] = value

            with rc_context(rc=opts['rcParams']):
                fig = timeseries(Time, y, **tsopts)

            meta["parameters"][i]['hapiplot']['figure'] = fig

        if opts['saveimage']:
            log('Writing %s' % fnameimg, opts)
            meta["parameters"][i]['hapiplot']['imagefile'] = fnameimg
        else:
            from io import BytesIO
            fnameimg = BytesIO()

        if opts['returnimage']:
            with rc_context(rc=opts['rcParams']):
                fig.canvas.print_figure(fnameimg)

            if opts['saveimage']:
                with open(fnameimg, mode='rb') as f:
                    meta["parameters"][i]['hapiplot']['image'] = f.read()
            else:
                meta["parameters"][i]['hapiplot']['image'] = fnameimg.getvalue(
                )
        else:
            with rc_context(rc=opts['rcParams']):
                fig.savefig(fnameimg)

            # Two calls to fig.tight_layout() may be needed b/c of bug in PyQt:
            # https://github.com/matplotlib/matplotlib/issues/10361
            if opts['_rcParams']['figure.bbox'] == 'tight':
                fig.tight_layout()

    return meta
def test_parse_string_input():
    Time = '1970-01-01T00:00:00.000Z'
    a = hapitime2datetime(Time, logging=logging)
    assert a[0].strftime("%Y-%m-%dT%H:%M:%S.%fZ") == expected
def test_parse_manual_1970_01Z():
    Time = np.array([b'1970-01Z'])
    a = hapitime2datetime(Time, logging=logging)
    assert a[0].strftime("%Y-%m-%dT%H:%M:%S.%fZ") == expected
def test_parse_ndarray_of_bytes_input():
    Time = np.array([b'1970-01-01T00:00:00.000Z'])
    a = hapitime2datetime(Time, logging=logging)
    assert a[0].strftime("%Y-%m-%dT%H:%M:%S.%fZ") == expected