hzeller / rpi-rgb-led-matrix

Controlling up to three chains of 64x64, 32x32, 16x32 or similar RGB LED displays using Raspberry Pi GPIO
GNU General Public License v2.0
3.64k stars 1.16k forks source link

Make "SetImage" accessible in Python please ... #152

Closed fesselbach closed 8 years ago

fesselbach commented 8 years ago

I have make a project with a chain of three modules some weeks ago by using the adafruit hat and lib with python. This works nice and really speedy. Now I'am beginning a new project with the passive three chain adapter and 3 x 3 modules and there is no function "SetImage"

I have tried the pure python solution with loop, but it is to slow (ca. 2s for 192 x 96)

I'am not familar with C or C++ at the raspi ... i can python only and now I'am breaking down ...

(I have tried to create a "pull request", but I have not understand, whats happend, if I click this button - please move this post if its needed)

hzeller commented 8 years ago

@Saij might know best how to implement that.

What is your current code ? It sounds slow, even for Python, to take 2 seconds for 18432 function calls.

fesselbach commented 8 years ago

Am 03.06.16 um 17:15 schrieb Henner Zeller:

@Saij https://github.com/Saij might know best how to implement that.

What is your current code ? It sounds slow, even for Python, to take 2 seconds for 18432 function calls.

— You are receiving this because you authored the thread. Reply to this email directly, view it on GitHub https://github.com/hzeller/rpi-rgb-led-matrix/issues/152#issuecomment-223607269, or mute the thread https://github.com/notifications/unsubscribe/AS0HoyeJWMs30weIg4CWCCG9tFQ6Htndks5qIEUYgaJpZM4ItbUj.

Hello,

its the code example from the bottom of this site:

https://github.com/hzeller/rpi-rgb-led-matrix/issues/91

Here is my complete code. Sent/Receive by UDP and load needs ca. 0.1s, painting by SetImage ca. 2..3s

!/usr/bin/python

from PIL import Image from time import sleep from rgbmatrix import RGBMatrix from rgbmatrix import graphics from StringIO import StringIO import socket, struct, fcntl, os, psutil

============================ string ops ==========================

def left(str, amount): return str[:amount] def right(str, amount): return str[-amount:] def mid(str, start, amount): return str[start:start+amount]

============================ init/setup ==========================

print("open") port=3307 width=192 height=32 chain=width/32 parallel=3

size=55350 #size of bmp 192 x 96 addenum=3 #size of additional commands

rem_adr="" command=""

matrix = RGBMatrix(height,chain,parallel)

host="0.0.0.0" font = graphics.Font() font.LoadFont("/home/pi/rpi-rgb-led-matrix-master/fonts/7x13.bdf") p=psutil.Process(os.getpid()) p.nice(16) print("start")

============================= get_ip =============================

def get_ip(): s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) try:

doesn't even have to be reachable

    s.connect(("10.255.255.255", 0))
    IP = s.getsockname()[0]
except:
    IP = "0.0.0.0"
finally:
    s.close()
return IP

============================== SetImage ===========================

def SetImage(dat,mx):

img = Image.open(StringIO(dat)) (w,h) = img.size

pix = img.load()

osc = mx.CreateFrameCanvas() for x in xrange(w): for y in xrange(h): (r,g,b) = pix[x,y] osc.SetPixel(x,y,r,g,b) sleep(0.015) osc = mx.SwapOnVSync(osc)

============================== recv image =========================

def recv_data(): length=size+addenum buffer= "" global rem_adr,command while len(buffer)<length: data,adr = sock.recvfrom(length - len(buffer)) buffer=buffer+data rem_adr=adr command=left(buffer,3) return right(buffer,size)

============================== idle ===========================

def idle(): matrix.Clear() matrix.Fill(10,10,10) ya=10 ye=40

col=graphics.Color(255,0,0) graphics.DrawLine(matrix, 160, ya, 160, ye, col) graphics.DrawLine(matrix, 161, ya, 161, ye, col) graphics.DrawLine(matrix, 162, ya, 162, ye, col)

col=graphics.Color(0,255,0) graphics.DrawLine(matrix, 170, ya, 170, ye, col) graphics.DrawLine(matrix, 171, ya, 171, ye, col) graphics.DrawLine(matrix, 172, ya, 172, ye, col)

col=graphics.Color(0,0,255) graphics.DrawLine(matrix, 180, ya, 180, ye, col) graphics.DrawLine(matrix, 181, ya, 181, ye, col) graphics.DrawLine(matrix, 182, ya, 182, ye, col)

col=graphics.Color(200,0,200)

graphics.DrawLine(matrix,0,0,192,0, col) graphics.DrawLine(matrix,0,95,192,95, col) graphics.DrawLine(matrix,0,1,0,95, col) graphics.DrawLine(matrix,191,1,191,95, col)

textColor = graphics.Color(255, 255, 0) graphics.DrawText(matrix,font,10,20,textColor,get_ip())

============================== init ===========================

def init(): global sock sock=socket.socket(socket.AF_INET, socket.SOCK_DGRAM) sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) sock.bind((host, port)) sock.settimeout(10)

print("init") init() idle() print("work")

============================== Main Loop ===========================

while True:

try:

  try:

     buf=recv_data()
     print("BUF:",len(buf)," CMD:",command)

     if command=="<I>": # re-init socket
        command=""
        buf=""
        matrix.Clear()
        init()

     elif command=="<X>": # close program
        command=""
        buf=""
        matrix.Clear()
        sleep(.2)
        os._exit(0)

     elif command=="<R>": # reboot raspi
        command=""
        buf=""
        matrix.Clear()
        sleep(.2)
        result=os.system("sudo reboot")
        sleep(.2)
        os._exit(0)

     elif command=="<?>": # answer for searching by ip
        command=""
        buf=""
        sock.sendto("<OK>",rem_adr)

     elif command=="IMG" and len(buf)==size: # image found in buf
        try:
           SetImage(buf,matrix)
           sock.sendto("<OK>",rem_adr)
           command=""
           buf=""
        except:
           sock.sendto("<EE>",rem_adr)
           command=""
           buf=""

     else:
        command=""
        buf=""
        sleep(0.1)

  except socket.timeout:
     command=""
     buf=""
     idle()

except KeyboardInterrupt: matrix.Clear() sleep(.2) os._exit(0)

hzeller commented 8 years ago

(unrelated) Why are you creating a frame canvas in each SetImage() ? You'll quickly run out of memory.

The idea with the swapping FrameCanvases is to create a second frame-canvas once somewhere, and then swap with something like osc = mx.SwapOnVSync(osc) What comes back is always the previous canvas, so you can use one canvas to SetPixel() while the other is displayed.

fesselbach commented 8 years ago

Am 03.06.16 um 17:50 schrieb Henner Zeller:

(unrelated) Why are you creating a frame canvas in each SetImage() ? You'll quickly run out of memory.

I have also tried by direct painting without swapping. the result is near the same (by time).

Saij commented 8 years ago

Sorry can't help you with this. I have to take a look on how to implement a good interface to PIL. I don't have any knowledge about this class.

hzeller commented 8 years ago

@Saij this looks like how the Adafruit library does SetImage() https://github.com/adafruit/rpi-rgb-led-matrix/blob/master/rgbmatrix.cc#L143 .. looks like there is a lot of cruft in the Python Image class.

@fesselbach I would suggest that you just send a plain preprocessed RGB buffer over to your program, as the Image class requires a lot computing (because it tries to be 'generic') which is probably what is slow.

@fesselbach the swapping is a good practice, but what you are doing is to create a new framebuffer every time with CreateFrameCanvas(), fill it once and give it to the matrix, but never re-use it. So I suggest to create it once (in main() ?) and assign it to some variable (in your current structure, this needs to be a global variable), and then do what you do now.

So do

  osc = mx.CreateFrameCanvas()

once when main starts, and remove this call in SetImage(); everything else stays the same. Then the SwapOnVSync() call will do what it should. If assigning it in main creates a global variable also accessible in SetImage() in Python (which I don't know if it does), then this should work.

fesselbach commented 8 years ago

Hello,

could be possible (with python) directy copy pre-prepared data inside the matrix object? like this:

data= socket_read matrix=data

what structures and types are inside the matrix?

thanks ...

hzeller commented 8 years ago

No, there is no buffer of some kind to copy. The matrix only provides a SetPixel() abstraction in both C++ and Python. So even in C++, you would do the nested loop with setting the values one after another.

(The inner data structure in C++ is decomposing the color values and optimizing the raw memory layout to the way it is outputting things to the GPIO, so it is not simply a buffer of RGB values. It is not exposed in the interface in any way)

fesselbach commented 8 years ago

Hi,

In my mac-application i create a raw block of image data with a simple structure of only: r-g-b-r-g-b... etc. This job needs a time for making and sending (UDP) ca. 50ms. I do this with Xojo (formerly RealBasic).

I have made the smallest code for raspi's python that I can do ... but its not significant faster - ca. 2,5 sec for drawing a picture with 192 x 96 pixels ... not good :-(((

def SetImage(dat): pos=0 w=192 h=96 rgb=map(ord,dat) global osc,matrix

for y in xrange(h): for x in xrange(w): osc.SetPixel(x,y,rgb[pos],rgb[pos+1],rgb[pos+2]) p +=3

osc=matrix.SwapOnVSync(osc)

hzeller commented 8 years ago

Cool, thanks for the minimal test. So it indeed looks that whatever the Python conversion is doing is doing something slow. This should be orders of magnitude faster if you write it in C++ (and also might only be 50 lines of code).

The Python wrapper wraps C++ and mabye it is more expensive ?

If you really want to go the Python way, it might be easier to make Python talk to a C API. I hear it is possible relatively easily, but I have never done Python so don't know for sure. Way easier than C++ though.

We got that covered: there is a C API for the LED matrix ( in include/led-matrix-c.h ). Maybe that would help ?

fesselbach commented 8 years ago

Hmmm, I suspect that there must be something like that, but I do not understand more than 10% of what it says. like this?

http://intermediate-and-advanced-software-carpentry.readthedocs.io/en/latest/c++-wrapping.html

It's not like that I was too lazy to learn that, but I need it soon and do not have much time.

That "SetImage" missing really is a nasty trap ...

hzeller commented 8 years ago

Well, it is free software, if you need something, you really should consider adding it yourself and send a pull request. Nothing gets done if we complain about it, just by fixing it. So, after your time-critical thing is done, you should spend the two hours needed learning about interfacing Python with C/C++ and provide a SetBuffer() function. I can't do it because I am not too interested in Python and Saij is busy with other stuff.

In the meantime, you could write a simple UDP server like this in C++ https://gist.github.com/hzeller/9ece7d7150c6d28ed1825eee85b5d243

depili commented 8 years ago

One solution (that I might work on later when my matrix arrives from China) is to have the C++ library deal with the control of the matrix and receive updated bitmaps via zeromq ( http://zguide.zeromq.org/ ) thus I can deal with interfacing with the data sources and producing the text for my matrix in language of my choice and still have the c++ library do all of the intensive matrix control.

ethur commented 8 years ago

I was in a similar boat, wanting to use Adafruit's older "rgbmatrix.cc" Python library with the current RGBMatrix library, and I documented my solution on Adafruit's Forum: https://forums.adafruit.com/viewtopic.php?f=50&t=98006

The update was fairly simple -- their library needed the third "parallel" argument added, and a function involving "SetWriteCycles" had to be remarked out. It's still early to tell if this is all that was required, but it allowed me to get my older Python scripts using SetImage (and all the other wonderful PIL functions, such as ImageFont) to work with the current hzeller library.