fpex0.baseline

  1import numpy as np
  2from scipy import interpolate
  3from scipy import integrate
  4from copy import copy
  5from fpex0.linreg import LinearRegression
  6
  7
  8class BaselineData:
  9    """
 10    Stores information about the baselevel.
 11    
 12    ## Parameters
 13    **reg_L**
 14    <br> Left linear part.
 15    
 16    **reg_R**
 17    <br> Right linear part.
 18    
 19    **onset** 
 20    <br> Onset of phase transition.
 21    
 22    **endset**
 23    <br> Endset of the phase transition.
 24    """
 25    def __init__(self, reg_L=None, reg_R=None, onset=None, endset=None):
 26        self.reg_L  = reg_L
 27        self.reg_R  = reg_R
 28        self.endset = endset
 29        self.onset  = onset
 30
 31class BaselineDetectionSettings:
 32    """
 33    Stores (default) baseline detection settings to be passed to `fpex0.baseline.detectLinearRange()`.
 34    
 35    ## Parameters
 36    Naming: 
 37    - R,L = right or left
 38    - abs, rel = absolute or relative
 39    - dev = deviation
 40    - initfraction = initial fraction of data to interpolate.
 41
 42    **LabsdevA**, **LabsdevB**, **LabsdevS2**
 43    
 44    **LreldevA**, **LreldevB**, **LreldevS2**
 45    
 46    **Linitfraction**
 47
 48    **RabsdevA**, **RabsdevB**, **RabsdevS2**
 49    
 50    **RreldevA**, **RreldevB**, **RreldevS2**
 51    
 52    **Rinitfraction**
 53    """
 54    def __init__(self):
 55        self.LabsdevA      = -1
 56        self.LabsdevB      = -1
 57        self.LabsdevS2     = -1
 58        self.LreldevA      = np.infty
 59        self.LreldevB      = 0.01
 60        self.LreldevS2     = 2.00
 61        self.Linitfraction = 0.1
 62        
 63        self.RabsdevA      = -1
 64        self.RabsdevB      = 0.05
 65        self.RabsdevS2     = -1
 66        self.RreldevA      = np.infty
 67        self.RreldevB      = np.infty
 68        self.RreldevS2     = 2.00
 69        self.Rinitfraction = 0.2
 70
 71
 72def detectLinearRange(X, Y, side, initlen, reldevAmax, reldevBmax, reldevS2max, absdevAmax, absdevBmax, absdevS2max):
 73    """
 74    Detect the stop position of the linear range.
 75
 76    ## Takes
 77    
 78    **X**: x-values
 79        
 80    **Y**: y-values
 81           
 82    **side**: 'L' or 'R' for "from left" or "from right"
 83        
 84    **initlen**
 85    <br> Length (in samples) to calculate the initial standard deviation.
 86         
 87    **reldevA** 
 88    <br> Acceptable relative deviation of initial y-axis intercept.
 89     
 90    **reldevB**
 91    <br> Acceptable relative deviation of initial slope.
 92     
 93    **reldevS2**
 94    <br> Acceptable relative deviation of initial squared standard deviation.
 95     
 96    **absdevA**
 97    <br> Acceptable absolute deviation of initial y-axis intercept.
 98     
 99    **absdevB**
100    <br> Acceptable absolute deviation of initial.
101     
102    **absdevS2**
103    <br> Acceptable absolute deviation of initial squared standard deviation.
104
105
106    ## Returns
107    **stopidx** 
108    <br> Position in X, where the linear range is estimated to be left.
109     
110    **reg**
111    <br> Final regression structure as returned by fpex0.linreg.LinearRegression.linReg.
112     
113    **initreg**
114    <br> Initial regression structure as returned by fpex0.linreg.LinearRegression.linReg.
115 
116    ### Comments
117     * Set the `reldev` to inf to disable
118     
119     * Set the `absdev` to  -1 to disable
120     
121     * The `reldev` is only be checked if value exceeds `absdev`
122    
123    """
124    
125    
126    # choose loop-range
127    if side.lower() == "left":
128        start = initlen
129        stop  = len(X)
130        step  = 1
131        # initial regression
132        initreg = LinearRegression()
133        initreg.linReg(X[:start], Y[:start])
134    elif side.lower() == "right":
135        start = len(X)-1 - initlen  # start at position left from initial range
136        stop  = -1                        # exclusive index 
137        step  = -1
138        # initial regression
139        initreg = LinearRegression()
140        initreg.linReg(X[start+1:], Y[start+1:])
141
142
143    reg = copy(initreg)
144    index = None
145
146    for index in range(start, stop, step):
147        # update regression
148        reg.linReg(X[index], Y[index])  
149
150        absdevA  = abs(reg.a - initreg.a    )   
151        reldevA  = abs(absdevA  / initreg.a )
152        absdevB  = abs(reg.b - initreg.b    )   
153        reldevB  = abs(absdevB  / initreg.b )
154        absdevS2 = abs(reg.s2 - initreg.s2  )  
155        reldevS2 = abs(absdevS2 / initreg.s2)
156
157        # check deviation
158        if (absdevA  > absdevAmax ) and (reldevA  > reldevAmax ):
159            break
160        if (absdevB  > absdevBmax ) and (reldevB  > reldevBmax ):
161            break
162        if (absdevS2 > absdevS2max) and (reldevS2 > reldevS2max):
163            break
164    stopidx = index
165
166    return stopidx, reg, initreg
167
168
169def getBaselinePrimitive(X, Y, index_l, icept_l, slope_l, index_r, icept_r, slope_r, bl_type, res=1000):
170    """
171    Retrieves the baselevel function for specified data.
172    
173    ## Takes
174      
175    **X**: x-values (temperatures, time, ...)
176     
177    **Y**: y-values (voltage, heat flux, ....)
178     
179    **index_l**
180    <br> Index where left linear part is left ("onset")
181     
182    **icept_l**
183    <br> y-intercept of left linear part.
184     
185    **slope_l**
186    <br> Slope of left linear part.
187     
188    **index_r**
189    <br> Index where right linear part is left ("onset").
190     
191    **icept_r**
192    <br> y-intercept of right linear part.
193     
194    **slope_r** 
195    <br> Slope of right linear part.
196     
197    **bl_type** 
198    <br> What type should the baselevel function have? "linear" or "sigmoidal".
199     
200    **res**
201    <br> Number of support points for sigmoidal (default: 100).
202    
203
204    ## Returns
205      
206    **blfun**
207    <br> Function handle of baselevel function
208     
209    """
210
211    # calculate values at "onset" and "offset"
212    yval_l = icept_l + X[index_l] * slope_l
213    yval_r = icept_r + X[index_r] * slope_r
214    yval_delta = yval_r - yval_l
215    
216    # calculate linear base level function
217    lin_bl_xvals = [        X[0]        , X[index_l], X[index_r],          X[-1]         ]
218    lin_bl_yvals = [icept_l+X[0]*slope_l,   yval_l  ,   yval_r  ,  icept_r+X[-1]*slope_r ]
219    lin_bl_fun  = interpolate.interp1d(lin_bl_xvals, lin_bl_yvals, 'linear')  # piecewise linear interpolation
220    
221    
222    if bl_type.lower() == 'linear':
223        return lin_bl_fun
224    
225    elif bl_type.lower() in ['sigmoid', 'sigmoidal']:
226        
227        # sigmoidal interpolation as in Fig. 3 of DIN 51007
228
229        # substract baseline and integrate peak part "in between"
230        Xmid = X[index_l:index_r+1]
231        Ymid = Y[index_l:index_r+1] - lin_bl_fun(Xmid)
232        Ymid = np.array([y if y >= 0 else 0 for y in Ymid])  # set negatives to zero
233        cumarea = integrate.cumtrapz(Ymid, Xmid)  # cumulative integral (area)
234        sigmoidal = np.concatenate(([0], cumarea)) / max(cumarea) * yval_delta + yval_l  # add a zero for consistency with matlab code
235
236        # interpolate integral at support points (res = #intervals)
237        sig_nodes = np.linspace(Xmid[0], Xmid[-1], res+1) # Here should be an error in the corresponding matlab code. It produces 1001 nodes for res=1000
238        sig_interp = interpolate.interp1d(Xmid, sigmoidal, 'linear')  # this is now a function object
239        sig_nodevals = sig_interp(sig_nodes)
240
241        # generate baseline function (piecewise cubic hermite interpolant)
242        sig_x = np.concatenate( ( [X[0]], [X[index_l-1]], sig_nodes, [X[index_r+1]],  [X[-1]]) )
243        sig_y = np.concatenate( ( [lin_bl_fun( X[0] )], [lin_bl_fun( X[index_l-1] )], sig_nodevals, [lin_bl_fun( X[index_r+1] )],  [lin_bl_fun( X[-1] )] ) )
244        blfun = interpolate.PchipInterpolator(sig_x, sig_y)  # this is now a function object
245        
246        return blfun
247
248
249
250def getBaseline(X, Y, bl_type='linear', blds=BaselineDetectionSettings()):
251    """
252    Retrieves the baselevel function for specified DSC data.
253    
254    ## Takes
255    **X**: 
256    <br> Numpy vector of x-values (e.g. temperatures) or list (!) of vectors.
257     
258    **Y**: 
259    <br> Numpy vector of y-values (e.g. cp-values) or list (!) of vectors.
260     
261    **bl_type**: 'linear' or 'sigmoidal'
262    (default: 'linear')
263     
264    **blds** 
265    <br> BaselineDetectionSettings-object containing the BaseLine Detection Setting
266    (default: Default BLDS object).
267    
268
269    ## Returns
270    **blfun**
271    <br> Function handle of baselevel function.
272     
273    **bldata**
274    <br> Structure containing information about the baseline.
275
276    OR if list have been passed:
277
278    **baseline_list**:
279    <br> List of tuples (blfun, bldata).
280    """
281    
282    
283    if type(X) is list:
284        baseline_list = [getBaseline(X[i], Y[i], bl_type, blds) for i in range(len(X))]
285        return baseline_list
286    
287    print("Detecting linear ranges")
288    peakPos = np.argmax(Y)
289    initlen_L = int( np.floor(blds.Linitfraction * peakPos) )
290    initlen_R = int( np.floor(blds.Rinitfraction * (len(X)-peakPos)) )
291    idx_L, reg_L, _ = detectLinearRange(X,Y,'left' ,initlen_L,blds.LreldevA, blds.LreldevB, blds.LreldevS2, blds.LabsdevA, blds.LabsdevB, blds.LabsdevS2)
292    idx_R, reg_R, _ = detectLinearRange(X,Y,'right',initlen_R,blds.RreldevA, blds.RreldevB, blds.RreldevS2, blds.RabsdevA, blds.RabsdevB, blds.RabsdevS2)
293    print('Done.')
294
295    # get baselevel function
296    blfun = getBaselinePrimitive(X, Y, idx_L, reg_L.a, reg_L.b, idx_R, reg_R.a, reg_R.b, bl_type)
297    
298    # small plausibility check:  slope of linear parts should be small;
299    maxslope = 0.1
300    if (abs(reg_L.b) > maxslope) or (abs(reg_R.b) > maxslope):
301       print("\n")
302       print(f"Slope of linear part is large: left: {reg_L.b}, right: {reg_R.b}. There's probably something wrong. Using proposed baseline instead!")
303       if abs(reg_L.b) > maxslope:
304           reg_L.b = 0
305           reg_L.a = Y[0]
306           idx_L = 1
307       if (abs(reg_R.b) > maxslope): 
308           reg_R.b = 0
309           reg_R.a = Y[-1]
310           idx_R = len[Y]-2
311       blfun = getBaselinePrimitive(X, Y, idx_L, reg_L.a, reg_L.b, idx_R, reg_R.a, reg_R.b, bl_type)
312
313    
314    # build data
315    
316    # classic onset/endset estimation: where the linear parts are left (poor estimation)
317    # bldata.onset  = X[idx_L]
318    # bldata.endset = X[idx_R]
319    
320    # better onset/endset estimation: point X where the data (X,Y) first falls below baseline (seen from peak maximum)
321    # with fallback using the ind_L or idx_R
322    bloffset = 0.02
323    temp = Y[0:peakPos+1] - blfun(X[0:peakPos+1])
324    counter = None
325    # get index of last value below bloffset
326    for k in range(len(temp)):
327        if temp[k] < bloffset:
328            counter = k
329    idxOnset = counter
330    
331    temp = Y[peakPos:] - blfun(X[peakPos:])
332    counter = None
333    # get index of first value below bloffset
334    for k in range(len(temp)):
335        if temp[k] < bloffset:
336            counter = k
337            break
338    idxEndset = counter + peakPos - 1
339    
340    if idxOnset is None:
341        idxOnset  = idx_L
342    if idxEndset is None:
343        idxEndset = idx_R
344    
345    # save data
346    bldata = BaselineData()  # save all together at the end?
347    bldata.reg_L  = reg_L  # regression information left
348    bldata.reg_R  = reg_R  # regression information right    
349    bldata.onset  = X[idxOnset]
350    bldata.endset = X[idxEndset]
351
352    return blfun, bldata
353
354
355def subtractBaseline(X, Yin, blfun, onset=None, endset=None, clearzero=True, nonnegative=True):
356    """
357    Subtracts the baseline from given points.
358    
359    ## Takes   
360    **X**: x values (e.g. vector temperatures)
361    
362    **Y**: y values or function (e.g. vector of cp values, or function cp(T))
363    
364    **blfun**
365    <br> Function handle to baseline function.
366    
367    **clearzero**
368    <br> Flag indicating to clear zeros (see DSC204_clearZeroFromMax) (default: True).
369    
370    **nonnegative**
371    <br> Flag indicating to ensure nonnegativity                      (default: True).
372    
373    **onset**
374    <br> Onset value (zero values are put below/left of this x value) [optional].
375    
376    **endset**
377    <br> Endset value (zero values are put above/right of this x value) [optional].
378    
379
380    ## Returns   
381    **Yvals** 
382    <br> Processed y values.
383
384    **Yfun**
385    <br> Interpolator of processed values.
386    """
387
388    if onset is None:
389        onset = min(X)
390    if endset is None:
391        endset = max(X)
392    
393    assert( len(X) == len(Yin) )
394    Yvals = Yin
395
396    # subtract baseline from Y data
397    Yvals = Yvals - blfun(X)
398
399    # make zeros outside the interval [onset, endset]
400    idx = [i for i in range(len(X)) if (X[i] < onset or X[i] > endset)]
401    Yvals[idx] = 0
402
403    # ensure nonnegativity
404    if nonnegative:
405        idx = [i for i in range(len(Yvals)) if Yvals[i] < 0]
406        Yvals[idx] = 0
407
408    # clear zero
409    if clearzero:
410       Yvals = clearZeroFromMax(Yvals)
411
412    
413    # for the interpolation function, add some zero space left and right
414    addlen = 5
415    XX = np.concatenate( ( X[0] + np.arange(-addlen,0), X       , X[-1] + np.arange(1,addlen+1) ) )
416    YY = np.concatenate( ( np.zeros(addlen)             ,  Yvals  , np.zeros(addlen)            ) )
417
418    # build function from values
419    Yfun = interpolate.PchipInterpolator(XX,YY)
420
421    return Yvals, Yfun
422
423
424def clearZeroFromMax(data):
425    """ 
426    Locates the maximum value in datavec, searches the first occurances
427    of a zero value to the left and to the right from the peak position
428    and deletes the noise before and after these positions.
429    
430    ## Takes 
431    **data**: Data array to be cleared
432    
433    ## Returns
434    **data**: Cleared data array
435    """
436    # Find index/position of maximal value
437    maxidx = np.argmax(data)
438    
439    # Find the positions of the first zeros, seen from the peak value
440    clearToIdx   = max( np.where(data[:maxidx+1]==0)[0] )
441    clearFromIdx = min( np.where(data[maxidx:]  ==0)[0] ) + maxidx
442    
443    # delete noise
444    if not (clearToIdx is None):
445        data[:clearToIdx] = 0
446    if not (clearFromIdx is None):
447        data[clearFromIdx:] = 0
448
449    return data
class BaselineData:
 9class BaselineData:
10    """
11    Stores information about the baselevel.
12    
13    ## Parameters
14    **reg_L**
15    <br> Left linear part.
16    
17    **reg_R**
18    <br> Right linear part.
19    
20    **onset** 
21    <br> Onset of phase transition.
22    
23    **endset**
24    <br> Endset of the phase transition.
25    """
26    def __init__(self, reg_L=None, reg_R=None, onset=None, endset=None):
27        self.reg_L  = reg_L
28        self.reg_R  = reg_R
29        self.endset = endset
30        self.onset  = onset

Stores information about the baselevel.

Parameters

reg_L
Left linear part.

reg_R
Right linear part.

onset
Onset of phase transition.

endset
Endset of the phase transition.

BaselineData(reg_L=None, reg_R=None, onset=None, endset=None)
26    def __init__(self, reg_L=None, reg_R=None, onset=None, endset=None):
27        self.reg_L  = reg_L
28        self.reg_R  = reg_R
29        self.endset = endset
30        self.onset  = onset
class BaselineDetectionSettings:
32class BaselineDetectionSettings:
33    """
34    Stores (default) baseline detection settings to be passed to `fpex0.baseline.detectLinearRange()`.
35    
36    ## Parameters
37    Naming: 
38    - R,L = right or left
39    - abs, rel = absolute or relative
40    - dev = deviation
41    - initfraction = initial fraction of data to interpolate.
42
43    **LabsdevA**, **LabsdevB**, **LabsdevS2**
44    
45    **LreldevA**, **LreldevB**, **LreldevS2**
46    
47    **Linitfraction**
48
49    **RabsdevA**, **RabsdevB**, **RabsdevS2**
50    
51    **RreldevA**, **RreldevB**, **RreldevS2**
52    
53    **Rinitfraction**
54    """
55    def __init__(self):
56        self.LabsdevA      = -1
57        self.LabsdevB      = -1
58        self.LabsdevS2     = -1
59        self.LreldevA      = np.infty
60        self.LreldevB      = 0.01
61        self.LreldevS2     = 2.00
62        self.Linitfraction = 0.1
63        
64        self.RabsdevA      = -1
65        self.RabsdevB      = 0.05
66        self.RabsdevS2     = -1
67        self.RreldevA      = np.infty
68        self.RreldevB      = np.infty
69        self.RreldevS2     = 2.00
70        self.Rinitfraction = 0.2

Stores (default) baseline detection settings to be passed to fpex0.baseline.detectLinearRange().

Parameters

Naming:

  • R,L = right or left
  • abs, rel = absolute or relative
  • dev = deviation
  • initfraction = initial fraction of data to interpolate.

LabsdevA, LabsdevB, LabsdevS2

LreldevA, LreldevB, LreldevS2

Linitfraction

RabsdevA, RabsdevB, RabsdevS2

RreldevA, RreldevB, RreldevS2

Rinitfraction

BaselineDetectionSettings()
55    def __init__(self):
56        self.LabsdevA      = -1
57        self.LabsdevB      = -1
58        self.LabsdevS2     = -1
59        self.LreldevA      = np.infty
60        self.LreldevB      = 0.01
61        self.LreldevS2     = 2.00
62        self.Linitfraction = 0.1
63        
64        self.RabsdevA      = -1
65        self.RabsdevB      = 0.05
66        self.RabsdevS2     = -1
67        self.RreldevA      = np.infty
68        self.RreldevB      = np.infty
69        self.RreldevS2     = 2.00
70        self.Rinitfraction = 0.2
def detectLinearRange( X, Y, side, initlen, reldevAmax, reldevBmax, reldevS2max, absdevAmax, absdevBmax, absdevS2max):
 73def detectLinearRange(X, Y, side, initlen, reldevAmax, reldevBmax, reldevS2max, absdevAmax, absdevBmax, absdevS2max):
 74    """
 75    Detect the stop position of the linear range.
 76
 77    ## Takes
 78    
 79    **X**: x-values
 80        
 81    **Y**: y-values
 82           
 83    **side**: 'L' or 'R' for "from left" or "from right"
 84        
 85    **initlen**
 86    <br> Length (in samples) to calculate the initial standard deviation.
 87         
 88    **reldevA** 
 89    <br> Acceptable relative deviation of initial y-axis intercept.
 90     
 91    **reldevB**
 92    <br> Acceptable relative deviation of initial slope.
 93     
 94    **reldevS2**
 95    <br> Acceptable relative deviation of initial squared standard deviation.
 96     
 97    **absdevA**
 98    <br> Acceptable absolute deviation of initial y-axis intercept.
 99     
100    **absdevB**
101    <br> Acceptable absolute deviation of initial.
102     
103    **absdevS2**
104    <br> Acceptable absolute deviation of initial squared standard deviation.
105
106
107    ## Returns
108    **stopidx** 
109    <br> Position in X, where the linear range is estimated to be left.
110     
111    **reg**
112    <br> Final regression structure as returned by fpex0.linreg.LinearRegression.linReg.
113     
114    **initreg**
115    <br> Initial regression structure as returned by fpex0.linreg.LinearRegression.linReg.
116 
117    ### Comments
118     * Set the `reldev` to inf to disable
119     
120     * Set the `absdev` to  -1 to disable
121     
122     * The `reldev` is only be checked if value exceeds `absdev`
123    
124    """
125    
126    
127    # choose loop-range
128    if side.lower() == "left":
129        start = initlen
130        stop  = len(X)
131        step  = 1
132        # initial regression
133        initreg = LinearRegression()
134        initreg.linReg(X[:start], Y[:start])
135    elif side.lower() == "right":
136        start = len(X)-1 - initlen  # start at position left from initial range
137        stop  = -1                        # exclusive index 
138        step  = -1
139        # initial regression
140        initreg = LinearRegression()
141        initreg.linReg(X[start+1:], Y[start+1:])
142
143
144    reg = copy(initreg)
145    index = None
146
147    for index in range(start, stop, step):
148        # update regression
149        reg.linReg(X[index], Y[index])  
150
151        absdevA  = abs(reg.a - initreg.a    )   
152        reldevA  = abs(absdevA  / initreg.a )
153        absdevB  = abs(reg.b - initreg.b    )   
154        reldevB  = abs(absdevB  / initreg.b )
155        absdevS2 = abs(reg.s2 - initreg.s2  )  
156        reldevS2 = abs(absdevS2 / initreg.s2)
157
158        # check deviation
159        if (absdevA  > absdevAmax ) and (reldevA  > reldevAmax ):
160            break
161        if (absdevB  > absdevBmax ) and (reldevB  > reldevBmax ):
162            break
163        if (absdevS2 > absdevS2max) and (reldevS2 > reldevS2max):
164            break
165    stopidx = index
166
167    return stopidx, reg, initreg

Detect the stop position of the linear range.

Takes

X: x-values

Y: y-values

side: 'L' or 'R' for "from left" or "from right"

initlen
Length (in samples) to calculate the initial standard deviation.

reldevA
Acceptable relative deviation of initial y-axis intercept.

reldevB
Acceptable relative deviation of initial slope.

reldevS2
Acceptable relative deviation of initial squared standard deviation.

absdevA
Acceptable absolute deviation of initial y-axis intercept.

absdevB
Acceptable absolute deviation of initial.

absdevS2
Acceptable absolute deviation of initial squared standard deviation.

Returns

stopidx
Position in X, where the linear range is estimated to be left.

reg
Final regression structure as returned by fpex0.linreg.LinearRegression.linReg.

initreg
Initial regression structure as returned by fpex0.linreg.LinearRegression.linReg.

Comments

  • Set the reldev to inf to disable

  • Set the absdev to -1 to disable

  • The reldev is only be checked if value exceeds absdev

def getBaselinePrimitive( X, Y, index_l, icept_l, slope_l, index_r, icept_r, slope_r, bl_type, res=1000):
170def getBaselinePrimitive(X, Y, index_l, icept_l, slope_l, index_r, icept_r, slope_r, bl_type, res=1000):
171    """
172    Retrieves the baselevel function for specified data.
173    
174    ## Takes
175      
176    **X**: x-values (temperatures, time, ...)
177     
178    **Y**: y-values (voltage, heat flux, ....)
179     
180    **index_l**
181    <br> Index where left linear part is left ("onset")
182     
183    **icept_l**
184    <br> y-intercept of left linear part.
185     
186    **slope_l**
187    <br> Slope of left linear part.
188     
189    **index_r**
190    <br> Index where right linear part is left ("onset").
191     
192    **icept_r**
193    <br> y-intercept of right linear part.
194     
195    **slope_r** 
196    <br> Slope of right linear part.
197     
198    **bl_type** 
199    <br> What type should the baselevel function have? "linear" or "sigmoidal".
200     
201    **res**
202    <br> Number of support points for sigmoidal (default: 100).
203    
204
205    ## Returns
206      
207    **blfun**
208    <br> Function handle of baselevel function
209     
210    """
211
212    # calculate values at "onset" and "offset"
213    yval_l = icept_l + X[index_l] * slope_l
214    yval_r = icept_r + X[index_r] * slope_r
215    yval_delta = yval_r - yval_l
216    
217    # calculate linear base level function
218    lin_bl_xvals = [        X[0]        , X[index_l], X[index_r],          X[-1]         ]
219    lin_bl_yvals = [icept_l+X[0]*slope_l,   yval_l  ,   yval_r  ,  icept_r+X[-1]*slope_r ]
220    lin_bl_fun  = interpolate.interp1d(lin_bl_xvals, lin_bl_yvals, 'linear')  # piecewise linear interpolation
221    
222    
223    if bl_type.lower() == 'linear':
224        return lin_bl_fun
225    
226    elif bl_type.lower() in ['sigmoid', 'sigmoidal']:
227        
228        # sigmoidal interpolation as in Fig. 3 of DIN 51007
229
230        # substract baseline and integrate peak part "in between"
231        Xmid = X[index_l:index_r+1]
232        Ymid = Y[index_l:index_r+1] - lin_bl_fun(Xmid)
233        Ymid = np.array([y if y >= 0 else 0 for y in Ymid])  # set negatives to zero
234        cumarea = integrate.cumtrapz(Ymid, Xmid)  # cumulative integral (area)
235        sigmoidal = np.concatenate(([0], cumarea)) / max(cumarea) * yval_delta + yval_l  # add a zero for consistency with matlab code
236
237        # interpolate integral at support points (res = #intervals)
238        sig_nodes = np.linspace(Xmid[0], Xmid[-1], res+1) # Here should be an error in the corresponding matlab code. It produces 1001 nodes for res=1000
239        sig_interp = interpolate.interp1d(Xmid, sigmoidal, 'linear')  # this is now a function object
240        sig_nodevals = sig_interp(sig_nodes)
241
242        # generate baseline function (piecewise cubic hermite interpolant)
243        sig_x = np.concatenate( ( [X[0]], [X[index_l-1]], sig_nodes, [X[index_r+1]],  [X[-1]]) )
244        sig_y = np.concatenate( ( [lin_bl_fun( X[0] )], [lin_bl_fun( X[index_l-1] )], sig_nodevals, [lin_bl_fun( X[index_r+1] )],  [lin_bl_fun( X[-1] )] ) )
245        blfun = interpolate.PchipInterpolator(sig_x, sig_y)  # this is now a function object
246        
247        return blfun

Retrieves the baselevel function for specified data.

Takes

X: x-values (temperatures, time, ...)

Y: y-values (voltage, heat flux, ....)

index_l
Index where left linear part is left ("onset")

icept_l
y-intercept of left linear part.

slope_l
Slope of left linear part.

index_r
Index where right linear part is left ("onset").

icept_r
y-intercept of right linear part.

slope_r
Slope of right linear part.

bl_type
What type should the baselevel function have? "linear" or "sigmoidal".

res
Number of support points for sigmoidal (default: 100).

Returns

blfun
Function handle of baselevel function

def getBaseline( X, Y, bl_type='linear', blds=<fpex0.baseline.BaselineDetectionSettings object>):
251def getBaseline(X, Y, bl_type='linear', blds=BaselineDetectionSettings()):
252    """
253    Retrieves the baselevel function for specified DSC data.
254    
255    ## Takes
256    **X**: 
257    <br> Numpy vector of x-values (e.g. temperatures) or list (!) of vectors.
258     
259    **Y**: 
260    <br> Numpy vector of y-values (e.g. cp-values) or list (!) of vectors.
261     
262    **bl_type**: 'linear' or 'sigmoidal'
263    (default: 'linear')
264     
265    **blds** 
266    <br> BaselineDetectionSettings-object containing the BaseLine Detection Setting
267    (default: Default BLDS object).
268    
269
270    ## Returns
271    **blfun**
272    <br> Function handle of baselevel function.
273     
274    **bldata**
275    <br> Structure containing information about the baseline.
276
277    OR if list have been passed:
278
279    **baseline_list**:
280    <br> List of tuples (blfun, bldata).
281    """
282    
283    
284    if type(X) is list:
285        baseline_list = [getBaseline(X[i], Y[i], bl_type, blds) for i in range(len(X))]
286        return baseline_list
287    
288    print("Detecting linear ranges")
289    peakPos = np.argmax(Y)
290    initlen_L = int( np.floor(blds.Linitfraction * peakPos) )
291    initlen_R = int( np.floor(blds.Rinitfraction * (len(X)-peakPos)) )
292    idx_L, reg_L, _ = detectLinearRange(X,Y,'left' ,initlen_L,blds.LreldevA, blds.LreldevB, blds.LreldevS2, blds.LabsdevA, blds.LabsdevB, blds.LabsdevS2)
293    idx_R, reg_R, _ = detectLinearRange(X,Y,'right',initlen_R,blds.RreldevA, blds.RreldevB, blds.RreldevS2, blds.RabsdevA, blds.RabsdevB, blds.RabsdevS2)
294    print('Done.')
295
296    # get baselevel function
297    blfun = getBaselinePrimitive(X, Y, idx_L, reg_L.a, reg_L.b, idx_R, reg_R.a, reg_R.b, bl_type)
298    
299    # small plausibility check:  slope of linear parts should be small;
300    maxslope = 0.1
301    if (abs(reg_L.b) > maxslope) or (abs(reg_R.b) > maxslope):
302       print("\n")
303       print(f"Slope of linear part is large: left: {reg_L.b}, right: {reg_R.b}. There's probably something wrong. Using proposed baseline instead!")
304       if abs(reg_L.b) > maxslope:
305           reg_L.b = 0
306           reg_L.a = Y[0]
307           idx_L = 1
308       if (abs(reg_R.b) > maxslope): 
309           reg_R.b = 0
310           reg_R.a = Y[-1]
311           idx_R = len[Y]-2
312       blfun = getBaselinePrimitive(X, Y, idx_L, reg_L.a, reg_L.b, idx_R, reg_R.a, reg_R.b, bl_type)
313
314    
315    # build data
316    
317    # classic onset/endset estimation: where the linear parts are left (poor estimation)
318    # bldata.onset  = X[idx_L]
319    # bldata.endset = X[idx_R]
320    
321    # better onset/endset estimation: point X where the data (X,Y) first falls below baseline (seen from peak maximum)
322    # with fallback using the ind_L or idx_R
323    bloffset = 0.02
324    temp = Y[0:peakPos+1] - blfun(X[0:peakPos+1])
325    counter = None
326    # get index of last value below bloffset
327    for k in range(len(temp)):
328        if temp[k] < bloffset:
329            counter = k
330    idxOnset = counter
331    
332    temp = Y[peakPos:] - blfun(X[peakPos:])
333    counter = None
334    # get index of first value below bloffset
335    for k in range(len(temp)):
336        if temp[k] < bloffset:
337            counter = k
338            break
339    idxEndset = counter + peakPos - 1
340    
341    if idxOnset is None:
342        idxOnset  = idx_L
343    if idxEndset is None:
344        idxEndset = idx_R
345    
346    # save data
347    bldata = BaselineData()  # save all together at the end?
348    bldata.reg_L  = reg_L  # regression information left
349    bldata.reg_R  = reg_R  # regression information right    
350    bldata.onset  = X[idxOnset]
351    bldata.endset = X[idxEndset]
352
353    return blfun, bldata

Retrieves the baselevel function for specified DSC data.

Takes

X:
Numpy vector of x-values (e.g. temperatures) or list (!) of vectors.

Y:
Numpy vector of y-values (e.g. cp-values) or list (!) of vectors.

bl_type: 'linear' or 'sigmoidal' (default: 'linear')

blds
BaselineDetectionSettings-object containing the BaseLine Detection Setting (default: Default BLDS object).

Returns

blfun
Function handle of baselevel function.

bldata
Structure containing information about the baseline.

OR if list have been passed:

baseline_list:
List of tuples (blfun, bldata).

def subtractBaseline( X, Yin, blfun, onset=None, endset=None, clearzero=True, nonnegative=True):
356def subtractBaseline(X, Yin, blfun, onset=None, endset=None, clearzero=True, nonnegative=True):
357    """
358    Subtracts the baseline from given points.
359    
360    ## Takes   
361    **X**: x values (e.g. vector temperatures)
362    
363    **Y**: y values or function (e.g. vector of cp values, or function cp(T))
364    
365    **blfun**
366    <br> Function handle to baseline function.
367    
368    **clearzero**
369    <br> Flag indicating to clear zeros (see DSC204_clearZeroFromMax) (default: True).
370    
371    **nonnegative**
372    <br> Flag indicating to ensure nonnegativity                      (default: True).
373    
374    **onset**
375    <br> Onset value (zero values are put below/left of this x value) [optional].
376    
377    **endset**
378    <br> Endset value (zero values are put above/right of this x value) [optional].
379    
380
381    ## Returns   
382    **Yvals** 
383    <br> Processed y values.
384
385    **Yfun**
386    <br> Interpolator of processed values.
387    """
388
389    if onset is None:
390        onset = min(X)
391    if endset is None:
392        endset = max(X)
393    
394    assert( len(X) == len(Yin) )
395    Yvals = Yin
396
397    # subtract baseline from Y data
398    Yvals = Yvals - blfun(X)
399
400    # make zeros outside the interval [onset, endset]
401    idx = [i for i in range(len(X)) if (X[i] < onset or X[i] > endset)]
402    Yvals[idx] = 0
403
404    # ensure nonnegativity
405    if nonnegative:
406        idx = [i for i in range(len(Yvals)) if Yvals[i] < 0]
407        Yvals[idx] = 0
408
409    # clear zero
410    if clearzero:
411       Yvals = clearZeroFromMax(Yvals)
412
413    
414    # for the interpolation function, add some zero space left and right
415    addlen = 5
416    XX = np.concatenate( ( X[0] + np.arange(-addlen,0), X       , X[-1] + np.arange(1,addlen+1) ) )
417    YY = np.concatenate( ( np.zeros(addlen)             ,  Yvals  , np.zeros(addlen)            ) )
418
419    # build function from values
420    Yfun = interpolate.PchipInterpolator(XX,YY)
421
422    return Yvals, Yfun

Subtracts the baseline from given points.

Takes

X: x values (e.g. vector temperatures)

Y: y values or function (e.g. vector of cp values, or function cp(T))

blfun
Function handle to baseline function.

clearzero
Flag indicating to clear zeros (see DSC204_clearZeroFromMax) (default: True).

nonnegative
Flag indicating to ensure nonnegativity (default: True).

onset
Onset value (zero values are put below/left of this x value) [optional].

endset
Endset value (zero values are put above/right of this x value) [optional].

Returns

Yvals
Processed y values.

Yfun
Interpolator of processed values.

def clearZeroFromMax(data):
425def clearZeroFromMax(data):
426    """ 
427    Locates the maximum value in datavec, searches the first occurances
428    of a zero value to the left and to the right from the peak position
429    and deletes the noise before and after these positions.
430    
431    ## Takes 
432    **data**: Data array to be cleared
433    
434    ## Returns
435    **data**: Cleared data array
436    """
437    # Find index/position of maximal value
438    maxidx = np.argmax(data)
439    
440    # Find the positions of the first zeros, seen from the peak value
441    clearToIdx   = max( np.where(data[:maxidx+1]==0)[0] )
442    clearFromIdx = min( np.where(data[maxidx:]  ==0)[0] ) + maxidx
443    
444    # delete noise
445    if not (clearToIdx is None):
446        data[:clearToIdx] = 0
447    if not (clearFromIdx is None):
448        data[clearFromIdx:] = 0
449
450    return data

Locates the maximum value in datavec, searches the first occurances of a zero value to the left and to the right from the peak position and deletes the noise before and after these positions.

Takes

data: Data array to be cleared

Returns

data: Cleared data array