Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

matplotlib: (re-)plot ping standard-input data. Doable?

I recently started tinkering with python and the matplotlib module(1.1.0) shipped with Enthought Python Distribution(the free version one). I thought of an interesting project I could do and came up with something like this:

  • get ping of an internet address
  • pipe that via sys.stdin into python script

now in the python script:

  • regex the answer time, if there is no answer time: use NaN or just 0 as number
  • plot data via matplotlib
  • add continuously data

I managed to get the ping via this: ping stackoverflow.com | python script.py get the answertime wasn't particulary hard. But when it comes to plotting the data. I am stuck. I know there is an animation module inside matplotlib, but I think a timer-based plot would be harder to program than this, and I don't know how events are used anyway. What I want:

  • Wait for sys.stdin to get a new string and thus the ping time
  • Add it to the data array
  • Plot the data array But it doesn't seem to be that easy. Besides this, Error handling is not yet done.. Unfortunately I couldn't find any comparable code, although I did a lot of googling about it :/ Maybe this design is not meant to be like this..

Does anybody have an idea how to accomplish this replotting? It doesn't need to be efficient, as the ping only comes in every second or so.. I thought about buffering the incoming stdin and do a regular timer-based plot, but I have now idea to do that.

Thank you in advance,

Jonas

Update1:

I can get rid of this error by using:

l1.set_xdata(range(len(data)))

before the l1.set_ydata(..), but still it doesn't plot anything and the window doesn't respond either. At least it shows the plot axis.

Code

import sys
import re
import numpy as np
import matplotlib.pyplot as plt


def main():
    if sys.stdin.isatty():
        print "Please use a pipe as stdin\nExample: ping stackoverflow.com | python script.py"
        return 0
    regex = re.compile('time=(\d+.\d+)')
    data = []
    fig = plt.figure()

    ax = fig.add_subplot(111)
    ax.set_yscale('linear') # or log
    ax.grid(True)

    l1, = ax.plot(data)
    fig.show()

    while True:
        #get ping, if no string: stream has ended
        line = sys.stdin.readline()
        if line == '':
            break
        #conversion: 64 bytes from 127.0.0.1: icmp_seq=0 ttl=45 time=100.873 ms --> 100.873
        match = regex.findall(line)
        number = 0.

        if len(match) > 1:
            raise ValueError()
        if len(match) == 1:
            try:
                number = float(match[0])
            except ValueError as e:
                print e
        #add number to array, plot the data
        data.append(number)
        l1.set_xdata(range(len(data)))
        l1.set_ydata(data)

        ax.relim()
        ax.autoscale()

        plt.draw()
        fig.canvas.flush_events()
    return 0

if __name__ == '__main__':
    sys.exit(main())
like image 466
Jonas K Avatar asked Jul 13 '26 06:07

Jonas K


2 Answers

Replotting the data seemed to work for me:

#!/usr/bin/python

import sys, re
import numpy as np
import matplotlib.pyplot as plt


def main():
    if sys.stdin.isatty():
        print "Please use a pipe as stdin\nExample: ping stackoverflow.com | python script.py"
        return 0
    regex = re.compile('time=(\d+.\d+)')

    data = [0]
    fig = plt.figure()
    ax = fig.add_subplot(111)
    #l1, = ax.plot(data)
    fig.show()

    while True:
        #get ping, if no string: stream has ended
        line = sys.stdin.readline()
        if line == '':
            break
        #conversion: 64 bytes from 127.0.0.1: icmp_seq=0 ttl=45 time=100.873 ms --> 100.873
        match = regex.findall(line)
        number = 0.

        if len(match) > 1:
            raise ValueError()
        if len(match) == 1:
            try:
                number = float(match[0])
            except ValueError as e:
                print e
        #add number to array, plot the data
        data.append(number)
        l1, = ax.plot(data, 'r')
        plt.draw()
    return 0

if __name__ == '__main__':
        sys.exit(main())
like image 162
Russ Avatar answered Jul 14 '26 20:07

Russ


Where you first have plt.show(), do not use that. Instead use fig.show() there. And then just plt.draw() in your loop. The rest seems good. The thing is that plt.show() starts a mainloop, locking further execution (outside the events for the figure). fig.show() seems not to, however the buttons probably will not work like this (I didn't try).

You will have to add autoscale though:

ax.relim()
ax.autoscale()
plt.draw()
# And this allows you to at least close the window (and crash the program by that ;))
fig.canvas.flush_events()

But really it should be cleaner not use pyplot for this. For that look into how to embed matplotlib into a gui, it is really easy and can be done for a couple of different gui toolkits.

like image 31
seberg Avatar answered Jul 14 '26 19:07

seberg



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!