Total Complexity | 65 |
Total Lines | 480 |
Duplicated Lines | 2.08 % |
Changes | 3 | ||
Bugs | 0 | Features | 0 |
Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.
Common duplication problems, and corresponding solutions are:
Complex classes like FastComtec 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 -*- |
||
149 | class FastComtec(Base, FastCounterInterface): |
||
150 | """ |
||
151 | unstable: Jochen Scheuer, Simon Schmitt |
||
152 | |||
153 | Hardware Class for the FastComtec Card. |
||
154 | """ |
||
155 | _modclass = 'FastComtec' |
||
156 | _modtype = 'hardware' |
||
157 | gated = ConfigOption('gated', False, missing='warn') |
||
158 | trigger_safety = ConfigOption('trigger_safety', 200e-9, missing='warn') |
||
159 | aom_delay = ConfigOption('aom_delay', 400e-9, missing='warn') |
||
160 | minimal_binwidth = ConfigOption('minimal_binwidth', 0.25e-9, missing='warn') |
||
161 | |||
162 | def __init__(self, config, **kwargs): |
||
163 | super().__init__(config=config, **kwargs) |
||
164 | |||
165 | self.log.debug('The following configuration was found.') |
||
166 | |||
167 | # checking for the right configuration |
||
168 | for key in config.keys(): |
||
169 | self.log.info('{0}: {1}'.format(key,config[key])) |
||
170 | #this variable has to be added because there is no difference |
||
171 | #in the fastcomtec it can be on "stopped" or "halt" |
||
172 | self.stopped_or_halt = "stopped" |
||
173 | self.timetrace_tmp = [] |
||
174 | |||
175 | def on_activate(self): |
||
176 | """ Initialisation performed during activation of the module. |
||
177 | """ |
||
178 | self.dll = ctypes.windll.LoadLibrary('dp7887.dll') |
||
179 | if self.gated: |
||
180 | self.change_sweep_mode(gated=True) |
||
181 | else: |
||
182 | self.change_sweep_mode(gated=False) |
||
183 | return |
||
184 | |||
185 | |||
186 | def on_deactivate(self): |
||
187 | """ Deinitialisation performed during deactivation of the module. |
||
188 | """ |
||
189 | return |
||
190 | |||
191 | def get_constraints(self): |
||
192 | """ Retrieve the hardware constrains from the Fast counting device. |
||
193 | |||
194 | @return dict: dict with keys being the constraint names as string and |
||
195 | items are the definition for the constaints. |
||
196 | |||
197 | The keys of the returned dictionary are the str name for the constraints |
||
198 | (which are set in this method). |
||
199 | |||
200 | NO OTHER KEYS SHOULD BE INVENTED! |
||
201 | |||
202 | If you are not sure about the meaning, look in other hardware files to |
||
203 | get an impression. If still additional constraints are needed, then they |
||
204 | have to be added to all files containing this interface. |
||
205 | |||
206 | The items of the keys are again dictionaries which have the generic |
||
207 | dictionary form: |
||
208 | {'min': <value>, |
||
209 | 'max': <value>, |
||
210 | 'step': <value>, |
||
211 | 'unit': '<value>'} |
||
212 | |||
213 | Only the key 'hardware_binwidth_list' differs, since they |
||
214 | contain the list of possible binwidths. |
||
215 | |||
216 | If the constraints cannot be set in the fast counting hardware then |
||
217 | write just zero to each key of the generic dicts. |
||
218 | Note that there is a difference between float input (0.0) and |
||
219 | integer input (0), because some logic modules might rely on that |
||
220 | distinction. |
||
221 | |||
222 | ALL THE PRESENT KEYS OF THE CONSTRAINTS DICT MUST BE ASSIGNED! |
||
223 | """ |
||
224 | |||
225 | constraints = dict() |
||
226 | |||
227 | # the unit of those entries are seconds per bin. In order to get the |
||
228 | # current binwidth in seconds use the get_binwidth method. |
||
229 | constraints['hardware_binwidth_list'] = list(self.minimal_binwidth * (2 ** np.array( |
||
230 | np.linspace(0,24,25)))) |
||
231 | constraints['max_sweep_len'] = 6.8 |
||
232 | return constraints |
||
233 | |||
234 | def configure(self, bin_width_s, record_length_s, number_of_gates=0, filename=None): |
||
235 | """ Configuration of the fast counter. |
||
236 | |||
237 | @param float bin_width_s: Length of a single time bin in the time trace |
||
238 | histogram in seconds. |
||
239 | @param float record_length_s: Total length of the timetrace/each single |
||
240 | gate in seconds. |
||
241 | @param int number_of_gates: optional, number of gates in the pulse |
||
242 | sequence. Ignore for not gated counter. |
||
243 | |||
244 | @return tuple(binwidth_s, record_length_s, number_of_gates): |
||
245 | binwidth_s: float the actual set binwidth in seconds |
||
246 | gate_length_s: the actual record length in seconds |
||
247 | number_of_gates: the number of gated, which are accepted, |
||
248 | None if not-gated |
||
249 | """ |
||
250 | |||
251 | # when not gated, record length = total sequence length, when gated, record length = laser length. |
||
252 | # subtract 200 ns to make sure no sequence trigger is missed |
||
253 | record_length_FastComTech_s = record_length_s |
||
254 | if self.gated: |
||
255 | # add time to account for AOM delay |
||
256 | no_of_bins = int((record_length_FastComTech_s + self.aom_delay) / self.set_binwidth(bin_width_s)) |
||
257 | else: |
||
258 | # subtract time to make sure no sequence trigger is missed |
||
259 | no_of_bins = int((record_length_FastComTech_s - self.trigger_safety) / self.set_binwidth(bin_width_s)) |
||
260 | |||
261 | self.set_length(no_of_bins, preset=1, cycles=number_of_gates) |
||
262 | |||
263 | if filename is not None: |
||
264 | self._change_filename(filename) |
||
265 | |||
266 | return (self.get_binwidth(), record_length_FastComTech_s, number_of_gates) |
||
267 | |||
268 | |||
269 | |||
270 | def get_status(self): |
||
271 | """ |
||
272 | Receives the current status of the Fast Counter and outputs it as return value. |
||
273 | 0 = unconfigured |
||
274 | 1 = idle |
||
275 | 2 = running |
||
276 | 3 = paused |
||
277 | -1 = error state |
||
278 | """ |
||
279 | status = AcqStatus() |
||
280 | self.dll.GetStatusData(ctypes.byref(status), 0) |
||
281 | if status.started == 1: |
||
282 | return 2 |
||
283 | elif status.started == 0: |
||
284 | if self.stopped_or_halt == "stopped": |
||
285 | return 1 |
||
286 | elif self.stopped_or_halt == "halt": |
||
287 | return 3 |
||
288 | else: |
||
289 | self.log.error('FastComTec neither stopped nor halt') |
||
290 | |||
291 | return -1 |
||
292 | else: |
||
293 | self.log.error( |
||
294 | 'There is an unknown status from FastComtec. The status message was %s' % (str(status.started))) |
||
295 | return -1 |
||
296 | |||
297 | def get_current_runtime(self): |
||
298 | """ |
||
299 | Returns the current runtime. |
||
300 | @return float runtime: in s |
||
301 | """ |
||
302 | status = AcqStatus() |
||
303 | self.dll.GetStatusData(ctypes.byref(status), 0) |
||
304 | return status.runtime |
||
305 | |||
306 | def get_current_sweeps(self): |
||
307 | """ |
||
308 | Returns the current runtime. |
||
309 | @return int sweeps: in sweeps |
||
310 | """ |
||
311 | status = AcqStatus() |
||
312 | self.dll.GetStatusData(ctypes.byref(status), 0) |
||
313 | return status.sweeps |
||
314 | |||
315 | def start_measure(self): |
||
316 | """Start the measurement. """ |
||
317 | status = self.dll.Start(0) |
||
318 | while self.get_status() != 2: |
||
319 | time.sleep(0.05) |
||
320 | return status |
||
321 | |||
322 | def pause_measure(self): |
||
323 | """Make a pause in the measurement, which can be continued. """ |
||
324 | self.stopped_or_halt = "halt" |
||
325 | status = self.dll.Halt(0) |
||
326 | while self.get_status() != 3: |
||
327 | time.sleep(0.05) |
||
328 | |||
329 | if self.gated: |
||
330 | self.timetrace_tmp = self.get_data_trace() |
||
331 | return status |
||
332 | |||
333 | def stop_measure(self): |
||
334 | """Stop the measurement. """ |
||
335 | self.stopped_or_halt = "stopped" |
||
336 | status = self.dll.Halt(0) |
||
337 | while self.get_status() != 1: |
||
338 | time.sleep(0.05) |
||
339 | |||
340 | if self.gated: |
||
341 | self.timetrace_tmp = [] |
||
342 | return status |
||
343 | |||
344 | def continue_measure(self): |
||
345 | """Continue a paused measurement. """ |
||
346 | if self.gated: |
||
347 | status = self.start_measure() |
||
348 | else: |
||
349 | status = self.dll.Continue(0) |
||
350 | while self.get_status() != 2: |
||
351 | time.sleep(0.05) |
||
352 | return status |
||
353 | |||
354 | def get_binwidth(self): |
||
355 | """ Returns the width of a single timebin in the timetrace in seconds. |
||
356 | |||
357 | @return float: current length of a single bin in seconds (seconds/bin) |
||
358 | |||
359 | The red out bitshift will be converted to binwidth. The binwidth is |
||
360 | defined as 2**bitshift*minimal_binwidth. |
||
361 | """ |
||
362 | return self.minimal_binwidth*(2**int(self.get_bitshift())) |
||
363 | |||
364 | |||
365 | def is_gated(self): |
||
366 | """ Check the gated counting possibility. |
||
367 | |||
368 | @return bool: Boolean value indicates if the fast counter is a gated |
||
369 | counter (TRUE) or not (FALSE). |
||
370 | """ |
||
371 | return self.gated |
||
372 | |||
373 | def get_data_trace(self): |
||
374 | """ |
||
375 | Polls the current timetrace data from the fast counter and returns it as a numpy array (dtype = int64). |
||
376 | The binning specified by calling configure() must be taken care of in this hardware class. |
||
377 | A possible overflow of the histogram bins must be caught here and taken care of. |
||
378 | If the counter is UNgated it will return a 1D-numpy-array with returnarray[timebin_index] |
||
379 | If the counter is gated it will return a 2D-numpy-array with returnarray[gate_index, timebin_index] |
||
380 | |||
381 | @return arrray: Time trace. |
||
382 | """ |
||
383 | setting = AcqSettings() |
||
384 | self.dll.GetSettingData(ctypes.byref(setting), 0) |
||
385 | N = setting.range |
||
386 | |||
387 | if self.gated: |
||
388 | bsetting=AcqSettings() |
||
389 | self.dll.GetSettingData(ctypes.byref(bsetting), 0) |
||
390 | H = bsetting.cycles |
||
391 | data = np.empty((H, int(N / H)), dtype=np.uint32) |
||
392 | |||
393 | else: |
||
394 | data = np.empty((N,), dtype=np.uint32) |
||
395 | |||
396 | p_type_ulong = ctypes.POINTER(ctypes.c_uint32) |
||
397 | ptr = data.ctypes.data_as(p_type_ulong) |
||
398 | self.dll.LVGetDat(ptr, 0) |
||
399 | time_trace = np.int64(data) |
||
400 | |||
401 | if self.gated and self.timetrace_tmp != []: |
||
402 | time_trace = time_trace + self.timetrace_tmp |
||
403 | |||
404 | return time_trace |
||
405 | |||
406 | |||
407 | def get_data_testfile(self): |
||
408 | ''' Load data test file ''' |
||
409 | data = np.loadtxt(os.path.join(get_main_dir(), 'tools', 'FastComTec_demo_timetrace.asc')) |
||
410 | time.sleep(0.5) |
||
411 | return data |
||
412 | |||
413 | |||
414 | # ========================================================================= |
||
415 | # Non Interface methods |
||
416 | # ========================================================================= |
||
417 | |||
418 | def get_bitshift(self): |
||
419 | """Get bitshift from Fastcomtec. |
||
420 | |||
421 | @return int settings.bitshift: the red out bitshift |
||
422 | """ |
||
423 | |||
424 | settings = AcqSettings() |
||
425 | self.dll.GetSettingData(ctypes.byref(settings), 0) |
||
426 | return int(settings.bitshift) |
||
427 | |||
428 | def set_bitshift(self, bitshift): |
||
429 | """ Sets the bitshift properly for this card. |
||
430 | |||
431 | @param int bitshift: |
||
432 | |||
433 | @return int: asks the actual bitshift and returns the red out value |
||
434 | """ |
||
435 | |||
436 | cmd = 'BITSHIFT={0}'.format(bitshift) |
||
437 | self.dll.RunCmd(0, bytes(cmd, 'ascii')) |
||
438 | return self.get_bitshift() |
||
439 | |||
440 | def set_binwidth(self, binwidth): |
||
441 | """ Set defined binwidth in Card. |
||
442 | |||
443 | @param float binwidth: the current binwidth in seconds |
||
444 | |||
445 | @return float: Red out bitshift converted to binwidth |
||
446 | |||
447 | The binwidth is converted into to an appropiate bitshift defined as |
||
448 | 2**bitshift*minimal_binwidth. |
||
449 | """ |
||
450 | bitshift = int(np.log2(binwidth/self.minimal_binwidth)) |
||
451 | new_bitshift=self.set_bitshift(bitshift) |
||
452 | |||
453 | return self.minimal_binwidth*(2**new_bitshift) |
||
454 | |||
455 | |||
456 | #TODO: Check such that only possible lengths are set. |
||
457 | def set_length(self, length_bins, preset=None, cycles=None, sequences=None): |
||
458 | """ Sets the length of the length of the actual measurement. |
||
459 | |||
460 | @param int length_bins: Length of the measurement in bins |
||
461 | |||
462 | @return float: Red out length of measurement |
||
463 | """ |
||
464 | constraints = self.get_constraints() |
||
465 | if length_bins * self.get_binwidth() < constraints['max_sweep_len']: |
||
466 | cmd = 'RANGE={0}'.format(int(length_bins)) |
||
467 | self.dll.RunCmd(0, bytes(cmd, 'ascii')) |
||
468 | cmd = 'roimax={0}'.format(int(length_bins)) |
||
469 | self.dll.RunCmd(0, bytes(cmd, 'ascii')) |
||
470 | if preset != None: |
||
471 | cmd = 'swpreset={0}'.format(preset) |
||
472 | self.dll.RunCmd(0, bytes(cmd, 'ascii')) |
||
473 | if cycles != None and cycles != 0: |
||
474 | cmd = 'cycles={0}'.format(cycles) |
||
475 | self.dll.RunCmd(0, bytes(cmd, 'ascii')) |
||
476 | if sequences != None and sequences != 0: |
||
477 | cmd = 'sequences={0}'.format(sequences) |
||
478 | self.dll.RunCmd(0, bytes(cmd, 'ascii')) |
||
479 | return self.get_length() |
||
480 | else: |
||
481 | self.log.error( |
||
482 | 'Length of sequence is too high: %s' % (str(length_bins * self.get_binwidth()))) |
||
483 | return -1 |
||
484 | |||
485 | def set_preset(self, preset): |
||
486 | cmd = 'swpreset={0}'.format(preset) |
||
487 | self.dll.RunCmd(0, bytes(cmd, 'ascii')) |
||
488 | return preset |
||
489 | |||
490 | def set_cycles(self, cycles): |
||
491 | cmd = 'cycles={0}'.format(cycles) |
||
492 | self.dll.RunCmd(0, bytes(cmd, 'ascii')) |
||
493 | return cycles |
||
494 | |||
495 | def get_length(self): |
||
496 | """ Get the length of the current measurement. |
||
497 | |||
498 | @return int: length of the current measurement |
||
499 | """ |
||
500 | setting = AcqSettings() |
||
501 | self.dll.GetSettingData(ctypes.byref(setting), 0) |
||
502 | return int(setting.range) |
||
503 | |||
504 | def _change_filename(self,name): |
||
505 | """ Changed the name in FCT""" |
||
506 | cmd = 'datname=%s'%name |
||
507 | self.dll.RunCmd(0, bytes(cmd, 'ascii')) |
||
508 | return name |
||
509 | |||
510 | def change_sweep_mode(self, gated): |
||
511 | if gated: |
||
512 | cmd = 'sweepmode={0}'.format(hex(1978500)) |
||
513 | self.dll.RunCmd(0, bytes(cmd, 'ascii')) |
||
514 | cmd = 'prena={0}'.format(hex(16)) #To select starts preset |
||
515 | # cmd = 'prena={0}'.format(hex(4)) #To select sweeps preset |
||
516 | self.dll.RunCmd(0, bytes(cmd, 'ascii')) |
||
517 | self.gated = True |
||
518 | else: |
||
519 | View Code Duplication | # fastcomtch standard settings for ungated acquisition (check manual) |
|
520 | cmd = 'sweepmode={0}'.format(hex(1978496)) |
||
521 | self.dll.RunCmd(0, bytes(cmd, 'ascii')) |
||
522 | cmd = 'prena={0}'.format(hex(0)) |
||
523 | self.dll.RunCmd(0, bytes(cmd, 'ascii')) |
||
524 | self.gated = False |
||
525 | return gated |
||
526 | |||
527 | |||
528 | def change_save_mode(self, mode): |
||
529 | """ Changes the save mode of p7887 |
||
530 | |||
531 | @param int mode: Specifies the save mode (0: No Save at Halt, 1: Save at Halt, |
||
532 | 2: Write list file, No Save at Halt, 3: Write list file, Save at Halt |
||
533 | |||
534 | @return int mode: specified save mode |
||
535 | """ |
||
536 | cmd = 'savedata={0}'.format(mode) |
||
537 | self.dll.RunCmd(0, bytes(cmd, 'ascii')) |
||
538 | return mode |
||
539 | |||
540 | def set_delay_start(self, delay_s): |
||
541 | """ Sets the record delay length |
||
542 | |||
543 | @param int delay_s: Record delay after receiving a start trigger |
||
544 | |||
545 | @return int mode: specified save mode |
||
546 | """ |
||
547 | |||
548 | # A delay can only be adjusted in steps of 6.4ns |
||
549 | delay_bins = np.rint(delay_s / 6.4e-9 /2.5) |
||
550 | cmd = 'fstchan={0}'.format(int(delay_bins)) |
||
551 | self.dll.RunCmd(0, bytes(cmd, 'ascii')) |
||
552 | return delay_bins |
||
553 | |||
554 | def get_delay_start(self): |
||
555 | """ Returns the current record delay length |
||
556 | |||
557 | @return float delay_s: current record delay length in seconds |
||
558 | """ |
||
559 | bsetting = AcqSettings() |
||
560 | self.dll.GetSettingData(ctypes.byref(bsetting), 0) |
||
561 | delay_s = bsetting.fstchan * 6.4e-9 *2.5 |
||
562 | #prena = bsetting.prena |
||
563 | return delay_s |
||
564 | |||
565 | # ========================================================================= |
||
566 | # The following methods have to be carefully reviewed and integrated as |
||
567 | # internal methods/function, because they might be important one day. |
||
568 | # ========================================================================= |
||
569 | |||
570 | def SetDelay(self, t): |
||
571 | #~ setting = AcqSettings() |
||
572 | #~ self.dll.GetSettingData(ctypes.byref(setting), 0) |
||
573 | #~ setting.fstchan = t/6.4 |
||
574 | #~ self.dll.StoreSettingData(ctypes.byref(setting), 0) |
||
575 | #~ self.dll.NewSetting(0) |
||
576 | self.dll.RunCmd(0, 'DELAY={0:f}'.format(t)) |
||
577 | return self.GetDelay() |
||
578 | |||
579 | def GetDelay(self): |
||
580 | setting = AcqSettings() |
||
581 | self.dll.GetSettingData(ctypes.byref(setting), 0) |
||
582 | return setting.fstchan * 6.4 |
||
583 | |||
584 | |||
585 | #former SaveData_fast |
||
586 | def SaveData_locally(self, filename, laser_index): |
||
587 | # os.chdir(r'D:\data\FastComTec') |
||
588 | data = self.get_data() |
||
589 | fil = open(filename + '.asc', 'w') |
||
590 | for i in laser_index: |
||
591 | for n in data[i:i+int(round(3000/(self.minimal_binwidth*2**self.GetBitshift()))) |
||
592 | +int(round(1000/(self.minimal_binwidth*2**self.GetBitshift())))]: |
||
593 | fil.write('{0!s}\n'.format(n)) |
||
594 | fil.close() |
||
595 | |||
596 | def SetLevel(self, start, stop): |
||
597 | setting = AcqSettings() |
||
598 | self.dll.GetSettingData(ctypes.byref(setting), 0) |
||
599 | def FloatToWord(r): |
||
600 | return int((r+2.048)/4.096*int('ffff',16)) |
||
601 | setting.dac0 = ( setting.dac0 & int('ffff0000',16) ) | FloatToWord(start) |
||
602 | setting.dac1 = ( setting.dac1 & int('ffff0000',16) ) | FloatToWord(stop) |
||
603 | self.dll.StoreSettingData(ctypes.byref(setting), 0) |
||
604 | self.dll.NewSetting(0) |
||
605 | return self.GetLevel() |
||
606 | |||
607 | def GetLevel(self): |
||
608 | setting = AcqSettings() |
||
609 | self.dll.GetSettingData(ctypes.byref(setting), 0) |
||
610 | def WordToFloat(word): |
||
611 | return (word & int('ffff',16)) * 4.096 / int('ffff',16) - 2.048 |
||
612 | return WordToFloat(setting.dac0), WordToFloat(setting.dac1) |
||
613 | |||
614 | #used in one script for SSR |
||
615 | #Todo: Remove |
||
616 | def Running(self): |
||
617 | s = self.GetStatus() |
||
618 | return s.started |
||
619 | |||
620 | def GetStatus(self): |
||
621 | status = AcqStatus() |
||
622 | self.dll.GetStatusData(ctypes.byref(status), 0) |
||
623 | return status |
||
624 | |||
625 | |||
626 | def load_setup(self, configname): |
||
627 | cmd = 'loadcnf={0}'.format(configname) |
||
628 | self.dll.RunCmd(0, bytes(cmd, 'ascii')) |