fpex0.baseline API documentation

# fpex0.baseline

View Source

      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: View Source

     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) View Source

    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: View Source

    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() View Source

    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): View Source

     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): View Source

    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>): View Source

    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): View Source

    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): View Source

    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