| Conditions | 14 |
| Total Lines | 150 |
| Lines | 0 |
| Ratio | 0 % |
| Changes | 0 | ||
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:
If many parameters/temporary variables are present:
Complex classes like PulseExtractionLogic.ungated_extraction() 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 -*- |
||
| 101 | def ungated_extraction(self, count_data, conv_std_dev, num_of_lasers): |
||
| 102 | """ Detects the laser pulses in the ungated timetrace data and extracts |
||
| 103 | them. |
||
| 104 | |||
| 105 | @param numpy.ndarray count_data: 1D array the raw timetrace data from an |
||
| 106 | ungated fast counter |
||
| 107 | @param int num_of_lasers: The total number of laser pulses inside the |
||
| 108 | pulse sequence |
||
| 109 | @param float conv_std_dev: standard deviation of the gaussian filter to be |
||
| 110 | applied for smoothing |
||
| 111 | |||
| 112 | @return 2D numpy.ndarray: 2D array, the extracted laser pulses of the |
||
| 113 | timetrace, dimensions: |
||
| 114 | 0: laser number, |
||
| 115 | 1: time bin |
||
| 116 | |||
| 117 | Procedure: |
||
| 118 | Edge Detection: |
||
| 119 | --------------- |
||
| 120 | |||
| 121 | The count_data array with the laser pulses is smoothed with a |
||
| 122 | gaussian filter (convolution), which used a defined standard |
||
| 123 | deviation of 10 entries (bins). Then the derivation of the convolved |
||
| 124 | time trace is taken to obtain the maxima and minima, which |
||
| 125 | corresponds to the rising and falling edge of the pulses. |
||
| 126 | |||
| 127 | The convolution with a gaussian removes nasty peaks due to count |
||
| 128 | fluctuation within a laser pulse and at the same time ensures a |
||
| 129 | clear distinction of the maxima and minima in the derived convolved |
||
| 130 | trace. |
||
| 131 | |||
| 132 | The maxima and minima are not found sequentially, pulse by pulse, |
||
| 133 | but are rather globally obtained. I.e. the convolved and derived |
||
| 134 | array is searched iteratively for a maximum and a minimum, and after |
||
| 135 | finding those the array entries within the 4 times |
||
| 136 | self.conv_std_dev (2*self.conv_std_dev to the left and |
||
| 137 | 2*self.conv_std_dev) are set to zero. |
||
| 138 | |||
| 139 | The crucial part is the knowledge of the number of laser pulses and |
||
| 140 | the choice of the appropriate std_dev for the gauss filter. |
||
| 141 | |||
| 142 | To ensure a good performance of the edge detection, you have to |
||
| 143 | ensure a steep rising and falling edge of the laser pulse! Be also |
||
| 144 | careful in choosing a large conv_std_dev value and using a small |
||
| 145 | laser pulse (rule of thumb: conv_std_dev < laser_length/10). |
||
| 146 | """ |
||
| 147 | |||
| 148 | # apply gaussian filter to remove noise and compute the gradient of the |
||
| 149 | # timetrace |
||
| 150 | |||
| 151 | conv_deriv = self._convolve_derive(count_data.astype(float), conv_std_dev) |
||
| 152 | |||
| 153 | # use a reference for array, because the exact position of the peaks or |
||
| 154 | # dips (i.e. maxima or minima, which are the inflection points in the |
||
| 155 | # pulse) are distorted by a large conv_std_dev value. |
||
| 156 | conv_deriv_ref = self._convolve_derive(count_data, 10) |
||
| 157 | |||
| 158 | # initialize arrays to contain indices for all rising and falling |
||
| 159 | # flanks, respectively |
||
| 160 | rising_ind = np.empty([num_of_lasers],int) |
||
| 161 | falling_ind = np.empty([num_of_lasers],int) |
||
| 162 | |||
| 163 | # Find as many rising and falling flanks as there are laser pulses in |
||
| 164 | # the trace: |
||
| 165 | for i in range(num_of_lasers): |
||
| 166 | |||
| 167 | # save the index of the absolute maximum of the derived time trace |
||
| 168 | # as rising edge position |
||
| 169 | rising_ind[i] = np.argmax(conv_deriv) |
||
| 170 | |||
| 171 | # refine the rising edge detection, by using a small and fixed |
||
| 172 | # conv_std_dev parameter to find the inflection point more precise |
||
| 173 | start_ind = int(rising_ind[i]-conv_std_dev) |
||
| 174 | if start_ind < 0: |
||
| 175 | start_ind = 0 |
||
| 176 | |||
| 177 | stop_ind = int(rising_ind[i]+conv_std_dev) |
||
| 178 | if stop_ind > len(conv_deriv): |
||
| 179 | stop_ind = len(conv_deriv) |
||
| 180 | |||
| 181 | if start_ind == stop_ind: |
||
| 182 | stop_ind = start_ind+1 |
||
| 183 | |||
| 184 | rising_ind[i] = start_ind + np.argmax(conv_deriv_ref[start_ind:stop_ind]) |
||
| 185 | |||
| 186 | # set this position and the surrounding of the saved edge to 0 to |
||
| 187 | # avoid a second detection |
||
| 188 | if rising_ind[i] < 2*conv_std_dev: del_ind_start = 0 |
||
| 189 | else: |
||
| 190 | del_ind_start = rising_ind[i] - 2*conv_std_dev |
||
| 191 | if (conv_deriv.size - rising_ind[i]) < 2*conv_std_dev: |
||
| 192 | del_ind_stop = conv_deriv.size-1 |
||
| 193 | else: |
||
| 194 | del_ind_stop = rising_ind[i] + 2*conv_std_dev |
||
| 195 | conv_deriv[del_ind_start:del_ind_stop] = 0 |
||
| 196 | |||
| 197 | # save the index of the absolute minimum of the derived time trace |
||
| 198 | # as falling edge position |
||
| 199 | falling_ind[i] = np.argmin(conv_deriv) |
||
| 200 | |||
| 201 | # refine the falling edge detection, by using a small and fixed |
||
| 202 | # conv_std_dev parameter to find the inflection point more precise |
||
| 203 | start_ind = int(falling_ind[i]-conv_std_dev) |
||
| 204 | if start_ind < 0: |
||
| 205 | start_ind = 0 |
||
| 206 | |||
| 207 | stop_ind = int(falling_ind[i]+conv_std_dev) |
||
| 208 | if stop_ind > len(conv_deriv): |
||
| 209 | stop_ind = len(conv_deriv) |
||
| 210 | |||
| 211 | if start_ind == stop_ind: |
||
| 212 | stop_ind = start_ind+1 |
||
| 213 | |||
| 214 | falling_ind[i] = start_ind + np.argmin(conv_deriv_ref[start_ind:stop_ind]) |
||
| 215 | |||
| 216 | # set this position and the sourrounding of the saved flank to 0 to |
||
| 217 | # avoid a second detection |
||
| 218 | if falling_ind[i] < 2*conv_std_dev: del_ind_start = 0 |
||
| 219 | else: |
||
| 220 | del_ind_start = falling_ind[i] - 2*conv_std_dev |
||
| 221 | if (conv_deriv.size - falling_ind[i]) < 2*conv_std_dev: |
||
| 222 | del_ind_stop = conv_deriv.size-1 |
||
| 223 | else: |
||
| 224 | del_ind_stop = falling_ind[i] + 2*conv_std_dev |
||
| 225 | conv_deriv[del_ind_start:del_ind_stop] = 0 |
||
| 226 | |||
| 227 | # sort all indices of rising and falling flanks |
||
| 228 | rising_ind.sort() |
||
| 229 | falling_ind.sort() |
||
| 230 | |||
| 231 | # find the maximum laser length to use as size for the laser array |
||
| 232 | laser_length = np.max(falling_ind-rising_ind) |
||
| 233 | |||
| 234 | #Todo: Find better method, here the idea is to take a histogram to find |
||
| 235 | # length of pulses |
||
| 236 | #diff = (falling_ind-rising_ind)[np.where( falling_ind-rising_ind > 0)] |
||
| 237 | #self.histo = np.histogram(diff) |
||
| 238 | #laser_length = int(self.histo[1][self.histo[0].argmax()]) |
||
| 239 | |||
| 240 | # initialize the empty output array |
||
| 241 | laser_arr = np.zeros([num_of_lasers, laser_length],int) |
||
| 242 | # slice the detected laser pulses of the timetrace and save them in the |
||
| 243 | # output array according to the found rising edge |
||
| 244 | for i in range(num_of_lasers): |
||
| 245 | if (rising_ind[i]+laser_length > count_data.size): |
||
| 246 | lenarr = count_data[rising_ind[i]:].size |
||
| 247 | laser_arr[i, 0:lenarr] = count_data[rising_ind[i]:] |
||
| 248 | else: |
||
| 249 | laser_arr[i] = count_data[rising_ind[i]:rising_ind[i]+laser_length] |
||
| 250 | return laser_arr.astype(int) |
||
| 251 | |||
| 320 |