Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reaction Diffusion script. #1734

Closed
enzyme69 opened this issue Jul 6, 2017 · 29 comments
Closed

Reaction Diffusion script. #1734

enzyme69 opened this issue Jul 6, 2017 · 29 comments

Comments

@enzyme69
Copy link
Collaborator

enzyme69 commented Jul 6, 2017

I stumbled into this RD Script Node by Zeffii:
https://gist.github.com/zeffii/9e156f0d37977fd1b0ca3c65d0ddc611

prtscr capture_16

If I am not wrong, this is similar to Processing one shown by Shiffman. I need to rewatch the video.

I am curious on how we can provide our own points (maybe using Grease Pencil) to start with.

@zeffii
Copy link
Collaborator

zeffii commented Jul 6, 2017

I am curious on how we can provide our own points (maybe using Grease Pencil) to start with.

This implementation (both versions) doesn't really offer a convenient way to do that. It initializes a 2d matrix of n*n (256*256 -- call this the UV Matrix) with random values between 0 and 0.05, and feeds that to the setup and then iterates over the 'chemcial reactions' (much like game of life but with different rules).

What you would need to do is comment out the random value assignments, and instead add an input for a set of vectors, and convert the content of vectors to something that can be represented as a 256*256 grid. (essentially rasterizing a stroke). and apply that to the UV matrix.

you will need to write code, or it won't happen.

@zeffii zeffii changed the title RD thing Reaction Diffusion script. Jul 6, 2017
@zeffii
Copy link
Collaborator

zeffii commented Jul 6, 2017

here is Reaction Diffusion (2) script for snlite

"""
in steps s d=500 n=2
in seed s d=14 n=2
out verts_out v
"""


def setup():

    import random
    import numpy as np

    class DiffReact2():

        verts = []
        params = []
        params.append((0.16, 0.08, 0.035, 0.065)) # Bacteria 1
        params.append((0.14, 0.06, 0.035, 0.065)) # Bacteria 2
        params.append((0.16, 0.08, 0.060, 0.062)) # Coral
        params.append((0.19, 0.05, 0.060, 0.062)) # Fingerprint
        params.append((0.10, 0.10, 0.018, 0.050)) # Spirals
        params.append((0.12, 0.08, 0.020, 0.050)) # Spirals Dense
        params.append((0.10, 0.16, 0.020, 0.050)) # Spirals Fast
        params.append((0.16, 0.08, 0.020, 0.055)) # Unstable
        params.append((0.16, 0.08, 0.050, 0.065)) # Worms 1
        params.append((0.16, 0.08, 0.054, 0.063)) # Worms 2
        params.append((0.16, 0.08, 0.035, 0.060)) # Zebrafish

        def __init__(self, np, steps=500, seed=4):
            n = 256
            imgx = n
            imgy = n

            random.seed(seed)
            (Du, Dv, F, k) = random.choice(self.params)

            Z = np.zeros((n+2, n+2), [('U', np.double), ('V', np.double)])
            U, V = Z['U'], Z['V']
            u, v = U[1:-1, 1:-1], V[1:-1, 1:-1]

            r = 20
            u[...] = 1.0
            U[n/2-r:n/2 + r, n/2-r:n/2 + r] = 0.50
            V[n/2-r:n/2 + r, n/2-r:n/2 + r] = 0.25
            u += 0.05 * np.random.random((n, n))
            v += 0.05 * np.random.random((n, n))

            ######### loop start ############

            p = 0
            for i in range(steps):
                Lu = (                 U[0:-2,1:-1] +
                      U[1:-1,0:-2] - 4*U[1:-1,1:-1] + U[1:-1,2:] +
                                       U[2:  ,1:-1] )
                Lv = (                 V[0:-2,1:-1] +
                      V[1:-1,0:-2] - 4*V[1:-1,1:-1] + V[1:-1,2:] +
                                       V[2:  ,1:-1] )
                uvv = u*v*v
                u += (Du*Lu - uvv +  F*(1-u))
                v += (Dv*Lv + uvv - (F+k)*v)

                pn = 100 * (i + 1) / steps # percent completed
                if pn != p:
                    p = pn
                    print("%" + str(p).zfill(2))

            ######### loop end ############
            
            add_vert = self.verts.append

            vMin=V.min(); vMax=V.max()
            for iy in range(imgy):
                for ix in range(imgx):
                    w = V[iy, ix]
                    c = int(255 * (w - vMin) / (vMax - vMin))
                    if c > 190:
                        add_vert((ix/40, iy/40, 0))

            label = "Du=" + str(Du) + " Dv=" + str(Dv) + " F=" + str(F) + " k=" + str(k)
            print(label)

    GK = DiffReact2(np, steps, seed)



verts_out.append(GK.verts)

@enzyme69
Copy link
Collaborator Author

enzyme69 commented Jul 7, 2017

Thanks @zeffii for the SNLite update. Just wondering with the steps and seed, it does not seem to update the data unless I hit "Reload" button. Is this by design? Also as I keep hitting reload, as the steps number get higher, it get slower and slower.

@enzyme69
Copy link
Collaborator Author

enzyme69 commented Jul 7, 2017

I did get some interesting patterns.

prtscr capture_25
prtscr capture_24
prtscr capture_23
prtscr capture_22
prtscr capture_21

@zeffii
Copy link
Collaborator

zeffii commented Jul 7, 2017

Think about why steps influences processing time.

Yeah it only works once per load reload.. by design.

@enzyme69 enzyme69 closed this as completed Jul 8, 2017
@enzyme69
Copy link
Collaborator Author

enzyme69 commented Jul 8, 2017

Will meditate and come back.

@zeffii
Copy link
Collaborator

zeffii commented Jul 8, 2017

gialumnf_bw

1200 frames

slightly modified script

"""
in steps s d=1200 n=2
in seed s d=14 n=2
in framenum s d=0 n=2
out verts_out v
"""


def setup():
    
    import random
    import numpy as np


    class DiffReact2():

        verts = []
        params = []
        params.append((0.16, 0.08, 0.035, 0.065)) # Bacteria 1
        params.append((0.14, 0.06, 0.035, 0.065)) # Bacteria 2
        params.append((0.16, 0.08, 0.060, 0.062)) # Coral
        params.append((0.19, 0.05, 0.060, 0.062)) # Fingerprint
        params.append((0.10, 0.10, 0.018, 0.050)) # Spirals
        params.append((0.12, 0.08, 0.020, 0.050)) # Spirals Dense
        params.append((0.10, 0.16, 0.020, 0.050)) # Spirals Fast
        params.append((0.16, 0.08, 0.020, 0.055)) # Unstable
        params.append((0.16, 0.08, 0.050, 0.065)) # Worms 1
        params.append((0.16, 0.08, 0.054, 0.063)) # Worms 2
        params.append((0.16, 0.08, 0.035, 0.060)) # Zebrafish

       
        def __init__(self, np, steps=500, seed=24):
            self.frame_storage = {}
            self.n = n = 256
            self.imgx = n
            self.imgy = n

            random.seed(seed)
            (Du, Dv, F, k) = random.choice(self.params)

            Z = np.zeros((n+2, n+2), [('U', np.double), ('V', np.double)])
            U, V = Z['U'], Z['V']
            u, v = U[1:-1, 1:-1], V[1:-1, 1:-1]

            r = 20
            u[...] = 1.0
            U[n/2-r:n/2 + r, n/2-r:n/2 + r] = 0.50
            V[n/2-r:n/2 + r, n/2-r:n/2 + r] = 0.25
            u += 0.05 * np.random.random((n, n))
            v += 0.05 * np.random.random((n, n))


            ######### loop start ############
            p = 0
            for i in range(steps):
                Lu = (                 U[0:-2,1:-1] +
                      U[1:-1,0:-2] - 4*U[1:-1,1:-1] + U[1:-1,2:] +
                                       U[2:  ,1:-1] )
                Lv = (                 V[0:-2,1:-1] +
                      V[1:-1,0:-2] - 4*V[1:-1,1:-1] + V[1:-1,2:] +
                                       V[2:  ,1:-1] )
                uvv = u*v*v
                u += (Du*Lu - uvv +  F*(1-u))
                v += (Dv*Lv + uvv - (F+k)*v)

                subverts = []
                add_vert = subverts.append

                vMin=V.min(); vMax=V.max()
                for iy in range(self.imgy):
                    for ix in range(self.imgx):
                        w = V[iy, ix]
                        c = int(255 * (w - vMin) / (vMax - vMin))
                        if c > 190:
                            add_vert((ix/40, iy/40, 0))
                self.store_frame(i, data=subverts)

                pn = int(100 * (i + 1) / steps) # percent completed
                if pn != p:
                    p = pn
                    print("%" + str(p).zfill(2))

            ######### loop end ############
            
            # label = "Du=" + str(Du) + " Dv=" + str(Dv) + " F=" + str(F) + " k=" + str(k)
            # print(label)

        def get_frame(self, number):
            return self.frame_storage.get(number)

        def store_frame(self, framestep, data):
            self.frame_storage[framestep] = data

    GK = DiffReact2(np, steps, seed)

verts = GK.get_frame(framenum)
verts_out.append(verts)

@zeffii zeffii mentioned this issue Sep 5, 2017
3 tasks
@enzyme69
Copy link
Collaborator Author

enzyme69 commented Feb 8, 2018

@zeffii I am revisiting this again to try it with "Marching Cubes" setup of @kalwalt ... but your latest SNL does not give any output,. except an error

Unexpected error: <class 'TypeError'>
on line:  38
Traceback (most recent call last):
  File "C:\Users\zeffi\Desktop\scripts\addons_contrib\sverchok\nodes\generator\script1_lite.py", line 390, in process_script
    self.inject_state(locals())
  File "C:\Users\zeffi\Desktop\scripts\addons_contrib\sverchok\nodes\generator\script1_lite.py", line 357, in inject_state
    setup_locals = local_variables.get('setup')()
TypeError: slice indices must be integers or None or have an __index__ method

Repository owner deleted a comment from enzyme69 Feb 8, 2018
Repository owner deleted a comment from enzyme69 Feb 8, 2018
Repository owner deleted a comment from enzyme69 Feb 8, 2018
Repository owner deleted a comment from enzyme69 Feb 8, 2018
@zeffii
Copy link
Collaborator

zeffii commented Feb 8, 2018

i don't know. it worked at one point :)

@zeffii
Copy link
Collaborator

zeffii commented Feb 8, 2018

i wish there was a more graphical way to see the changes over time in script1_lite.py. I can't so quickly see changes in code that I would be concerned about..(or see obvious reasons for that to fail)

In that... we strip out all the working code of the script, and see if the imports still work.

@zeffii
Copy link
Collaborator

zeffii commented Feb 8, 2018

that looks like this..

"""
in steps s d=500 n=2
in seed s d=14 n=2
out verts_out v
"""


def setup():

    import random
    import numpy as np

    class DiffReact2():

        verts = []
        params = []
        params.append((0.16, 0.08, 0.035, 0.065)) # Bacteria 1
        params.append((0.14, 0.06, 0.035, 0.065)) # Bacteria 2
        params.append((0.16, 0.08, 0.054, 0.063)) # Worms 2
        params.append((0.16, 0.08, 0.035, 0.060)) # Zebrafish

        def __init__(self, np, steps=500, seed=4):

            random.seed(seed)
            f = random.choice(self.params)
            print(f)

            m = np.random.random((4, 4))
            print(m)
            
            self.verts = [[0,0,0], [0,0,1], [0,0,2]]


    GK = DiffReact2(np, steps, seed)


verts_out.append(GK.verts)

image

and the output to console.

(0.16, 0.08, 0.035, 0.065)
[[ 0.73409624  0.45047941  0.36955963  0.25219913]
 [ 0.68085704  0.51810137  0.28898026  0.48457896]
 [ 0.76903492  0.71459185  0.47065317  0.46539896]
 [ 0.29274072  0.86584027  0.44647848  0.70933723]]

this means the mechanism works.. but numpy has changed in someway... i think

@zeffii
Copy link
Collaborator

zeffii commented Feb 8, 2018

this means that the part I was worried might be broken is, in-fact, still functional. The next step is to look at just the numpy code.

@zeffii
Copy link
Collaborator

zeffii commented Feb 8, 2018

it's this line ( On Line 36 ) but you have to add a number of lines because setup doesn't begin until 8, that makes .. line 44.

 44           U[n/2-r:n/2 + r, n/2-r:n/2 + r] = 0.50

@zeffii
Copy link
Collaborator

zeffii commented Feb 8, 2018

boiling it down to

import random
import numpy as np

n = 256
seed = 20

random.seed(seed)

Z = np.zeros((n+2, n+2), [('U', np.double), ('V', np.double)])
U, V = Z['U'], Z['V']
u, v = U[1:-1, 1:-1], V[1:-1, 1:-1]

r = 20
u[...] = 1.0
print('OK1')
U[n/2-r:n/2 + r, n/2-r:n/2 + r] = 0.50     # <----- tentacles
print('OK2')
V[n/2-r:n/2 + r, n/2-r:n/2 + r] = 0.25
print('OK3')            

@zeffii
Copy link
Collaborator

zeffii commented Feb 8, 2018

because n/2 isn't an Integer, the slice operator fails.

@zeffii
Copy link
Collaborator

zeffii commented Feb 8, 2018

this does execute

import random
import numpy as np

n = 256
seed = 20

random.seed(seed)

Z = np.zeros((n+2, n+2), [('U', np.double), ('V', np.double)])
U, V = Z['U'], Z['V']
u, v = U[1:-1, 1:-1], V[1:-1, 1:-1]

r = 20
ndiv2 = int(n/2)

u[...] = 1.0
U[ndiv2-r:ndiv2 + r, ndiv2-r:ndiv2 + r] = 0.50
V[ndiv2-r:ndiv2 + r, ndiv2-r:ndiv2 + r] = 0.25

@zeffii
Copy link
Collaborator

zeffii commented Feb 8, 2018

it appears from v1.12.0 onwards, numpy doesn't support float indices.

@zeffii
Copy link
Collaborator

zeffii commented Feb 8, 2018

if you recall the error message says it..

TypeError: slice indices must be integers or None or have an __index__ method

@enzyme69
Copy link
Collaborator Author

enzyme69 commented Feb 9, 2018

Hmm..
nqanh/affordance-net#10

I did copy paste the numpy from outside into Blender.

@enzyme69
Copy link
Collaborator Author

enzyme69 commented Feb 9, 2018

So reloading the SNL prints out some kind of interesting values, but the output to Sverchok is integer?

@zeffii
Copy link
Collaborator

zeffii commented Feb 9, 2018

https://gist.github.com/zeffii/4270f3f23e694c4b360b1bc7cb9c22f0 (this is a script only.. not a layout)

click on that. look at it. look at the tab that says "revisions" and see the change that needed to be made to the code. Now it will run in old and new numpy versions.

image

Never mind the other scripts for RD that i made.

@enzyme69
Copy link
Collaborator Author

enzyme69 commented Feb 9, 2018

@zeffii Thanks Zeff for updating this script. What I found interesting is that with this SNL you did some calculation and there is this nice 0%-100% processing indicator... and then the node stops updating, only updates frame and it was then interactive.

@zeffii
Copy link
Collaborator

zeffii commented Feb 9, 2018

@enzyme69 yes, first the node calculates all the states for the given number of frames, and stores them all inside a dict. one state for each frame. Then after all calcs completed you can feed it a frame and it will do a lookup on the dict, and get the state for that frame number.

The upside of this is that you can calculate all frames for a given size+seed once, and scrub through them (interactively). The downside is that you wait once, a long time, to process everything first. The seed is also static per "master calculation". (it doesn't make much sense to have a dynamic seed for something that grows from an initial state)

it's not super fast, if we ever moved stuff to compiled C/C++ these would be the kinds of things that would be prime candidates.

@zeffii
Copy link
Collaborator

zeffii commented Feb 9, 2018

essentially this caches all frames first. ( like baking an animation )

The other uncached approach is less interactive because it first must calculate all states from frame 0 each time. meaning.. if you are at frame 90 and you jump to frame 91, first a dumb approach would have to calculate all frames between 0 and 90, to be able to move to frame 91.

because you can't get frame 90 without first.. at some point calculating all in between frames, it makes sense to cache them anyway.

A smart person would implement an algorithm that progressively caches all the states/frames that it has visited, giving better interactivity between small frame jumps and all previously seen frames. Because i'm not excited about it, that person won't be me.

@enzyme69
Copy link
Collaborator Author

enzyme69 commented Feb 11, 2018

@zeffii is the "data caching" something you made for SNL? Or is it Blender thing? I noticed that it does cache well, however there is occassional stutter when played back or scrubbed back and forth. Not sure why.

I was looking at your RD script and you did this "frame_storage" is this something you invented?

    def get_frame(self, number):
        return self.frame_storage.get(number)

    def store_frame(self, framestep, data):
        self.frame_storage[framestep] = data

@zeffii
Copy link
Collaborator

zeffii commented Feb 11, 2018

those two functions just get and set data for a key (which happens to be unique per keyframe number). I hardly invented this. this is what caching is.

the occasional stutter is to be expected, this is not intended to be realtime...or boast great performance. It was just me showing what could be done using the setup() to cache.

@asahidari
Copy link
Contributor

asahidari commented Aug 27, 2021

Sorry for jumping in, but just inspired by @zeffii 's code and SNLite 'setup' feature (, sorry I once miss-spelled 'zeffi').

These are 3d and run-on-mesh version, originated from the 2d script. One major difference is using C code with ctypes load_library (Python-only versions are also included, but very slow...).
https://github.com/asahidari/ReactionDiffusion_SNLite_b3d

I hope recent numba integration will work well, and these might be some benefit for future developments.😀
ReactionDiffusion3d
ReactionDiffusionOnMesh

@vicdoval
Copy link
Collaborator

Awesome @asahidari !!

@zeffii
Copy link
Collaborator

zeffii commented May 31, 2022

@asahidari

Sorry for jumping in, but just inspired by @zeffii 's code and SNLite 'setup' feature (, sorry I once miss-spelled 'zeffi').

sorry i did not see this earlier. it's super cool :) also nice to see the other examples using C code in snlite.

i've only now started to dig into numba a little. mind blowing stuff. (and your stuff is too!)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants