Completed
Push — master ( ae4288...40b1cc )
by
unknown
11s
created

TraceAnalysisLogic.analyze_flip_prob4()   F

Complexity

Conditions 16

Size

Total Lines 84

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 16
c 1
b 0
f 0
dl 0
loc 84
rs 2

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

Complexity

Complex classes like TraceAnalysisLogic.analyze_flip_prob4() often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
# -*- coding: utf-8 -*-
2
"""
3
This file contains the general Qudi trace analysis logic.
4
Qudi is free software: you can redistribute it and/or modify
5
it under the terms of the GNU General Public License as published by
6
the Free Software Foundation, either version 3 of the License, or
7
(at your option) any later version.
8
Qudi is distributed in the hope that it will be useful,
9
but WITHOUT ANY WARRANTY; without even the implied warranty of
10
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
GNU General Public License for more details.
12
You should have received a copy of the GNU General Public License
13
along with Qudi. If not, see <http://www.gnu.org/licenses/>.
14
Copyright (c) the Qudi Developers. See the COPYRIGHT.txt file at the
15
top-level directory of this distribution and at <https://github.com/Ulm-IQO/qudi/>
16
"""
17
18
from qtpy import QtCore
19
import numpy as np
20
from scipy.signal import gaussian
21
from scipy.ndimage import filters
22
import scipy.integrate as integrate
23
from scipy.interpolate import InterpolatedUnivariateSpline
24
from collections import OrderedDict
25
26
from core.module import Connector
27
from logic.generic_logic import GenericLogic
28
29
30
class TraceAnalysisLogic(GenericLogic):
31
    """ Perform a gated counting measurement with the hardware.  """
32
33
    _modclass = 'TraceAnalysisLogic'
34
    _modtype = 'logic'
35
36
    # declare connectors
37
    counterlogic1 = Connector(interface='CounterLogic')
38
    savelogic = Connector(interface='SaveLogic')
39
    fitlogic = Connector(interface='FitLogic')
40
41
    sigHistogramUpdated = QtCore.Signal()
42
    sigAnalysisResultsUpdated = QtCore.Signal()
43
44
    def __init__(self, config, **kwargs):
45
        """ Create CounterLogic object with connectors.
46
        @param dict config: module configuration
47
        @param dict kwargs: optional parameters
48
        """
49
        super().__init__(config=config, **kwargs)
50
51
        self.log.debug('The following configuration was found.')
52
53
        # checking for the right configuration
54
        for key in config.keys():
55
            self.log.debug('{0}: {1}'.format(key, config[key]))
56
57
        self.hist_data = None
58
        self._hist_num_bins = None
59
        self.spin_flip_prob = 0
60
        self.fidelity_left = 0
61
        self.fidelity_right = 0
62
63
    def on_activate(self):
64
        """ Initialisation performed during activation of the module.
65
        """
66
67
        # self._counter_logic = self.get_connector('counterlogic1')
68
        self._save_logic = self.get_connector('savelogic')
69
        self._fit_logic = self.get_connector('fitlogic')
70
        self.trace = np.array([])
71
72
        # self._counter_logic.sigGatedCounterFinished.connect(self.do_calculate_histogram)
73
74
75
        self.current_fit_function = 'No Fit'
76
77
    def on_deactivate(self):
78
        """ Deinitialisation performed during deactivation of the module.
79
        """
80
        return
81
82
    def set_num_bins_histogram(self, num_bins, update=True):
83
        """ Set the number of bins
84
        @param int num_bins: number of bins for the histogram
85
        @param bool update: if the change of bins should evoke a recalculation
86
                            of the histogram.
87
        """
88
        self._hist_num_bins = num_bins
89
90
        if update:
91
            self.do_calculate_histogram()
92
93
    def do_calculate_histogram(self, mode='normal'):
94
        """ Passes all the needed parameters to the appropriated methods.
95
        @return:
96
        """
97
        if mode == 'normal':
98
            self.hist_data = self.calculate_histogram(self._counter_logic.countdata[0],
99
                                                      self._hist_num_bins)
100
        if mode == 'fastcomtec':
101
            self.sigHistogramUpdated.emit()
102
103
    def calculate_histogram(self, trace, num_bins=None, custom_bin_arr=None):
104
        """ Calculate the histogram of a given trace.
105
        @param np.array trace: a 1D trace
106
        @param int num_bins: number of bins between the minimal and maximal
107
                             value of the trace. That must be an integer greater
108
                             than or equal to 1.
109
        @param np.array custom_bin_arr: optional, 1D array. If a specific,
110
                                        non-uniform binning array is desired
111
                                        then it can be passed to the numpy
112
                                        routine. Then the parameter num_bins is
113
                                        ignored. Otherwise a uniform binning is
114
                                        applied by default.
115
        @return: np.array: a 2D array, where first entry are the x_values and
116
                           second entry are the count values. The length of the
117
                           array is normally determined by the num_bins
118
                           parameter.
119
        Usually the bins for the histogram are taken to be equally spaced,
120
        ranging from the minimal to the maximal value of the input trace array.
121
        """
122
123
        if custom_bin_arr is not None:
124
            hist_y_val, hist_x_val = np.histogram(trace, custom_bin_arr,
125
                                                  density=False)
126
        else:
127
128
            # analyze the trace, and check whether all values are the same
129
            difference = trace.max() - trace.min()
130
131
            # if all values are the same, run at least the method with an zero
132
            # array. That will ensure at least an output:
133
            if np.isclose(0, difference) and num_bins is None:
134
                # numpy can handle an array of zeros
135
                num_bins = 50
136
                hist_y_val, hist_x_val = np.histogram(trace, num_bins)
137
138
            # if no number of bins are passed, then take the integer difference
139
            # between the counts, that will prevent strange histogram artifacts:
140
            elif not np.isclose(0, difference) and num_bins is None:
141
                hist_y_val, hist_x_val = np.histogram(trace, int(difference))
142
143
            # a histogram with self defined number of bins
144
            else:
145
                hist_y_val, hist_x_val = np.histogram(trace, num_bins)
146
147
        self.hist_data = np.array([hist_x_val, hist_y_val])
148
        self.sigHistogramUpdated.emit()
149
150
        return self.hist_data
151
152
    def analyze_flip_prob(self, trace, num_bins=None, threshold=None):
153
        """General method, which analysis how often a value was changed from
154
           one data point to another in relation to a certain threshold.
155
        @param np.array trace: 1D trace of data
156
        @param int num_bins: optional, if a specific size for the histogram is
157
                             desired, which is used to calculate the threshold.
158
        @param float threshold: optional, if a specific threshold is going to be
159
                                used, otherwise the threshold is calculated from
160
                                the data.
161
        @return tuple(flip_prop, param):
162
                      float flip_prop: the actual flip probability
163
                      int num_of_flips: the total number of flips
164
                      float fidelity: the fidelity
165
                      float threshold: the calculated or passed threshold
166
                      float lifetime_dark: the lifetime in the dark state in s
167
                      float lifetime_bright: lifetime in the bright state in s
168
        """
169
170
        hist_data = self.calculate_histogram(trace=trace, num_bins=num_bins)
171
        threshold_fit, fidelity, fit_param = self.calculate_threshold(hist_data)
172
        bin_trace = self.calculate_binary_trace(trace, threshold_fit)
173
174
        # here the index_arr contain all indices where the state is above
175
        # threshold, indicating the bright state.
176
        index_arr, filtered_arr = self.extract_filtered_values(trace, threshold_fit, below=False)
177
178
        # by shifting the index_arr one value further, one will investigate
179
        # basically the next state, where a change has happened.
180
        next_index_arr = index_arr + 1
181
182
        # Just for safety neglect the last value in the index_arr so that one
183
        # will not go beyond the array.
184
        next_filtered_bin_arr = bin_trace[next_index_arr[:-1]]
185
186
        # calculate how many darkstates are present in the array, remember
187
        # filtered_arr contains all the bright states.
188
        num_dark_state = len(trace) - len(filtered_arr)
189
        num_bright_state = len(filtered_arr)
190
191
        # extract the number of state, which has been flipped to dark state
192
        # (True) started in the bright state (=False)
193
        num_flip_to_dark = len(np.where(next_filtered_bin_arr == True)[0])
194
195
        # flip probability:
196
        # In the array filtered_bin_arr all states are in bright state meaning
197
        # that if you would perform for
198
        #   filtered_bin_arr = bin_trace[index_arr]
199
        # the mean value with filtered_bin_arr.mean() then you should get 0.0
200
        # since every entry in that array is False. By looking at the next index
201
        # it might be that some entries turn to True, i.e. a flip from bright to
202
        # dark occurred. Then you get a different mean value, which would
203
        # indicate how many states are flipped from bright (False) to dark (True).
204
        # If all the next states would be dark (True), then you would perform a
205
        # perfect flip into the dark state, meaning a flip probability of 1.
206
        flip_prob = next_filtered_bin_arr.mean()
207
208
        # put all the calculated parameters in a proper dict:
209
        param = OrderedDict()
210
        param['num_dark_state'] = num_dark_state  # Number of Dark States
211
        param['num_bright_state'] = num_bright_state  # Number of Bright States
212
        param['num_flip_to_dark'] = num_flip_to_dark  # Number of flips from bright to dark
213
        param['fidelity'] = fidelity  # Fidelity of Double Poissonian Fit
214
        param['threshold'] = threshold_fit  # Threshold
215
216
        # add the fit parameter to the output parameter:
217
        param.update(fit_param)
218
219
        return flip_prob, param
220
221
    def analyze_flip_prob2(self, trace, threshold=1, analyze_mode='full'):
222
        """General method, which analysis how often a value was changed from
223
           one data point to another in relation to a certain threshold.
224
        @param np.array trace: 1D trace of data
225
        @param int num_bins: optional, if a specific size for the histogram is
226
                             desired, which is used to calculate the threshold.
227
        @param float threshold: optional, if a specific threshold is going to be
228
                                used, otherwise the threshold is calculated from
229
                                the data.
230
        @return tuple(flip_prop, param):
231
                      float flip_prop: the actual flip probability
232
                      int num_of_flips: the total number of flips
233
                      float fidelity: the fidelity
234
                      float threshold: the calculated or passed threshold
235
                      float lifetime_dark: the lifetime in the dark state in s
236
                      float lifetime_bright: lifetime in the bright state in s
237
        """
238
        no_flip = 0.0
239
240
        if analyze_mode == 'full':
241
            for ii in range(len(trace) - 1):
242
                if trace[ii] > threshold and trace[ii + 1] > threshold:
243
                    no_flip = no_flip + 1
244
245
                elif trace[ii] < threshold and trace[ii + 1] < threshold:
246
                    no_flip = no_flip + 1
247
248
            probability = 1.0 - (no_flip / len(trace))
249
            lost_events = 0.0
250
251
        if analyze_mode == 'dark':
252
            dark_counter = 0.0
253
            for ii in range(len(trace) - 1):
254
                if trace[ii] < threshold:
255
                    dark_counter = dark_counter + 1
256
                    if trace[ii + 1] < threshold:
257
                        no_flip = no_flip + 1
258
            probability = 1.0 - (no_flip / dark_counter)
259
            lost_events = (1.0 - (dark_counter / len(trace))) * 100
260
261
        if analyze_mode == 'bright':
262
            bright_counter = 0.0
263
            for ii in range(len(trace) - 1):
264
                if trace[ii] > threshold:
265
                    bright_counter = bright_counter + 1
266
                    if trace[ii + 1] > threshold:
267
                        no_flip = no_flip + 1
268
            probability = 1.0 - (no_flip / bright_counter)
269
            lost_events = (1.0 - (bright_counter / len(trace))) * 100
270
271
        return probability, lost_events
272
273
    def analyze_flip_prob3(self, trace, init_threshold=[1, 1], ana_threshold=[1, 1], analyze_mode='full'):
274
        """General method, which analysis how often a value was changed from
275
           one data point to another in relation to a certain threshold.
276
        @param np.array trace: 1D trace of data
277
        @param float threshold: optional, if a specific threshold is going to be
278
                                used, otherwise the threshold is calculated from
279
                                the data.
280
        @return tuple(flip_prop, param):
281
                      float flip_prop: the actual flip probability
282
                      int num_of_flips: the total number of flips
283
                      float fidelity: the fidelity
284
                      float threshold: the calculated or passed threshold
285
                      float lifetime_dark: the lifetime in the dark state in s
286
                      float lifetime_bright: lifetime in the bright state in s
287
        """
288
        no_flip = 0.0
289
        flip = 0.0
290
291
        # find all indices in the trace-array, where the value is above init_threshold[1]
292
        init_high = np.where(trace[:-1] > init_threshold[1])[0]
293
        # find all indices in the trace-array, where the value is below init_threshold[0]
294
        init_low = np.where(trace[:-1] < init_threshold[0])[0]
295
        # find all indices in the trace-array, where the value is above ana_threshold[1]
296
        ana_high = np.where(trace > ana_threshold[1])[0]
297
        # find all indices in the trace-array, where the value is below ana_threshold[0]
298
        ana_low = np.where(trace < ana_threshold[0])[0]
299
300
        if analyze_mode == 'bright' or analyze_mode == 'full':
301
            # analyze the trace where the data were the nuclear was initalized into one direction
302
            for index in init_high:
303
                # check if the following data point is in the analysis array
304
                if index + 1 in ana_high:
305
                    no_flip = no_flip + 1
306
                elif index + 1 in ana_low:
307
                    flip = flip + 1
308
309
        if analyze_mode == 'dark' or analyze_mode == 'full':
310
            # repeat the same if the nucleus was initalized into the other array
311
            for index in init_low:
312
                # check if the following data point is in the analysis array
313
                if index + 1 in ana_high:
314
                    flip = flip + 1
315
                elif index + 1 in ana_low:
316
                    no_flip = no_flip + 1
317
318
        # the flip probability is given by the number of flips divided by the total number of analyzed data points
319
        if (flip + no_flip) == 0:
320
            self.log.error('There is not enough data to anaylsis SSR!')
321
        else:
322
            probability = flip / (flip + no_flip)
323
        # the number of lost events is given by the length of the time_trace minus the number of analyzed data points
324
        lost_events = len(trace) - (flip + no_flip)
325
326
        return probability, lost_events
327
328
    def analyze_flip_prob4(self, trace, bins=30, init_threshold = [1,1], ana_threshold = [1,1], analyze_mode='full'):
329
        """
330
        Method which calculates the histogram, the fidelity and the flip probability of a time trace.
331
        :param trace:
332
        :param bins:
333
        :param init_margin:
334
        :param ana_margin:
335
        :param analyze_mode:
336
        :return:
337
        """
338
339
        self.calculate_histogram(trace, bins)
340
        axis = self.hist_data[0][:-1] + (self.hist_data[0][1] - self.hist_data[0][0]) / 2.
341
        data = self.hist_data[1]
342
343
        try:
344
            hist_fit_x, hist_fit_y, param_dict, fit_result = self.do_doublegaussian_fit(axis, data)
345
            fit_params = fit_result.best_values
346
347
            # calculate the fidelity for the left and right part from the threshold
348
            center1 = fit_params['g0_center']
349
            center2 = fit_params['g1_center']
350
            std1 = fit_params['g0_sigma']
351
            std2 = fit_params['g1_sigma']
352
            gaussian1 = lambda x: fit_params['g0_amplitude'] * np.exp(-(x - center1) ** 2 / (2 * std1 ** 2))
353
            gaussian2 = lambda x: fit_params['g1_amplitude'] * np.exp(-(x - center2) ** 2 / (2 * std2 ** 2))
354
            if center1 > center2:
355
                gaussian = gaussian1
356
                gaussian1 = gaussian2
357
                gaussian2 = gaussian
358
            area_left1 = integrate.quad(gaussian1, -np.inf, init_threshold[0])
359
            area_left2 = integrate.quad(gaussian2, -np.inf, init_threshold[0])
360
            area_right1 = integrate.quad(gaussian1, init_threshold[1], np.inf)
361
            area_right2 = integrate.quad(gaussian2, init_threshold[1], np.inf)
362
            self.fidelity_left = area_left1[0] / (area_left1[0] + area_left2[0])
363
            self.fidelity_right = area_right2[0] / (area_right1[0] + area_right2[0])
364
        except:
365
            self.log.warning('Not enough data points yet!')
366
367
        # calculate the flip probability
368
        no_flip = 0.0
369
        flip = 0.0
370
        # find all indices in the trace-array, where the value is above init_threshold[1]
371
        init_high = np.where(trace[:-1] > init_threshold[1])[0]
372
        # find all indices in the trace-array, where the value is below init_threshold[0]
373
        init_low = np.where(trace[:-1] < init_threshold[0])[0]
374
        # find all indices in the trace-array, where the value is above ana_threshold[1]
375
        ana_high = np.where(trace > ana_threshold[1])[0]
376
        # find all indices in the trace-array, where the value is below ana_threshold[0]
377
        ana_low = np.where(trace < ana_threshold[0])[0]
378
379
        if analyze_mode == 'bright' or analyze_mode == 'full':
380
            # analyze the trace where the data were the nuclear was initalized into one direction
381
            for index in init_high:
382
                # check if the following data point is in the analysis array
383
                if index + 1 in ana_high:
384
                    no_flip = no_flip + 1
385
                elif index + 1 in ana_low:
386
                    flip = flip + 1
387
        if analyze_mode == 'dark' or analyze_mode == 'full':
388
            # repeat the same if the nucleus was initalized into the other array
389
            for index in init_low:
390
                # check if the following data point is in the analysis array
391
                if index + 1 in ana_high:
392
                    flip = flip + 1
393
                elif index + 1 in ana_low:
394
                    no_flip = no_flip + 1
395
396
        # the flip probability is given by the number of flips divided by the total number of analyzed data points
397
        if (flip + no_flip) == 0:
398
            self.log.error('There is not enough data to anaylsis SSR!')
399
        else:
400
            self.spin_flip_prob = flip / (flip + no_flip)
401
        # the number of lost events is given by the length of the time_trace minus the number of analyzed data points
402
        lost_events = len(trace) - (flip + no_flip)
403
404
        results_dict = dict()
405
        results_dict['fidelity_left'] = self.fidelity_left
406
        results_dict['fidelity_right'] = self.fidelity_right
407
        results_dict['flip_prob'] = self.spin_flip_prob
408
409
        self.sigAnalysisResultsUpdated.emit()
410
411
        return self.spin_flip_prob, lost_events, hist_fit_x, hist_fit_y, fit_result
412
413
    def analyze_flip_prob_postselect(self):
414
        """ Post select the data trace so that the flip probability is only
415
            calculated from a jump from below a threshold value to an value
416
            above threshold.
417
        @return:
418
        """
419
        pass
420
421
    def get_fit_functions(self):
422
        """ Return all fit functions, which are currently implemented for that module.
423
        @return list: with string entries denoting the name of the fit.
424
        """
425
        return ['No Fit', 'Gaussian', 'Double Gaussian', 'Poisson',
426
                'Double Poisson']
427
428
429
    def do_fit(self, fit_function=None):
430
        """ Makes the a fit of the current fit function.
431
        @param str fit_function: name of the chosen fit function.
432
        @return tuple(x_val, y_val, fit_results):
433
                    x_val: a 1D numpy array containing the x values
434
                    y_val: a 1D numpy array containing the y values
435
                    fit_results: a string containing the information of the fit
436
                                 results.
437
        You can obtain with get_fit_methods all implemented fit methods.
438
        """
439
440
        if self.hist_data is None:
441
            hist_fit_x = []
442
            hist_fit_y = []
443
            param_dict = OrderedDict()
444
            fit_result = None
445
            return hist_fit_x, hist_fit_y, param_dict, fit_result
446
        else:
447
448
            # self.log.debug((self.calculate_threshold(self.hist_data)))
449
450
            # shift x axis to middle of bin
451
            axis = self.hist_data[0][:-1] + (self.hist_data[0][1] - self.hist_data[0][0]) / 2.
452
            data = self.hist_data[1]
453
454
            if fit_function == 'No Fit':
455
                hist_fit_x, hist_fit_y, fit_param_dict, fit_result = self.do_no_fit()
456
                return hist_fit_x, hist_fit_y, fit_param_dict, fit_result
457
            elif fit_function == 'Gaussian':
458
                hist_fit_x, hist_fit_y, fit_param_dict, fit_result = self.do_gaussian_fit(axis, data)
459
                return hist_fit_x, hist_fit_y, fit_param_dict, fit_result
460
            elif fit_function == 'Double Gaussian':
461
                hist_fit_x, hist_fit_y, fit_param_dict, fit_result = self.do_doublegaussian_fit(axis, data)
462
                return hist_fit_x, hist_fit_y, fit_param_dict, fit_result
463
            elif fit_function == 'Poisson':
464
                hist_fit_x, hist_fit_y, fit_param_dict, fit_result = self.do_possonian_fit(axis, data)
465
                return hist_fit_x, hist_fit_y, fit_param_dict, fit_result
466
            elif fit_function == 'Double Poisson':
467
                hist_fit_x, hist_fit_y, fit_param_dict, fit_result = self.do_doublepossonian_fit(axis, data)
468
                return hist_fit_x, hist_fit_y, fit_param_dict, fit_result
469
470
    def do_no_fit(self):
471
        """ Perform no fit, basically return an empty array.
472
        @return tuple(x_val, y_val, fit_results):
473
                    x_val: a 1D numpy array containing the x values
474
                    y_val: a 1D numpy array containing the y values
475
                    fit_results: a string containing the information of the fit
476
                                 results.
477
        """
478
        hist_fit_x = []
479
        hist_fit_y = []
480
        param_dict = {}
481
        fit_result = None
482
        return hist_fit_x, hist_fit_y, param_dict, fit_result
483
484
    def analyze_lifetime(self, trace, dt, method='postselect',
485
                         distr='gaussian_normalized', state='|-1>', num_bins=50):
486
        """ Perform an lifetime analysis of a 1D time trace. The analysis is
487
            based on the method provided ( for now only post select is implemented ).
488
        @param numpy array trace: 1 D array
489
        @param string method: The method used for the lifetime analysis
490
        @param string distr: distribution used for analysis
491
        @param string state: State that the mw was applied to
492
        @param int num_bins: number of bins used in the histogram to determine the threshold before digitalisation
493
                             of data
494
        @return: dictionary containing the lifetimes of the different states |0>, |1>, |-1> in the case of the HMM method
495
                 For the postselect method only lifetime for bright and darkstate is returned, keys are 'bright_state' and
496
                 'dark_state'
497
        """
498
        lifetime_dict = {}
499
500
        if method == 'postselect':
501
            if distr == 'gaussian_normalized':
502
                hist_y_val, hist_x_val = np.histogram(trace, num_bins)
503
                hist_data = np.array([hist_x_val, hist_y_val])
504
                threshold_fit, fidelity, param_dict = self.calculate_threshold(hist_data=hist_data,
505
                                                                               distr='gaussian_normalized')
506
                threshold = threshold_fit
507
508
            # helper functions to get and analyze the timetrace
509
            def analog_digitial_converter(cut_off, data):
510
                digital_trace = []
511
                for data_point in data:
512
                    if data_point >= cut_off:
513
                        digital_trace.append(1)
514
                    else:
515
                        digital_trace.append(0)
516
                return digital_trace
517
518
            def time_in_high_low(digital_trace, dt):
519
                """
520
                What I need this function to do is to get all consecutive {1, ... , n} 1s or 0s and add
521
                them up and put into a list to later make a histogram from them.
522
                """
523
                occurances = []
524
                index = 0
525
                index2 = 0
526
527
                while (index < len(digital_trace)):
528
                    occurances.append(0)
529
                    # start following the consecutive 1s
530
                    while (digital_trace[index] == 1):
531
                        occurances[index2] += 1
532
                        if index == (len(digital_trace) - 1):
533
                            occurances = np.array(occurances)
534
                            return occurances * dt
535
                        else:
536
                            index += 1
537
                    if digital_trace[index - 1] == 1:
538
                        index2 += 1
539
                        occurances.append(0)
540
                    # start following the consecutive 0s
541
                    while (digital_trace[index] == 0):
542
                        occurances[index2] -= 1
543
                        if index == (len(digital_trace) - 1):
544
                            occurances = np.array(occurances)
545
                            return occurances * dt
546
                        else:
547
                            index += 1
548
                    index2 += 1
549
550
            digital_trace = analog_digitial_converter(threshold, trace)
551
            time_array = time_in_high_low(digital_trace, dt)
552
553
            # now we need to make a histogram as well as a fit
554
            # what would be a good estimate for the number of bins
555
556
            # longest = np.max(np.array(occurances))
557
            # number of steps in between, rather not use that for now
558
            # est_bins = np.int(longest/dt)
559
560
            time_array_high = np.array([ii for ii in filter(lambda x: x > 0, time_array)])
561
            time_array_low = np.array([ii for ii in filter(lambda x: x < 0, time_array)])
562
563
            # get lifetime of bright state
564
            time_hist_high = np.histogram(time_array_high, bins=num_bins)
565
            vals = [i for i in filter(lambda x: x[1] > 0, enumerate(time_hist_high[0][0:num_bins]))]
566
567
            indices = np.array([val[0] for val in vals])
568
            indices = np.array([np.int(indice) for indice in indices])
569
            self.log.debug('threshold {0}'.format(threshold))
570
            self.log.debug('time_array:{0}'.format(time_array))
571
            self.log.debug('time_array_high:{0}'.format(time_array_high))
572
            self.log.debug('time_hist_high:{0}'.format(time_hist_high))
573
            self.log.debug('indices: {0}'.format(indices))
574
            self.debug_lifetime_x = time_hist_high[1][indices]
575
            self.debug_lifetime_y = time_hist_high[0][indices]
576
            para = dict()
577
            para['offset'] = {"value": 0.0, "vary": False}
578
            result = self._fit_logic.make_decayexponential_fit(time_hist_high[1][indices],
579
                                                               time_hist_high[0][indices],
580
                                                               self._fit_logic.estimate_decayexponential,
581
                                                               add_params=para)
582
            bright_liftime = result.params['lifetime']
583
            # for debug purposes give also the results back of the fits for now
584
            lifetime_dict['result_bright'] = result
585
            # also give back the data used for the fit
586
            lifetime_dict['bright_raw'] = np.array([time_hist_high[1][indices], time_hist_high[0][indices]])
587
588
            # get lifetime of dark state
589
            time_hist_low = np.histogram(time_array_low, bins=num_bins)
590
            vals = [i for i in filter(lambda x: x[1] > 0, enumerate(time_hist_low[0][0:num_bins]))]
591
            indices = np.array([val[0] for val in vals])
592
            indices = np.array([np.int(indice) for indice in indices])
593
            values = np.array([val[1] for val in vals])
594
            # positive axis
595
            mirror_axis = -time_hist_low[1][indices]
596
            result = self._fit_logic.make_decayexponential_fit(mirror_axis,
597
                                                               values,
598
                                                               self._fit_logic.estimate_decayexponential,
599
                                                               add_params=para)
600
            dark_liftime = result.params['lifetime']
601
            lifetime_dict['result_dark'] = result
602
603
            lifetime_dict['bright_state'] = bright_liftime.value
604
            lifetime_dict['dark_state'] = dark_liftime.value
605
            # also give back the data used for the fit
606
            lifetime_dict['dark_raw'] = np.array([mirror_axis, values])
607
608
        return lifetime_dict
609
610
    def do_gaussian_fit(self, axis, data):
611
        """ Perform a gaussian fit.
612
        @param axis:
613
        @param data:
614
        @return:
615
        """
616
        model, params = self._fit_logic.make_gaussian_model()
617
        if len(axis) < len(params):
618
            self.log.warning('Fit could not be performed because number of '
619
                             'parameters is larger than data points.')
620
            return self.do_no_fit()
621
622
        else:
623
624
            parameters_to_substitute = dict()
625
            update_dict = dict()
626
627
            # TODO: move this to "gated counter" estimator in fitlogic
628
            #      make the filter an extra function shared and usable for other
629
            #      functions
630
            gauss = gaussian(10, 10)
631
            data_smooth = filters.convolve1d(data, gauss / gauss.sum(), mode='mirror')
632
633
            # integral of data corresponds to sqrt(2) * Amplitude * Sigma
634
            function = InterpolatedUnivariateSpline(axis, data_smooth, k=1)
635
            Integral = function.integral(axis[0], axis[-1])
636
            amp = data_smooth.max()
637
            sigma = Integral / amp / np.sqrt(2 * np.pi)
638
            amplitude = amp * sigma * np.sqrt(2 * np.pi)
639
640
            update_dict['offset'] = {'min': 0, 'max': data.max(), 'value': 1e-15, 'vary': False}
641
            update_dict['center'] = {'min': axis.min(), 'max': axis.max(), 'value': axis[np.argmax(data)]}
642
            update_dict['sigma'] = {'min': -np.inf, 'max': np.inf, 'value': sigma}
643
            update_dict['amplitude'] = {'min': 0, 'max': np.inf, 'value': amplitude}
644
645
            result = self._fit_logic.make_gaussian_fit(x_axis=axis,
646
                                                       data=data,
647
                                                       estimator=self._fit_logic.estimate_gaussian_peak,
648
                                                       units=None,  # TODO
649
                                                       add_params=update_dict)
650
            # 1000 points in x axis for smooth fit data
651
            hist_fit_x = np.linspace(axis[0], axis[-1], 1000)
652
            hist_fit_y = model.eval(x=hist_fit_x, params=result.params)
653
654
            param_dict = OrderedDict()
655
656
            # create the proper param_dict with the values:
657
            param_dict['sigma_0'] = {'value': result.params['sigma'].value,
658
                                     'error': result.params['sigma'].stderr,
659
                                     'unit': 'Occurrences'}
660
661
            param_dict['FWHM'] = {'value': result.params['fwhm'].value,
662
                                  'error': result.params['fwhm'].stderr,
663
                                  'unit': 'Counts/s'}
664
665
            param_dict['Center'] = {'value': result.params['center'].value,
666
                                    'error': result.params['center'].stderr,
667
                                    'unit': 'Counts/s'}
668
669
            param_dict['Amplitude'] = {'value': result.params['amplitude'].value,
670
                                       'error': result.params['amplitude'].stderr,
671
                                       'unit': 'Occurrences'}
672
673
            param_dict['chi_sqr'] = {'value': result.chisqr, 'unit': ''}
674
675
            return hist_fit_x, hist_fit_y, param_dict, result
676
677
    def do_doublegaussian_fit(self, axis, data):
678
        model, params = self._fit_logic.make_gaussiandouble_model()
679
680
        update_dict = dict()
681
        update_dict['offset'] = {'min': 0, 'max': data.max(), 'value': 1e-15, 'vary': False}
682
        #update_dict['g0_center'] = {'min': axis.min(), 'max': axis.max()}
683
        #update_dict['g1_center'] = {'min': axis.min(), 'max': axis.max()}
684
        #update_dict['g0_amplitude'] = {'min': 0, 'max': 2 * data.max()}
685
        #update_dict['g1_amplitude'] = {'min': 0, 'max': 2 * data.max()}
686
687
        if len(axis) < len(params):
688
            self.log.warning('Fit could not be performed because number of '
689
                             'parameters is larger than data points')
690
            return self.do_no_fit()
691
692
        else:
693
            result = self._fit_logic.make_gaussiandouble_fit(axis, data, self._fit_logic.estimate_gaussiandouble_peak,
694
                                                             add_params=update_dict)
695
696
            # 1000 points in x axis for smooth fit data
697
            hist_fit_x = np.linspace(axis[0], axis[-1], 1000)
698
            hist_fit_y = model.eval(x=hist_fit_x, params=result.params)
699
700
            # this dict will be passed to the formatting method
701
            param_dict = OrderedDict()
702
703
            # create the proper param_dict with the values:
704
            param_dict['sigma_0'] = {'value': result.params['g0_sigma'].value,
705
                                     'error': result.params['g0_sigma'].stderr,
706
                                     'unit': 'Counts/s'}
707
708
            param_dict['FWHM_0'] = {'value': result.params['g0_fwhm'].value,
709
                                    'error': result.params['g0_fwhm'].stderr,
710
                                    'unit': 'Counts/s'}
711
712
            param_dict['Center_0'] = {'value': result.params['g0_center'].value,
713
                                      'error': result.params['g0_center'].stderr,
714
                                      'unit': 'Counts/s'}
715
716
            param_dict['Amplitude_0'] = {'value': result.params['g0_amplitude'].value,
717
                                         'error': result.params['g0_amplitude'].stderr,
718
                                         'unit': 'Occurrences'}
719
720
            param_dict['sigma_1'] = {'value': result.params['g1_sigma'].value,
721
                                     'error': result.params['g1_sigma'].stderr,
722
                                     'unit': 'Counts/s'}
723
724
            param_dict['FWHM_1'] = {'value': result.params['g1_fwhm'].value,
725
                                    'error': result.params['g1_fwhm'].stderr,
726
                                    'unit': 'Counts/s'}
727
728
            param_dict['Center_1'] = {'value': result.params['g1_center'].value,
729
                                      'error': result.params['g1_center'].stderr,
730
                                      'unit': 'Counts/s'}
731
732
            param_dict['Amplitude_1'] = {'value': result.params['g1_amplitude'].value,
733
                                         'error': result.params['g1_amplitude'].stderr,
734
                                         'unit': 'Occurrences'}
735
736
            param_dict['chi_sqr'] = {'value': result.chisqr, 'unit': ''}
737
738
            return hist_fit_x, hist_fit_y, param_dict, result
739
740
    def do_doublepossonian_fit(self, axis, data):
741
        model, params = self._fit_logic.make_multiplepoissonian_model(no_of_functions=2)
742
        if len(axis) < len(params):
743
            self.log.warning('Fit could not be performed because number of '
744
                             'parameters is smaller than data points')
745
            return self.do_no_fit()
746
747
        else:
748
            result = self._fit_logic.make_doublepoissonian_fit(x_axis=axis,
749
                                                               data=data,
750
                                                               add_params=None)
751
752
            # 1000 points in x axis for smooth fit data
753
            hist_fit_x = np.linspace(axis[0], axis[-1], 1000)
754
            hist_fit_y = model.eval(x=hist_fit_x, params=result.params)
755
756
            # this dict will be passed to the formatting method
757
            param_dict = OrderedDict()
758
759
            # create the proper param_dict with the values:
760
            param_dict['lambda_0'] = {'value': result.params['p0_mu'].value,
761
                                      'error': result.params['p0_mu'].stderr,
762
                                      'unit': 'Counts/s'}
763
            param_dict['Amplitude_0'] = {'value': result.params['p0_amplitude'].value,
764
                                         'error': result.params['p0_amplitude'].stderr,
765
                                         'unit': 'Occurrences'}
766
            param_dict['lambda_1'] = {'value': result.params['p1_mu'].value,
767
                                      'error': result.params['p1_mu'].stderr,
768
                                      'unit': 'Counts/s'}
769
            param_dict['Amplitude_1'] = {'value': result.params['p1_amplitude'].value,
770
                                         'error': result.params['p1_amplitude'].stderr,
771
                                         'unit': 'Occurrences'}
772
773
            param_dict['chi_sqr'] = {'value': result.chisqr, 'unit': ''}
774
            # removed last return value <<result>> here, because function calculate_threshold only expected
775
            # three return values
776
            return hist_fit_x, hist_fit_y, param_dict
777
778
    def do_possonian_fit(self, axis, data):
779
        model, params = self._fit_logic.make_poissonian_model()
780
        if len(axis) < len(params):
781
            self.log.error('Fit could not be performed because number of '
782
                           'parameters is smaller than data points')
783
            return self.do_no_fit()
784
        else:
785
            result = self._fit_logic.make_poissonian_fit(x_axis=axis, data=data,
786
                                                         estimator=self._fit_logic.estimate_poissonian, add_params=None)
787
788
            # 1000 points in x axis for smooth fit data
789
            hist_fit_x = np.linspace(axis[0], axis[-1], 1000)
790
            hist_fit_y = model.eval(x=hist_fit_x, params=result.params)
791
792
            # this dict will be passed to the formatting method
793
            param_dict = OrderedDict()
794
795
            # create the proper param_dict with the values:
796
            param_dict['lambda'] = {'value': result.params['mu'].value,
797
                                    'error': result.params['mu'].stderr,
798
                                    'unit': 'Counts/s'}
799
800
            param_dict['chi_sqr'] = {'value': result.chisqr, 'unit': ''}
801
802
            return hist_fit_x, hist_fit_y, param_dict, result
803
804
    def get_poissonian(self, x_val, mu, amplitude):
805
        """ Calculate, bases on the passed values a poisson distribution.
806
        @param float mu: expected value of poisson distribution
807
        @param float amplitude: Amplitude to which is multiplied on distribution
808
        @param int,float or np.array x_val: x values for poisson distribution,
809
                                            also works for numbers (int or float)
810
        @return np.array: a 1D array with the calculated poisson distribution,
811
                          corresponding to given parameters/ x values
812
        Calculate a Poisson distribution according to:
813
            P(k) =  mu^k * exp(-mu) / k!
814
        """
815
816
        model, params = self._fit_logic.make_poissonian_model()
817
818
        return model.eval(x=np.array(x_val), poissonian_mu=mu, poissonian_amplitude=amplitude)
819
820
    def guess_threshold(self, hist_val=None, trace=None, max_ratio_value=0.1):
821
        """ Assume a distribution between two values and try to guess the threshold.
822
        @param np.array hist_val: 1D array which represent the y values of a
823
                                    histogram of a trace. Optional, if None
824
                                    is passed here, the passed trace will be
825
                                    used for calculations.
826
        @param np.array trace: optional, 1D array containing the y values of a
827
                               meausured counter trace. If None is passed to
828
                               hist_y_val then the threshold will be calculated
829
                               from the trace.
830
        @param float max_ratio_value: the ratio how strong the lower y values
831
                                       will be cut off. For max_ratio_value=0.1
832
                                       all the data which are 10% or less in
833
                                       amptitude compared to the maximal value
834
                                       are neglected.
835
        The guess procedure tries to find all values, which are
836
        max_ratio_value * maximum value of the histogram of the trace and
837
        selects those by indices. Then taking the first an the last might and
838
        assuming that the threshold is in the middle, gives a first estimate
839
        of the threshold value.
840
        FIXME: That guessing procedure can be improved!
841
        @return float: a guessed threshold
842
        """
843
844
        if hist_val is None and trace is not None:
845
            hist_val = self.calculate_histogram(trace)
846
847
        hist_val = np.array(hist_val)  # just to be sure to have a np.array
848
        indices_arr = np.where(hist_val[1] > hist_val[1].max() * max_ratio_value)[0]
849
        guessed_threshold = hist_val[0][int((indices_arr[-1] + indices_arr[0]) / 2)]
850
851
        return guessed_threshold
852
853
    def calculate_threshold(self, hist_data=None, distr='poissonian'):
854
        """ Calculate the threshold by minimizing its overlap with the poissonian fits.
855
        @param np.array hist_data: 2D array which represent the x and y values
856
                                   of a histogram of a trace.
857
               string distr: tells the function on what distribution it should calculate
858
                             the threshold ( Added because it might happen that one normalizes data
859
                             between (-1,1) and then a poissonian distribution won't work anymore.
860
        @return tuple(float, float):
861
                    threshold: the calculated threshold between two overlapping
862
                               poissonian distributed peaks.
863
                    fidelity: the measure how good the two peaks are resolved
864
                              according to the calculated threshold
865
        The calculation of the threshold relies on fitting two poissonian
866
        distributions to the count histogram and minimize a threshold with
867
        respect to the overlap area:
868
        """
869
        # in any case calculate the hist data
870
        x_axis = hist_data[0][:-1] + (hist_data[0][1] - hist_data[0][0]) / 2.
871
        y_data = hist_data[1]
872
        if distr == 'poissonian':
873
            # perform the fit
874
875
            hist_fit_x, hist_fit_y, param_dict = self.do_doublepossonian_fit(x_axis, y_data)
876
877
            if param_dict.get('lambda_0') is None:
878
                self.log.error('The double poissonian fit does not work! Take at '
879
                               'least a dummy value, in order not to break the '
880
                               'routine.')
881
                amp0 = 1
882
                amp1 = 1
883
884
                param_dict['Amplitude_0'] = {'value': amp0, 'unit': 'occurences'}
885
                param_dict['Amplitude_1'] = {'value': amp0, 'unit': 'occurences'}
886
887
                # make them a bit different so that fit works.
888
                mu0 = hist_data[0][:].mean() - 0.1
889
                mu1 = hist_data[0][:].mean() + 0.1
890
891
                param_dict['lambda_0'] = {'value': mu0, 'unit': 'counts'}
892
                param_dict['lambda_1'] = {'value': mu1, 'unit': 'counts'}
893
894
            else:
895
896
                mu0 = param_dict['lambda_0']['value']
897
                mu1 = param_dict['lambda_1']['value']
898
899
                amp0 = param_dict['Amplitude_0']['value']
900
                amp1 = param_dict['Amplitude_1']['value']
901
902
            if mu0 < mu1:
903
                first_dist = self.get_poissonian(x_val=hist_data[0], mu=mu0, amplitude=amp0)
904
                sec_dist = self.get_poissonian(x_val=hist_data[0], mu=mu1, amplitude=amp1)
905
            else:
906
                first_dist = self.get_poissonian(x_val=hist_data[0], mu=mu1, amplitude=amp1)
907
                sec_dist = self.get_poissonian(x_val=hist_data[0], mu=mu0, amplitude=amp0)
908
909
            # create a two poissonian array, where the second poissonian
910
            # distribution is add as negative values. Now the transition from
911
            # positive to negative values will get the threshold:
912
            difference_poissonian = first_dist - sec_dist
913
914
            trans_index = 0
915
            for i in range(len(difference_poissonian) - 1):
916
                # go through the combined histogram array and the point which
917
                # changes the sign. The transition from positive to negative values
918
                # will get the threshold:
919
                if difference_poissonian[i] < 0 and difference_poissonian[i + 1] >= 0:
920
                    trans_index = i
921
                    break
922
                elif difference_poissonian[i] > 0 and difference_poissonian[i + 1] <= 0:
923
                    trans_index = i
924
                    break
925
926
            threshold_fit = hist_data[0][trans_index]
927
928
            # Calculate also the readout fidelity, i.e. sum the area under the
929
            # first peak before the threshold of the first and second distribution
930
            # and take the ratio of that area. Do the same thing after the threshold
931
            # (of course with a reversed choice of the distribution). If the overlap
932
            # in both cases is very small, then the fidelity is good, if the overlap
933
            # is identical, then fidelity indicates a poor separation of the peaks.
934
935
            if mu0 < mu1:
936
                area0_low = self.get_poissonian(hist_data[0][0:trans_index], mu0, 1).sum()
937
                area0_high = self.get_poissonian(hist_data[0][trans_index:], mu0, 1).sum()
938
                area1_low = self.get_poissonian(hist_data[0][0:trans_index], mu1, 1).sum()
939
                area1_high = self.get_poissonian(hist_data[0][trans_index:], mu1, 1).sum()
940
941
                area0_low_amp = self.get_poissonian(hist_data[0][0:trans_index], mu0, amp0).sum()
942
                area0_high_amp = self.get_poissonian(hist_data[0][trans_index:], mu0, amp0).sum()
943
                area1_low_amp = self.get_poissonian(hist_data[0][0:trans_index], mu1, amp1).sum()
944
                area1_high_amp = self.get_poissonian(hist_data[0][trans_index:], mu1, amp1).sum()
945
946
            else:
947
                area1_low = self.get_poissonian(hist_data[0][0:trans_index], mu0, 1).sum()
948
                area1_high = self.get_poissonian(hist_data[0][trans_index:], mu0, 1).sum()
949
                area0_low = self.get_poissonian(hist_data[0][0:trans_index], mu1, 1).sum()
950
                area0_high = self.get_poissonian(hist_data[0][trans_index:], mu1, 1).sum()
951
952
                area1_low_amp = self.get_poissonian(hist_data[0][0:trans_index], mu0, amp0).sum()
953
                area1_high_amp = self.get_poissonian(hist_data[0][trans_index:], mu0, amp0).sum()
954
                area0_low_amp = self.get_poissonian(hist_data[0][0:trans_index], mu1, amp1).sum()
955
                area0_high_amp = self.get_poissonian(hist_data[0][trans_index:], mu1, amp1).sum()
956
957
            # Now calculate how big is the overlap relative to the sum of the other
958
            # part of the area, that will give the normalized fidelity:
959
            fidelity = 1 - (area1_low / area0_low + area0_high / area1_high) / 2
960
961
            area0 = self.get_poissonian(hist_data[0][:], mu0, amp0).sum()
962
            area1 = self.get_poissonian(hist_data[0][:], mu1, amp1).sum()
963
964
            # try this new measure for the fidelity
965
            fidelity2 = 1 - ((area1_low_amp / area1) / (area0_low_amp / area0) + (area0_high_amp / area0) / (
966
            area1_high_amp / area1)) / 2
967
968
            param_dict['normalized_fidelity'] = fidelity2
969
970
            return threshold_fit, fidelity, param_dict
971
972
        # this works if your data is normalized to the interval (-1,1)
973
        if distr == 'gaussian_normalized':
974
            # first some helper functions
975
            def two_gaussian_intersect(m1, m2, std1, std2, amp1, amp2):
976
                """
977
                function to calculate intersection of two gaussians
978
                """
979
                a = 1 / (2 * std1 ** 2) - 1 / (2 * std2 ** 2)
980
                b = m2 / (std2 ** 2) - m1 / (std1 ** 2)
981
                c = m1 ** 2 / (2 * std1 ** 2) - m2 ** 2 / (2 * std2 ** 2) - np.log(amp2 / amp1)
982
                return np.roots([a, b, c])
983
984
            def gaussian(counts, amp, stdv, mean):
985
                return amp * np.exp(-(counts - mean) ** 2 / (2 * stdv ** 2)) / (stdv * np.sqrt(2 * np.pi))
986
987
            try:
988
                result = self._fit_logic.make_gaussiandouble_fit(x_axis, y_data,
989
                                                                 self._fit_logic.estimate_gaussiandouble_peak)
990
                # calculating the threshold
991
                # NOTE the threshold is taken as the intersection of the two gaussians, while this should give
992
                # a good approximation I doubt it is mathematical exact.
993
994
                mu0 = result.params['g0_center'].value
995
                mu1 = result.params['g1_center'].value
996
                sigma0 = result.params['g0_sigma'].value
997
                sigma1 = result.params['g1_sigma'].value
998
                amp0 = result.params['g0_amplitude'].value / (sigma0 * np.sqrt(2 * np.pi))
999
                amp1 = result.params['g1_amplitude'].value / (sigma1 * np.sqrt(2 * np.pi))
1000
                candidates = two_gaussian_intersect(mu0, mu1, sigma0, sigma1, amp0, amp1)
1001
1002
                # we want to get the intersection that lies between the two peaks
1003
                if mu0 < mu1:
1004
                    threshold = [i for i in filter(lambda x: (x > mu0) & (x < mu1), candidates)]
1005
                else:
1006
                    threshold = [i for i in filter(lambda x: (x < mu0) & (x > mu1), candidates)]
1007
1008
                threshold = threshold[0]
1009
1010
                # now we want to get the readout fidelity
1011
                # of the bigger peak ( most likely the two states that aren't driven by the mw pi pulse )
1012
                if mu0 < mu1:
1013
                    gc0 = integrate.quad(lambda counts: gaussian(counts, amp1, sigma1, mu1), -1, 1)
1014
                    gp0 = integrate.quad(lambda counts: gaussian(counts, amp1, sigma1, mu1), -1, threshold)
1015
                else:
1016
                    gc0 = integrate.quad(lambda counts: gaussian(counts, amp0, sigma0, mu0), -1, 1)
1017
                    gp0 = integrate.quad(lambda counts: gaussian(counts, amp0, sigma0, mu0), -1, threshold)
1018
1019
                # and then the same for the other peak ]
1020
1021
                if mu0 > mu1:
1022
                    gc1 = integrate.quad(lambda counts: gaussian(counts, amp1, sigma1, mu1), -1, 1)
1023
                    gp1 = integrate.quad(lambda counts: gaussian(counts, amp1, sigma1, mu1), threshold, 1)
1024
                else:
1025
                    gc1 = integrate.quad(lambda counts: gaussian(counts, amp0, sigma0, mu0), -1, 1)
1026
                    gp1 = integrate.quad(lambda counts: gaussian(counts, amp0, sigma0, mu0), threshold, 1)
1027
1028
                param_dict = {}
1029
                fidelity = 1 - (gp0[0] / gc0[0] + gp1[0] / gc1[0]) / 2
1030
                fidelity1 = 1 - (gp0[0] / gc0[0])
1031
                fidelity2 = 1 - gp1[0] / gc1[0]
1032
                threshold_fit = threshold
1033
                # if the fit worked, add also the result to the param_dict, which might be useful for debugging
1034
                param_dict['result'] = result
1035
            except:
1036
                self.log.error('could not fit the data')
1037
                error = True
1038
                fidelity = 0
1039
                threshold_fit = 0
1040
                param_dict = {}
1041
                new_dict = {}
1042
                new_dict['value'] = np.inf
1043
                param_dict['chi_sqr'] = new_dict
1044
1045
            return threshold_fit, fidelity, param_dict
1046
1047
    def calculate_binary_trace(self, trace, threshold):
1048
        """ Calculate for a given threshold all the trace values und output a
1049
            binary array, where
1050
                True = Below or equal Threshold
1051
                False = Above Threshold.
1052
        @param np.array trace: a 1D array containing the y data, e.g. ccunts
1053
        @param float threshold: value to decide whether a point in the trace
1054
                                is below/equal (True) or above threshold (False).
1055
        @return np.array: 1D trace of the length(trace) but now with boolean
1056
                          entries
1057
        """
1058
        return trace <= threshold
1059
1060
    def extract_filtered_values(self, trace, threshold, below=True):
1061
        """ Extract only those values, which are below or equal a certain Threshold.
1062
        @param np.array trace:
1063
        @param float threshold:
1064
        @return tuple(index_array, filtered_array):
1065
                    np.array index_array: 1D integer array containing the
1066
                                          indices of the passed trace array
1067
                                          which are equal or below the threshold
1068
                    np.array filtered_array: the actual values of the trace,
1069
                                             which are equal or below threshold
1070
        """
1071
        if below:
1072
            index_array = np.where(trace <= threshold)[0]
1073
        else:
1074
            index_array = np.where(trace > threshold)[0]
1075
        filtered_array = trace[index_array]
1076
        return index_array, filtered_array
1077
1078