Skip to content

Commit

Permalink
Fixes from in-person testing. Have most of the hardware working (at l…
Browse files Browse the repository at this point in the history
…east at a low level) and have the logic for the laser working. The wavemeter is crashing as soon as we start, but hopefully that will be the last large hurdle to get it operational
  • Loading branch information
lange50 committed Mar 11, 2025
1 parent b872859 commit d232eb5
Show file tree
Hide file tree
Showing 16 changed files with 198 additions and 157 deletions.
61 changes: 53 additions & 8 deletions cfg/terascan.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ logic:
daq_reader_logic:
module.Class: 'common.daq_reader_logic.DAQReaderLogic'
connect:
daq: daq_dummy
daq: nidaq #daq_reader_dummy
options:
update_interval: 0 # Period in ms to check for data updates. Integers only. 0 is as fast as possible

Expand All @@ -65,27 +65,72 @@ logic:
channel_buffer_size: 1048576 # optional (default: 1MSample)
max_raw_data_bytes: 1073741824 # optional (default: 1GB)
connect:
streamer: instream_dummy
streamer: wavemeter # instream_dummy


fast_counter_logic:
module.Class: 'common.fast_counter_logic.FastCounterLogic'
connect:
fast_counter: fast_counter_dummy
fast_counter: swabian_timetagger # fast_counter_dummy


scanning_laser_logic:
module.Class: 'common.scanning_laser_logic.ScanningLaserLogic'
connect:
laser: scanning_laser_dummy
laser: solstis_laser # scanning_laser_dummy
options:
min_wavelength: 0.775 # in um
max_wavelength: 0.790 # in um
min_wavelength: 0.700 # in um
max_wavelength: 0.800 # in um


hardware:

daq_dummy:
# Real Hardware:
nidaq:
module.Class: 'daq.nidaq.NIDAQ'
options:
device_str: 'Dev2'
channels:
signal:
description: 'Laser Lock TTL'
type: 0 # 0 for Digital, 1 for Analog
name: 'line0' # The name as idintified by the card
port: 1 # port number identified by the card

wavemeter_proxy:
module.Class: 'wavemeter.high_finesse_proxy.HighFinesseProxy'
options:
watchdog_interval: 1.0 # how often the watchdog checks for errors/changes in s
dllPath: 'C:\Program Files (x86)\HighFinesse\Wavelength Meter WS6 3167\Projects\64\wlmData.dll'

wavemeter:
module.Class: 'wavemeter.high_finesse_wavemeter.HighFinesseWavemeter'
connect:
proxy: wavemeter_proxy
options:
channels:
laser:
switch_ch: 1 # channel on the wavemeter switch
unit: 'm' # wavelength (m) or frequency (Hz)
exposure: 10 # exposure time in ms, optional

swabian_timetagger:
module.Class: 'timetagger.swabian_tagger.SwabianTimeTagger'
options:
channels:
counts: 1 # label and channel number on the time tagger (1,2,3,4)

solstis_laser:
module.Class: 'laser.solstis_laser.SolstisLaser'
options:
host_ip_addr: '192.168.1.225' # IP address of control computer
laser_ip_addr: '192.168.1.222' # IP address of laser
laser_port: 39933 # Port number to connect on
scan_rate: 13 # See "solstis_constants.py" TeraScanRate Enum for values
scan_type: 2 # see "solstis_constants.py" TeraScanType Enum for values

# Dummy Hardware:
daq_reader_dummy:
module.Class: 'dummy.daq_reader_dummy.DAQReaderDummy'

instream_dummy:
Expand All @@ -110,7 +155,7 @@ hardware:
module.Class: 'dummy.fast_counter_dummy.FastCounterDummy'


laser_dummy:
scanning_laser_dummy:
module.Class: 'dummy.scanning_laser_dummy.ScanningLaserDummy'


12 changes: 8 additions & 4 deletions src/qudi/gui/terascan/terascan_gui.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ def _start_stop_pressed(self) -> None:
if self._mw.start_stop_button.text() == 'Start Measurement':
self._mw.start_stop_button.setText('Stop Measurement')
self._mw.plot_widget.setXRange(self._start_wavelength, self._stop_wavelength)
print('start measurement in terascan_gui')
self.sigStartMeasurement.emit()
else:
self._mw.start_stop_button.setText('Start Measurement')
Expand All @@ -130,14 +131,17 @@ def _start_stop_pressed(self) -> None:
def _scan_finished(self) -> None:
self._mw.start_stop_button.setText('Start Measurement')

@QtCore.Slot(List[TerascanData])
@QtCore.Slot(object)
def _receive_data(self, data: List[TerascanData]) -> None:
img = np.zeros((len(data),2))
for i in range(len(data)):
img[i][0] = data[i].wavelength
img[i][1] = data[i].counts
# print(np.average(data[i].counts))
#img[i][0] = data[i].wavelength
#img[i][1] = data[i].counts
img[i][0] = 775 + 15 * np.random.rand()
img[i][1] = 100 * np.random.rand()

self._mw.data_item.setData(x = img[:,0], y = img[:, 1])
self._mw.data_item.setData(x = img[:, 0], y = img[:, 1])


@QtCore.Slot(float)
Expand Down
2 changes: 2 additions & 0 deletions src/qudi/gui/terascan/terascan_main_window.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,12 +54,14 @@ def __init__(self, *args, **kwargs):
self.start_wavelength.setButtonSymbols(QtWidgets.QAbstractSpinBox.NoButtons)
self.start_wavelength.setAlignment(QtCore.Qt.AlignHCenter)
self.start_wavelength.setRange(0.3, 2)
self.start_wavelength.setDecimals(4)

self.stop_wavelength_label = QtWidgets.QLabel('Stop Wavelength (um)')
self.stop_wavelength = QtWidgets.QDoubleSpinBox()
self.stop_wavelength.setButtonSymbols(QtWidgets.QAbstractSpinBox.NoButtons)
self.stop_wavelength.setAlignment(QtCore.Qt.AlignHCenter)
self.stop_wavelength.setRange(0.3, 2)
self.stop_wavelength.setDecimals(4)

self.plot_widget = pg.PlotWidget()
self.plot_widget.getPlotItem().setContentsMargins(1, 1, 1, 1)
Expand Down
56 changes: 27 additions & 29 deletions src/qudi/hardware/camera/andor_camera.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from ctypes import *
import numpy as np
import time
from collections.abc import Callable

import pyAndorSDK2 as sdk
from pyAndorSDK2 import atmcd_errors
Expand All @@ -10,7 +11,7 @@
from qudi.interface.camera_interface import CameraInterface

class AndorCamera(CameraInterface):
""" Hardware class for Andor Camera. Currently assumes there is only one
r""" Hardware class for Andor Camera. Currently assumes there is only one
Andor camera attached
Example config for copy-paste:
Expand All @@ -28,7 +29,7 @@ class AndorCamera(CameraInterface):
default_exposure: 1 # in seconds
"""

_dll_location: str = ConfigOption(
name='dll_location',
default=r'C:\Users\hoodl\Downloads\pyAndorSDK2_NEW\pyAndorSDK2',
Expand Down Expand Up @@ -85,11 +86,11 @@ def on_activate(self):

initialization_funcs = [
('Setting Read Mode', lambda: self.andor.SetReadMode(4)), #4 = imaging mode, reads each pixel

('Setting VS Speed', lambda: self.andor.SetVSSpeed(4)), #4 index = 3.3 us
('Setting VS Amplitude', lambda: self.andor.SetVSAmplitude(0)), #keep 0
('Setting HS Speed', lambda: self.andor.SetHSSpeed(0, 1)), #1 index = 5.0 (middle speed)

('Setting Pre-amp Gain', lambda: self.andor.SetPreAmpGain(2)), #index 2 = 5.1, highest
('Setting Baseline Clamp', lambda: self.andor.SetBaselineClamp(1)), #1 = on, keep baseline clamp on
('Setting Output Amplifyer', lambda: self.andor.SetOutputAmplifier(0)), #0 = EMCCD camera
Expand All @@ -98,7 +99,7 @@ def on_activate(self):
('Setting Count Mode', lambda: self.andor.SetCountConvertMode(1)), #0 = bit counts, 1 = electrons, 2 = photons
('Setting Trigger Mode', lambda: self.andor.SetTriggerMode(0)), #0 = internal trigger (auto)
('Setting Image Binning Parameters', lambda: self.andor.SetImage(1, 1, 1, self.xpixels, 1, self.ypixels)), #sets horizontal and vertical binning

('Turning on Cooler', lambda: self.andor.CoolerON()), #turn on cooler
('Setting Cooler Mode', lambda: self.andor.SetCoolerMode(1)), #set cooler to maintain temp after shutoff (1)
('Setting Temperature', lambda: self.andor.SetTemperature(self.set_temp)) #sets temperature (kind of self explanatory)
Expand All @@ -113,39 +114,37 @@ def on_activate(self):
attempts = 0
while attempts < 3:
current_temp = self._andor.GetTemperature()[1]
if np.abs(current_temp - self.set_temp) > self._temp_tolerance:
self._andor.SetTemperature(self.set_temp)
if np.abs(current_temp - self.set_temp) <= self._temp_tolerance:
break

self.log.warning(
f'Andor temperature ({current_temp}) not within tolerance of set point ({self.set_temp}).\nPausing to allow it to stabilize attempt {attempts}/3'
)
time.sleep(5)
attempts += 1
self._andor.SetTemperature(self.set_temp)

else:
break
self.log.warning(
f'Andor temperature ({current_temp}) not within tolerance of set point ({self.set_temp}).\nPausing to allow it to stabilize attempt {attempts}/3'
)
time.sleep(5)
attempts += 1

current_temp = self._andor.GetTemperature()[1]
if np.abs(current_temp - self.set_temp) > self._temp_tolerance:
self.log.warning(f'Andor temperature not within set tolerance. Stabilized at: {current_temp} C.\nPROCEED WITH CAUTION!')

def on_deactivate(self):
self._andor.SetTemperature(self._shutoff_temp)

attempts = 0
while attempts < 3:
current_temp = self._andor.GetTemperature()[1]
if np.abs(current_temp - self._shutoff_temp) > self._temp_tolerance:
self._andor.SetTemperature(self._shutoff_temp)
if np.abs(current_temp - self._shutoff_temp) <= self._temp_tolerance:
break

self.log.warning(
f'Andor temperature ({current_temp}) not within tolerance of shutoff set point ({self.set_temp}).\nPausing to allow it to stabilize attempt {attempts}/3'
)
time.sleep(5)
attempts += 1
self._andor.SetTemperature(self._shutoff_temp)

else:
break
self.log.warning(
f'Andor temperature ({current_temp}) not within tolerance of shutoff set point ({self.set_temp}).\nPausing to allow it to stabilize attempt {attempts}/3'
)
time.sleep(5)
attempts += 1

current_temp = self._andor.GetTemperature()[1]
self.log.info(f'Shutoff temperature stabilized at {current_temp} C.')
Expand Down Expand Up @@ -242,8 +241,7 @@ def stop_acquisition(self) -> bool:
"""
self._andor.AbortAcquisition()
(ret) = self._andor.GetStatus()
success = self._error_handler(ret)
return success
return self._error_handler(ret)

def get_acquired_data(self) -> np.ndarray:
""" Return an array of last acquired image.
Expand Down Expand Up @@ -311,7 +309,7 @@ def _error_handler(self, ret: int, msg: str = None) -> bool:
if (ret != atmcd_errors.Error_codes.DRV_SUCCESS.value):
err = atmcd_errors.Error_Codes(ret).name
out = f'Andor Error: {err}' if msg is None \
else f'Andor Error -- {msg}: {err}'
else f'Andor Error -- {msg}: {err}'
self.log.exception(out)
return False
return True
Expand All @@ -322,12 +320,12 @@ def _error_handler(self, ret: int, msg: str = None) -> bool:
def _abort_if_running(self) -> None:
""" Convenience function to check if currently acquiring or waiting
"""

if self._check_if_running():
self._andor.AbortAcquisition()


def _multi_run_functions(self, named_funcs: list[tuple[str, function]]) -> bool:
def _multi_run_functions(self, named_funcs: list[tuple[str, Callable]]) -> bool:
""" Convenience function to run a list of named functions in order.
Returns True on success and False on failure.
"""
Expand Down
19 changes: 8 additions & 11 deletions src/qudi/hardware/daq/nidaq.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,8 @@
import nidaqmx
import numpy as np
from PySide2 import QtCore
from typing import Union, Optional, List, Tuple, Sequence, Any, Dict
from typing import List, Any, Dict


from qudi.core.configoption import ConfigOption
from qudi.core.connector import Connector
from qudi.util.mutex import Mutex
from qudi.interface.daq_reader_interface import DAQReaderInterface, InputType, \
ReaderVal

Expand All @@ -25,13 +21,13 @@ class NIDAQ(DAQReaderInterface):
signal:
description: 'Input Signal'
type: 0 # 0 for Digital, 1 for Analog
name: 'line0' # The name as idintified by the card
name: 'line0' # The name as identified by the card
port: 1 # port number identified by the card
"""

# config options
_daq_name: str = ConfigOption(name='device_str', default='Dev1',
missing='warn')
missing='warn')

_daq_ch_config: Dict[str, Dict[str, Any]] = ConfigOption(
name='channels',
Expand All @@ -56,7 +52,7 @@ def on_activate(self):
name=v['name'],
port=v['port'],
description=v['description'],
) for _,v in self._daq_ch_config
) for _,v in self._daq_ch_config.items()
)

self._analog_channels: List[ReaderVal] = []
Expand Down Expand Up @@ -98,12 +94,13 @@ def get_reading(self) -> List[ReaderVal]:

if (len(self._digital_channels) > 0):
with nidaqmx.Task() as task:
for i in self._analog_channels:
for i in self._digital_channels:
chan = self._get_channel(i)
task.di_channels.add_di_chan(chan)

data = task.read()
if len(data) == 1:
# if len(data) == 1:
if not isinstance(data, list):
data = [data]

self._update_vals(data, self._analog_channels)
Expand All @@ -116,7 +113,7 @@ def get_reading(self) -> List[ReaderVal]:


def _get_channel(self, chan: ReaderVal) -> str:
return f'{self._daq_name}/port{chan.port}/{chan.name}'
return f"{self._daq_name}/port{chan.port}/{chan.name}"

def _update_vals(self, vals: List[float], chans: List[ReaderVal]) -> None:
""" Updates channels in-place with new value. Assumes one sample per channel
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ class DAQReaderDummy(DAQReaderInterface):
Example config for copy-paste:
daq_dummy:
daq_reader_dummy:
module.Class: 'dummy.daq_reader_dummy.DAQReaderDummy'
"""

Expand All @@ -41,13 +41,12 @@ def __init__(self, *args, **kwargs):
def on_activate(self):
""" Initialisation performed during activation of the module.
"""
daq = self._daq()
self._current_data = daq.get_reading()
self._current_data = 0

self.__timer = QtCore.QTimer()
self.__timer.timeout.connect(50) # Switches every 50 ms
self.__timer.timeout.connect(self.__data_update)
self.__timer.setSingleShot(False)
self.__timer.start(int(self.__data_update))
self.__timer.start(50) # Switches every 50 ms


def on_deactivate(self):
Expand All @@ -56,10 +55,10 @@ def on_deactivate(self):
self.__timer.stop()
self.__timer.timeout.disconnect()
self.__timer = None

@property
def active_channels(self) -> List[str]:
""" Read-only property returning the currently configured active channel names """
return 'Dummy Channel'
return ['Dummy Channel']


def get_reading(self) -> List[ReaderVal]:
Expand Down
Loading

0 comments on commit d232eb5

Please sign in to comment.