diff --git a/FastComtecMCS8A/MCS8A_default_config.SET b/FastComtecMCS8A/MCS8A_default_config.SET new file mode 100644 index 0000000000000000000000000000000000000000..2f611f43cd471e4d9f940bbc50c65e51fb3d7425 --- /dev/null +++ b/FastComtecMCS8A/MCS8A_default_config.SET @@ -0,0 +1,65 @@ +[MCS8A A] 208 FW 3.104 SV 1.243 +range=89408 +periods=2 +sweepmode=22fc3088 +fstchan=0 +holdafter=0 +streamstatus=0 +calreg0=0 +calreg1=0 +calreg2=0 +calreg3=0 +swpreset=1 +prena=6 +syncout=0 +cycles=2 +sequences=1 +tagbits=16 +vdac0=1079c +vdac1=479c +vdac2=43b4 +vdac3=3b4 +vdac4=3b4 +vdac5=3b4 +vdac6=3b4 +vdac7=3b4 +timepreset=0.000 +digio=0 +digval=0 +autoinc=0 +savedata=2 +mpafmt=dat +sephead=1 +fmt=dat +smoothpts=5 +wndwidth=163 +wndheight=286 +sysdef=0 +[CHN1] +range=89408 +active=1 +bitshift=18 +cftfak=2580100 +evpreset=10 +roimin=0 +roimax=256 +caloff=0.000000 +calfact=6710886.400000 +calfact2=0 +calfact3=0 +calunit=nsec +caluse=1 +[CHN2] +range=89408 +active=1 +bitshift=18 +cftfak=2580100 +evpreset=10 +roimin=0 +roimax=200 +caloff=0.000000 +calfact=6710886.400000 +calfact2=0 +calfact3=0 +calunit=nsec +caluse=1 diff --git a/FastComtecMCS8A/__init__.py b/FastComtecMCS8A/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/FastComtecMCS8A/blacs_tabs.py b/FastComtecMCS8A/blacs_tabs.py new file mode 100644 index 0000000000000000000000000000000000000000..dabedede841b5ffa4516458792506db36cc88a52 --- /dev/null +++ b/FastComtecMCS8A/blacs_tabs.py @@ -0,0 +1,23 @@ +from blacs.device_base_class import DeviceTab + +class MCS8ATab(DeviceTab): + + def initialise_workers(self): + self.event_queue.logging_enabled =True + + connection_table = self.settings['connection_table'] + props = connection_table.find_by_name(self.device_name).properties + + self.create_worker( + 'main_worker', + 'user_devices.FastComtecMCS8A.blacs_workers.MCS8AWorker', + { + "config_path": props["config_path"] + }, + ) + self.primary_worker = 'main_worker' + + def initialise_GUI(self): + widgets,_,_ = self.auto_create_widgets() + self.supports_smart_programming(True) + \ No newline at end of file diff --git a/FastComtecMCS8A/blacs_workers.py b/FastComtecMCS8A/blacs_workers.py new file mode 100644 index 0000000000000000000000000000000000000000..538e683c99d7c0ec6a2fd4f82e86687bea54afbd --- /dev/null +++ b/FastComtecMCS8A/blacs_workers.py @@ -0,0 +1,108 @@ +##################################################################### +# # +# /user_devices/FastComtecMCS8A/blacs_workers.py # +# # +# Jan 2025, Leolab Cavity # +# # +# # +##################################################################### + +from . import fastcomtec_api +from blacs.tab_base_classes import Worker +from labscript_utils.labconfig import LabConfig +import os +import labscript_utils.h5_lock +import h5py +import numpy as np +import time +from datetime import datetime + +class MCS8AWorker(Worker): + + def init(self): + self.base_dir = LabConfig().get("DEFAULT","experiment_shot_storage") + "\\MCS8A" + os.makedirs(self.base_dir, exist_ok=True) # create MCS8A folder if it doesn't exist yet + + self.device = fastcomtec_api.FastComTec() + # print("Status: ", self.device.get_acq_status()) + if getattr(self.device.get_acq_status(),"started") == 1: + time.sleep(1) + self.device.halt_measurement() + self.dataindex=0 + + # load (default) config + if not os.path.realpath(self.config_path) == self.config_path.replace("/","\\"): + self.config_path = os.path.dirname(os.path.realpath(__file__)) + "\\" + self.config_path + self.config_path = self.config_path.replace("\\", "/") + print("Load config...", self.config_path) + self.device.load_config(self.config_path) + + + # Save data only in list file and in binary format + self.device._send_dll_command("mpafmt=dat") + self.device._send_dll_command("savedata=2") + + # Set how long each measurement lasts, i.e. how ofen a new file is started. + # We don't want it too long, because then the single files get huge, but we also don't + # want it too often, because each stopping and starting takes some time. + self.maxduration = 600 # seconds + self.device._send_dll_command("bitshift=18") # 400ps * 2^0x18 = 0.0067 s + range_int = np.ceil(self.maxduration / (400e-12 * 2**0x18) / 64) * 64 + self.device._send_dll_command(f"range={int(range_int)}") + + def get_new_mpapath(self): + mpaname = self.device_name + "_" + datetime.now().strftime("%Y-%d-%m_%H-%M-%S") + ".mpa" + return self.base_dir + "\\" + mpaname + + def transition_to_buffered(self, device_name, h5_file, initial_values, fresh): + # If device is already started, we leave it running and just save the datapath and index in the h5file. + # If the device is not running, or already longer than the defined duration, send the stop command, set new datafile and start new measurement. + # TODO: How to handle parameters (start new measurment when they changed, or treat parameters statically) + with h5py.File(h5_file,'r') as f: + stop_time = f["devices/" + f["connection table"].attrs["master_pseudoclock"]].attrs["stop_time"] + if getattr(self.device.get_acq_status(),"started") != 1 or time.perf_counter()-getattr(self,"start_time",-self.maxduration)>self.maxduration-stop_time-3: + if self.dataindex==0: + self.device.halt_measurement() + while getattr(self.device.get_acq_status(),"started") == 1: + # If the current measurement doesn't have enought time left for this shot, wait until measurment ends. + time.sleep(0.1) + print("Wait until last measurement stopped...", end="\r") + self.device.halt_measurement() + mpapath = self.get_new_mpapath() + self.device._send_dll_command(f"mpaname={mpapath}") + self.datapath = mpapath.replace(".mpa",".lst") + self.dataindex = 0 + print("Set filename...", self.datapath.split("\\")[-1]) + + # self.device.erase_measruement() # Don't erase because this takes super long for some reason + self.device.start_measurement() + self.start_time = time.perf_counter() + time.sleep(2) # some buffer time to make sure the measurement already started for sure + # TODO: What's the minimum time to wait here? + + # Save where to find the measurement data (which file and within file) + with h5py.File(h5_file,'r+') as f: + group = f[f"devices/{self.device_name}"] + group.attrs["datapath"] = self.datapath + group.attrs["dataindex"] = self.dataindex + self.dataindex += group.attrs["n_trigger"] + + return {} + + def transition_to_manual(self): + return True + + def program_manual(self, values): + return {} + + def check_remote_values(self): + return True + + def abort_buffered(self): + return self.transition_to_manual() + + def abort_transition_to_buffered(self): + return True + + def shutdown(self): + self.device._send_dll_command("exit") \ No newline at end of file diff --git a/FastComtecMCS8A/docs/FastComTec_Doc_From_Manual.txt b/FastComtecMCS8A/docs/FastComTec_Doc_From_Manual.txt new file mode 100644 index 0000000000000000000000000000000000000000..afc8b50e8adea7d5c7d2b822cc4d9368d583457a --- /dev/null +++ b/FastComtecMCS8A/docs/FastComTec_Doc_From_Manual.txt @@ -0,0 +1,204 @@ +[MCS8A] 1004 ; Device and serial number +range=4096 ; Spectrum length +periods=2 ; Number of periods for folding +fstchan=0 ; acquisition delay = number of first bin / 64 +holdafter=0 ; hold after sweep in units of 64 basic dwelltimes +sweepmode=227ea080 ; (hex) sweepmode & 0xF: 0 = normal, + ; 1=differential (relative to first stop in sweep) + ; 4=sequential + ; 5=seq.+diff (Ch1), bit0 = differential mode + ; 9=differential to stop in Ch2, bit3 = Ch2 ref (diff.mode) + ; 0xF = Corr.+diff (Ch2) + ; bit 4: Softw. Start + ; bit 5: "Don't show" tagbits + ; bit 6: Endless + ; bit 7: Start event generation + ; bit 8: Enable Tag bits + ; bit 9: start with rising edge + ; bit 10: time under threshold for pulse width + ; bit 11: pulse width mode for any spectra with both edges enabled + ; bit 12: abandon Sweepcounter in Data + ; bit 13: "one-hot" mode with tagbits + ; bit 14: start ref (diff.mode) (MCS8) + ; bit 15: MCS8, MCS8A1: enable start input sampling + ; bit 16..bit 22 ~(input channel enable) + ; MCS8A: bit 16..bit 23 ~(input channel enable) + ; bit 26: 100 ps time resolution instead of 80 ps + ; bit 27: Folded + ; bit 28: Interleaved + ; bit 29: MCS8A: don't use trigger, use CH1 to start sweep + ; bit 30: Use only 1 decoder +calreg0=fe00 ; result of input delay calibration, l + ; lower 8 bits: start channel (always 0), + ; higher 8 bits, first channel after start +calreg1=0 ; second, third channel +calreg2=0 ; .. +calreg3=0 ; .. +swpreset=1000000 ; Sweep-Preset value +prena=4 ; bit 0: realtime preset enabled + ; bit 1: + ; bit 2: sweep preset enabled + ; bit 3: ROI preset enabled + ; bit 4: Starts preset enabled + ; bit 5: ROI2 preset enabled + ; bit 6: ROI3 preset enabled + ; bit 7: ROI4 preset enabled + ; bit 8: ROI5 preset enabled + ; bit 9: ROI6 preset enabled + ; bit 10: ROI7 preset enabled + ; bit 11: ROI8 preset enabled +syncout=0 ; LOWORD: sync out; bit 0..5 NIM syncout, bit 8..13 TTL syncout + ; bit7: NIM syncout_invert, bit15: TTL syncout_invert + ; 0="0", 1=5 MHz, 2=50 MHz, 3=100 MHz, 4=1/(80 ps or 100 ps * 64), + ; 5=1/(80 ps or 100 ps * 32), 6=PLL ref clock, 7=Input 0(Start), 8=Input 1, + ; 9=Input 2, 10..13=Input 3..7, 15=GO in, 16=Start_of_sweep, 17=Armed, + ; 18=SWEEP_ON, 19=WINDOW, 20=HOLD_OFF, 21=EOS_DEADTIME + ; 22=TIME[0],...,51=TIME[29], 52...63=SWEEP[0]..SWEEP[11] +cycles=18 ; cycles for sequential mode +sequences=1 ; for sequential mode (default 1) + ; specifies how often to repeat + ; after performing cycles acquisitions +tagbits=16 ; number of tagbits +vdac0=8fa ; (hex) dac0 value (start) voltage=2.048 * (1-vdac/2048) + ; bit 16: start with rising edge + ; (bit 14, 15) : 0=falling, 1=rising, 2=both, 3=both+CFT +vdac1=8fa ; dac1 value +vdac2=8fa ; dac2 value +vdac3=8fa ; ... +vdac4=8fa ; ... +vdac5=8fa ; ... +vdac6=8fa ; ... +vdac7=8fa ; …dac7 value +dac0v=-0.2 ; dac0 value = -0.2 V (start) +dac1v=-0.2 ; dac1 value = -0.2 V (STOP1) +.. +dac7v=-0.2 ; dac7 value in Volt +dacv=-0.1 ; set dac1..dac7 to -0.1 V, all dacs without dac0 (start) +dacv+=0.001 ; increase all dac values without dac0 by 1 mV +dac0v+=0.001 ; increase dac0 value by 1 mV (start) +rtpreset=20.000 ; Timepreset (seconds) +digio=0 ; LOWORD: Use of Dig I/O, GO Line: + ; bit 0: status dig 0..3 + ; bit 1: Output digval and increment digval after stop + ; bit 2: Invert polarity + ; bit 3: Push-Pull output, not possible + ; bit 4: Start with Input Dig 4 + ; bit 5: Start with Input GO + ; bit 8: GOWATCH + ; bit 9: GO High at Start + ; bit 10: GO Low at Stop + ; bit 11: Clear at triggered start + ; bit 12: Only triggered start +digval=0 ; digval=0..255 value for samplechanger +autoinc=0 ; 1=Enable auto increment of filename +savedata=0 ; 0=No Save at Halt + ; 1=Save at Halt + ; 2=Write list file, no save at Halt + ; 3=Write lit file, Save at Halt +mpafmt=asc ; data fromat used in MPA files + ; (dat=binary, asc=ASCII, csv=CSV) +sephead=0 ; 1=Seperated Header file (extension .MP) and + ; Data file (extension dat, asc or spe) for seperated spectra +fmt=asc ; data format used in seperated spectra (extension .MP) + ; (dat=binary, asc=ASCII, csv=CSV) +smoothpts=5 ; number of points to average for a smooth operation +wndwidth=155 ; width of server window +wndheight=273 ; height of server window +sysdef=0 ; System definition word: + ; bit0=0, bit1=0: dev#0 in system 1 + ; bit0=1, bit1=0: dev#0 in system 2 + ; bit0=0, bit1=1: dev#0 in system 3 + ; bit0=1, bit1=1: dev#0 in system 4 + ; ... + ; bit6=1, bit7=1: dev#3 in system 4 + ; bit 31: any preset stops all + +[CHN1] ; the following section concerns parameters of CHN1 +range=4096 ; Spectrum length +active=1 ; Spectrum definition words for CHN1..8: + ; active & 0xF ==0 not used + ; ==1..4 enabled, system 1..4 + ; bit 8: Enable Tag bits + ; bit 9: start with rising edge + ; bit 10: time under threshold for pulse width + ; bit 11: pulse width mode for any spectra with both edges enabled + ; Spectrum definition words for calc. Spectra: + ; active & 0xF ==3 MAP, ((x-xoffs)>>xsh) x ((y-yoffs)>>ysh) + ; bit4=1: x zoomed MAP + ; bit5=1: y zoomed MAP + ; ==5 SUM, (x + y)>>xsh + ; ==6 DIFF,(x - y + range)>>xsh + ; ==7 ANY, (for compare) + ; ==8 COPY, x + ; ==10 SW-HIS, Sweep History + ; bit 8..11 xsh, bit 12..15 ysh or bit 8..15 xsh + ; HIWORD(active) = condition no. (0=no condition) +bitshift=0 ; LOWORD: Binwidth = 2 ^ (bitshift) + ; HIWORD: Threshold for Coinc +cftfak=2580100 ; LOWORD: 256 * cft factor (t_after_peak / t_to_peak) + ; HIWORD: max pulse width for CFT +evpreset=10 ; ROI preset value +roimin=0 ; lower ROI limit +roimax=4096 ; upper limit: roimin <= channel < roimax +caloff=0.000000 ; calibration parameter: offset +calfact=0.080000 ; calibration parameter: factor +calfact2=0 +calfact3=0 +calunit=nsec ; calibration unit +caluse=1 ; bit 0=1: use calibration, higher bits: calibration formula +roi=0 9986 ;starts ; rectangular ROI from 0 to 9986 with name “starts†(lower left, upper right corner) + +[CHN2] ; the following section concerns parameters of CHN2 +... + +The following commands perform actions and therefore usually are not included in the MCS8A.SET file: + +start ; Clears the data and starts a new acquisition of system 1. + ; Further execution of the .CTL file is suspended until any + ; acquisition stops due to a preset. +halt ; Stops acquisition of system 1 if one is running. +cont ; Continues acquisition of system 1. If a time preset + ; is already reached, the time preset is prolongated + ; by the value which was valid when the “start“ command + ; was executed. Further execution of the .CTL file + ; is suspended (see start). +erase ; Clears all spectra of system 1. +savecnf ; Writes the settings into MCS8A.SET +mpaname=filename ; Defines the mpa filename +savempa ; Saves all configuration and spectra data. + ; An existing file is overwritten. +pushname ; pushes the actual mpa filename on an internal stack that can hold 4 names +popname ; pops the last mpa filename from the internal stack +loadmpa ; Loads mpa data; the filename + ; must be specified before with a command mpaname=... +addmpa ; Adds mpa data to actual spectra; the filename + ; must be specified before with a command mpaname=... +submpa ; Subtracts mpa data from actual spectra; the filename + ; must be specified before with a command mpaname=... +MC_A ; Sets actual input channel to MC_A (STOP1) for the rest of + ; the control file. +MC_B ; Sets actual multichannel analyzer to MC_B (STOP2) + ; … MC_H (ch8) +savedat ; Saves data of actual channel as separated + ; spectrum (extension .MP) An existing file + ; is overwritten. +loaddat ; Loads data of actual channel, the filename + ; must be specified before with a command datname=... +adddat ; Adds data into actual spectra; the filename + ; must be specified before with a command datname=... +subdat ; Subtracts data from actual spectra channel; the filename + ; must be specified before with a command datname=... +smooth ; Smoothes the data in actual spectra +erasedat ; Clears the data of actual spectra. +exit ; Exits the server (and MPANT) programs +alert Message ; Displays a Messagebox containing Message and an OK + ; button that must be pressed before execution can continue. +waitinfo 5000 Message ; Displays a Messagebox containing Message, an OK + ; and an END button. After the specified time (5000 msec) + ; the Messagebox vanishes and execution continues. OK + ; continues immediately, END escapes execution. +beep * ; Makes a beep. The character '*' may be replaced with '?', '!' or + ; left empty. The corresponding sound is defined in the WIN.INI + ; file in the [sounds] section. +delay 4000 ; Waits specified time (4000 msec = 4 sec). +pulse 100 ; Outputs a pulse of 100 ms duration at dig 3 \ No newline at end of file diff --git a/FastComtecMCS8A/docs/Fastcomtec_parameter_notes.md b/FastComtecMCS8A/docs/Fastcomtec_parameter_notes.md new file mode 100644 index 0000000000000000000000000000000000000000..691701ce8c711697ff125da5c7d1b6181ce35e36 --- /dev/null +++ b/FastComtecMCS8A/docs/Fastcomtec_parameter_notes.md @@ -0,0 +1,138 @@ +# Basic Modes of Operation + +## 1. Stop after sweep mode (p.35f) +When the MCS8A is armed it waits for a START input signal. When one occurs the sweep is started / +triggered, meaning the time starts to count. Now the arrival times of the STOP input signals relative to the +start are acquired on all channels that are enabled. A STOP event can be either a falling or a rising edge or +both. Even further signals into the START input can be acquired. Since the type of edge is detected and +marked in the acquired data even Time-over-Threshold or pulse width measurements can be accomplished. + +The data acquisition can be ended after a given time via the Time preset. In the edit field Range the length +of the spectrum can be entered. A Bin width of 1 means the highest time resolution. The Bin width can be +chosen in powers of 2 up to 1677216 times the elementary dwell time. If an Acq. Delay is specified, data are +acquired in a sweep not before the specified time. Hold after sweep allows to wait a specified time after a +sweep before the next sweep can be started. + +### 1.1. Start of measurement +#### 1.1.1. Endless mode (p.44) +mode of the measurement can be endless if the corresponding checkbox is crossed (in settings) +#### 1.1.2. Software start (p.44) +Doesn't require a start signal (checkbox in settings dialogue) + +### 1.2. Termination of measurement +terminated by any of the preset (2) conditions +#### 1.2.1. Time preset (total acquisition time) - THIS +in steps of 2.56 ns - total measurement time (in the document: "sweep and so the data acquisition ends after that time" but with sweep preset you can get multiple sweeps. So this is more like a total measurement time) +#### 1.2.2. Sweep preset (number of sweeps)- THIS ++ 48-bit sweep counter counts the real start of a new sweep rather than the completion of the sweeps ++ preset enabled: after preselected number of sweeps any further start of a sweep is prohibited +#### 1.2.3. ROI preset (number of counts) - THIS +Stop measurement after certain number of counts. ROI Preset +### 1.3. Single sweep settings +#### 1.3.1. Acquisiton delay (counting delay after START) - THIS +in steps of 2.56 ns - accept only STOP events (= signals) that arrive after the selected delay +#### 1.3.2. Length of a single sweep - THIS +The Sweep length is defined by the Range times the Bin Width. +#### 1.3.3. Hold off time (pause time after sweep)- THIS +in steps of 2.56 ns - allows new start only after this additional time has elapsed after the end of the sweep +#### 1.3.4. End of sweep dead-time - THIS +< 50ns +#### 1.3.5. Selection range lmits - THIS +Acq.Delay + TimeRange + Hold-Off ≤ 248 x 2.56 ns = 8.3 days max. + + + +## 2. Tagged spectra acquisiton (p.36) +multi detector experiment: measure which detector has fired (time resolution 5.12ns), still maintain 80 ps bin width + + + +# Measurement Examples +## 3. Pulse width measurement (p.45) +As the edge information is contained in bit 4 of the data (a 1 in that bit means falling edge) it is possible to +distinguish stop events from rising and falling signals and it is possible to analyze the pulse width of the +signals, if Both Edges are chosen. There is a choice between "Over threshold" or "Under threshold" for +the Pulse width analyzing, depending on the corresponding setting in the "inputs" dialog. To see the events +from rising and falling signals separated, just enable Pulse width in the settings dialog and set y-Range to 2 +inside the box labeled â€2D spectraâ€. You will get a two dimensional spectra with a y-dimension of 2, for y=0 it +contains the stop events from rising edges and for y=1 from falling edges. You can see here the separation +time between both edges. If you set then the y-Range to a value larger than the maximum pulse width in +channels, you will get a 2-dimensional spectra with the time of the first edge as x coordinate and the pulse +width as y coordinate (see chapter 2.5.4). (There is more text in the document ...) + +# Input Settings + +## 4. ROI Preset (p.44) +ROI Preset checkboxes marked: measurement stopped after acquiring more events than specified (events are counted only if they are within the ROI limits, i.e. >= the lower limit and < the upper limit) + +## 5. Treshold voltage (p.44) - THIS ++ Fast NIM (-0.3 V) or customized ++ Falling or Rising Edge ++ See "Measurement examples" or document p.45 for pulse width measurement + +# Electrical ratings (p.99) + +## 6. START / STOP (discriminator) inputs ++ Voltage range -2.0 … +3.0 V ++ Threshold voltage -1.0 … +1.5 V +### 6.1. Absolute maximum ratings ++ DC input current ± 80 mA ++ any discriminator input -3.0 … +4.0 V + +# Notes +## 7. Dark count histogram +### 7.1. Parameter Correspondance +#### 7.1.1. Number of repetions - Sweep preset +#### 7.1.2. Expectation value $\lambda$ - controlle by Time range +Range * bin width + +# How to set the settings +## Active channels ++ active = 1 ++ and dont forget sweepmode ! +## 8. Start event generation (???) +If start event generation is checked, a start event is inserted into the data stream and counted. + +## 9. After sweep +### 9.1. Save ++ savedata=1 +### 9.2. Auto increment data files ++ autoinc=1 + +## 10. Time Settings +### 10.1. Binning (binwidth) ++ bitshift=0 ; LOWORD: Binwidth = 2 ^ (bitshift) ++ calfact=0.400000 * Binwidth (a setting only required to propperly show the histogram in software) +### 10.2. Range (per channel) ++ range=4096 (in steps of 64) +### 10.3. Hold off time ++ holdafter=6.4 (in of 6.4 ns) +### 10.4. Acquisition delay ++ fstchan = 1 ; acquisition delay = fstchan * 6.4ns ++ caloff=6.4 * fstchan ; MPNAT software calibration for shwoing histogram +## 11. Presets +### 11.1. Prena (is HEX!) ++ prena=4 ++ ; bit 0: realtime preset enabled + ; bit 1: + ; bit 2: sweep preset enabled + ; bit 3: ROI preset enabled + ; bit 4: Starts preset enabled + ; bit 5: ROI2 preset enabled + ; bit 6: ROI3 preset enabled + ; bit 7: ROI4 preset enabled + ; bit 8: ROI5 preset enabled + ; bit 9: ROI6 preset enabled + ; bit 10: ROI7 preset enabled + ; bit 11: ROI8 preset enabled + +Examples ++ prena = 3 : time preset ++ prena = 6 : sweep preset ++ prena = 7 : sweep & time preset ++ prena = 2 : nothing +### 11.2. Time preset ++ WRONG: rtpreset=20.000; Timepreset (seconds) [from doc] ++ RIHGT: timepreset = 20.000; (seconds) [observed] +### 11.3. Sweep preset ++ swpreset=20000000 \ No newline at end of file diff --git a/FastComtecMCS8A/fastcomtec_api.py b/FastComtecMCS8A/fastcomtec_api.py new file mode 100644 index 0000000000000000000000000000000000000000..75c580ad3a745c73d607cb51a9091bf795cbaa2f --- /dev/null +++ b/FastComtecMCS8A/fastcomtec_api.py @@ -0,0 +1,161 @@ +# -*- coding: utf-8 -*- + +import ctypes +try: + from . import fastcomtec_types +except ImportError: + import fastcomtec_types +import re +from typing import NamedTuple, List + +class FastComTec(): + """ Hardware Class for the FastComtec MCS8. """ + + def __init__(self, dll_path = r'C:\Windows\System32\DMCS8.dll', **kwargs) -> None: + self.dll = ctypes.windll.LoadLibrary(dll_path) + ######################################## + # Fastcomtec Types + ######################################## + def get_acq_status(self) -> fastcomtec_types.AcqStatus: + status = fastcomtec_types.AcqStatus() + self.dll.GetStatusData(ctypes.byref(status), 0) + return status + + def get_acq_settings(self) -> fastcomtec_types.AcqSettings: + settings = fastcomtec_types.AcqSettings() + self.dll.GetSettingData(ctypes.byref(settings), 0) + return settings + + def get_board_settings(self) -> fastcomtec_types.BoardSetting: + boardsettings = fastcomtec_types.BoardSetting() + self.dll.GetMCSSetting(ctypes.byref(boardsettings), 0) + return boardsettings + + def get_acq_data(self) -> fastcomtec_types.AcqData: + return NotImplementedError # code below doesn't give data for some reason + data = fastcomtec_types.AcqData() + self.dll.GetData(ctypes.byref(data), 0) + return data + ######################################## + # DLL Interaction + ######################################## + def _send_dll_command(self, command: str) -> None: + self.dll.RunCmd(0, bytes(command, ' utf-8')) + + def _send_dll_commands(self, commands: List[str]) -> None: + for cmd in commands: + self._send_dll_command(cmd) + ######################################## + # Measurement + ######################################## + def start_measurement(self) -> None: + self._send_dll_command("start") + + def halt_measurement(self) -> None: + self._send_dll_command("halt") + + def erase_measruement(self) -> None: + self._send_dll_command("erase") + ######################################## + # Saving and Loading Data and Config + ######################################## + def load_config(self, file_path: str) -> None: + """Doesn't have to be a .SET file, can also be a .mpa file.""" + self._send_dll_command(f"loadcnf {file_path}") + + def save_config(self) -> None: + """Config stored in C:/mcs8x64/MCS8A.SET""" + self._send_dll_command("savecnf") + + def save_config_and_data_seperately(self, file_path: str, ) -> None: + """ + NOTE: + + The Config gets saved twice, but both time with CHN1 as a header . + + TOTALSUM, ROISUM, MAXVAL for Channel 2 might be totally wrong + """ + self._send_dll_command(f"datname={file_path}_CHN1") + self._send_dll_command("MC_A") + self._send_dll_command("savedat") + self._send_dll_command(f"datname={file_path}_CHN2") + self._send_dll_command("MC_B") + self._send_dll_command("savedat") + + def save_config_with_data(self, file_path: str) -> None: + self._send_dll_command(f"mpaname={file_path}") + self._send_dll_command("savempa") + ######################################## + # Change Individual Settings + ######################################## + def set_time_preset(self, time_preset_sec: float) -> None: + self._send_dll_command(f"timepreset={time_preset_sec}") + def set_sweep_preset(self, swpreset: int) -> None: + self._send_dll_command(f"timepreset={swpreset}") + + +######################################## +# Separate config and data +######################################## +def separate_config_and_data(file_path: str) -> None: + path_split_up = file_path.split(".") + path_split_up[-2] += "_config" + config_path = ".".join(path_split_up) + + path_split_up = file_path.split(".") + path_split_up[-2] += "_data" + data_path = ".".join(path_split_up) + + regex = re.compile("(TDAT)") + match = False + with open(file_path, "r") as f: + lines = f.readlines() + write_data = False + with open(config_path, "w+") as config_file: + with open(data_path, "w+") as data_file: + for line in lines: + match = regex.search(line) + if match: + write_data = True + if write_data: + data_file.write(line) + else: + config_file.write(line) + +class BoundaryInfo(NamedTuple): + boundary_id: str + line_number: int + +def separate_config_and_data_per_channel(file_path: str) -> None: + path_split_up = file_path.split(".") + path_split_up[-2] += "_config" + config_path = ".".join(path_split_up) + + # read in file + with open(file_path, "r") as f: + lines = f.readlines() + + boundaries: List[BoundaryInfo] = [] + + # find boundaries of data sections + regex_channel_data = re.compile("(TDAT)") + match = False + for i, line in enumerate(lines): + match = regex_channel_data.search(line) + if match: + boundaries.append(BoundaryInfo(line.split(",")[0][1:], i)) # exclue "[" in data_id + + # write config file + with open(config_path, "w+") as config_file: + config_file.write("".join(lines[:boundaries[0].line_number])) + + # write data files + for i in range(len(boundaries)): + path_split_up = file_path.split(".") + path_split_up[-2] += f"_data_{boundaries[i].boundary_id}" + data_path = ".".join(path_split_up) + with open(data_path, "w+") as data_file: + if (i+1) < len(boundaries): + data_file.write("".join(lines[boundaries[i].line_number:boundaries[i+1].line_number])) + else: + data_file.write("".join(lines[boundaries[i].line_number:])) + + diff --git a/FastComtecMCS8A/fastcomtec_types.py b/FastComtecMCS8A/fastcomtec_types.py new file mode 100644 index 0000000000000000000000000000000000000000..150bfef47295c5b09a92dda7ebd4ffc490dc2ee9 --- /dev/null +++ b/FastComtecMCS8A/fastcomtec_types.py @@ -0,0 +1,259 @@ +import ctypes # general doc about Structures: https://docs.python.org/3/library/ctypes.html#structures-and-unions + +""" +Remark to the usage of ctypes: +All Python types except integers (int), strings (str), and bytes (byte) objects +have to be wrapped in their corresponding ctypes type, so that they can be +converted to the required C data type. + +ctypes type C type Python type +---------------------------------------------------------------- +c_bool _Bool bool (1) +c_char char 1-character bytes object +c_wchar wchar_t 1-character string +c_byte char int +c_ubyte unsigned char int +c_short short int +c_ushort unsigned short int +c_int int int +c_uint unsigned int int +c_long long int +c_ulong unsigned long int +c_longlong __int64 or + long long int +c_ulonglong unsigned __int64 or + unsigned long long int +c_size_t size_t int +c_ssize_t ssize_t or + Py_ssize_t int +c_float float float +c_double double float +c_longdouble long double float +c_char_p char * + (NUL terminated) bytes object or None +c_wchar_p wchar_t * + (NUL terminated) string or None +c_void_p void * int or None + +""" +# Reconstruct the proper structure of the variables, which can be extracted +# from the header file 'struct.h'. + + +class AcqStatus(ctypes.Structure): + """ Create a structured Data type with ctypes where the dll can write into. + + This object handles and retrieves the acquisition status data from the + Fastcomtec. + + int started; // acquisition status: 1 if running, 0 else + double runtime; // running time in seconds + double totalsum; // total events + double roisum; // events within ROI + double roirate; // acquired ROI-events per second + double nettosum; // ROI sum with background subtracted + double sweeps; // Number of sweeps + double stevents; // Start Events + unsigned long maxval; // Maximum value in spectrum + """ + def __init__(self): + super().__init__() + self.started : ctypes.c_int + self.runtime : ctypes.c_double + self.totalsum : ctypes.c_double + self.roisum : ctypes.c_double + self.roirate : ctypes.c_double + self.ofls : ctypes.c_double + self.sweeps : ctypes.c_double + self.stevents : ctypes.c_double + self.maxval : ctypes.c_ulong + + _fields_ = [('started', ctypes.c_int), + ('runtime', ctypes.c_double), + ('totalsum', ctypes.c_double), + ('roisum', ctypes.c_double), + ('roirate', ctypes.c_double), + ('ofls', ctypes.c_double), + ('sweeps', ctypes.c_double), + ('stevents', ctypes.c_double), + ('maxval', ctypes.c_ulong), ] + + def __repr__(self) -> str: + return 'started : ' + str(self.started ) + '\n' \ + 'runtime : ' + str(self.runtime ) + '\n' \ + 'totalsum: ' + str(self.totalsum) + '\n' \ + 'roisum : ' + str(self.roisum ) + '\n' \ + 'roirate : ' + str(self.roirate ) + '\n' \ + 'ofls : ' + str(self.ofls ) + '\n' \ + 'sweeps : ' + str(self.sweeps ) + '\n' \ + 'stevents: ' + str(self.stevents) + '\n' \ + 'maxval : ' + str(self.maxval ) + +class AcqData(ctypes.Structure): + """ Create a structured Data type with ctypes where the dll can write into. + + This object handles and retrieves the acquisition data of the Fastcomtec. + """ + def __init__(self): + super().__init__() + self.s0 : ctypes._Pointer + self.region : ctypes._Pointer + self.comment: ctypes.c_char_p + self.cnt : ctypes._Pointer + self.hs0 : ctypes.c_int + self.hrg : ctypes.c_int + self.hcm : ctypes.c_int + self.hct : ctypes.c_int + + _fields_ = [('s0', ctypes.POINTER(ctypes.c_ulong)), + ('region', ctypes.POINTER(ctypes.c_ulong)), + ('comment', ctypes.c_char_p), + ('cnt', ctypes.POINTER(ctypes.c_double)), + ('hs0', ctypes.c_int), + ('hrg', ctypes.c_int), + ('hcm', ctypes.c_int), + ('hct', ctypes.c_int), ] + + def __repr__(self) -> str: + return 's0 : ' + str(self.s0 ) + '\n' \ + 'region : ' + str(self.region ) + '\n' \ + 'comment: ' + str(self.comment) + '\n' \ + 'cnt : ' + str(self.cnt ) + '\n' \ + 'hs0 : ' + str(self.hs0 ) + '\n' \ + 'hrg : ' + str(self.hrg ) + '\n' \ + 'hcm : ' + str(self.hcm ) + '\n' \ + 'hct : ' + str(self.hct ) + +class AcqSettings(ctypes.Structure): + def __init__(self): + super().__init__() + self.range : ctypes.c_long + self.cftfak : ctypes.c_long + self.roimin : ctypes.c_long + self.roimax : ctypes.c_long + self.nregions : ctypes.c_long + self.caluse : ctypes.c_long + self.calpoints : ctypes.c_long + self.param : ctypes.c_long + self.offset : ctypes.c_long + self.xdim : ctypes.c_long + self.bitshift : ctypes.c_ulong + self.active : ctypes.c_long + self.eventpreset : ctypes.c_double + self.dummy1 : ctypes.c_double + self.dummy2 : ctypes.c_double + self.dummy3 : ctypes.c_double + + _fields_ = [('range', ctypes.c_long), + ('cftfak', ctypes.c_long), + ('roimin', ctypes.c_long), + ('roimax', ctypes.c_long), + ('nregions', ctypes.c_long), + ('caluse', ctypes.c_long), + ('calpoints', ctypes.c_long), + ('param', ctypes.c_long), + ('offset', ctypes.c_long), + ('xdim', ctypes.c_long), + ('bitshift', ctypes.c_ulong), + ('active', ctypes.c_long), + ('eventpreset', ctypes.c_double), + ('dummy1', ctypes.c_double), + ('dummy2', ctypes.c_double), + ('dummy3', ctypes.c_double), ] + + def __repr__(self) -> str: + return 'range : ' + str(self.range ) + '\n' \ + 'cftfak : ' + str(self.cftfak ) + '\n' \ + 'roimin : ' + str(self.roimin ) + '\n' \ + 'roimax : ' + str(self.roimax ) + '\n' \ + 'nregions : ' + str(self.nregions ) + '\n' \ + 'caluse : ' + str(self.caluse ) + '\n' \ + 'calpoints : ' + str(self.calpoints ) + '\n' \ + 'param : ' + str(self.param ) + '\n' \ + 'offset : ' + str(self.offset ) + '\n' \ + 'xdim : ' + str(self.xdim ) + '\n' \ + 'bitshift : ' + str(self.bitshift ) + '\n' \ + 'active : ' + str(self.active ) + '\n' \ + 'eventpreset: ' + str(self.eventpreset) + '\n' \ + 'dummy1 : ' + str(self.dummy1 ) + '\n' \ + 'dummy2 : ' + str(self.dummy2 ) + '\n' \ + 'dummy3 : ' + str(self.dummy3 ) + +class BoardSetting(ctypes.Structure): + def __init__(self): + super().__init__() + self.sweepmode : ctypes.c_long + self.prena : ctypes.c_long + self.cycles : ctypes.c_long + self.sequences : ctypes.c_long + self.syncout : ctypes.c_long + self.digio : ctypes.c_long + self.digval : ctypes.c_long + self.dac0 : ctypes.c_long + self.dac1 : ctypes.c_long + self.dac2 : ctypes.c_long + self.dac3 : ctypes.c_long + self.dac4 : ctypes.c_long + self.dac5 : ctypes.c_long + self.fdac : ctypes.c_int + self.tagbits : ctypes.c_int + self.extclk : ctypes.c_int + self.maxchan : ctypes.c_long + self.serno : ctypes.c_long + self.ddruse : ctypes.c_long + self.active : ctypes.c_long + self.holdafter : ctypes.c_double + self.swpreset : ctypes.c_double + self.fstchan : ctypes.c_double + self.timepreset: ctypes.c_double + + _fields_ = [('sweepmode', ctypes.c_long), + ('prena', ctypes.c_long), + ('cycles', ctypes.c_long), + ('sequences', ctypes.c_long), + ('syncout', ctypes.c_long), + ('digio', ctypes.c_long), + ('digval', ctypes.c_long), + ('dac0', ctypes.c_long), + ('dac1', ctypes.c_long), + ('dac2', ctypes.c_long), + ('dac3', ctypes.c_long), + ('dac4', ctypes.c_long), + ('dac5', ctypes.c_long), + ('fdac', ctypes.c_int), + ('tagbits', ctypes.c_int), + ('extclk', ctypes.c_int), + ('maxchan', ctypes.c_long), + ('serno', ctypes.c_long), + ('ddruse', ctypes.c_long), + ('active', ctypes.c_long), + ('holdafter', ctypes.c_double), + ('swpreset', ctypes.c_double), + ('fstchan', ctypes.c_double), + ('timepreset', ctypes.c_double),] + + def __repr__(self) -> str: + return 'sweepmode : ' + str(self.sweepmode ) + '\n' \ + 'prena : ' + str(self.prena ) + '\n' \ + 'cycles : ' + str(self.cycles ) + '\n' \ + 'sequences : ' + str(self.sequences ) + '\n' \ + 'syncout : ' + str(self.syncout ) + '\n' \ + 'digio : ' + str(self.digio ) + '\n' \ + 'digval : ' + str(self.digval ) + '\n' \ + 'dac0 : ' + str(self.dac0 ) + '\n' \ + 'dac1 : ' + str(self.dac1 ) + '\n' \ + 'dac2 : ' + str(self.dac2 ) + '\n' \ + 'dac3 : ' + str(self.dac3 ) + '\n' \ + 'dac4 : ' + str(self.dac4 ) + '\n' \ + 'dac5 : ' + str(self.dac5 ) + '\n' \ + 'fdac : ' + str(self.fdac ) + '\n' \ + 'tagbits : ' + str(self.tagbits ) + '\n' \ + 'extclk : ' + str(self.extclk ) + '\n' \ + 'maxchan : ' + str(self.maxchan ) + '\n' \ + 'serno : ' + str(self.serno ) + '\n' \ + 'ddruse : ' + str(self.ddruse ) + '\n' \ + 'active : ' + str(self.active ) + '\n' \ + 'holdafter : ' + str(self.holdafter ) + '\n' \ + 'swpreset : ' + str(self.swpreset ) + '\n' \ + 'fstchan : ' + str(self.fstchan ) + '\n' \ + 'timepreset: ' + str(self.timepreset) diff --git a/FastComtecMCS8A/labscript_devices.py b/FastComtecMCS8A/labscript_devices.py new file mode 100644 index 0000000000000000000000000000000000000000..a435fb81ab6a21f91039f78d6b3cf72bd0c19353 --- /dev/null +++ b/FastComtecMCS8A/labscript_devices.py @@ -0,0 +1,49 @@ +##################################################################### +# # +# /user_devices/FastComtecMCS8A/labscript_devices.py # +# # +# Feb. 2024, Leonard Group # +# # +# # +##################################################################### +from labscript import TriggerableDevice, Trigger, set_passed_properties, config + +class MCS8A(TriggerableDevice): + description = 'MCS8A' + + @set_passed_properties( + property_names={"connection_table_properties":["config_path"]} + ) + def __init__(self, name, parent_device, connection, SPCM_gate_parent=None, SPCM_gate_connection=None, config_path="MCS8A_default_config.SET", **kwargs): + super().__init__(name, parent_device, connection, **kwargs) + self.BLACS_connection = "SPCM MCS8A" + self.config_path = config_path + self.labels = {} + + if SPCM_gate_parent is not None: + self.SPCM_gate = Trigger( + f"{name}_SPCM_gate", SPCM_gate_parent, SPCM_gate_connection, trigger_edge_type="falling" + ) + + def acquire(self, t, duration, trigger_duration=None, label=""): + if trigger_duration is None: + trigger_duration = duration + self.labels[t] = label + self.trigger(t,trigger_duration) + if hasattr(self,"SPCM_gate"): + self.SPCM_gate.trigger(t,duration) + + def generate_code(self, hdf5_file): + group = hdf5_file.create_group(f"devices/{self.name}") + labels_group = group.create_group("labels") + for t,label in self.labels.items(): + labels_group.attrs[str(t)] = label + + group.attrs["n_trigger"] = len(self.trigger_device.triggerings) + durations = [triggering[1] for triggering in self.trigger_device.triggerings] + group.create_dataset("durations", compression = config.compression, data = durations) + + super().generate_code(hdf5_file) + + + diff --git a/FastComtecMCS8A/mcs8a_structures.py b/FastComtecMCS8A/mcs8a_structures.py new file mode 100644 index 0000000000000000000000000000000000000000..1744076ca17e3a039cf04539cb46cd793c7035e8 --- /dev/null +++ b/FastComtecMCS8A/mcs8a_structures.py @@ -0,0 +1,52 @@ +from ctypes import * + +class ACQSTATUS(Structure): + _fields_ = [("started", c_ulong), #aquisition status + ("maxval", c_ulong), #maxval + ("cnt", c_double*8)] #status: runtime in msec, ofls, + #total sum, roi sum, roi rate,sweeps, + #starts + +class ACQSETTING(Structure): + _fields_ = [("range", c_long), + ("cftfak", c_long), #LOWORD: 256*cft factor(t_after_peak / t_to_peak); HIWORD:max pulse width for CFT + ("roimin", c_long), #lower ROI limit + ("roimax", c_long), #upper limit: roimin<=channel<roimax + ("nregions", c_long), #number of regions + ("caluse", c_long), #bit0: 1 if calibration used, higher bits: formula + ("calpoints", c_long),#number of calibration points + ("param", c_long), #(reserved:) for MAP and POS:LOWORD=x, HIWORD=y + ("offset", c_long), #(reserved:) zoomed MAPS: LOWORD: xoffset, HIWORD, yoffset + ("xdim", c_long), #(reserved:) x resolution of maps + ("bitshift", c_ulong),#LOWORD:Binwidth=2^(bitshift); HIWORD: Threshold for Coinc + ("active", c_long), + ("roipreset", c_double),#ROI preset value + ("dummy1", c_double), + ("dummy2", c_double), + ("dummy3", c_double)] + +class BOARDSETTING(Structure): # This is a structure type describing special MCS6 hardware settings + _fields_ = [("sweepmode", c_long), + ("prena", c_long), + ("cycles", c_long), + ("sequences", c_long), + ("syncout", c_long), + ("digio", c_long), + ("digval", c_long), + ("dac0", c_long),#DAC0 value (START) + ("dac1", c_long),#DAC1 value (STOP 1) + ("dac2", c_long),#DAC2 value (STOP 2) + ("dac3", c_long),#DAC3 value (STOP 3) + ("dac4", c_long),#DAC4 value (STOP 4) + ("dac5", c_long),#DAC5 value (STOP 5) + ("fdac", c_int), + ("tagbits", c_int), + ("extclk", c_int),#use external clock + ("maxchan", c_long), + ("serno", c_long), + ("ddruse", c_long), + ("active", c_long),#module in system + ("holdafter", c_double), + ("swpreset", c_double),#sweep preset value + ("fstchan", c_double),#acquisition delay + ("timepreset", c_double)]#time preset diff --git a/FastComtecMCS8A/register_classes.py b/FastComtecMCS8A/register_classes.py new file mode 100644 index 0000000000000000000000000000000000000000..fb9fb737d5b55e2b6258f0cb752a13605065498f --- /dev/null +++ b/FastComtecMCS8A/register_classes.py @@ -0,0 +1,16 @@ +##################################################################### +# # +# /user_devices/DCAMCamera/register_classes.py # +# # +# Jan 2023, Marvin Holten # +# # +# # +##################################################################### + +from labscript_devices import register_classes + +register_classes( + 'MCS8A', + BLACS_tab='user_devices.FastComtecMCS8A.blacs_tabs.MCS8ATab', + runviewer_parser=None, +) diff --git a/FastComtecMCS8A/runviewer_parser.py b/FastComtecMCS8A/runviewer_parser.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/FastComtecMCS8A/testing/fastcomtec_plotting.py b/FastComtecMCS8A/testing/fastcomtec_plotting.py new file mode 100644 index 0000000000000000000000000000000000000000..98d2dfe880c9a635892690110f4df168dd01164d --- /dev/null +++ b/FastComtecMCS8A/testing/fastcomtec_plotting.py @@ -0,0 +1,32 @@ +import matplotlib.pyplot as plt +import numpy as np + +def plot_raw_histogram(config_path: str, data_path: str) -> None: + data = np.genfromtxt(data_path, dtype=np.int64, skip_header=1, delimiter='\n') + print(data) + plt.plot(data) + plt.xlabel("bin") + plt.ylabel("count") + plt.show() + +def plot_raw_histograms(config_path: str, data_path_1: str, data_path_2: str) -> None: + fig, axs = plt.subplots(2, 1) + data_1 = np.genfromtxt(data_path_1, dtype=np.int64, skip_header=1, delimiter='\n') + data_2 = np.genfromtxt(data_path_2, dtype=np.int64, skip_header=1, delimiter='\n') + x_values = (76.8/0.4 + np.arange(0, len(data_1))) * 0.4 + axs[0].plot(x_values, data_1, color = "blue") + axs[0].set_xlabel("$\Delta t$ (ns)") + axs[0].set_ylabel("count") + axs[0].set_xlim(76.8, (4096 + 76.8/0.4)*0.4) + axs[0].set_xticks(np.arange(100, 1800, 100), [str(n) for n in np.arange(100, 1800, 100)]) + axs[0].set_title("CHN1, 10 MHz fast NIM") + axs[1].plot(data_2, color = "skyblue") + axs[1].set_xlabel("bin") + axs[1].set_ylabel("count") + axs[1].set_xlim(0, 4096) + axs[1].set_title("CHN2, 10 MHz $V_{pp} = 1 V$") + print("Sum CHN1", data_1.sum()) + print("Max CHN1", data_1.max()) + print("Sum CHN2", data_2.sum()) + print("Max CHN2", data_2.max()) + plt.show() \ No newline at end of file diff --git a/FastComtecMCS8A/testing/spcm_tt_histogram.py b/FastComtecMCS8A/testing/spcm_tt_histogram.py new file mode 100644 index 0000000000000000000000000000000000000000..e424daa7213137f0a92a9add72ab14f1fa8d58e4 --- /dev/null +++ b/FastComtecMCS8A/testing/spcm_tt_histogram.py @@ -0,0 +1,130 @@ +import matplotlib.pyplot as plt +import numpy as np +import fastcomtec_api + +def printkw(**keywordarguments) -> None: + for kw in keywordarguments: + print(kw, f"{keywordarguments[kw]:e}") + +def print_kw_enum(**keywordarguments) -> None: + for kw in keywordarguments: + print(kw) + for i, el in enumerate(keywordarguments[kw]): + print(f"{i:>2}:", el) + print("----------------------------") + +def tt_calculations(): + bin_widths = 2**np.arange(0, 25) + fundamental_time_resolution = 400*10**(-12) + time_resolutions = fundamental_time_resolution * bin_widths + bins_per_second = 1 / time_resolutions + + print_kw_enum(bin_widths = bin_widths, time_res = time_resolutions, bins_per_s = bins_per_second) + + bin_selection = 24 + total_measurement_duration_sec = 300 + + printkw(bins = bins_per_second[bin_selection] * total_measurement_duration_sec) + + # print_kw(total_time = 29824*time_resolutions[22]) + +def calculate_dark_count_histogram(file: str, name: str, total_time_sec: int, num_measurements: int): + + data = np.loadtxt(file, dtype=np.int32, skiprows=1) + # print(data.shape) + data_50s_split = np.array_split(data, num_measurements) + counts_per_measurement = [] + for ar in data_50s_split: + # print(ar.shape) + counts_per_measurement.append(ar.sum()) + bins=np.arange(np.array(counts_per_measurement).max() + 1) + plt.hist(counts_per_measurement, bins, density=True, edgecolor='black', linewidth=1.2) + plt.xticks(bins+0.5, [str(b) for b in bins]) + plt.title(f"SPCM {name} Histogram\n{num_measurements} $\cdot$ {total_time_sec/num_measurements}s measurements") + plt.ylabel("P(n)") + plt.xlabel("n") + plt.show() + +def calculate_test_diode_histogram(file: str, name: str, total_time_sec: int, num_measurements: int): + print("Loading data ...") + data = np.loadtxt(file, dtype=np.int32, skiprows=1) + # print(data.shape) + data_50s_split = np.array_split(data, num_measurements) + counts_per_measurement = [] + print("Evaluating counts ...") + for ar in data_50s_split: + # print(ar.shape) + counts_per_measurement.append(ar.sum()) + min_count = np.array(counts_per_measurement).min() + max_count = np.array(counts_per_measurement).max() + 1 + # bins=np.arange(max_count) + bins = np.linspace(min_count, max_count + 1, num=25) + bin_width = (max_count - min_count)/25 + print("Plotting histogram ...") + plt.hist(counts_per_measurement, bins, edgecolor='black', linewidth=1.2) + plt.xticks(bins+bin_width * 0.5, [f"{int(b):.5e}" for b in bins], rotation=90) + plt.title(f"SPCM {name} Histogram\n{num_measurements} $\cdot$ {total_time_sec/num_measurements}s measurements") + plt.ylabel("P(n)") + plt.xlabel("n") + print("Done") + plt.show() + print("Exiting") + +def calculate_darkcount_histogram_tripple(): + data_100_0 = np.loadtxt(r"C:\Users\admin\benjamin_code\FastComTec_self\measurements\100s_darkcount_data_TDAT1.mpa", dtype=np.int32, skiprows=1) + print(data_100_0.shape) + data_100_1 = np.loadtxt(r"C:\Users\admin\benjamin_code\FastComTec_self\measurements\100s-1_darkcount_data_TDAT1.mpa", dtype=np.int32, skiprows=1) + print(data_100_1.shape) + data_100_2 = np.loadtxt(r"C:\Users\admin\benjamin_code\FastComTec_self\measurements\100s-2_darkcount_data_TDAT1.mpa", dtype=np.int32, skiprows=1) + print(data_100_2.shape) + data_combined = np.hstack((data_100_0, data_100_1, data_100_2)) + seconds = 300 + data_split = np.array_split(data_combined, seconds) + + counts_per_sec = [] + for ar in data_split: + # print(ar.shape) + counts_per_sec.append(ar.sum()) + # print(len(data_split)) + bins=np.arange(25) + plt.hist(counts_per_sec, bins, density=True) + plt.xticks(bins, [str(b) for b in bins]) + plt.title(f"SPCM Dark Count Histogram ({seconds}x1s measurements)") + plt.ylabel("P(n)") + plt.xlabel("n") + plt.tight_layout() + plt.show() + +def light_calculations(): + P0 = 2.5 * 10** (-3) + wavelen = 780 * 10**(-9) + h = 6 * 10**(-34) + c = 3 * 10**8 + photon_energy = c/wavelen * h + num_photons = P0 / photon_energy + attenuation_dB = -5 + printkw(photon_energy = photon_energy) + printkw(num_photons = num_photons) + printkw(attenuated = num_photons * 10**attenuation_dB) + + +plt.rcParams.update({'figure.autolayout': True}) + +### Split file +# fastcomtec_api.separate_config_and_data_per_channel(r"C:\Users\admin\benjamin_code\FastComTec_self\measurements\Thorlabs Laser Diode\50s-2_5OD.mpa") + +### Dark counts +# calculate_dark_count_histogram(r"C:\Users\admin\benjamin_code\FastComTec_self\measurements\Dark counts\50s_darkcount_data_TDAT1.mpa" , "Dark Count", 50, 50) +# calculate_dark_count_histogram(r"C:\Users\admin\benjamin_code\FastComTec_self\measurements\Dark counts\100s_darkcount_data_TDAT1.mpa", "Dark Count", 100, 100) +# calculate_darkcount_histogram_tripple() + +### Thorlabs Diode +light_calculations() +# calculate_test_diode_histogram(r"C:\Users\admin\benjamin_code\FastComTec_self\measurements\Thorlabs Laser Diode\50s_5OD_data_TDAT1.mpa", "780nm Diode Counts", 50, 50) + + + +### Calculations +# tt_calculations() +# light_calculations() + diff --git a/FastComtecMCS8A/testing/test.py b/FastComtecMCS8A/testing/test.py new file mode 100644 index 0000000000000000000000000000000000000000..acb6fca036b8dfcbf6b1057204ca08e577d6903a --- /dev/null +++ b/FastComtecMCS8A/testing/test.py @@ -0,0 +1,119 @@ +import fastcomtec_api +import time +import fastcomtec_plotting + + +######################################################## +### Basics +######################################################## +def load_dll(): + print("--- Load Dll ---") + device = fastcomtec_api.FastComTec() + print(device.get_acq_status()) + +def start_halt_erase(): + print("--- Start Halt Erase ---") + device = fastcomtec_api.FastComTec() + device.erase_measruement() + device.start_measurement() + print(device.get_acq_status()) + print() + time.sleep(5) + device.halt_measurement() + print(device.get_acq_status()) + print() + +def start_halt_wo_erase(): + print("--- Start Halt wo Erase ---") + device = fastcomtec_api.FastComTec() + print(device.get_acq_status()) + print() + device.start_measurement() + time.sleep(5) + device.halt_measurement() + print(device.get_acq_status()) + print() + +def load_cfg(): + device = fastcomtec_api.FastComTec() + device.load_config("C:/Users/admin/benjamin_code/FastComTec_self/configs/ExampleMeasurement1_config.mpa") +######################################################## +### Fastcomtec Types +######################################################## +def test_get_data(): + device = fastcomtec_api.FastComTec() + device.start_measurement() + data = device.get_acq_data() + device.halt_measurement() + print(data) + +def get_fastcomtec_types(): + print("--- FastComTec types ----") + device = fastcomtec_api.FastComTec() + print("--- AcqSettings ---") + print(device.get_acq_settings()) + print("--- AcqStatus ---") + print(device.get_acq_status()) + print("--- BoardSettings ---") + print(device.get_board_settings()) +######################################################## +### Example measurements +######################################################## +def two_channel_measurement(): + device = fastcomtec_api.FastComTec() + device.load_config("C:/Users/admin/benjamin_code/FastComTec_self/configs/TwoChannelMeasurement_config.mpa") + device.erase_measruement() + device._send_dll_command("MC_A") + device.start_measurement() + time.sleep(1) + device.halt_measurement() + device.save_config_with_data("C:/Users/admin/benjamin_code/FastComTec_self/measurements/TwoChannelMeasurement.mpa") + fastcomtec_api.separate_config_and_data_per_channel("C:/Users/admin/benjamin_code/FastComTec_self/measurements/TwoChannelMeasurement.mpa") + +def example_measurement_1(): + device = fastcomtec_api.FastComTec() + device.load_config("C:/Users/admin/benjamin_code/FastComTec_self/configs/ExampleMeasurement1_config.mpa") + device.erase_measruement() + # device._send_dll_command("MC_A") + device.start_measurement() + time.sleep(1) + device.halt_measurement() + output_file = "C:/Users/admin/benjamin_code/FastComTec_self/test_example.mpa" + device.save_config_with_data(output_file) + # device.save_config_and_data_seperately("C:/Users/admin/benjamin_code/FastComTec_self/test_example") + fastcomtec_api.separate_config_and_data_per_channel(output_file) + +def load_cfg_and_perform_measurement(config_path_full: str, output_path_full: str, measurement_time: float): + device = fastcomtec_api.FastComTec() + device.load_config(config_path_full) + device.erase_measruement() + # device._send_dll_command("MC_A") + device.start_measurement() + time.sleep(measurement_time) + device.halt_measurement() + device.save_config_with_data(output_path_full) + fastcomtec_api.separate_config_and_data_per_channel(output_path_full) + +######################################################## +### Histogram plotting +######################################################## +def print_data(): + fastcomtec_plotting.plot_raw_histogram("test_example_config.mpa", "test_example_data.mpa") + fastcomtec_plotting.plot_raw_histograms("test_example_config.mpa", "test_example_data_TDAT0.mpa", "test_example_data_TDAT1.mpa") + +def safe_config(): + device = fastcomtec_api.FastComTec() + device.save_config() + +def custom_measurement(): + load_cfg_and_perform_measurement( + config_path_full="C:/Users/admin/benjamin_code/FastComTec_self/configs/test.mpa", + output_path_full="C:/Users/admin/benjamin_code/FastComTec_self/measurements/TwoChannelMeasurement.mpa", + measurement_time=1 + ) + +def run_tests(): + device = fastcomtec_api.FastComTec() + device._send_dll_command("bitshift=32") + device.save_config() +run_tests() diff --git a/FastComtecMCS8A/testing/tryout.py b/FastComtecMCS8A/testing/tryout.py new file mode 100644 index 0000000000000000000000000000000000000000..a41c0b6a75c9aa672ce3c6f3c719377a6b0e3f54 --- /dev/null +++ b/FastComtecMCS8A/testing/tryout.py @@ -0,0 +1,21 @@ + +import ctypes as ct + +# storing a ctypes long value +value_1 = ct.c_long(10) + +# using pointer() method we are pointing to the +# value_1 variable and storing it in ptr +ptr = ct.pointer(value_1) + + +print("-----") +print(ptr.contents) +print(ptr[0]) +print(ptr[1]) +print("-----") +print(id(ptr)) +print(id(ptr[0])) +print(id(ptr[1])) +ptr = ptr + 1 +print(id(ptr)) \ No newline at end of file