Wednesday, November 30, 2016

Freestyle Libre external thermistor data dump

I’ve received a couple of questions about the Libre thermistor temperature curves in my previous blog post. One of the reason I did not post actual data is that I wasn’t 100% sure of their absolute validity (in relative terms, they were enough for my purpose). The data I used last year was derived through a polynomial fit, not the ideal option, when there is a better approximation formula that is commonly used for NTC thermistors.
That is why, today, I decided to double check the data and provide it.
Usual caveats
  • thermistor have tolerances, 5%, 10%, generating errors.
  • my multimeter probably suffers for a certain error.
  • my contraption may introduce additional resistance.
  • my heating bed’s thermistor also has its own error.
  • the thermistor is extremely reactive: breathing on it changes its resistance, testing it in an open environment or under some insulation changes the values.
  • I have seen thermistors not behaving as expected.
As usual, take your own measures.
Here is the test environment. The glass bowl serves as a temperature stabilizer. I am patching on an old Libre sensor.
I ran a few measures at 55°C, 35°C, 0°C (separately), averaged them and solved the 3 equation system
1/T1 = a +b(log(R1))+c(log(R1))^3
1/T2 = a +b(log(R2))+c(log(R2))^3
1/T3 = a +b(log(R3))+c(log(R3))^3
to derive the Steinhart-Hart coefficients (working in kOhms rather than Ohms.
The Wolfram Alpha query for that system is
1/328=x+y*(ln(31.5))+z*(ln(31.5))^3, 1/308 = x +y*(ln(83))+z*(ln(83))^3, 1/273= x +y*(ln(330))+z*(ln(330))^3
It matches closely the solution I got in numpy. Here’s the plotted “theoretical“ curve versus the separately observed data. Please note that this can’t be directly converted in the Libre bits and bytes and is not necessarily exactly what the Libre sees.
The thermistor seems to be a 120k NTC thermistor and not a 100k thermistor. That was either a quick wrong assumption back in 2015, or a thermistor variation (I don’t remember and don’t have that thermistor for a retest anyway).

Edit 30/11 – after additional measures with a third thermistor, taking the gradient over my bed in consideration and testing older thermistors, the old assumption of a 100k thermistor could very well be the correct one, in that case, the curve below simply shifts to the left. It is correct in relative, but not absolute terms
And here is is the python code containing the experimental data. Again, if you are interested, by all means, take your own measures (and eventually let me know). I have observed slightly different values, possibly a 100k NTC and, at least in one case, drastically different values. It is hard for me to know if the thermistor was damaged of if Abbott uses two different types. And yes, I am aware that the decimals in the data dump are probably pointless, but I just noted the values as the rig stabilized.
import matplotlib.pyplot as plt
import numpy as np
import math as m

# 0C
K = 273

# takes the coefficients, measured resistance, returns temp in celsius (note kOhms)
def SteinhartHart(a, b, c, r):
    invT = a + b * m.log(r) + c * (m.pow(m.log(r), 3))
    TK = 1 / invT
    T = TK - K
    return T

# thermistor measures
Observed = [(40, 69), (40, 66), (39, 72), (39, 69), (38, 74), (38, 72), (37, 76), (37, 76.5), (36, 79),
            (36, 78.34), (35, 83), (34, 86), (33, 89), (32, 93), (32, 93), (31, 96.8), (31.3, 95), (30.5, 98),
            (30.5, 99), (29.9, 101), (27.6, 111), (27, 114), (25.8, 116.6), (25.2, 122.2), (25.9, 117.4),
            (30, 100), (29, 104), (28, 108), (27, 112), (26.7, 114), (26.2, 116.8), (25.7, 118.4), (24.8, 123.3),
            (55, 32.9), (62.2, 20), (0.1, 340), ]

fig = plt.figure()
ax = fig.add_subplot(111)
ax.set_title('Ext. Therm (Steinhart-Hart)')
ax.set_ylabel('Temp. Celsius')

# plot observed data
v, t = zip(*Observed)
line2 = ax.scatter(t, v, c='r', marker='.', label="Measures")

# steinhart - hart, linalg code omitted, double checked with wolfram alpha kOhms
a = 0.00270712
b = 0.0000629848
c = 0.00000302854

# generates Steinhart-Hart Curve
x = np.linspace(1, 350, 350)
s = []
for i in x:
    s.append((SteinhartHart(a, b, c, i)))
line = ax.plot(x, s, c='g', label="Steinhart-Hart")

Blog issues, sorry...

Editing the last post in Open Live Writer seems to throw an exception currently. Blog may go up/down during the next few days. Sorry.

Thursday, November 24, 2016

Hard limits on Freestyle Libre third party applications (part 1–does temperature matter?)

Before I begin addressing one of the issues that intrinsically impacts all the third party Freestyle Libre applications I am aware of, let me restate a few things
  • Back in early 2015, I had a decent (for my purpose) solution that I felt would not extend well to others. That is mostly why I did not release it.
  • I supported and still support the BlueReader project. I am not against open source solutions. When the BlueReaders I ordered arrive, I will certainly enjoy experimenting with them. I will, however, not rely on them for my son, even if the Libre becomes our main monitoring solution.
  • I stopped my Libre investigations in early 2015, both because obtaining sensors from France was a drag and because what I learned in the process of investigating the Libre had helped me improve the results I got from the G4. I am just resuming them in preparation of the BlueReader.
  • piggy backing on the Libre sensor doesn’t sound like an attractive solution as far as I am concerned as it is only likely to increase micro traumas issues.
  • There is a fundamental difference between what the community calls “raw” from the Dexcom and the “raw” one gets from the Libre. In a nutshell, the Dexcom “raw” is not raw. The Libre “raw” is raw.


Temperature again

One of the things that differentiates the Dexcom “raw” from the Libre raw glucose data is that the Libre data is not compensated for temperature: that is the job of the reader or the phone application, working on the thermistor data.
Does it matter? Let’s start with a small explanation intended for software engineers who don’t have a biochemistry or medical background. Sensors rely on an enzymatic reaction. The enzyme used is often glucose oxydase (but it is not the only enzyme that would work, some BGMeters use hexokinase for example). The Senseonics sensors will use a different method based on fluorescence.
The activity of enzymes vary greatly depending on the local concentrations (Michaelis Menten kinetics), the pH and on the temperature. The local concentration issues are usually solved by the sensor membranes: they control the concentration of glucose and oxygen to match, as best as they can, the linear response zone of the Michelis Menten equation with their sensors. This video on amperometric glucose sensing is a must watch if you are interested
The pH is usually stable, except in DKA, of course, but at that point you have other things to worry about than your sensor’s accuracy (the acidosis can be so bad that standard laboratory tests may become unreliable). That is one aspect third parties don’t have to worry about much.

Temperature: does it matter in theory?

But what about the temperature? Intuitively, one may think “well, I guess it doesn’t matter much…”. Or, if you care, try to address it in a generic way. One of my first ideas, back in 2014-2015, was to get a some “glucose oxydase activity curve as a function of the temperature” and compensate for that.

Unfortunately, both the “I don’t care” and the “I will fix” ideas are wrong, as it can be seen in the image below (from this paper)
The relative activity of glucose oxydase varies immensely according to the pH but, as I said, we don’t care much as the pH variations shown here are well outside the physiological range. The temperature: we must care a lot about because the variation of activity can be is drastic within a few degrees and, finally, the way it is attached (difference between blue and red line). While it may be possible to find the exact temperature dependent response curve of Abbott’s wired enzyme, I suspect it is not publicly available and it would have to be experimentally derived. That realization killed my somewhat naïve hope to auto-magically correct the reported activity as a function of temperature. As far as some open source projects are concerned, not worrying about temperature compensation is a classical example of not knowing enough to even know there could be a problem. It is only when you know a bit more that you realize, to a better extent, that you don’t know.

Does it matter in practice?

Armed with a bit of side knowledge in the dirty details (more about this later) and a basic understanding of what Abbott is doing, I ran more specific experiments to investigate the response to temperature changes, the correction and the delay applied to the correction. Here is such an example.
I spent some time in a warm bath, enough to significantly increase my subcutaneous temperature and the enzyme activity.  My real BG  before the bath (as measured twice with the Abbott BG Meter) was 106 mg/dL, my BG after the bath was 109 mg/dL (shown as the star below). In normal, uncorrected, conditions, my calculated ISIG with the parameters I derived for my son would have been around 130 (not ideal, but there are temperature differences between my son’s skin and mine, we’ll get to that later). In relative terms, the response to my stable ISIG almost doubled. One word of caution, listing all the situations, bath temperature, submersion or non submersion of the sensor, etc… would be way too long for this blog and way to tedious for me to detail: let’s just say you don’t want a video of my person wiggling in a bath.
t2, shown here, is the temperature reported by the board thermistor. The Libre “official” check (round dot) sat between the direct conversion value and the SMBG value. Clearly, there is some compensation going on.
Let’s talk a minute about the thermistors, which I have arbitrarily named t1 and t2, the “close to skin” and “TI board thermistor” respectively and summarize what I know about them.

t1 – I have measured several thermistors and derived values from my observations, from which I have build a response curve. Limitations are: 1. I am not sure Abbott always uses the same thermistor as I have measured different values. This could be a variation in the thermistors delivered to Abbott, a simple factory/software configuration option, a defect. 2. I don’t have a direct way to measure the exact temperature the thermistor is reporting when applied as I can’t sneak another thermistor under the patch. 3. I can obtain reasonable values by tweaking and twisting the data, but I am not sure Abbott tweaks it the way I do.

t2 – I am quite sure of that one. With a bit of bit tweaking, it does match neatly with board/package temperatures that I can more easily measure. Limitations here are: 1. I do not know if Abbott interprets it the way I do. They could be seeing 32.5C when I see 33C or the opposite. This matters for additional adjustments for further processing, especially any eventual derivative based prediction methods. 2. temperature variations may be smoothed (per my observations and the Abbott patent).
Their respective responses are roughly (disregard the y-axis units, there is some post processing involved, the internal thermistor response isn’t linear, even if the line may make it appear so)
In the graph below, you can see what happens when I cool the sensor with ice for a few minutes after the bath. The board temperature falls precipitously, while the speed of the actual reaction continues to decrease (I did not cool my skin/core in this case, but the warming of the bath is beginning to wear out). My BG (the star) is still stable, the official Libre value is now 147 but the Libre is clearly (flags, not shown here) losing his marbles and will go error 373 for a while after that torture.

In order to make the difference in behavior visible, here are a couple of snapshots in stable conditions
I did run a few tests in stable conditions in order to address any doubts I had on my thermistor interpretation. Here is another external sensor warming
and another, less aggressive, external sensor cooling
As I said above, there is a delta between my direct interpretation and the direct interpretation of the parameters I derived used quite successfully for my son two years ago – here is an example from back then that matched exactly, in stable temperature conditions.
and another example
and a new view of the meal and bath incident (changing temperature conditions on that same sensor I correlated perfectly with)
and the original report (early to 2015) of that meal and bath incident and my interpretation of it.

At this stage, key points to remember
  • temperature variations can’t be ignored and may have a major impact on the signal.
  • my son’s parameters are different from mine and can be explained in great part by different skin temperatures (but I haven’t validated that by actually measuring it under the sensor).
  • temperature compensation effects may/often are delayed until some threshold has passed and the Libre considers it as real.
This is all for now. At this stage, before we get into the hows and whys, I simply hope to have convinced you of the effect of temperature on the Libre raw signal: minor changes such as wearing a jacket or a t-shirt are less visible but are present. I could have posted several more illustrations and will possibly do so if some points need clarification.
In the next posts (when time allows), we’ll talk a bit more on how Abbott tackles the problem of finding the temperature of the sensing site and how it differs from Dexcom’s temperature compensation. The exploitation of the double thermistor design is really fun and interesting (well, at least to me) even if I feel that it does not work too well in some cases. It seems there is a simpler (but much more expensive for Abbott?) way to address that issue. We’ll get there… (some time…)
That’s all for today.

Friday, November 18, 2016

FreeStyle Libre Patterns - non T1D (part1)

Just a quick dump of some FreeStyle Libre Sensor running on a non T1D, non T2D person. I will post interesting situations as soon as they arise. Please note that, even in normal individuals, the glycemic response to different aliments may vary. See this ground-breaking article in Cell for an in depth look at the issue:

Personalized Nutrition by Prediction of Glycemic Responses

That being said, testing foods and meals on a non T1D person offers useful insights for my son's management as the 'net' effect they have is more or less isolated from perturbations. Something that is harder to control for a non T1D person is likely to have amplified effects in T1Ds

Libre startup sequence and some fun

Post insertion, we start tickling the temperature compensation a bit (that part was done in the context of another, more tech oriented, experiment). The sensor is cooled with ice around midnight and warmed against a radiator around 2 AM. It is then left alone so it can proceed with its usual slightly noisy lowish start.

Stable sensor, some food and a bath

Sensor has stabilized: 2 eggs at 13:30, no effect, as expected. Warm bath - not hot - at 14:30. Now this gets interesting! Spot check at 146 mg/dL while there is absolutely no real reason to climb. History rewritten post-facto (through a well known... map simplification algorithm). This is again a clear example of the Libre's temperature compensation issues. As soon as the sensor cools, it resumes cruising around 100 mg/dL and some chicken breast at 16:00 have, as expected, zero effect.

Let's think about that for a minute: a warm bath (sensor fully immersed for 5-6 minutes) leads, in this case, to an almost 50% error on spot checks in a stable non T1D person. It does, however, corrects itself quickly.

Meals and exercise

Pre-made Greek salad consisting of lettuce, feta, olives, oil and some breadcrumbs at 18h20. Slow rise (as expected, way more fat than carbs) and the return to baseline has already begun at 20:45 when subject climbs on ergometric bike for a relatively intense 30 minutes spin. Hitting near hypoglycemia level at 21h20. A bit surprising as as test subject expects better counter regulation (but maybe not enough time for it to kick in). Double checked by strip tests - seems correct.  Time for two slices of gluten free bread with cheese. Response as expected.

The banana

A medium sized banana at 12:30. That single fruit does indeed have a major impact! While - according to its own strip tester - the Libre may have overestimated the impact, that single banana was much worse than the Greek salad three times the weight...

Greek salad again and an apple

Going from the Greek Salad again - based on specs (pre-made fresh food bowl), identical to the first one but let's add a medium sized apple as dessert. Impact remains lower than the banana effect.

Steak and chocolate

Steak at 18h45 has no impact as expected. Mild bump from around 30 grams of 90% cocoa chocolate.

More soon.