Nuke Filter Curve

Part 01 – Smoothing a curve

Final Goal – create a Nuke Gizmo that will mimic the filter option in the curve editor,  giving a real-time controller that will allow to look at and smooth the curve.

That should be easy… that’s what I told myself a couple of weeks ago when I came up with the idea of the smooth tool. needless to say, I changed my mind.

Let’s break-down the process:

– Create the filter effect using expression and math
– Create a Gizmo, light to use, with real-time feedback, and with no more than 2/3 sliders
– Make everything look cool ( very important and satisfying part of the process)

– Extra Feature for V_002 // Automatically recognise the class of the node plugged and replace the values that need to be smoothed

GREAT! Sounds like a plan! Let’s make a quick and rough version of it, I need to visualise all the problems.

It’s pretty self-explanatory, a Matrix as the main container of data, 3 rows ( Position, Scale, Rotation ), 5 columns (2D XY, 3D XYZ).
I Don’t think will be the way to go, but at least I wrote some ideas down that helped to better visualise the structure of the gizmo and how not to do it.

Smoothing the curve –  Adjacent Average

How do we smooth a series of values? well, what I thought is: ” this is going to be quite easy! it’s probably going to be an Average right?!? 

Perfect, this is the sequence of values I’m trying to filter:

{  0 1 -1 2 -2 3 6 -6 -3 -5 7 1 9 -9 1 -1 2 -2 3 9 -9 0} = original values

and I Made a proxy gizmo called SmoothMe1 with a knob called SmoothMeframes that should in theory then mimic the filter value.

Let’s see what the result averaging the previous frame value and the next frame value will be.

This is the expression I’m using:

(OriginalValues(frame-SmoothMe1.SmoothMeFrames)+OriginalValues(frame+SmoothMe1.SmoothMeFrames))/2

Brown = OriginalValues

LightBlue = Average +1-1

it’s definitely compressing the value in somehow, looks promising to me! Let’s compare it with the values I’m trying to match

Brown = OriginalValues

LightBlue = Average +1-1

Red = Filter 1

Wow a completely different result from what I was expecting, First Impression? it feels like looking 1 frame forward and backwards to smooth the curve it’s not enough, it looks and it feels more like a weighted overall average mmh…

Smoothing the curve –  5/7/9 values Average

Let’s try a few things, the average above was taking in consideration -t and +t

let’s try with 5 values instead of 2 ( -2 frame + -1 frame + current frame +1 frame +2 frame ) /5 or in TCL world

((OriginalValues.translate.x(frame-SmoothMe6.SmoothMeFrames))+(OriginalValues.translate.x(frame+SmoothMe6.SmoothMeFrames))+(OriginalValues.translate.x(frame))+(OriginalValues.translate.x(frame+(SmoothMe6.SmoothMeFrames+1)))+(OriginalValues.translate.x(frame-(SmoothMe6.SmoothMeFrames-1))))/5

Purple = Filter 1

Pink = 5 values Average

I think it’s going in the right direction but unfortunately is not there yet, as you can see from the curve editor above there are still some spikes, will they go away averaging even more values? Let’s give it a try!

Purple = Filter 1

Pink = 5 values Average

Green = 7 values Average

Orange = 9 values Average

Nothing still, it feels like a simple average isn’t the way to go, I’m only concerned about the mid-section since before frame 0 and after frame 20 I don’t have values, reason why as it is right now it will never match at the edges of the curve.

Smoothing the curve – Average of the average

Ok, Maybe it’s not a matter of doing an average of 2 or more values, even if it feels like the Nuke filtering is taking in consideration every single value… mmm let’s try something silly, I need to get my head around this. I will take the 3 values average and re-run the average on top of the new values, again and again, to see where it will bring me.

this is taking in consideration a +1 and -1

and this is taking in consideration +10 -10

whether you like it or not, still far far away from the solution, I mean quite lovely and colourful result, we can probably gain some Cool motion graphics effects using this kind of algorithm, but if you haven’t noticed it, if I could run this script forever we will end up with a flat line that will destroy any original value, the  purple matrix.0 have a positive value in the nuke filter, in my case it will never reach that point. On top of that, the values are not changing enough, the original values have a range of +9 -9, The filtered Curve have a range of +2-2, and the heavy average of the average brought the range to only 6.5 – 6.5 more or less. Not enough, wrong direction.

Ok, frustration apart, I’m starting to have some ideas on how to solve this problem and before that, I need to make sure that I’m not in the wrong math world. The next 2 test should fail… if they don’t I will be without any resources…
oh yes, I’m writing while I’m testing, it’s helping me to focus on the problem/solution analysing it from a different point of view.

Smoothing the curve – Smoothstep

Returns 0 if x is less than a, returns 1 if x is greater or equal to b, returns a smooth cubic interpolation otherwise.
is it working? no perfect, next one

Smoothing the curve – Median

the median is the mean of the middle two numbers: this is (4 + 5) ÷ 2, which is 4.5 or 4 1/2. (In more technical terms, this interprets the median as the fully trimmed mid-range). The formula used to find the middle number of a data set of n numerically ordered numbers is (n + 1) ÷ 2.  As you can see apart from the first 2 values, very very far away.

Smoothing the curve – The Answer

I started looking at the curve and the operations in a completely different way, let’s forget that I’m working in Nuke and let’s try to reverse engineer a few group of values:

  • [a] – Original Values {1 2 3 4 5 6 7 8 9 10 -10 -9 -8 -7 -6 -5 -4 -3 -2 -1}
  • [a] – filtered Values {1.600000024 2.200000048 3 4 5 6 7 8 4.800000191 1.600000024 -1.600000024 -4.800000191 -8 -7 -6 -5 -4 -3 -2.200000048 -1.600000024}
  • [b] – Original Values {10 -10 10 -10 10 -10 10 -10 10 2 -2 2 -2 2 -2 3 -3 3 -3 2}
  • [b] – Filtered Values {6 2 2 -2 2 -2 2 0.400000006 2 0.400000006 2 0.400000006 -0.400000006 0.6000000238 -0.400000006 0.6000000238 -0.400000006 0.400000006 0.200000003 1.200000048}

Let’s have a look at group [a] isnt’it amazing that 3 4 5 6 7 8 and the negative version of it are not changing? Why would they? Or Why would they not? After a lot of tests, I was struggling to get to the perfect answer until a guy called Dan Ring gave me the input I was looking for: filter_multiple.tcl . ‘Filter’ is added to the menu by Nuke’s menu.py. ‘Filter’ calls ‘filter_multiple.tcl’ which calls ‘animation_multiple.tcl’.

and that’s when everything clicked:

Filter_Multiple is looping animation_filter and animation_filter it’s a very simple Tcl function that is just doing this:

set i [animation_increment] set i2 [expr 2*$i] animation selected move y (y(x-$i2)+y(x-$i)+y(x)+y(x+$i)+y(x+$i2))/5

that can be easily translated into

((OriginalValues(frame))+(OriginalValues(frame-1))+(OriginalValues(frame-2))+(OriginalValues(frame+1))+(OriginalValues(frame+2)))/5

and this is the result! From one side I’m very happy! From the other one tough I can see a series of problems coming, actually let’s say a series of solution that I will have to find.

  • How come is not matching in the Intra-frame areas? Does it matter?

    Value-wise no it won’t make any difference but since the whole idea behind the tool is to have a real-time slider that will have the same behaviour as the filtering in Nuke it needs to be perfect or almost.

  • How?

    I can get an exact match only if I generate keyframes and set the interpolation to smooth after those keyframes exist.

  • Didn’t I already try a similar type of average?

    Yes, I did! However, I was using a variable instead of frame +1 or frame +2, and what happens if that variable is not 1? it will break entirely the math (something must have gone wrong without even realising it).

  • How do we achieve the filtering amount?

    It should be pretty straightforward, I need to create a function ( let’s call it “FoundryAverage”)  that needs run X amount of times, where X is the knob.value that the user will be able to control in the properties panel.

  • Is Tcl the right way of doing it? 

    I still don’t know if it is and since I’m not very good at it, it sounds like a good reason to keep trying to learn more and more. I’d like to get to the final result using only tcl, if not I’ll just do it in Python.

Most Importantly tough my questions are:

Can we define and run a tcl function directly in the expression editor? If not, is creating an external .tcl in the nuke directory the only solution?

This is the end of the first part of my case study, if you reached the end of the article and you liked it, please let me know with a message on your favourite platform.

TO BE CONTINUED