Now we get to the interesting stuff. The following code is from method CalcTemp() in ThermalExpert-class.
This is where we end up when the temperature for some pixel location is to be calculated. Usually the way it goes is:
GetPointTemperature(int _x, int _y)
find ambient correction temperature and call:
-> PointTemperatur.GetPointTemp(double _ambientCorr)
-> CalcTemp(int _x, int _y) in ThermalExpert class!
private double CalcTemp(int _x, int _y) {
int imgWidth = this.commData.getWindowingWidth(); // 384
int count = 0;
double[] tempCalc = new double[9]; // temperature is calced/averaged over 3x3 patch!
if (!this.shutterLess.m_bIsST) { // is set to true on shutterLess Ctr
return 0.0d;
}
int i;
double avgTemp = 0.0d;
// 3x3 patch around pixel of interest..
for (int y = -1; y < 2; y += 1) {
for (int x = -1; x < 2; x += 1) {
double interGain = 0.0d;
double interOs = 0.0d;
int pos = ((y + _y) * imgWidth) + (x + _x);
if (this.commData.m_pbyDeadData[pos] == null) {
// interGain and interOs as always... y=a+b*T+c*T²+d*T³
for (int order = 0; order < 4; order += 1) {
interGain += (double) (this.shutterLess.m_pdSL_FpaTempArray[order] * this.shutterLess.m_pdSL_Gain[(pos * 4) + order]);
interOs += (double) (this.shutterLess.m_pdSL_FpaTempArray[order] * this.shutterLess.m_pdSL_Offset[(pos * 4) + order]);
}
double dST_Gain = 256.0d / interGain;
double dST_Low = interOs;
double dST_High = dST_Gain + dST_Low;
this.shutterLess.getClass();
// 8192/256 * interGain * m_piRecvWinData[pos] = 32 * interGain * m_piRecvWinData[pos]!
double d = (8192.0d / dST_Gain) * ((double) this.commData.m_piRecvWinData[pos]); // int[] m_piRecvWinData = raw sensor data as 16 bits!
this.shutterLess.getClass();
// 16384 * (256/interGain + interOs)
double d2 = 16384.0d * dST_High;
this.shutterLess.getClass();
// d = 32*interGain*m_piRecvWinData[pos]
// d2 = 16384 * (256/interGain + interOs)
// dST_Low = interOs
// dST_Gain = 256/interGain
double dPixel = d + ((d2 - (24576.0d * dST_Low)) / dST_Gain); // dPixel = 32*[(m_piRecvWinData[pos] - interOs) * interGain + 512] ?!
tempCalc[count] = this.shutterLess.getTargetTemp(dPixel); //TBD: check in EXCEL!! -> yes this is correct
count += 1;
}
}
}
//removed the boring averaging stuff
...
}
Input is commData.m_piRecvWinData[pos] which is an int[] of size [384*288] that holds the raw 16 bit detector values.
I think the comments explain the mechanism fairly well except from the part where 'interGain' and 'interOffset' are calculated.
Both values come from the initial FlashReadShutterless(). As stated in one of the previous posts the data from frame 3 ... 10 and 11 to 18 results in a gain map called shutterLess.m_pdSL_Gain[4*384*288] and shutterLess.m_pdSL_Offset[4*384*288] where for each image pixel there are 4 float with the order of magnitude [1.0 0.1 0.01 0.001] for the gain map and [7000 -20 0.1 0.002] for the offsets. For each pixel!
There is another array called shutterLess.m_pdSL_FpaTempArray[4] that hold 4 floats and is filled at the beginning of each call to display() (see earlier post). The values in there are of the form [1.0 T T² T³], where T is the current FPA temperature. Essentially what is in there ist FPAtemp^N with N=0...3.
The gain and offset values then are calculated with a cubic Polynom where the coefficients are the 4 values per pixel (c0 ... c3) and the variable it T (FPAtemp):
interGain = c0 + c1*T + c2*T² + c3*T³ ~= 1.0 + 0.1*T + 0.01*T² + 0.001*T³ or whatever the values for the pixel are
and respectively for the offset:
interOffset = a0 + a1*T + a2*T² + a3*T³ ....
The rest is pretty straight forward, altough quite complicated in code (not sure if this is due to some compiler optimizations or intended obfuscation?) but the formula breaks down to:
dPixel = 32*[(m_piRecvWinData[pos] - interOs) * interGain + 512]
Which delivers a double value for each pixel.
Finally this value is converted to a temperature via shutterLess.getTargetTemp(dPixel) which just applies a more or less simple formula as seen in code here:
public double getTargetTemp(double _output) {
if (169533.38422877376d < (7146.4357337d - _output) * 9.8736116d) {
return 0.0d;
}
// depending Gain and Offset values this may be in fact the calibration curve...
// m_dTemperatureGain = 1.0 initially -> set in FlashReadShutterlessMultiOffset() after temp offset ~1.25?
// m_dTemperatureOffset = 0.0 initially -> set in FlashReadShutterlessMultiOffset() directly after FPA temp ~3.5?
// m_dTestTempOffset = 0.0 initially -> set via setMaunalTemperatureOffset() but seems to called nowhere!!
// return (((Gain * (-411.74d + Math.sqrt(169533.38d - ((7146.43d - _output) * 9.87d)))) / 4.93d) - Offset) + manualOffset;
return (((this.m_dTemperatureGain * (-411.744319d + Math.sqrt(169533.38422877376d - ((7146.4357337d - _output) * 9.8736116d)))) / 4.9368058d) - this.m_dTemperatureOffset) + this.m_dTestTempOffset;
}
Et viola 16 bit raw data to temperature!
What is funny to mention is that for display there is a lot more going on with additional offsets. They are coming from FlashReadMultiOffset() in frames 19 ... 26 OR from the shutter calibration. But they seem to be unused when actually calculating temperatures.
The missing puzzle piece here is a bit earlier in GetPointTemp() of ThermalExpert class:
public double GetPointTemperature(int _x, int _y) {
double ambientCorrTemp = getAmbientCorrTemp(getAmbientTemp((double) this.shutterLess.m_dFpaTemp));
this.m_pointTemp.SetPoint(convResolution(_x, _y));
this.m_pointTemp.GetPoint();
return this.m_pointTemp.GetPointTemp(ambientCorrTemp);
}
getAmbientTemp:
The input again is the actual FPA temperature shutterLess.m_dFpaTemp.
commData.m_pdMultiOs_FpaTemp[4] are the 4 FPA temps form the init frames 19...26 at pixel position 1531 in FlashReadMultiOffset. In my case [51.21 33.84 62.63 43.38].
private double getAmbientTemp(double _temp) {
if (_temp >= this.commData.m_pdMultiOs_FpaTemp[1]) {
return (this.ambientConstA[1] * _temp) + this.ambientConstB[1];
}
return (this.ambientConstA[0] * _temp) + this.ambientConstB[0];
}
The ambientConstA/B are also calculated in FlashReadMultiOffset(). Those 4 FPA temperatures are sorted and then this is done:
double[] multiChamberTemp = new double[3]; // this is fixed
multiChamberTemp[0] = 5.0d;
multiChamberTemp[1] = 25.0d;
multiChamberTemp[2] = 35.0d;
for (pos = 0; pos < 2; pos += 1) {
if (sortFpaTemp[pos + 1] - sortFpaTemp[pos] == 0.0d) { // no in both positions (at least with my data, and any useful data i guess...)
this.ambientConstA[pos] = 1.0d; //this is only double[2] (all those arrays!)
this.ambientConstB[pos] = 0.0d;
} else {
// pos=0: = (25.0 - 5.0) / (43.38 - 33.84) = 2.0964
// pos=1: = (35.0 - 25.0) / (51.21 - 43.38) = 1.2771
this.ambientConstA[pos] = (multiChamberTemp[pos + 1] - multiChamberTemp[pos]) / (sortFpaTemp[pos + 1] - sortFpaTemp[pos]);
// pos=0: = 5.0 - 2.0964*33.84 = -65.943
// pos=1: = 25.0 - 1.2771*43.38 = -30.401
this.ambientConstB[pos] = multiChamberTemp[pos] - (this.ambientConstA[pos] * sortFpaTemp[pos]);
}
}
In my case the values should be:
A[0] = 2.0964 A[1] = 1.2771
B[0] = -65.943 B[1] = -30.401
So depending on which code path is executed and assuming and FPAtemp of 40°C getAmbientTemp should return:
2.09 * 40 - 65.94 = 17.66 or
1.27 * 40 - 30.40 = 20.40
getAmbientCorrTemp:
More than easy ... altough not really clear why and what happens.
Maybe some sort of estimation what intensity is received from the lens?
private double getAmbientCorrTemp(double _ambientTemp) {
return (-0.2d * _ambientTemp) + 5.0d;
}
Using the values from above getAmbientCorrTemp will return:
-0.2 * 17.66 + 5.0 = 1.468 or
-0.2 * 20.40 + 5.0 = 0.920
And finally PointTemperature.GetPointTemp():
Which just calls the CalcTemp method shown in detail above and adds the ambient correction temperature.
public double GetPointTemp(double _ambientCorr) {
this.m_dTemp = ThermalExpert.this.CalcTemp(this.m_Point.x, this.m_Point.y) + _ambientCorr;
return this.m_dTemp;
}
Now that is really all I have for the moment ...
If all goes well on the weekend I may be able to put some C# code together and try all this. The only issue I got so far is that I receive ReadTimeOut exceptions when querying the data from USB which resulted in a crash and loss of USB connection. I used the code from joe-c "TEQ1Thermal_003Maps" project to get my 50 frames for analysis. I tried a Thread.Sleep(100) between read request which resulted in exceptions after a couple of frames. To be save I raised it to Sleep(500) to just get a dataset to start with - that worked out. Anyone got any ideas on this or maybe 'save' working code?
Thanks and good night gents...
EDIT: there was an error in the calcualtions for the ambientCorr factors (used 0 as first temp instead of 5) - this is corrected now.