Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding PLE scanner application #1

Merged
merged 26 commits into from
Oct 24, 2024
Merged
Changes from 24 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
462c146
Copied PLE files from qt3uw/qt3-utils branch LaneUW:main
LaneUW Oct 5, 2024
100ef17
Copy Lane's updated, working PLE code
nyama8 Oct 9, 2024
c498850
Adjusted QT3Scan to use new sample count method.
LaneUW Oct 10, 2024
6c81ffb
Removed a sleep command when voltage is changing for PLE
LaneUW Oct 10, 2024
77ff889
stashing changes
nyama8 Oct 11, 2024
1f01a32
Merging feature-addple
nyama8 Oct 12, 2024
c889e96
Merge branch 'feature-addple' of https://github.com/UW-Quantum-Defect…
nyama8 Oct 12, 2024
bb454d1
Update voltage controller limits
nyama8 Oct 12, 2024
be61a0c
Added comments, modified GUI, removed unused code
nyama8 Oct 14, 2024
3597169
Added comments,, restructuring scan algorithm
nyama8 Oct 14, 2024
410b77c
Changed logger.info statements to logger.debug to clean up output
nyama8 Oct 15, 2024
7e48fac
Updated PLE implementation; removed unused code; added comments
nyama8 Oct 15, 2024
0762439
Removed unimplemented PleScanner.reset() call
nyama8 Oct 15, 2024
6807e54
Shashing changes to repump implementation before modifying yaml config
nyama8 Oct 15, 2024
ffd29d2
Implemented pulsed repump. Modified YAML structure and loading.
nyama8 Oct 16, 2024
78257c6
Separated application_controller instance from main application. It i…
nyama8 Oct 16, 2024
656ddf6
Scans now open in new windows
nyama8 Oct 16, 2024
653a028
Implemented method to save scans and data
nyama8 Oct 17, 2024
f78c11e
Fixed scan save file_metadata encoding
nyama8 Oct 17, 2024
8f20f31
Added example code for reading PLE hdf5 output files
nyama8 Oct 17, 2024
de64e7e
Added repump laser DAQ toggle and improved GUI scan xtick placement
nyama8 Oct 19, 2024
874fa24
Moved PLE code to new counter controller and NiDAQ timed rate counter…
nyama8 Oct 20, 2024
5331c54
Reverted changes from previous RateCounterBase and NiDaqDigitialInput…
nyama8 Oct 20, 2024
be5db14
Fixed qt3scan to remove references to sample time at the daq counter …
nyama8 Oct 20, 2024
15d980d
changing back to original version
NnguyenHTommy Oct 23, 2024
39a1980
Re-added function description in nidaqedgecounter.py
nyama8 Oct 24, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions src/qt3utils/applications/controllers/lockin_controller.py
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this script specific to a certain lock-in device/model?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe this was an addition from @LaneUW at an earlier stage of the code (where we were trying to be universal). I suspect that we should remove this for now and let people implement this on their own (or add support for specific hardware when addressing #4).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this is general, needs documentation for the params that need to be set so that anyone can hook up any lock-in and use this code. Some testing should be done and documented somewhere to show that it works.

Some guidelines that I found helpful when dealing with any pull request:
When you issue a pull request, be very clear and verbose about the changes you are making. New code must be reviewed by another colleague before it gets merged to master. Your pull request should include things like

  • a statement describing what is changed or new
  • a reference to the Issue being fixed here (Github will automatically generate a handy link)
  • a statement describing why you chose your specific implementation
  • results of tests on your hardware setup, which could be data, screenshots, etc. There should be a clear record demonstrating functionality.
  • a Jupyter notebook in the "examples" folder that demonstrate usage and changes
  • documentation

I think it would be useful to follow these especially when pulling on the main branch of qdl-utils

Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import logging
import numpy as np

import nidaqmx

class Lockin:
def __init__(self, logger_level) -> None:
self.logger = logging.getLogger(__name__)
self.logger.setLevel(logger_level)
self.device_name = ""
self.signal_channel = ""
self.sample_number = 20
self.rate = 20.0
self.timeout = 10

def read(self) -> np.ndarray:
with nidaqmx.Task() as task:
task.ai_channels.add_ai_voltage_chan(self.device_name + '/' + self.signal_channel, max_val=10)
task.timing.cfg_samp_clk_timing(rate=self.rate, samps_per_chan=self.sample_number)
c = task.read(number_of_samples_per_channel=self.sample_number, timeout=self.timeout)
return np.array(c)

def configure(self, config_dict: dict) -> None:
"""
This method is used to configure the data controller.
"""
self.device_name = config_dict.get('daq_name', self.device_name)
self.signal_channel = config_dict.get('signal_channels', self.signal_channel)
self.sample_number = config_dict.get('sample_number', self.sample_number)
self.rate = config_dict.get('rate', self.rate)
self.timeout = config_dict.get('timeout', self.timeout)
59 changes: 59 additions & 0 deletions src/qt3utils/applications/controllers/nidaq_rate_counter.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
QT3PLE:
ApplicationController:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this implemented this way? Other piece of hardware (nidaq and spectrometers) doesn't have an application controller in the yaml file.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it is probably better to implement YAML files separately for each application since the logic of the generic "config_from_yaml" function will be specific to each application. Having the header be the name of the application should help in ensuring that the purpose of the config files are well-defined.

Alternatively we could make a "super"-config file which has all hardware and applications in one file, but it'll probably be a bit messy.

import_path : qt3utils.datagenerators.plescanner
class_name : PleScanner
configure :
controller: VoltageController
readers :
daq_reader : DAQCounter
auxiliary_controllers :
daq_writer : RepumpController

DAQCounter:
import_path : qt3utils.applications.controllers.nidaqedgecounter
class_name : QT3PleNIDAQEdgeCounterController
configure :
daq_name : Dev1 # NI DAQ Device Name
signal_terminal : PFI0 # NI DAQ terminal connected to input digital TTL signal
clock_terminal : # Specifies the digital input terminal to the NI DAQ to use for a clock. If left blank, interprets as None or NULL
clock_rate: 100000 # NI DAQ clock rate in Hz
sample_time_in_seconds : 1
read_write_timeout : 10 # timeout in seconds for read/write operations
signal_counter : ctr2 # NI DAQ counter to use for counting the input signal, e.g. ctr0, ctr1, ctr2, or ctr3

VoltageController:
import_path : qt3utils.nidaq.customcontrollers
class_name : VControl
configure :
daq_name : Dev1 # NI DAQ Device Name
write_channels : ao3 # NI DAQ analog output channels to use for writing position
read_channels : ai3 # NI DAQ analog input channels to use for reading position
min_position: -3 # conversion factor from volts to microns, can also supply a list [8,8,8] or [6,4.2,5]
max_position: 5 # the voltage value that defines the position 0,0,0, can also supply a list [0,0,0] or [5,5,5]
scale_nm_per_volt: 1 # microns

RepumpController:
import_path : qt3utils.nidaq.customcontrollers
class_name : ArbitraryDAQVoltageController
configure :
daq_name : Dev2 # NI DAQ Device Name
write_channels : ao0 # NI DAQ analog output channels to use for writing position
read_channels : # NI DAQ analog input channels to use for reading position
min_voltage: 0 # conversion factor from volts to microns, can also supply a list [8,8,8] or [6,4.2,5]
max_voltage: 1 # the voltage value that defines the position 0,0,0, can also supply a list [0,0,0] or [5,5,5]


# notes

# clock_rate:
# Specifies the clock rate in Hz. If using an external clock,
# you should specifiy the clock rate here so that the correct counts per
# second are displayed. If using the internal NI DAQ clock (default behavior),
# this value specifies the clock rate to use. Per the NI DAQ manual,
# use a suitable clock rate for the device for best performance, which is an integer
# multiple downsample of the digital sample clock.

# clock_terminal:
# Specifies the digital input terminal to the NI DAQ to use for a clock.
# If None, the internal NI DAQ clock is used. Otherwise, a string value
# specifies the terminal to use for the clock.
39 changes: 39 additions & 0 deletions src/qt3utils/applications/controllers/nidaq_wm_ple.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
QT3PLE:
ApplicationController:
import_path : qt3utils.datagenerators.plescanner
class_name : PleScanner
configure :
controller: VoltageController
readers :
daq_readers:
daq_reader : DAQReader
wm_readers:
wm_reader : WavemeterDllController

DAQReader:
import_path : qt3utils.applications.controllers.lockin_controller
class_name : Lockin
configure :
daq_name : Silicon_DAQ # NI DAQ Device Name
signal_channels : ai21 # NI DAQ analog input channels to use for reading position
sample_number: 20 # Specifies the sampling rate in samples per channel per second
rate: 20.0 #Specifies the number of samples to acquire or generate for each channel in the task
timeout: 20 # Specifies the amount of time in seconds to wait for samples to become available

VoltageController:
import_path : qt3utils.nidaq.customcontrollers
class_name : VControl
configure :
daq_name : Silicon_DAQ # NI DAQ Device Name
write_channels : ao3 # NI DAQ analog output channels to use for writing voltage
read_channels : ai0 # NI DAQ analog input channels to use for reading voltage
min_position: -10 # conversion factor from volts to microns, can also supply a list [8,8,8] or [6,4.2,5]
max_position: 10 # the voltage value that defines the position 0,0,0, can also supply a list [0,0,0] or [5,5,5]
scale_nm_per_volt: 1 # microns
num_measurements_per_batch: 10

WavemeterDllController:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would make sense to remove this whole file and have users make their own. Or have users input their own file paths for the wavemeter they are using

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will probably remove these extra files as the initial conception of the code was overly general.

import_path : qt3utils.applications.controllers.wavemeter_controller
class_name : WavemeterDllController
configure :
dll_path : C:/Users/Fulab/Downloads/00392-2-06-A_CustomerCD621/00392-2-06-A_Customer CD 621/Programming Interface/x64/CLDevIFace.dll
73 changes: 68 additions & 5 deletions src/qt3utils/applications/controllers/nidaqedgecounter.py
Original file line number Diff line number Diff line change
@@ -136,10 +136,8 @@ def print_config(self) -> None:
print(self.last_config_dict) # we dont' use the logger because we want to be sure this is printed to stdout



class QT3ScanNIDAQEdgeCounterController(QT3ScopeNIDAQEdgeCounterController):
"""
Implements the qt3utils.applications.qt3scan.interface.QT3ScanCounterDAQControllerInterface for a NIDAQ edge counter.
"""
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we add some description of this function back?


def __init__(self, logger_level):
super().__init__(logger_level)
@@ -148,8 +146,73 @@ def __init__(self, logger_level):
def clock_rate(self) -> float:
return self.data_generator.clock_rate

def sample_counts(self, num_batches: int) -> np.ndarray:
def sample_counts(self, num_batches: int, sample_time: float=-1) -> np.ndarray:
if sample_time > 0:
self.data_generator.sample_time = sample_time
return self.data_generator.sample_counts(num_batches)

def sample_count_rate(self, data_counts: np.ndarray) -> np.ndarray:
return self.data_generator.sample_count_rate(data_counts)

@property
def num_data_samples_per_batch(self) -> int:
return self.data_generator.num_data_samples_per_batch

@num_data_samples_per_batch.setter
def num_data_samples_per_batch(self, value: int):
self.data_generator.num_data_samples_per_batch = value


class QT3PleNIDAQEdgeCounterController(QT3ScopeNIDAQEdgeCounterController):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is good b/c it's in a seperate class


def __init__(self, logger_level):
super().__init__(logger_level)

# Change the data generator to the timed input rate counter
self.data_generator = daqsamplers.NiDaqTimedDigitalInputRateCounter()

# Rewrite the configure function for the timed data generator
def configure(self, config_dict: dict) -> None:
"""
This method is used to configure the data controller.
"""
self.logger.debug("calling configure on the nidaq edge counter data controller")
self.last_config_dict.update(config_dict)

self.data_generator.daq_name = config_dict.get('daq_name', self.data_generator.daq_name)
self.data_generator.signal_terminal = config_dict.get('signal_terminal', self.data_generator.signal_terminal)
self.data_generator.clock_terminal = config_dict.get('clock_terminal', self.data_generator.clock_terminal)
self.data_generator.clock_rate = config_dict.get('clock_rate', self.data_generator.clock_rate)
self.data_generator.sample_time_in_seconds = config_dict.get('sample_time_in_seconds', self.data_generator.sample_time_in_seconds)
self.data_generator.read_write_timeout = config_dict.get('read_write_timeout', self.data_generator.read_write_timeout)
self.data_generator.signal_counter = config_dict.get('signal_counter', self.data_generator.signal_counter)


def sample_counts(self, num_batches: int, sample_time: float=-1) -> np.ndarray:
if sample_time > 0:
self.data_generator.sample_time = sample_time
return self.data_generator.sample_counts(num_batches)

def sample_count_rate(self, data_counts: np.ndarray) -> np.ndarray:
return self.data_generator.sample_count_rate(data_counts)


@property
def clock_rate(self) -> float:
return self.data_generator.clock_rate

def sample_counts(self, num_batches: int, sample_time: float=-1) -> np.ndarray:
if sample_time > 0:
self.data_generator.sample_time = sample_time
return self.data_generator.sample_counts(num_batches)

def sample_count_rate(self, data_counts: np.ndarray) -> np.floating:
def sample_count_rate(self, data_counts: np.ndarray) -> np.ndarray:
return self.data_generator.sample_count_rate(data_counts)

@property
def num_data_samples_per_batch(self) -> int:
return self.data_generator.num_data_samples_per_batch

@num_data_samples_per_batch.setter
def num_data_samples_per_batch(self, value: int):
self.data_generator.num_data_samples_per_batch = value
87 changes: 87 additions & 0 deletions src/qt3utils/applications/controllers/wavemeter_controller.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import abc
import ctypes
import logging


class WavemeterController(abc.ABC):
"""
Base class for other types of wavemeter controllers to inherit from
"""
def __init__(self, logger_level):
self.logger = logging.getLogger(__name__)
self.logger.setLevel(logger_level)

@abc.abstractmethod
def open(self):
"""
Override this method to open and initialize wavemeter
"""
pass

@abc.abstractmethod
def read_wavemeter(self):
"""
Override this method to read the value from the wavemeter
"""
pass

@abc.abstractmethod
def close_wavemeter(self):
"""
Override this method to close the connection to the wavemeter
"""
pass

@abc.abstractmethod
def configure(self, config_dict: dict):
"""
Override this method to configure the wavemeter from a dict set via yaml file
"""
pass


class WavemeterDllController(WavemeterController):
"""
Class for interfacing with wavemeter hardware
"""
def __init__(self, logger_level, dll_path=""):
super(WavemeterDllController, self).__init__(logger_level)
self.dll_path = dll_path
if not dll_path == "":
self.open(self.dll_path)
self.last_config_dict = {}

def open(self, dll_path) -> None:
"""
Set the path to the dll used for interfacing with the wavemeter
"""
self._mydll = ctypes.cdll.LoadLibrary(dll_path)
self._mydll.CLGetLambdaReading.restype = ctypes.c_double
self._dh = self._mydll.CLOpenUSBSerialDevice(4)
if self._dh == -1:
raise Exception("Failed to connect to wave meter.")

def read(self) -> float:
"""
Return the value from the wavemeter via the dll
"""
return self._mydll.CLGetLambdaReading(self._dh)

def close(self) -> None:
"""
Close the connection to the wavemeter via the dll
"""
ending = self._mydll.CLCloseDevice(self._dh)
if ending == -1:
raise Exception("Failed to properly close connection to wave meter.")
else:
device_handle=None

def configure(self, config_dict: dict) -> None:
"""
This method is used to configure the data controller.
"""
self.logger.debug("calling configure on the wave meter controller")
self.dll_path = config_dict.get('dll_path', self.dll_path)
self.open(self.dll_path)

Binary file not shown.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading