Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Matplotlib: using a figure object to initialize a plot

I am building a class of plot tools for a specific experiment. I currently have two plot methods, a static plot using imshow(), and a "movie" format also using imshow() .

Both methods and any future methods, get parameters that are the same for any specific plotting method that I might write. I have all those parameters in a config object at the time the plot class is used.

I don't want to rewrite code in every plot method. I would like to initialize an object (AxesImage I think) that will have these args set: vmin, vmax, extent_dim, Xlocs, Xlabels, Ylocs, Ylabels.

Then I just pass that object to various methods that do some other specific thing. I don't understand how to do this...

import matplotlib.pyplot as plt

data = data_dict[type] # could be real part of a complex number, phase, or the mag...
v_min, v_max = self.get_data_type_scale(data_dict, Type)
freq = data_dict['freq']

# essentially sets the aspect of the plot since the x and y resolutions could be different   
extent_dim = self._get_extent(2)
# gets the labels for physical dimensions of the experiment
Xlocs,Xlabels,Ylocs,Ylabels = self._get_ticks(5,5,extent_dim)

# in the guts of a plot method, the basic idea is the call below.  

plt.imshow(data[0,:,:],cmap='jet',vmin=v_min,...
vmax=v_max,origin='lower', extent = extent_dim)

plt.title('Type:  %s  Freq: %.3e Hz' %(Type,data_dict['freq'][0]) )
plt.xticks(Xlocs, Xlabels)
plt.yticks(Ylocs,Ylabels)
like image 405
wbg Avatar asked Aug 17 '13 01:08

wbg


2 Answers

You need to understand a bit of architecture of matplotlib first (see here for a long article by the founder and current lead developer). At the bottom of the backend layer which deals with rendering and talking to the hardware. On top of that layer are artists which know how to draw them selves by tell the backend object what to do. On top of that layer is the pyplot state machine interface which mimics MATLAB.

Everything you see in a figure is represented internally as an Artist and artists can contain other artists. For example, the Axes object keeps track of it's children Artists which are the axes spines, tickes, labels, your lines or images etc and Axes objects are children of Figure objects. When you tell a figure to draw itself (via fig.canvas.draw()) all the children artists are drawn recursively.

One draw back of this design is that a given instantiation of an Artist can be in exactly one figure (and moving them between figures is hard) so you can't make a AxesImage object and then keep reusing it.

This design also separates what Artists know about. Axes objects know about things like tick location and labels and the display range (which it does by knowing about Axis object, but that is getting even more into the weeds). Things like vmin and vmax are encapsulated in Normalize (doc) objects which the AxesImage keeps track of. This means that you will need to separate how you deal with everything on your list.

I would suggest either using a factory-like pattern here, or a curry-like pattern

Factory-like:

def set_up_axes(some, arguements):
    '''
    Factory to make configured axes (
    '''
    fig, ax = plt.subplots(1, 1) # or what ever layout you want
    ax.set_*(...)
    return fig, ax


my_norm = matplotlib.colors.Normalize(vmin, mmax) # or write a factory to do fancier stuff
fig, ax = set_up_axes(...)
ax.imshow(..., norm=my_norm)
fig2, ax2 = set_up_axes(...)
ax2.imshow(..., norm=mynorm)

You can wrap up a whole set of kwargs to easily re-use them as such:

my_imshow_args = {'extent':[...],
                  'interpolation':'nearest',
                  'norm': my_norm,
                   ...}

ax2.imshow(..., **my_imshow_args)

Curry-like:

def my_imshow(im, ax=None, *args, **kwargs):
    if ax is None:
        ax = plt.gca()
    # do all of your axes set up
    ax.set_xlim(..)

    # set default vmin and vmax
    # you can drop some of these conditionals if you don't want to be
    # able to explicitly override the defaults
    if 'norm' not in kwargs:
        vmin = kwargs.pop('vmin', None)
        vmax = kwargs.pop('vmax', None)
        if vmin is None:
            vmin = default_vmin # or what ever
        if vmax is None:
            vmax = default_vmax
        my_norm = matplotlib.colors.Normalize(vmin, mmax)
        kwargs['norm'] = norm

    # add a similar block for `extent` 
    # or any other kwargs you want to change the default of

    ax.figure.canvas.draw() # if you want to force a re-draw
    return ax.imshow(im, *args, **kwargs)

If you want to be super clever, you can monkey-patch plt.imshow with your version

plt.imshow = my_imshow

There is also the rcParams interface which will allow you to change the default values of many bits and pieces of matplotlib in a global way.

And yet another way to accomplish this (through partial)

like image 129
tacaswell Avatar answered Oct 08 '22 05:10

tacaswell


To show a plot you'll want to use fig.canvas.draw() where fig is an instance of the Figure class. fig.canvas.draw() is the API version of the interactive shell (read: pylab) function draw()

If you need to get the Axes or Figure from an AxesImage object you can call either im.get_axes() or im.get_figure(), respectively.

As far as writing "good" object-oriented code the user interface examples might be good place to start.

like image 44
Phillip Cloud Avatar answered Oct 08 '22 05:10

Phillip Cloud