Search code examples
pythonarraysmatrixtrigonometryled

Don't understand Sin equation of plasma effect to 2d array for LED strip matrix in Python


I successfully got the code below, from Github, working on a raspberry pi and a 8 x 16 LED matrix using PWM on pin 18. Since it uses sin and cos functions to create the graphics, I wanted to try to make my own effects. I borrowed and effect from this page but when I put it into the function I only get red LEDs.

Code of Plasma Example

import time
import math
import colorsys
from neopixel import *

# LED strip configuration:
LED_COUNT = 128      # Number of LED pixels.
LED_PIN = 18      # GPIO pin connected to the pixels (must support PWM!).
LED_FREQ_HZ = 800000  # LED signal frequency in hertz (usually 800khz)
LED_DMA = 10       # DMA channel to use for generating signal (try 10)
LED_BRIGHTNESS = 128  # Set to 0 for darkest and 255 for brightest
# True to invert the signal (when using NPN transistor level shift)
LED_INVERT = False

# Draws an animated horizontal bar test pattern for alignment
def bar(w,h,t):
    out = [ Color( 0, 0, 0 ) for x in range( w * h ) ]
    y = int(t * 4) % h
    for x in range(0, w):
        out[ x + y * w ] = Color( 10, 10, 10 )
    return out

# Draws an animated colorful plasma effect
def plasma (w, h, t):
    out = [ Color( 0, 0, 0 ) for x in range( w * h ) ]
    for x in range( w ):
        for y in range( h ):
            hue = 4.0 + math.sin( t + x ) + math.sin( t + y / 4.5 ) \
                + math.sin( x + y + t ) + math.sin( math.sqrt( ( x + t ) ** 2.0 + ( y + 1.5 * t ) ** 2.0 ) / 4.0 )
            hsv = colorsys.hsv_to_rgb( hue / 8.0, 1, 1 )
            out[ x + y * w ] = Color( *[ int( round( c * 10.0 ) ) for c in hsv ] )
    return out

# Main program logic follows:
if __name__ == '__main__':
    # Create NeoPixel object with appropriate configuration.
    strip = Adafruit_NeoPixel(
        LED_COUNT,
        LED_PIN,
        LED_FREQ_HZ,
        LED_DMA,
        LED_INVERT,
        LED_BRIGHTNESS
    )
    # Intialize the library (must be called once before other functions).
    strip.begin()

    t = 0.0                                                                       # time
    dt = 0.1                                                                      # speed of time

    for i in range( 0, strip.numPixels(), 1):                                     # iterate over all LEDs
        strip.setPixelColor( i, Color( 0, 0, 0 ) )                                # set LED to black (off)

    while True:
        t = t + dt                                                                # increment time
        pic = plasma( 8, 16, t )                                                  # render plasma of size 8x8 at time t
        #pic = bar( 8, 16, t )                                                    # remove comment for bar test pattern
        for i in range( 0, strip.numPixels(), 1 ):                                # iterate over all LEDs
            strip.setPixelColor(                                                  # set pixel to color in picture
                i,
                pic[ i ]
            )
        strip.show()                                                              # update LEDs
        time.sleep(0.01) 

Plasma Function

def plasma (w, h, t):
    out = [ Color( 0, 0, 0 ) for x in range( w * h ) ]
    for x in range( w ):
        for y in range( h ):
            hue = 4.0 + math.sin( t + x ) + math.sin( t + y / 4.5 ) \
                + math.sin( x + y + t ) + math.sin( math.sqrt( ( x + t ) ** 2.0 + ( y + 1.5 * t ) ** 2.0 ) / 4.0 )
            hsv = colorsys.hsv_to_rgb( hue / 8.0, 1, 1 )
            out[ x + y * w ] = Color( *[ int( round( c * 10.0 ) ) for c in hsv ] )
    return out

Modified Function that only Shows Red

The hue equation is changed. Formula from this website about plasma graphics.

def plasma (w, h, t):
    out = [ Color( 0, 0, 0 ) for x in range( w * h ) ]
    for x in range( w ):
        for y in range( h ):
            hue = int(math.sin((x+y)/4.0)) # Throws error without int
            hsv = colorsys.hsv_to_rgb( hue / 8.0, 1, 1 )
            out[ x + y * h ] = Color( *[ int( round( c * 10.0 ) ) for c in hsv ] )
    return out

I also do not understand how this array notation works.

out[ x + y * h ] = Color( *[ int( round( c * 10.0 ) ) for c in hsv ] )

I understand that it is flattening the array but don't know how.

Please advise. Thank you.


Solution

  • With the help of a friend I figured out this function and can now make my own patterns.

    I was typing in the second example from the https://lodev.org/cgtutor/plasma.html website wrong.

    I also realized that all values in HSV colorspace are between 0 and 1 so I had to divide by 256 to get meaningful values.

    hue = ((128 +(128*math.sin( y )/8.0)))))256
    

    In the example function the line hsv = colorsys.hsv_to_rgb( hue / 8.0, 1, 1 ) the hue variable only affects the 1st part of the triplet. The values I was passing were making the hue red. To make the sin patterns like the example the hue or pixel value must be passed to the third part of the hsv the "value" part.

    hsv = colorsys.hsv_to_rgb( 1, 1, hue ) This still makes red because the first value is 1. If you change the first value then you get different colors. For example .5 gives a blue color.

    The key is to put the generated sin values in the hsv "value" slot to generate the gradients.

    out[ x + y * h ] = Color( *[ int( round( c * 10.0 ) ) for c in hsv ] )
    

    I also understand this part of the code as well. The confusing part was the out[ x + y * h] part. Now I understand that this is assigning the position of the pixel values in the array to be passed to the LED strand.

    I had to modify this code because my LED strands are wired so the signal alternates back and forth in the matrix.

    if y % 2 == 0: # if column even
        out[ x + (h * y)] = color ( *[int( round( c * plasmaBright ) ) for c in hsv ] )
    else: # if column odd
        out[ (y * h) + (h -1 -x) ] = color ( *[int( round( c * plasmaBright ) ) for c in hsv ] )
    

    This code checks if the column is even and flips every other one.