Ryan May wrote:
On Wed, May 6, 2009 at 8:53 AM, Robert Cimrman <cimrm...@ntc.zcu.cz> wrote:

Ryan May wrote:

On Wed, May 6, 2009 at 7:57 AM, Robert Cimrman <cimrm...@ntc.zcu.cz>
wrote:

Just for the record: Ryan May's example in this thread, that uses pipes,
inspired me to try pipes as well, instead of queues
(multiprocessing.Pipe instead of Queue) and the "hanging problem", i.e.
the problem that Ctrl-C interrupted the program, but it had to be killed
to stop, disappeared. I can fix the script that I sent in message [1]
and provide it, if there is interest. (Currently I have fixed only the
version that is within sfepy).


I know I'd be interested.  With your permission, it might make a nice
example as well.

Permission granted :) I have sent the script in response to William.


Done.  I like the fact that with your example, everything is self-contained
in a single script.

Exactly, the details of starting another python process are hidden, the multiprocessing module is really nice.

You might want to add

import matplotlib
matplotlib.use('GtkAgg')

to the script, and remove "from Queue import Empty".

FYI: I am sending also a more complex example - a Log class used in sfepy, which supports multiple subplots, labels, logarithmic plots etc. The file contains some other support classes too, so that it works standalone. It is not very polished, but it serves its purpose.

r.



import matplotlib
matplotlib.use('GtkAgg')

import numpy as nm
from multiprocessing import Process, Pipe
from matplotlib.ticker import LogLocator, AutoLocator
import pylab
import gobject

def get_default( arg, default, msg_if_none = None ):
    
    if arg is None:
        out = default
    else:
        out = arg

    if (out is None) and (msg_if_none is not None):
        raise ValueError( msg_if_none )

    return out

class Struct( object ):
    def __init__( self, **kwargs ):
        if kwargs:
            self.__dict__.update( kwargs )

    def __str__( self ):
        """Print instance class, name and items in alphabetical order."""
        ss = "%s" % self.__class__.__name__
        if hasattr( self, 'name' ):
            ss += ":%s" % self.name
        ss += '\n'

        keys, vals = self.__dict__.keys(), self.__dict__.values()
        order = nm.argsort(keys)
        for ii in order:
            key, val = keys[ii], vals[ii]

            if issubclass( val.__class__, Struct ):
                ss += "  %s:\n    %s" % (key, val.__class__.__name__)
                if hasattr( val, 'name' ):
                    ss += ":%s" % val.name
                ss += '\n'
            else:
                aux = "\n" + str( val )
                aux = aux.replace( "\n", "\n    " );
                ss += "  %s:\n%s\n" % (key, aux[1:])
        return( ss.rstrip() )

    def __repr__( self ):
        ss = "%s" % self.__class__.__name__
        if hasattr( self, 'name' ):
            ss += ":%s" % self.name
        return ss

class Output( Struct ):
    """Factory class providing output (print) functions.

    Example:

    >>> output = Output( 'sfepy:' )
    >>> output( 1, 2, 3, 'hello' )
    >>> output.prefix = 'my_cool_app:'
    >>> output( 1, 2, 3, 'hello' )
    """

    def __init__(self, prefix, filename=None, combined=False, **kwargs):
        Struct.__init__(self, **kwargs)

        self.prefix = prefix

        self.set_output(filename, combined)
        
    def __call__(self, *argc, **argv):
        self.output_function(*argc, **argv)

    def set_output(self, filename=None, combined=False, append=False):
        """Set the output function - all SfePy printing is accomplished by
        it. If filename is None, output is to screen only, otherwise it is to
        the specified file, moreover, if combined is True, both the ways are
        used.

        Arguments:
                filename - print into this file
                combined - print both on screen and into a file
                append - append to an existing file instead of overwriting it
        """
        self.level = 0
        def output_screen( *argc, **argv ):
            format = '%s' + ' %s' * (len( argc ) - 1)
            msg =  format % argc

            if msg.startswith( '...' ):
                self.level -= 1

            print self._prefix + ('  ' * self.level) + msg

            if msg.endswith( '...' ):
                self.level += 1

        def output_file( *argc, **argv ):
            format = '%s' + ' %s' * (len( argc ) - 1)
            msg =  format % argc

            if msg.startswith( '...' ):
                self.level -= 1

            fd = open( filename, 'a' )
            print >>fd, self._prefix + ('  ' * self.level) + msg
            fd.close()

            if msg.endswith( '...' ):
                self.level += 1

        def output_combined( *argc, **argv ):
            output_screen( *argc, **argv )
            output_file( *argc, **argv )
    
        if filename is None:
            self.output_function = output_screen

        else:
            if not append:
                fd = open( filename, 'w' )
                fd.close()

            if combined:
                self.output_function = output_combined
            else:
                self.output_function = output_file

    def get_output_function(self):
        return self.output_function

    def set_output_prefix( self, prefix ):
        if len( prefix ) > 0:
            prefix += ' '
        self._prefix = prefix
        
    def get_output_prefix( self ):
        return self._prefix[:-1]
    prefix = property( get_output_prefix, set_output_prefix )

output = Output( 'main:' )

class ProcessPlotter( Struct ):
    output = Output( 'plotter:', filename='plotter.log' )
    output = staticmethod( output )

    def __init__( self, aggregate = 100 ):
        Struct.__init__( self, aggregate = aggregate )

    def process_command( self, command ):
        self.output( command[0] )

        if command[0] == 'iseq':
            self.iseq = command[1]

        elif command[0] == 'plot':
            ii = self.iseq
            name = self.seq_data_names[ii]
            try:
                ig = self.igs[ii]
                ax = self.ax[ig]
                ax.set_yscale( self.yscales[ig] )
                ax.yaxis.grid( True )
                ax.plot( command[1], command[2] )

                if self.yscales[ig] == 'log':
                    ymajor_formatter = ax.yaxis.get_major_formatter()
                    ymajor_formatter.label_minor( True )
                    yminor_locator = LogLocator()
                else:
                    yminor_locator = AutoLocator()
                self.ax[ig].yaxis.set_minor_locator( yminor_locator )
            except:
                print ii, name
                raise

        elif command[0] == 'clear':
            for ig in range( self.n_gr ):
                self.ax[ig].cla()

        elif command[0] == 'legends':
            for ig in range( self.n_gr ):
                self.ax[ig].legend( self.data_names[ig] )
                if self.xaxes[ig]:
                    self.ax[ig].set_xlabel( self.xaxes[ig] )
                if self.yaxes[ig]:
                    self.ax[ig].set_ylabel( self.yaxes[ig] )

        elif command[0] == 'save':
            self.fig.savefig( command[1] )


    def terminate( self ):
        if self.ii:
            self.output( 'processed %d commands' % self.ii )
        self.output( 'ended.' )
        pylab.close( 'all' )

    def poll_draw( self ):

        def call_back():
            self.ii = 0
            
            while 1:
                if not self.pipe.poll():
                    break

                command = self.pipe.recv()
                can_break = False

                if command is None:
                    self.terminate()
                    return False
                elif command[0] == 'continue':
                    can_break = True
                else:
                    self.process_command( command )

                if (self.ii >= self.aggregate) and can_break:
                    break

                self.ii += 1

            if self.ii:
                self.fig.canvas.draw()
                self.output( 'processed %d commands' % self.ii )

            return True

        return call_back
    
    def __call__( self, pipe, data_names, igs, seq_data_names, yscales,
                  xaxes, yaxes ):
        """Sets-up the plotting window, sets GTK event loop timer callback to
        callback() returned by self.poll_draw(). The callback does the actual
        plotting, taking commands out of `pipe`, and is called every second."""
        self.output( 'starting plotter...' )
#        atexit.register( self.terminate )

        self.pipe = pipe
        self.data_names = data_names
        self.igs = igs
        self.seq_data_names = seq_data_names
        self.yscales = yscales
        self.xaxes = xaxes
        self.yaxes = yaxes
        self.n_gr = len( data_names )

        self.fig = pylab.figure()

        self.ax = []
        for ig in range( self.n_gr ):
            isub = 100 * self.n_gr + 11 + ig
            self.ax.append( self.fig.add_subplot( isub ) )
        
        self.gid = gobject.timeout_add( 1000, self.poll_draw() )

        self.output( '...done' )
        pylab.show()

def name_to_key( name, ii ):
    return name + (':%d' % ii)

class Log( Struct ):
    """Log data and (optionally) plot them in the second process via
    ProcessPlotter."""

    def from_conf( conf, data_names ):
        """`data_names` ... tuple of names grouped by subplots:
                            ([name1, name2, ...], [name3, name4, ...], ...)
        where name<n> are strings to display in (sub)plot legends."""
        if not isinstance( data_names, tuple ):
            data_names = (data_names,)

        obj = Log(data_names, **conf)

        return obj
    from_conf = staticmethod( from_conf )

    def __init__(self, data_names, is_plot=True, aggregate=200, yscales=None,
                 xaxes=None, yaxes=None):
        """`data_names` ... tuple of names grouped by subplots:
                            ([name1, name2, ...], [name3, name4, ...], ...)
        where name<n> are strings to display in (sub)plot legends."""
        Struct.__init__(self, data_names = data_names, seq_data_names = [],
                        igs = [], data = {}, x_values = {}, n_calls = 0,
                        plot_pipe = None)

        ii = 0
        for ig, names in enumerate( self.data_names ):
            self.x_values[ig] = []
            for name in names:
                key = name_to_key( name, ii )
                self.data[key] = []
                self.igs.append( ig )
                self.seq_data_names.append( name )
                ii += 1
        self.n_arg = len( self.igs )
            
        self.n_gr = len( self.data_names )

        self.is_plot = get_default( is_plot, True )
        self.yscales = get_default( yscales, ['linear'] * self.n_arg )
        self.xaxes = get_default( xaxes, ['iteration'] * self.n_arg )
        self.yaxes = get_default( yaxes, [''] * self.n_arg )
        self.aggregate = get_default( aggregate, 100 )

        self.can_plot = (pylab is not None) and (Process is not None)

        if self.is_plot and (not self.can_plot):
            output( 'warning: log plot is disabled, install pylab (GTKAgg)' )
            output( '         and multiprocessing' )
    
    def __call__( self, *args, **kwargs ):
        finished = False
        save_figure = ''
        x_values = None
        if kwargs:
            if 'finished' in kwargs:
                finished = kwargs['finished']
            if 'save_figure' in kwargs:
                save_figure = kwargs['save_figure']
            if 'x' in kwargs:
                x_values = kwargs['x']

        if save_figure and (self.plot_pipe is not None):
            self.plot_pipe.send( ['save', save_figure] )

        if finished:
            self.terminate()
            return

        ls = len( args ), self.n_arg
        if ls[0] != ls[1]:
            msg = 'log called with wrong number of arguments! (%d == %d)' % ls
            raise IndexError( msg )

        for ii, name in enumerate( self.seq_data_names ):
            aux = args[ii]
            if isinstance( aux, nm.ndarray ):
                aux = nm.array( aux, ndmin = 1 )
                if len( aux ) == 1:
                    aux = aux[0]
                else:
                    raise ValueError, 'can log only scalars (%s)' % aux
            key = name_to_key( name, ii )
            self.data[key].append( aux )

        for ig in range( self.n_gr ):
            if (x_values is not None) and x_values[ig]:
                self.x_values[ig].append( x_values[ig] )
            else:
                self.x_values[ig].append( self.n_calls )

        if self.is_plot and self.can_plot:
            if self.n_calls == 0:
#                atexit.register( self.terminate )

                self.plot_pipe, plotter_pipe = Pipe()
                self.plotter = ProcessPlotter( self.aggregate )
                self.plot_process = Process( target = self.plotter,
                                             args = (plotter_pipe,
                                                     self.data_names,
                                                     self.igs,
                                                     self.seq_data_names,
                                                     self.yscales,
                                                     self.xaxes, self.yaxes) )
                self.plot_process.daemon = True
                self.plot_process.start()

            self.plot_data()
            
        self.n_calls += 1

    def terminate( self ):
        if self.is_plot and self.can_plot:
            self.plot_pipe.send( None )
            self.plot_process.join()
            self.n_calls = 0
            output( 'terminated' )

    def plot_data( self ):
        send =  self.plot_pipe.send

        send( ['clear'] )
        for ii, name in enumerate( self.seq_data_names ):
            key = name_to_key( name, ii )
            try:
                send( ['iseq', ii] )
                send( ['plot',
                      nm.array( self.x_values[self.igs[ii]] ),
                      nm.array( self.data[key] )] )
            except:
                print ii, name, self.data[key]
                raise
        send( ['legends'] )
        send( ['continue'] )


def main():
    log = Log((['sin( x )', 'cos( x )'], ['exp( x )']),
              aggregate=100, yscales=['linear', 'log'],
              xaxes=['angle', None], yaxes=[None, 'a function'])

    for x in nm.linspace( 0, 4.0 * nm.pi, 200 ):
        output( 'x: ', x )
        log( nm.sin( x ), nm.cos( x ), nm.exp( x ), x = [x, None] )

    print log
    raw_input('press Enter...')

    log( finished = True )

if __name__ == '__main__':
    main()
------------------------------------------------------------------------------
The NEW KODAK i700 Series Scanners deliver under ANY circumstances! Your
production scanning environment may not be a perfect world - but thanks to
Kodak, there's a perfect scanner to get the job done! With the NEW KODAK i700
Series Scanner you'll get full speed at 300 dpi even with all image 
processing features enabled. http://p.sf.net/sfu/kodak-com
_______________________________________________
Matplotlib-users mailing list
Matplotlib-users@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/matplotlib-users

Reply via email to