 # Scaling an interpolation routine

Hi guys, I need some advice on how to go about scaling an interpolation routine for an AMG8833.

I am building a GUI using the GUISlice library and builder, and on one page I have a thermal cam.
I am using the code from Kris Kasprzak as it is simple, works well, and was easy to adapt for use with the GUISlice lib.

His code interpolates the 8x8 to a 70x70 grid, and each cell in the grid is 3x3 pixels, resulting in a picture size of 210 x 210. Unfortunately this size does not work for my GUI due to other buttons, taskbar ext.
I need something more along the lines of 180 x 250 or less, but also enough to make the most of the available space.

My very crude, poorly executed workaround was to change the number of rows and colomns to 60, and make each cell 3x4, giving me a picture size of 180 x 240.
Here is how it looks in my loop.

`````` if (gslc_GetPageCur(&m_gui) == E_PG4) {
if (pauseThermal == false) {

float cursorTemp = (pixels  + pixels  + pixels  + pixels ) / 4;
snprintf(charCursTemp, 6, "%.2f", cursorTemp);

for (row = 0; row < 8; row ++) {
for (col = 0; col < 60; col ++) {
aLow =  col / 10 + (row * 8);
aHigh = (col / 10) + 1 + (row * 8);
intPoint =   (( pixels[aHigh] - pixels[aLow] ) / 10.0 );
incr = col % 10;
val = (intPoint * incr ) +  pixels[aLow];
HDTemp[ (7 - row) * 10][col] = val;
}
}
for (col = 0; col < 60; col ++) {
for (row = 0; row < 60; row ++) {
aLow =  (row / 10 ) * 10;
aHigh = aLow + 10;
intPoint =   (( HDTemp[aHigh][col] - HDTemp[aLow][col] ) / 10.0 );
incr = row % 10;
val = (intPoint * incr ) +  HDTemp[aLow][col];
HDTemp[ row ][col] = val;
}
}
for (row = 0; row < 60; row ++) {
if (ShowGrid < 0) {
BoxWidth = 3;
}
else {
if ((row % 10 == 9) ) {
BoxWidth = 2;
}
else {
BoxWidth = 3;
}
}
for (col = 0; col < 60; col++) {
if (ShowGrid < 0) {
BoxHeight = 4;
}
else {
if ( (col % 10 == 9)) {
BoxHeight = 2;
}
else {
BoxHeight = 3;
}
}
val = HDTemp[ row ][col];
red = constrain(255.0 / (c - b) * val - ((b * 255.0) / (c - b)), 0, 255);
if ((val > MinTemp) & (val < a)) {
green = constrain(255.0 / (a - MinTemp) * val - (255.0 * MinTemp) / (a - MinTemp), 0, 255);
}
else if ((val >= a) & (val <= c)) {
green = 255;
}
else if (val > c) {
green = constrain(255.0 / (c - d) * val - (d * 255.0) / (c - d), 0, 255);
}
else if ((val > d) | (val < a)) {
green = 0;
}
if (val <= b) {
blue = constrain(255.0 / (a - b) * val - (255.0 * b) / (a - b), 0, 255);
}
else if ((val > b) & (val <= d)) {
blue = 0;
}
else if (val > d) {
blue = constrain(240.0 / (MaxTemp - d) * val - (d * 240.0) / (MaxTemp - d), 0, 240);
}
gslc_tsRect rCamRect = { (col * 4) + 76, (row * 3) + 12,  BoxHeight, BoxWidth};
gslc_tsColor nCamCol_rgb = { red, green, blue};
gslc_DrvDrawFillRect(&m_gui, rCamRect, nCamCol_rgb);
}

//gslc_DrvDrawTxt(&m_gui,190, 100,E_DOSIS_BOLD8,charCursTemp,0,cursorCol,0);
for (int j = 0; j < 3; j++) {
gslc_DrvDrawLine (&m_gui, 196 + j, 85, 196 + j, 115, nWhite);
gslc_DrvDrawLine (&m_gui, 181, 99 + j, 211, 99 + j, nWhite);
gslc_ElemSetTxtStr(&m_gui, m_pElemOutTxt23 , charCursTemp);

}
}
if (autoScale == true) {
SetTempScale();
}
snprintf(MinTempChar, 3, "%d", MinTemp);
gslc_ElemSetTxtStr(&m_gui, mintempBtn  , MinTempChar);
snprintf(MaxTempChar, 3, "%d", MaxTemp);
gslc_ElemSetTxtStr(&m_gui, maxtempBtn, MaxTempChar);

}
}
``````

I literally just changed every 70 to a 60, and changed the box size to 3 x 4, it works, but its obviously not the correct way to go (60 is not divisable by 7!) .

I believe the correct way to re-scale would be to change the interpolation from 10x10 to 8x8, so instead of a 70x70 I have 56x56, which can then be scaled to 168 x 224 by making each cell 3x4 pixels, and also speeding things up at the same time, as less calculations are being computed.

Unfortunately Im a bit stuck, and would appreciate some guidance implementing this. Ive played around a bit, but its a bit out of my league…

Any help would be appreciated.

Can you explain a bit more abstract what scaling has to be done? You talk about an AMG8833 and boxes and grids, and I cannot fit all these together. Your code also does not enlighten me much Do you think that it makes sense to squeeze a quadratic image into a rectangular one? At least I'd think that you should cut off the excess pixels and continue with quadratic boxes in a rectangular grid.

Im sorry, its a bit tricky to articulate whats going on, but Ill try.

I am using some sample code that interpolates an 8x8 thermal camera to 70x70. (values from pixel 1 and 2 are taken from the thermal array and 10 interpolated values are created from them, then pixel 2 and 3, and so on, resulting in a 70x70 interpolated array created from the 8x8)

I am trying to scale this down to 56x56. (8 interpolated values are created between each pixel instead of 10)

The original code with notes is here

The Adafruit code for the AMG8833 sensor interpolates the data onto an output array of arbitrary size. My version of that code also allows for output pixel blocks of arbitrary size. It has some debug print statements to check for correct operation, and includes a heat map color lookup table.

``````#ifndef interp_H
#define interp_H

//heatmap color table
const uint16_t camColors[] = {0x480F,
0x400F,0x400F,0x400F,0x4010,0x3810,0x3810,0x3810,0x3810,0x3010,0x3010,
0x3010,0x2810,0x2810,0x2810,0x2810,0x2010,0x2010,0x2010,0x1810,0x1810,
0x1811,0x1811,0x1011,0x1011,0x1011,0x0811,0x0811,0x0811,0x0011,0x0011,
0x0011,0x0011,0x0011,0x0031,0x0031,0x0051,0x0072,0x0072,0x0092,0x00B2,
0x00B2,0x00D2,0x00F2,0x00F2,0x0112,0x0132,0x0152,0x0152,0x0172,0x0192,
0x0192,0x01B2,0x01D2,0x01F3,0x01F3,0x0213,0x0233,0x0253,0x0253,0x0273,
0x0293,0x02B3,0x02D3,0x02D3,0x02F3,0x0313,0x0333,0x0333,0x0353,0x0373,
0x0394,0x03B4,0x03D4,0x03D4,0x03F4,0x0414,0x0434,0x0454,0x0474,0x0474,
0x0494,0x04B4,0x04D4,0x04F4,0x0514,0x0534,0x0534,0x0554,0x0554,0x0574,
0x0574,0x0573,0x0573,0x0573,0x0572,0x0572,0x0572,0x0571,0x0591,0x0591,
0x05C9,0x05C8,0x05E8,0x05E8,0x05E7,0x05E7,0x05E6,0x05E6,0x05E6,0x05E5,
0x05E5,0x0604,0x0604,0x0604,0x0603,0x0603,0x0602,0x0602,0x0601,0x0621,
0x0621,0x0620,0x0620,0x0620,0x0620,0x0E20,0x0E20,0x0E40,0x1640,0x1640,
0x1E40,0x1E40,0x2640,0x2640,0x2E40,0x2E60,0x3660,0x3660,0x3E60,0x3E60,
0x3E60,0x4660,0x4660,0x4E60,0x4E80,0x5680,0x5680,0x5E80,0x5E80,0x6680,
0x6680,0x6E80,0x6EA0,0x76A0,0x76A0,0x7EA0,0x7EA0,0x86A0,0x86A0,0x8EA0,
0x8EC0,0x96C0,0x96C0,0x9EC0,0x9EC0,0xA6C0,0xAEC0,0xAEC0,0xB6E0,0xB6E0,
0xBEE0,0xBEE0,0xC6E0,0xC6E0,0xCEE0,0xCEE0,0xD6E0,0xD700,0xDF00,0xDEE0,
0xDEC0,0xDEA0,0xDE80,0xDE80,0xE660,0xE640,0xE620,0xE600,0xE5E0,0xE5C0,
0xE5A0,0xE580,0xE560,0xE540,0xE520,0xE500,0xE4E0,0xE4C0,0xE4A0,0xE480,
0xE460,0xEC40,0xEC20,0xEC00,0xEBE0,0xEBC0,0xEBA0,0xEB80,0xEB60,0xEB40,
0xEB20,0xEB00,0xEAE0,0xEAC0,0xEAA0,0xEA80,0xEA60,0xEA40,0xF220,0xF200,
0xF1E0,0xF1C0,0xF1A0,0xF180,0xF160,0xF140,0xF100,0xF0E0,0xF0C0,0xF0A0,
0xF080,0xF060,0xF040,0xF020,0xF800,};
// function prototypes
float get_point(float *p, uint8_t rows, uint8_t cols, int8_t x, int8_t y);
void set_point(float *p, uint8_t rows, uint8_t cols, int8_t x, int8_t y, float f);
void get_adjacents_1d(float *src, float *dest, uint8_t rows, uint8_t cols, int8_t x, int8_t y);
void get_adjacents_2d(float *src, float *dest, uint8_t rows, uint8_t cols, int8_t x, int8_t y);
float cubicInterpolate(float p[], float x);
float bicubicInterpolate(float p[], float x, float y);
void interpolate_image(float *src, uint8_t src_rows, uint8_t src_cols,
float *dest, uint8_t dest_rows, uint8_t dest_cols);

float get_point(float *p, uint8_t rows, uint8_t cols, int8_t x, int8_t y) {
if (x < 0)        x = 0;
if (y < 0)        y = 0;
if (x >= cols)    x = cols - 1;
if (y >= rows)    y = rows - 1;
return p[y * cols + x];
}

void set_point(float *p, uint8_t rows, uint8_t cols, int8_t x, int8_t y, float f) {
//    p[y * cols + x] = f; } //was, for output array (SJR)

// send blocks of points to display (global boxsize)
int colorTemp;
uint8_t colorIndex, x1, y1, x2, y2;
if ((x < 0) || (x >= cols)) return;
if ((y < 0) || (y >= rows)) return;

x1=x*boxsize;
x2=x1+boxsize-1;
y1=y*boxsize;
y2=y1+boxsize-1;

if(f >= MAXTEMP) colorTemp = MAXTEMP;
else if(f <= MINTEMP) colorTemp = MINTEMP;
else colorTemp = f;

colorIndex = map(colorTemp, MINTEMP, MAXTEMP, 0, 255);
colorIndex = constrain(colorIndex, 0, 255);
colorTemp=camColors[colorIndex];
OLED_DrawRectangle(x1, y1, x2, y2, colorTemp);  //fill must be set by pensize()
}

// src is a grid src_rows * src_cols
// dest is a pre-allocated grid, dest_rows*dest_cols
void interpolate_image(float *src, uint8_t src_rows, uint8_t src_cols,
float *dest, uint8_t dest_rows, uint8_t dest_cols) {
float mu_x = (src_cols - 1.0) / (dest_cols - 1.0);
float mu_y = (src_rows - 1.0) / (dest_rows - 1.0);

float frac_x,frac_y,out;

for (uint8_t y_idx=0; y_idx < dest_rows; y_idx++) {
for (uint8_t x_idx=0; x_idx < dest_cols; x_idx++) {
float x = x_idx * mu_x;
float y = y_idx * mu_y;
//       Serial.print("("); Serial.print(y_idx); Serial.print(", "); Serial.print(x_idx); Serial.print(") = ");
//       Serial.print("("); Serial.print(y); Serial.print(", "); Serial.print(x); Serial.print(") => ");
/*
Serial.print("[");
for (uint8_t i=0; i<16; i++) {
}
Serial.println("]");
*/
frac_x = x - (int)x; // we only need the ~delta~ between the points
frac_y = y - (int)y; // we only need the ~delta~ between the points
//       Serial.print("\tInterp: "); Serial.println(out);
set_point(dest, dest_rows, dest_cols, x_idx, y_idx, out);
}
}
}

// p is a list of 4 points, 2 to the left, 2 to the right
float cubicInterpolate(float p[], float x) {
float r = p + (0.5 * x * (p - p + x*(2.0*p - 5.0*p + 4.0*p - p + x*(3.0*(p - p) + p - p))));
/*
Serial.print("interpolating: [");
Serial.print(p,2); Serial.print(", ");
Serial.print(p,2); Serial.print(", ");
Serial.print(p,2); Serial.print(", ");
Serial.print(p,2); Serial.print("] w/"); Serial.print(x); Serial.print(" = ");
Serial.println(r);
*/
return r;
}

// p is a 16-point 4x4 array of the 2 rows & columns left/right/above/below
float bicubicInterpolate(float p[], float x, float y) {
float arr = {0,0,0,0};
arr = cubicInterpolate(p+0, x);
arr = cubicInterpolate(p+4, x);
arr = cubicInterpolate(p+8, x);
arr = cubicInterpolate(p+12, x);
return cubicInterpolate(arr, y);
}

// src is rows*cols and dest is a 4-point array passed in already allocated!
void get_adjacents_1d(float *src, float *dest, uint8_t rows, uint8_t cols, int8_t x, int8_t y) {
//    Serial.print("adj_1d("); Serial.print(x); Serial.print(", "); Serial.print(y); Serial.println(")");
// pick two items to the left
dest = get_point(src, rows, cols, x-1, y);
dest = get_point(src, rows, cols, x, y);
// pick two items to the right
dest = get_point(src, rows, cols, x+1, y);
dest = get_point(src, rows, cols, x+2, y);
}
``````

I was originally tried this code but I was unable to get it working in conjunction with GUISlice, and the code from Kris uses some math to generate an RGB value rather than reading from an array, making use of a larger color palet

some math to generate an RGB value

That method was too slow for me, on a 16 MHz Arduino. Hence the lookup table.

jremington:
That method was too slow for me, on a 16 MHz Arduino. Hence the lookup table.

I am using an ESP32, Arduino was no where near enough for what ive got going on over here

You should have no problem getting the Adafruit interpolation to work. Their code is correct.

I have the adafruit interpolation up and running.
Some questions;
Can you please give me a brief explanation on how your code works, and how to change the size of the picture and resolution?

The Adafruit method filled up the target array point by point. Since that takes up memory, and accomplishes nothing for speed, I send the result of the interpolation directly to the display (after conversion of the temperature value to the heatmap color index).

These call arguments to function set_point() are the output array dimensions, which obviously limits the output array size to 255 max in either dimension. I think it is OK to make them them integers, but the code would need to be checked for (16 bit) integer overflow if you do.
uint8_t rows, uint8_t cols,

The changes I made to Adafruit code were mostly to comment out this line in function set_point() (which stored the interpolated value in the output array)

``````//    p[y * cols + x] = f; } //was, for output array (SJR)
``````

and to add this code to look up the color and send a block to the display (typically 3x3 pixels)

``````// send blocks of points to display (global boxsize)
int colorTemp;
uint8_t colorIndex, x1, y1, x2, y2;
if ((x < 0) || (x >= cols)) return;
if ((y < 0) || (y >= rows)) return;

x1=x*boxsize;
x2=x1+boxsize-1;
y1=y*boxsize;
y2=y1+boxsize-1;

if(f >= MAXTEMP) colorTemp = MAXTEMP;
else if(f <= MINTEMP) colorTemp = MINTEMP;
else colorTemp = f;

colorIndex = map(colorTemp, MINTEMP, MAXTEMP, 0, 255);
colorIndex = constrain(colorIndex, 0, 255);
colorTemp=camColors[colorIndex];
OLED_DrawRectangle(x1, y1, x2, y2, colorTemp);  //fill must be set by pensize()
``````

I also added some print statements to verify that the interpolation was working correctly, and then commented them back out, because it does work correctly.

Thankyou for your help, I appreciate it alot.

Just to be clear, my goal in not speed, rather the best picture I can achieve in the avaliable space that I have (this is why 70x70 wasnt working, because a box size of 3x4 is too big (210x280) and 2x3 is too small (140x210)).

I will try to implement your code as it seems very refined, but I have some further questions.

1. I assume this code replaces interpolation.cpp completely, I just run this in the .ino and ditch the extra .cpp? Do you possibly have a working example I can play around with?

2. You said you tried generating rgb using math rather than having a LUT. Do you still have this code floating around? I would like to try it out. Because I am using GUISlice, I have to use RGB for the drawfillrect, with the adafruit example I have to convert the 16bit hext to rgb before using it. Plus I like the larder color range. I can only make out about 9 different colors when using the adafruit example, but the example from Kris had much better color transitions.

3. When using the Adafruit interpolation, I hit some kind of a wall at 41x41 rows/columns. Any more than that and it crashes. Before I investigate further I just wanted to ask if its a known bug?
If not then it probably has something to do with how I am using it in conjunction with the GUISlice lib.

Below is the main code, which reads out the sensor and displays on an OLED. I did not try going above 32x32 for the output map (although the result is 128x128 because I send 4x4 blocks).

``````#define INPUT_COLS 8
#define INPUT_ROWS 8
#define INPUT_PIXEL_ARRAY_SIZE (INPUT_ROWS*INPUT_COLS)
#define INTERPOLATED_COLS 32
#define INTERPOLATED_ROWS 32
#define DISPLAY_COLS 128
#define DISPLAY_ROWS 128
#define MINTEMP 0.
#define MAXTEMP 10.

#include <Wire.h>

float pixels[AMG88xx_PIXEL_ARRAY_SIZE];
int npix = AMG88xx_PIXEL_ARRAY_SIZE;

//TX = 9, RX=8, reset_display=7
#include <AltSoftSerial.h>
AltSoftSerial altSerial;

float dest_2d;  //output value for box fill
int boxsize; //pixel blocksize for output

#include "oled160drv.h"
#include "interp.h"

char powerup = 1; //OLED display powered up

// timeout set to four minutes
#define SCREENSAVER 4*60*1000UL

void setup() {
// put your setup code here, to run once:
Serial.begin(9600);
Serial.println("test interp");
altSerial.begin(19200);
OLED_Init();
OLED_Clear();  //have to set font size first or row spacing doesn't work properly
OLED_Pensize(0); //  fill rectangles
bool status;

// initialize thermal sensor with defaults
status = amg.begin();
if (!status) {
Serial.println("Could not find thermal sensor.");
while (1);
}

boxsize = min(DISPLAY_COLS / INTERPOLATED_COLS, DISPLAY_ROWS / INTERPOLATED_COLS);
Serial.print("Box size = ");
Serial.println(boxsize);
delay(100); // sensor is still booting
}

float x, y;
int i;
int counter = 0;

void loop() {
counter++;

if ( (millis() > SCREENSAVER) && powerup) {
Serial.println("screensaver activated, power down");
powerup = 0;
OLED_PowerDn();
while (1);
}
Serial.print("image ");
Serial.println(counter);

//read all the pixels, bottom to top

x=y=0.0;

for (i = 1; i <= npix; i++) {
if (y < pixels[i - 1]) y = pixels[i - 1]; //get max
x += pixels[i - 1]; //calc average
}
x /= (float)npix;
y = y - x; //subtract max (NOT USED atm)

for (i = 1; i <= npix; i++) {
y = pixels[i - 1] - x;
if (y < 0.0) y = 0.0;
if (y > MAXTEMP) y = MAXTEMP;
pixels[i - 1] = y;
}

interpolate_image(&pixels, INPUT_ROWS, INPUT_COLS, &dest_2d, INTERPOLATED_ROWS, INTERPOLATED_COLS);

}
``````

I don’t have the version of the code that calculates the heatmap lookup values anymore, because I wrote a standalone C program to do that for arbitrary color gradients, using a PC (for the Code::Blocks IDE). It also generates the Arduino code initialization statements.

Here it is:

``````#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <inttypes.h>

void getHeatMapColor(float value, float *red, float *green, float *blue)
{
const int NUM_COLORS = 4;
static float color[] = { {0,0,.5}, {1,0,0}, {.8,.8,0}, {.7,.7,.7} };
// A static array of 5 colors:  (blue, red,  yellow, white) using {r,g,b} for each.
// guess at matching intensities -- works pretty well!
int idx1;        // |-- Our desired color will be between these two indexes in "color".
int idx2;        // |
float fractBetween = 0;  // Fraction between "idx1" and "idx2" where our value is.

if(value <= 0)      {  idx1 = idx2 = 0;            }    // accounts for an input <=0
else if(value >= 1)  {  idx1 = idx2 = NUM_COLORS-1; }    // accounts for an input >=0
else
{
value = value * (NUM_COLORS-1);        // Will multiply value by 3.
idx1  = floor(value);                  // Our desired color will be after this index.
idx2  = idx1+1;                        // ... and before this index (inclusive).
fractBetween = value - (float)idx1;    // Distance between the two indexes (0-1).
}

*red   = (color[idx2] - color[idx1])*fractBetween + color[idx1];
*green = (color[idx2] - color[idx1])*fractBetween + color[idx1];
*blue  = (color[idx2] - color[idx1])*fractBetween + color[idx1];
}
int main()   // to test the code
{
float r,g,b,value;
uint16_t table;

int i=0;
for(i=0; i<256; i++)
{
value=((float)i)/255.0;
getHeatMapColor(value, &r, &g, &b);

uint16_t result, red, green, blue, ir, ig, ib ;

ir = 255.0*r;
ig = 255.0*g;
ib = 255.0*b;

red = ir * 31 / 255;
green = ig * 63/ 255;
blue = ib * 31 / 255;

green = green << 5;
red = red << 11;

result = red | green | blue;
table[i]=result;
printf(" {%d, %d, %d, %u},\r\n",ir,ig,ib,result);
}
//  print data table for array initialization.
printf(" ={");
int j=0;
for(i=0; i<256; i++) {
j++;
printf("%d,", table[i]);
if (j==10) {printf("\r\n"); j=0;}
}
printf("}\r\n");  //extra "," to remove by editing.
}
``````

Thanks for that.

Do you have any tips for generating the value float between 0 and 1?
Maybe some trick to generate this from colorTemp?

i have no idea if this is how its intended to be used, but here is what I am trying to do with your code:

``````void getHeatMapColor(float value, int *red, int *green, int *blue)
{
const int NUM_COLORS = 5;
static float color[] = { {0,0,20}, {125,0,155}, {225,60,35},{225,165,0},{255,250,210}  };
// A static array of 5 colors:  (blue, red,  yellow, white) using {r,g,b} for each.
// guess at matching intensities -- works pretty well!
int idx1;        // |-- Our desired color will be between these two indexes in "color".
int idx2;        // |
float fractBetween = 0;  // Fraction between "idx1" and "idx2" where our value is.

if(value <= 0)      {  idx1 = idx2 = 0;            }    // accounts for an input <=0
else if(value >= 1)  {  idx1 = idx2 = NUM_COLORS-1; }    // accounts for an input >=0
else
{
value = value * (NUM_COLORS-1);        // Will multiply value by 3.
idx1  = floor(value);                  // Our desired color will be after this index.
idx2  = idx1+1;                        // ... and before this index (inclusive).
fractBetween = value - (float)idx1;    // Distance between the two indexes (0-1).
}

*red   = (color[idx2] - color[idx1])*fractBetween + color[idx1];
*green = (color[idx2] - color[idx1])*fractBetween + color[idx1];
*blue  = (color[idx2] - color[idx1])*fractBetween + color[idx1];
}

void drawpixels(float *p, uint8_t rows, uint8_t cols, uint8_t boxWidth, uint8_t boxHeight, boolean showVal) {
int colorTemp;
for (int y=0; y<rows; y++) {
for (int x=0; x<cols; x++) {
float val = get_point(p, rows, cols, x, y); //up to this point is directly from the adafruit example
value = map (val,MINTEMP,MAXTEMP,0,1); // map val to a float between 0 and 1 for getHeatMapColor()
//create box dimensions
gslc_tsRect rCamRect = {
boxWidth * x,
boxHeight * y,
boxWidth,
boxHeight};
getHeatMapColor(value, &r, &g, &b); //pull rgb from getHeatMapColor function
gslc_tsColor nCamCol_rgb888;        //put rgb values into GUISlice struct
nCamCol_rgb888.r =  r;
nCamCol_rgb888.g =  g;
nCamCol_rgb888.b =  b;
gslc_DrvDrawFillRect(&m_gui, rCamRect, nCamCol_rgb888);    //draw thermal
}
}
}
``````

unfortunately it just shows the first and last colors (0,0,20 and 255,250,210) and does not produce a gradient.

Any ideas?

Never mind, I found the problem...
I dont get a float if I use map().
So i mapped value to 1000
then made a float value2 = value/1000 to get my floating point for your color map generator function.
It works very well, I could even add some sliders to my gui to alter the RGB values in the function to make the color map modifiable 8)

I appreciate your help alot. Im going to call it a night and tomorrow I will try to implement your changes to the adafruit example, and try to figure out why I crash and burn when interperlating more than 41 rows and columns

Yes, map() is an integer function. Glad it is working!

crash and burn when interpolating more than 41 rows and columns

This test code interpolates an 8x8 image (with a single non-zero point) up to 128x128 with no obvious problems (runs on a PC using the Code::Blocks IDE). I did not try larger sizes, and 255 is max anyway.

``````// test of Adafruit's "interp.h"
// interpolate an 8x8 array up to 128x128

#include <stdio.h>
#include <stdlib.h>
#include <inttypes.h>

float get_point(float *p, uint8_t rows, uint8_t cols, int8_t x, int8_t y);
void set_point(float *p, uint8_t rows, uint8_t cols, int8_t x, int8_t y, float f);
void get_adjacents_1d(float *src, float *dest, uint8_t rows, uint8_t cols, int8_t x, int8_t y);
void get_adjacents_2d(float *src, float *dest, uint8_t rows, uint8_t cols, int8_t x, int8_t y);
float cubicInterpolate(float p[], float x);
float bicubicInterpolate(float p[], float x, float y);
void interpolate_image(float *src, uint8_t src_rows, uint8_t src_cols,
float *dest, uint8_t dest_rows, uint8_t dest_cols);

float get_point(float *p, uint8_t rows, uint8_t cols, int8_t x, int8_t y) {
if (x < 0)        x = 0;
if (y < 0)        y = 0;
if (x >= cols)    x = cols - 1;
if (y >= rows)    y = rows - 1;
return p[y * cols + x];
}

void set_point(float *p, uint8_t rows, uint8_t cols, int8_t x, int8_t y, float f) {
if ((x < 0) || (x >= cols)) return;
if ((y < 0) || (y >= rows)) return;
p[y * cols + x] = f;
}

// src is a grid src_rows * src_cols
// dest is a pre-allocated grid, dest_rows*dest_cols
void interpolate_image(float *src, uint8_t src_rows, uint8_t src_cols,
float *dest, uint8_t dest_rows, uint8_t dest_cols) {
float mu_x = (src_cols - 1.0) / (dest_cols - 1.0);
float mu_y = (src_rows - 1.0) / (dest_rows - 1.0);

for (uint8_t y_idx=0; y_idx < dest_rows; y_idx++) {
for (uint8_t x_idx=0; x_idx < dest_cols; x_idx++) {
float x = x_idx * mu_x;
float y = y_idx * mu_y;
//Serial.print("("); Serial.print(y_idx); Serial.print(", "); Serial.print(x_idx); Serial.print(") = ");
//Serial.print("("); Serial.print(y); Serial.print(", "); Serial.print(x); Serial.print(") = ");
/*
Serial.print("[");
for (uint8_t i=0; i<16; i++) {
}
Serial.println("]");
*/
float frac_x = x - (int)x; // we only need the ~delta~ between the points
float frac_y = y - (int)y; // we only need the ~delta~ between the points
float out = bicubicInterpolate(adj_2d, frac_x, frac_y);
//Serial.print("\tInterp: "); Serial.println(out);
set_point(dest, dest_rows, dest_cols, x_idx, y_idx, out);
}
}
}

// p is a list of 4 points, 2 to the left, 2 to the right
float cubicInterpolate(float p[], float x) {
float r = p + (0.5 * x * (p - p + x*(2.0*p - 5.0*p + 4.0*p - p + x*(3.0*(p - p) + p - p))));
/*
Serial.print("interpolating: [");
Serial.print(p,2); Serial.print(", ");
Serial.print(p,2); Serial.print(", ");
Serial.print(p,2); Serial.print(", ");
Serial.print(p,2); Serial.print("] w/"); Serial.print(x); Serial.print(" = ");
Serial.println(r);
*/
return r;
}

// p is a 16-point 4x4 array of the 2 rows & columns left/right/above/below
float bicubicInterpolate(float p[], float x, float y) {
float arr = {0,0,0,0};
arr = cubicInterpolate(p+0, x);
arr = cubicInterpolate(p+4, x);
arr = cubicInterpolate(p+8, x);
arr = cubicInterpolate(p+12, x);
return cubicInterpolate(arr, y);
}

// src is rows*cols and dest is a 4-point array passed in already allocated!
void get_adjacents_1d(float *src, float *dest, uint8_t rows, uint8_t cols, int8_t x, int8_t y) {
//Serial.print("("); Serial.print(x); Serial.print(", "); Serial.print(y); Serial.println(")");
// pick two items to the left
dest = get_point(src, rows, cols, x-1, y);
dest = get_point(src, rows, cols, x, y);
// pick two items to the right
dest = get_point(src, rows, cols, x+1, y);
dest = get_point(src, rows, cols, x+2, y);
}

// src is rows*cols and dest is a 16-point array passed in already allocated!
void get_adjacents_2d(float *src, float *dest, uint8_t rows, uint8_t cols, int8_t x, int8_t y) {
//Serial.print("("); Serial.print(x); Serial.print(", "); Serial.print(y); Serial.println(")");
float arr;
for (int8_t delta_y = -1; delta_y < 3; delta_y++) { // -1, 0, 1, 2
float *row = dest + 4 * (delta_y+1); // index into each chunk of 4
for (int8_t delta_x = -1; delta_x < 3; delta_x++) { // -1, 0, 1, 2
row[delta_x+1] = get_point(src, rows, cols, x+delta_x, y+delta_y);
}
}
}

int main()
{
const int size=128;
float img1={0}; //input image
img1=100.0;  //set one centrally located point to non-zero for testing
float img2[size][size]; //output image
int i,j;
interpolate_image(img1,8,8,img2,size,size);

// check for nonzero pixels in output image and print their indices plus interpolated value

for (i=0; i< size; i++) {
for (j=0; j< size; j++) {
if(img2[i][j]>0.0) printf("[%d,%d]= %8.4f\n",i,j,img2[i][j]);
}
}
return 0;
}
``````

Postscript:

The results of this test, which interpolates an 8x8 array to 16x16 are interesting, because the negative regions clearly show the errors introduced by the bicubic interpolation (i.e. attempting to fit a smooth curve to a delta function).

Only main() is posted, all the rest is the same.

``````int main()
{
const int size=16;
float img1={0};
img1=1.0;
float img2[size][size];
int i,j;
interpolate_image(img1,8,8,img2,size,size);
for (i=0; i< size; i++) {
for (j=0; j< size; j++) {
printf("%7.3f",img2[i][j]); //print all values of the 16x16 matrix
}
printf("\n");
}
return 0;
}
``````

Result, with img1=1.0

``````  0.000  0.000  0.000  0.000  0.000  0.000  0.000  0.000  0.000  0.000  0.000  0.000  0.000  0.000  0.000  0.000
0.000  0.000  0.000  0.000  0.000  0.000  0.000  0.000  0.000  0.000  0.000  0.000  0.000  0.000  0.000  0.000
0.000  0.000  0.000  0.000  0.000  0.000  0.000  0.000  0.000  0.000  0.000  0.000  0.000  0.000  0.000  0.000
0.000  0.000  0.000  0.000  0.000  0.000  0.000  0.000  0.000  0.000  0.000  0.000  0.000  0.000  0.000  0.000
0.000  0.000  0.000  0.000  0.000  0.000  0.000  0.000  0.000  0.000  0.000  0.000  0.000  0.000  0.000  0.000
0.000  0.000  0.000  0.000  0.000  0.001  0.002 -0.009 -0.032 -0.034 -0.012  0.002  0.002  0.000  0.000  0.000
0.000  0.000  0.000  0.000  0.000  0.002  0.004 -0.016 -0.054 -0.058 -0.021  0.003  0.003  0.000  0.000  0.000
0.000  0.000  0.000  0.000  0.000 -0.009 -0.016  0.061  0.210  0.225  0.082 -0.012 -0.012  0.000  0.000  0.000
0.000  0.000  0.000  0.000  0.000 -0.032 -0.054  0.210  0.724  0.776  0.284 -0.043 -0.041  0.000  0.000  0.000
0.000  0.000  0.000  0.000  0.000 -0.034 -0.058  0.225  0.776  0.832  0.304 -0.046 -0.044  0.000  0.000  0.000
0.000  0.000  0.000  0.000  0.000 -0.012 -0.021  0.082  0.284  0.304  0.111 -0.017 -0.016  0.000  0.000  0.000
0.000  0.000  0.000  0.000  0.000  0.002  0.003 -0.012 -0.043 -0.046 -0.017  0.003  0.002  0.000  0.000  0.000
0.000  0.000  0.000  0.000  0.000  0.002  0.003 -0.012 -0.041 -0.044 -0.016  0.002  0.002  0.000  0.000  0.000
0.000  0.000  0.000  0.000  0.000  0.000  0.000  0.000  0.000  0.000  0.000  0.000  0.000  0.000  0.000  0.000
0.000  0.000  0.000  0.000  0.000  0.000  0.000  0.000  0.000  0.000  0.000  0.000  0.000  0.000  0.000  0.000
0.000  0.000  0.000  0.000  0.000  0.000  0.000  0.000  0.000  0.000  0.000  0.000  0.000  0.000  0.000  0.000
``````

Do you think these errors are causing the crash?
Later, when I have time, I will reproduce the problem and take a look at the serial monitor. Hopefully I get some kind of a stack error I can run through the exception decoder to get a hint at what’s going on.
If not then I’ll pump it full of serial prints and try to find exactly where it crashes.

Do you think these errors are causing the crash?

Seems unlikely. Array bound violations are more likely.

Post some information on the crash itself. Error messages are almost always helpful.

This is what I get from the decoded stack error:

``````    PC: 0x40081fd1

Decoding stack results
0x40089a6b: multi_heap_internal_unlock at /Users/ficeto/Desktop/ESP32/ESP32/esp-idf-public/components/heap/multi_heap.c line 380
0x4008a0a2: multi_heap_malloc at /Users/ficeto/Desktop/ESP32/ESP32/esp-idf-public/components/heap/multi_heap_poisoning.c line 206
0x40084b61: heap_caps_malloc at /Users/ficeto/Desktop/ESP32/ESP32/esp-idf-public/components/heap/heap_caps.c line 111
0x40084b92: heap_caps_malloc_default at /Users/ficeto/Desktop/ESP32/ESP32/esp-idf-public/components/heap/heap_caps.c line 140
0x40084e65: _malloc_r at /Users/ficeto/Desktop/ESP32/ESP32/esp-idf-public/components/newlib/syscalls.c line 37
0x400d7dbd: i2cAddQueue at C:\Users\MickW\Documents\Arduino\hardware\espressif\esp32\cores\esp32\esp32-hal-i2c.c line 1076
``````

This is using the original Adafruit interpolation example with #define INTERPOLATED_COLS and #define INTERPOLATED_ROWS both set higher than 41 (in this particular example they are set to 42).

I am not sure exactly whats going on, hopefully you can help shed some light on the problem.

I also have some further questions;

-At what point will math calculated rgb’s outperform a LUT? For example, if I made a LUT with 500 color values would this still be faster than using your getHeatMapColor() function?

-Why am I only seeing 10-15 different colors at any one time? (the LUT contains 256 different colors, and the getHeatMapColor() function should generate many more color variations?)

-Can you please explain your modified version of the Adafuit example more thoroughly? It seems you have moved some functions from interpolation.cpp to the main .ino file. I have been unable to implement it, as I am unsure exactly which steps to take. For example, in the adafruit example the setPoint function looks like this:

``````void set_point(float *p, uint8_t rows, uint8_t cols, int8_t x, int8_t y,
float f) {
if ((x < 0) || (x >= cols))
return;
if ((y < 0) || (y >= rows))
return;
p[y * cols + x] = f;
}
``````

Wheras your version only contains p[y * cols + x] = f; , Which is commented out, meaning the function should do nothing, something I cannot understand.