Skip to content

Commit

Permalink
Merge pull request #63 from KCL-BMEIS/62-raw-waveforms-in-conveninece…
Browse files Browse the repository at this point in the history
…-methods

#62 raw waveforms in convenience methods
  • Loading branch information
crnbaker authored Jun 26, 2024
2 parents 5d9584f + e0a02ce commit 75cbe61
Show file tree
Hide file tree
Showing 11 changed files with 45 additions and 34 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/spectrumdevice-docs-pages.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ jobs:
- name: Set up Python 3.10
uses: actions/setup-python@v3
with:
python-version: "3.10"
python-version: "3.12"
- name: Install dependencies
run: |
python -m pip install --upgrade pip
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/spectrumdevice-integration-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ jobs:
strategy:
fail-fast: false
matrix:
python-version: ["3.9", "3.10", "3.11", "3.12"]
python-version: ["3.10", "3.11", "3.12"]

steps:
- uses: actions/checkout@v3
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/spectrumdevice-unit-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ jobs:
strategy:
fail-fast: false
matrix:
python-version: ["3.9", "3.10", "3.11", "3.12"]
python-version: ["3.10", "3.11", "3.12"]

steps:
- uses: actions/checkout@v3
Expand Down
5 changes: 2 additions & 3 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ classifiers =
Operating System :: POSIX :: Linux
Operating System :: Microsoft :: Windows :: Windows 10
Operating System :: MacOS :: MacOS X
Programming Language :: Python :: 3.9
Programming Language :: Python :: 3.10
Programming Language :: Python :: 3.11
Programming Language :: Python :: 3.12
Expand All @@ -34,8 +33,8 @@ package_dir =
= src
include_package_data = True
install_requires =
numpy>=1.21.4
python_requires = >=3.8
numpy>=1.26.2
python_requires = >=3.10

[options.packages.find]
where = src
Expand Down
4 changes: 2 additions & 2 deletions src/example_scripts/pulse_generator_example.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ def pulse_generator_example(mock_mode: bool) -> None:

# Set the card's sample rate. This affects the precision with which pulse timings can be chosen, and the min and max
# allowed pulse periods
card.set_sample_rate_in_hz(100000)
card.set_sample_rate_in_hz(8000000)
# Enable a single channel of the card. Although not used in this example, the number of enabled channels affects
# the precision with which pulse timings can be chosen and the min and max allowed pulse periods
card.set_enabled_analog_channels([0])
Expand Down Expand Up @@ -62,7 +62,7 @@ def pulse_generator_example(mock_mode: bool) -> None:
# The period is the length of the whole pulse (high-voltage length + 0V length)
# The duty cycle is the high-voltage length divided by the period
pulse_output_settings = PulseGeneratorOutputSettings(
period_in_seconds=1e-3, duty_cycle=0.5, num_pulses=2, delay_in_seconds=0.0, output_inversion=False
period_in_seconds=1e-3, duty_cycle=0.01, num_pulses=1000, delay_in_seconds=0.0, output_inversion=False
)
pulse_gen.configure_output(pulse_output_settings, coerce=False)

Expand Down
6 changes: 3 additions & 3 deletions src/spectrumdevice/devices/awg/synthesis.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
from numpy import float_, iinfo, issubdtype, signedinteger, pi, sin, linspace, int_, ones, int16
from numpy import float64, iinfo, issubdtype, signedinteger, pi, sin, linspace, int_, ones, int16
from numpy.typing import NDArray


def make_full_scale_sine_waveform(
frequency_in_hz: float, sample_rate_in_hz: int, num_cycles: float, dtype: type = int16
) -> tuple[NDArray[float_], NDArray[int_]]:
) -> tuple[NDArray[float64], NDArray[int_]]:
"""Create a sine waveform covering the full range of the given data type. The resulting waveform is intended to
be transferred to the AWG's on-board memory for generation.
Expand All @@ -25,7 +25,7 @@ def make_full_scale_sine_waveform(

def make_full_scale_rect_waveform(
sample_rate_in_hz: int, duration_in_seconds: float, dtype: type = int16
) -> tuple[NDArray[float_], NDArray[int_]]:
) -> tuple[NDArray[float64], NDArray[int_]]:
"""Create a rectangular waveform covering the full range of the given data type. The resulting waveform is intended
to be transferred to the AWG's on-board memory for generation.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from abc import ABC
from typing import List

from spectrumdevice.measurement import Measurement
from spectrumdevice.measurement import Measurement, RawWaveformType, VoltageWaveformType
from spectrumdevice.devices.abstract_device import AbstractSpectrumDevice
from spectrumdevice.devices.digitiser.digitiser_interface import (
SpectrumDigitiserAnalogChannelInterface,
Expand Down Expand Up @@ -76,14 +76,18 @@ def configure_acquisition(self, settings: AcquisitionSettings) -> None:
if settings.timestamping_enabled:
self.enable_timestamping()

def execute_standard_single_acquisition(self) -> Measurement:
def execute_standard_single_acquisition(self, raw: bool = False) -> Measurement:
"""Carry out a single measurement in standard single mode and return the acquired waveforms.
This method automatically carries out a standard single mode acquisition, including handling the creation
of a `TransferBuffer` and the retrieval of the acquired waveforms. After being called, it will wait until a
trigger event is received before carrying out the acquisition and then transferring and returning the acquired
waveforms. The device must be configured in SPC_REC_STD_SINGLE acquisition mode.
Args:
raw (bool, optional): Set to true to obtain raw (i.e. 16-bit integer) waveforms, instead of floating point
voltage waveforms.
Returns:
measurement (Measurement): A Measurement object. The `.waveforms` attribute of `measurement` will be a list
of 1D NumPy arrays, each array containing the waveform data received on one channel, in channel order.
Expand All @@ -101,11 +105,13 @@ def execute_standard_single_acquisition(self) -> Measurement:
self.define_transfer_buffer()
self.start_transfer()
self.wait_for_transfer_chunk_to_complete()
waveforms = self.get_waveforms()[0]
waveforms: list[RawWaveformType] | list[VoltageWaveformType] = (
self.get_raw_waveforms()[0] if raw else self.get_waveforms()[0]
)
self.stop() # Only strictly required for Mock devices. Should not affect hardware.
return Measurement(waveforms=waveforms, timestamp=self.get_timestamp())

def execute_finite_fifo_acquisition(self, num_measurements: int) -> List[Measurement]:
def execute_finite_fifo_acquisition(self, num_measurements: int, raw: bool = False) -> List[Measurement]:
"""Carry out a finite number of FIFO mode measurements and then stop the acquisitions.
This method automatically carries out a defined number of measurement in Multi FIFO mode, including handling the
Expand All @@ -118,6 +124,8 @@ def execute_finite_fifo_acquisition(self, num_measurements: int) -> List[Measure
Args:
num_measurements (int): The number of measurements to carry out.
raw (bool, optional): Set to true to obtain raw (i.e. 16-bit integer) waveforms, instead of floating point
voltage waveforms.
Returns:
measurements (List[Measurement]): A list of Measurement objects with length `num_measurements`. Each
Measurement object has a `waveforms` attribute containing a list of 1D NumPy arrays. Each array is a
Expand All @@ -133,9 +141,10 @@ def execute_finite_fifo_acquisition(self, num_measurements: int) -> List[Measure
self.execute_continuous_fifo_acquisition()
measurements = []
for _ in range(num_measurements // self.batch_size):
measurements += [
Measurement(waveforms=frame, timestamp=self.get_timestamp()) for frame in self.get_waveforms()
]
waveforms: list[list[RawWaveformType]] | list[list[VoltageWaveformType]] = (
self.get_raw_waveforms() if raw else self.get_waveforms()
)
measurements += [Measurement(waveforms=frame, timestamp=self.get_timestamp()) for frame in waveforms]
self.stop()
return measurements

Expand Down
6 changes: 3 additions & 3 deletions src/spectrumdevice/devices/digitiser/digitiser_card.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import logging
from typing import List, Optional, Sequence, cast

from numpy import float_, int16, mod, squeeze, zeros
from numpy import float64, int16, mod, squeeze, zeros
from numpy.typing import NDArray

from spectrum_gmbh.py_header.regs import (
Expand Down Expand Up @@ -173,13 +173,13 @@ def get_raw_waveforms(self) -> List[List[NDArray[int16]]]:

return repeat_acquisitions

def get_waveforms(self) -> List[List[NDArray[float_]]]:
def get_waveforms(self) -> List[List[NDArray[float64]]]:
"""Get a list of the most recently transferred waveforms, in channel order, in Volts as floats.
See get_raw_waveforms() for details.
Returns:
waveforms (List[List[NDArray[float_]]]): A list of lists of 1D numpy arrays, one inner list per acquisition
waveforms (List[List[NDArray[float64]]]): A list of lists of 1D numpy arrays, one inner list per acquisition
and one array per enabled channel, in channel order. To average the acquisitions:
`np.array(waveforms).mean(axis=0)`
Expand Down
8 changes: 4 additions & 4 deletions src/spectrumdevice/devices/digitiser/digitiser_interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,14 @@
from datetime import datetime
from typing import List, Optional

from numpy import float_, int16, ndarray
from numpy.typing import NDArray
from numpy import ndarray

from spectrumdevice.devices.abstract_device.device_interface import SpectrumDeviceInterface
from spectrumdevice.devices.abstract_device.channel_interfaces import (
SpectrumAnalogChannelInterface,
SpectrumIOLineInterface,
)
from spectrumdevice.measurement import VoltageWaveformType, RawWaveformType
from spectrumdevice.settings import AcquisitionMode, AcquisitionSettings
from spectrumdevice import Measurement
from spectrumdevice.settings.channel import InputImpedance, InputCoupling, InputPath
Expand Down Expand Up @@ -105,11 +105,11 @@ def execute_continuous_fifo_acquisition(self) -> None:
raise NotImplementedError()

@abstractmethod
def get_raw_waveforms(self) -> List[List[NDArray[int16]]]:
def get_raw_waveforms(self) -> List[List[RawWaveformType]]:
raise NotImplementedError()

@abstractmethod
def get_waveforms(self) -> List[List[NDArray[float_]]]:
def get_waveforms(self) -> List[List[VoltageWaveformType]]:
raise NotImplementedError()

@abstractmethod
Expand Down
8 changes: 4 additions & 4 deletions src/spectrumdevice/devices/digitiser/digitiser_star_hub.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from threading import Thread
from typing import Callable, Dict, List, Optional, Sequence, TypeVar

from numpy import float_, int16
from numpy import float64, int16
from numpy.typing import NDArray

from spectrumdevice.devices.abstract_device import (
Expand All @@ -23,7 +23,7 @@
from spectrumdevice.settings.device_modes import AcquisitionMode


WAVEFORM_TYPE_VAR = TypeVar("WAVEFORM_TYPE_VAR", NDArray[float_], NDArray[int16])
WAVEFORM_TYPE_VAR = TypeVar("WAVEFORM_TYPE_VAR", NDArray[float64], NDArray[int16])


# noinspection PyTypeChecker
Expand Down Expand Up @@ -73,14 +73,14 @@ def wait_for_acquisition_to_complete(self) -> None:
for card in self._child_cards:
card.wait_for_acquisition_to_complete()

def get_waveforms(self) -> List[List[NDArray[float_]]]:
def get_waveforms(self) -> List[List[NDArray[float64]]]:
"""Get a list of the most recently transferred waveforms, as floating point voltages.
This method gets the waveforms from each child card and joins them into a new list, ordered by channel number.
See `SpectrumDigitiserCard.get_waveforms()` for more information.
Returns:
waveforms (List[List[NDArray[float_]]]): A list lists of 1D numpy arrays, one inner list per acquisition,
waveforms (List[List[NDArray[float64]]]): A list lists of 1D numpy arrays, one inner list per acquisition,
and one array per enabled channel, in channel order.
"""
return self._get_waveforms_in_threads(SpectrumDigitiserCard.get_waveforms)
Expand Down
13 changes: 8 additions & 5 deletions src/spectrumdevice/measurement.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
from datetime import datetime
from dataclasses import dataclass
from typing import List, Optional

from numpy import float_
from numpy import int16, float64
from numpy.typing import NDArray


VoltageWaveformType = NDArray[float64]
RawWaveformType = NDArray[int16]


@dataclass
class Measurement:
"""Measurement is a dataclass for storing a set of waveforms generated by a single acquisition, with a timestamp."""

waveforms: List[NDArray[float_]]
"""Contains the acquired waveforms as a list of 1D NumPy arrays"""
timestamp: Optional[datetime]
waveforms: list[VoltageWaveformType] | list[RawWaveformType]
"""Contains the acquired waveforms as a list of 1D NumPy arrays or either floats or ints"""
timestamp: datetime | None
"""The time at which the acquisition was triggered, as a datetime.datetime object"""

0 comments on commit 75cbe61

Please sign in to comment.