New `map()` function in math evaluator.

In the last few days, I’ve been thinking a lot about how I could introduce a new function, as “generic” as possible, in the G’MIC mathematical expression evaluator, to do vector-to-vector transformation, including the stuffs that we don’t really have, such as:

  1. Value or color mapping (like what command map does).
  2. Image warping, when the vector is seen as an image (like what command warp does).
  3. Permutation of image axes (somehow like what command permute does).

Reimplementing all commands permute, map and warp directly in the math parser seemed a bit overkill to me, so I tried to figure out what generic function could do all this at once (even if there are still very specific things that can’t be done).

I ended up implementing the function:

map(X,P,_nb_channelsX,_nb_channelsP,_boundary_conditions)

which is simple enough to use and does indeed allow very generic transformations on vectors.

Basically, this function does the same as command map:
It considers that vector X is an input image of size (size(X)/nb_channelsX,1,1,nb_channelsX), and P is a palette, i.e., another image of size (size(P)/nb_channelsP,1,1,nb_channelsP), and it returns an image Y of size (size(X)/nb_channelsX,1,1,nb_channelsX*nb_channelsP) such that

Y[k] = P[X[x]],  for each channel of Y.

Let me show how map() can handles the three mentioned cases:

  1. Value or color mapping.

Obviously, map() can be used just as the map command. This is the simplest example:

map_for_map :
  sp david,256 luminance n 0,255
  palette hot
  eval "
    X = crop(#0);
    P = crop(#1);
    Y = map(X,P,1,3);
    store('out',Y,w#0,h#0,1,s#1);
  "
  $out


map() works exactly the same as command map, so there are no limitations of using map() compared the corresponding command.

  1. Image warping:

Amusingly, it turns out that you can also use map() to warp images as well.
The idea is to convert the warping map into an offset map, which will be the X image that we send to map():

map_for_warp : 
  sp david,256 # [0]: Color image to warp
  100%,100%,1,2 rand. -1,1 b. 10%,0 n. -30,30 +. '[x,y]' # [1]: Random 2D warping map
  +warp[0] [1] # [2]: Result using 'warp' command.

  # Apply warp in math evaluator, using `map()` command:
  eval "
    P = crop(#0);

    # Convert warp map into offset map.
    X = round(crop(#1,0,0,0,1,w#1,h#1,d#1,1))*w#0 + round(crop(#1,0,0,0,0,w#1,h#1,d#1,1));

    Y = map(X,P,1,3);
    store('out',Y,w#0,h#0,1,s#0)"
  $out

Here, there are some limitations of using map() compared to the warp command:

  • Interpolation is restricted to the nearest neighbors mode.
  • boundary conditions works a bit differently, as it is just handled for vector indices that go outside [0,size(P)], while command warp is a bit smarter for managing boundary conditions.
  1. Permutation of images axes:

Same idea as for warp here, we define an offset map corresponding to the desired permutation, and we are ready to go with map().

map_for_permute :
  sp david,256
  {[h,w]},1,1,"y + x*w#0 + c*wh#0" # Define offset map for permutation (invert x/y axes)
  eval "
    P = crop(#0);
    X = crop(#1);
    Y = map(X,P,1,3);
    store('out',Y,w#1,h#1,1,s#0)"
  $out

At this point, I can say that map() is clearly a great addition to the G’MIC math evaluator!
It doesn’t require zillions of parameters, and it is capable of doing quite complicated things on vectors. Of course, it’s still more convenient to use the classical commands map, warp and permute, but if you really need to perform such actions inside the math evaluator, you won’t have to write some slow loops to do so.

1 Like

Having a built-in lookup table command will help, no doubt. Perhaps this makes it easier to implement certain hash algos, like MD5, SHA - a few of those have simple pseudocode on wikipedia now. Integer math might be troublesome though.

Is this an allusion to some G’MIC glitch filters? I think it would be interesting to try to use these hash algorithm to generate effects.

To have as general functions, but of course the purpose is whatever anyone wants. Might have a go myself if you don’t - at a glance the algorithms seem straightforward.

A question about using map() with dynamic arrays (da_push() etc.): is that even possible or worthwhile?

I’ve tried various ways of working around the issues, e.g. using copy() and resize(), but I can’t find an elegant way to do it. Is it better just to use a loop with vector indices and be done with it?

I would say : no.
A dynamic array by definition has a size that can change during the evaluation of a math expression, so you cannot get its content (e.g. with crop()) as a static vector (which is what the math parser supports as a datatype).
As a consequence, using any vector-valued function of the math parser with a dynamic array is not possible (map being one of them).

In that case, it’s probably better to make a repeat(da_size(),k, ...) custom loop.

PS: One question could be: “why the math evaluator only supports static vectors ?”.

The answer is not “because it was easier to code” :slight_smile: .
Handling constant-size vectors is a lot more efficient when evaluating a math expression, e.g. because operations on vectors can be unrolled at the compilation time, because accessing the vector components with a constant integer as an index (e.g. x = vec[1]) do not require additional indirections, etc.
Having dynamic-sized vectors would mean all these benefit vanish, and simple expressions involving 2D or 3D vectors, or complexes would definitely require more time to evaluate.

2 Likes

What are you trying to do exactly? I was able to avoid the need for map in my last commit code by using a function which calculates integer representation and I’m using dynamic array to sort colors.

Thanks, if I get stuck I’ll take a look. I’m translating an “active set” algorithm for non-negative least squares; was hoping to more or less do like-for-like, but I’ll have to change it a bit :slight_smile: