Compare commits

...

4 Commits

Author SHA1 Message Date
Anne de Jong 514ed1aa32 Added physicalOutputQty for daq devices, added possibility to inspect from Python whether device has monitor. Added unit for equation in Qtys. Version bump 1.3.0
Building, testing and releasing LASP if it has a tag / Build-Test-Ubuntu (push) Successful in 2m49s Details
Building, testing and releasing LASP if it has a tag / Release-Ubuntu (push) Has been skipped Details
2024-01-10 12:26:38 +01:00
Anne de Jong 0be8dd71d9 Bugfixes: store UUID attribute early when recording is done. Some small improvements
Building, testing and releasing LASP if it has a tag / Build-Test-Ubuntu (push) Successful in 2m15s Details
Building, testing and releasing LASP if it has a tag / Release-Ubuntu (push) Successful in -1m38s Details
2023-12-19 14:34:47 +01:00
Anne de Jong 2cd4c616b3 Bump 1.2.0
Building, testing and releasing LASP if it has a tag / Build-Test-Ubuntu (push) Successful in 2m11s Details
Building, testing and releasing LASP if it has a tag / Release-Ubuntu (push) Successful in -1m25s Details
2023-12-19 14:04:43 +01:00
Anne de Jong 311a1274bf Added fromFile() method to overcome problem of multiple times opening same file 2023-12-19 14:03:46 +01:00
10 changed files with 102 additions and 33 deletions

View File

@ -44,22 +44,13 @@ Daq::Daq(const DeviceInfo &devinfo, const DaqConfiguration &config)
: DaqConfiguration(config), DeviceInfo(devinfo) {
DEBUGTRACE_ENTER;
if (duplexMode()) {
if (neninchannels() == 0) {
throw rte("Duplex mode enabled, but no input channels enabled");
}
if (nenoutchannels() == 0) {
throw rte("Duplex mode enabled, but no output channels enabled");
}
}
if(!duplexMode() && monitorOutput) {
throw rte("Output monitoring only allowed when running in duplex mode");
throw rte("Duplex mode requires enabling both input and output channels. Please make sure at least one output channel is enabled, or disable hardware output loopback in DAQ configuration.");
}
if (!hasInternalOutputMonitor && monitorOutput) {
throw rte(
"Output monitor flag set, but device does not have output monitor");
"Output monitor flag set, but device does not have hardware output monitor.");
}
if (!config.match(devinfo)) {

View File

@ -35,12 +35,14 @@ DaqConfiguration::DaqConfiguration(const DeviceInfo &device) {
us i = 0;
for (auto &inch : inchannel_config) {
inch.name = "Unnamed input channel " + std::to_string(i);
inch.rangeIndex = device.prefInputRangeIndex;
i++;
}
i = 0;
for (auto &outch : outchannel_config) {
outch.name = "Unnamed output channel " + std::to_string(i);
outch.rangeIndex = device.prefOutputRangeIndex;
i++;
}

View File

@ -68,14 +68,26 @@ public:
us prefFramesPerBlockIndex = 0;
/**
* @brief Available ranges for the input, i.e. +/- 1V and/or +/- 10 V etc.
* @brief Available ranges for the input, i.e. +/- 1V and/or +/- 10 V etc.
*/
dvec availableInputRanges;
/**
* @brief Its preffered range
* @brief Available ranges for the output, i.e. +/- 1V and/or +/- 10 V etc.
*/
dvec availableOutputRanges;
/**
* @brief Its preffered input range
*/
int prefInputRangeIndex = 0;
/**
* @brief Its preffered output range
*/
int prefOutputRangeIndex = 0;
/**
* @brief The number of input channels available for the device
*/
@ -125,13 +137,29 @@ public:
bool duplexModeForced = false;
/**
* @brief The physical quantity of the output signal. For 'normal' audio
* @brief Indicates whether the device is able to run in duplex mode. If false,
* devices cannot run in duplex mode, and the `duplexModeForced` flag is meaningless.
*/
bool hasDuplexMode = false;
/**
* @brief The physical quantity of the input signal from DAQ. For 'normal' audio
* interfaces, this is typically a 'number' between +/- full scale. For some
* real DAQ devices however, the input quantity corresponds to a physical signal,
* such a Volts.
*/
DaqChannel::Qty physicalInputQty = DaqChannel::Qty::Number;
/**
* @brief The physical quantity of the output signal from DAQ. For 'normal' audio
* devices, this is typically a 'number' between +/- full scale. For some
* devices however, the output quantity corresponds to a physical signal,
* real DAQ devices however, the input quantity corresponds to a physical signal,
* such a Volts.
*/
DaqChannel::Qty physicalOutputQty = DaqChannel::Qty::Number;
/**
* @brief String representation of DeviceInfo
*

View File

@ -99,6 +99,7 @@ void fillRtAudioDeviceInfo(DeviceInfoList &devinfolist) {
d.ninchannels = devinfo.inputChannels;
d.availableInputRanges = {1.0};
d.availableOutputRanges = {1.0};
RtAudioFormat formats = devinfo.nativeFormats;
if (formats & RTAUDIO_SINT8) {

View File

@ -68,6 +68,7 @@ void fillUlDaqDeviceInfo(DeviceInfoList &devinfolist) {
}
devinfo.physicalOutputQty = DaqChannel::Qty::Voltage;
devinfo.physicalInputQty = DaqChannel::Qty::Voltage;
devinfo.availableDataTypes.push_back(
DataTypeDescriptor::DataType::dtype_fl64);
@ -79,7 +80,9 @@ void fillUlDaqDeviceInfo(DeviceInfoList &devinfolist) {
devinfo.availableFramesPerBlock = {512, 1024, 2048, 4096, 8192};
devinfo.availableInputRanges = {1.0, 10.0};
devinfo.availableOutputRanges = {10.0};
devinfo.prefInputRangeIndex = 0;
devinfo.prefOutputRangeIndex = 0;
devinfo.ninchannels = 4;
devinfo.noutchannels = 1;
@ -90,6 +93,7 @@ void fillUlDaqDeviceInfo(DeviceInfoList &devinfolist) {
devinfo.hasInternalOutputMonitor = true;
devinfo.hasDuplexMode = true;
devinfo.duplexModeForced = true;
// Finally, this devinfo is pushed back in list

View File

@ -29,6 +29,9 @@ void init_deviceinfo(py::module& m) {
devinfo.def_readonly("availableInputRanges",
&DeviceInfo::availableInputRanges);
devinfo.def_readonly("prefInputRangeIndex", &DeviceInfo::prefInputRangeIndex);
devinfo.def_readonly("availableOutputRanges",
&DeviceInfo::availableOutputRanges);
devinfo.def_readonly("prefOutputRangeIndex", &DeviceInfo::prefOutputRangeIndex);
devinfo.def_readonly("ninchannels", &DeviceInfo::ninchannels);
devinfo.def_readonly("noutchannels", &DeviceInfo::noutchannels);
@ -36,7 +39,10 @@ void init_deviceinfo(py::module& m) {
devinfo.def_readonly("hasInputACCouplingSwitch",
&DeviceInfo::hasInputACCouplingSwitch);
devinfo.def_readonly("hasDuplexMode", &DeviceInfo::hasDuplexMode);
devinfo.def_readonly("duplexModeForced", &DeviceInfo::duplexModeForced);
devinfo.def_readonly("hasInternalOutputMonitor", &DeviceInfo::hasInternalOutputMonitor);
devinfo.def_readonly("physicalInputQty", &DeviceInfo::physicalInputQty);
devinfo.def_readonly("physicalOutputQty", &DeviceInfo::physicalOutputQty);
}

View File

@ -5,7 +5,7 @@ requires-python = ">=3.10"
description = "Library for Acoustic Signal Processing"
license = { "file" = "LICENSE" }
authors = [{ "name" = "J.A. de Jong", "email" = "j.a.dejong@ascee.nl" }]
version = "1.1.0"
version = "1.3.0"
keywords = ["DSP", "DAQ", "Signal processing"]

View File

@ -62,8 +62,9 @@ class Qty:
name: str
# I.e.: Pascal
unit_name: str
# I.e.: Pa
# I.e.: -, Pa, V
unit_symb: str
# I.e.: ('dB SPL') <== tuple of possible level units
level_unit: object
# Contains a tuple of possible level names, including its reference value.
@ -92,6 +93,18 @@ class Qty:
"""
return self.cpp_enum.value
@property
def unit_symb_eq(self):
"""Unit symbol to be used in equations
Returns:
String: V, Pa, 1,
"""
if self.unit_symb != '-':
return self.unit_symb
else:
return '1'
@unique

View File

@ -1,7 +1,6 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from __future__ import annotations
"""!
Author: J.A. de Jong - ASCEE
@ -127,7 +126,7 @@ def scaleBlockSens(block, sens):
fac = 2 ** (8 * sw - 1) - 1
else:
fac = 1.0
return block.astype(LASP_NUMPY_FLOAT_TYPE) / fac / sens[np.newaxis, :]
return block.astype(LASP_NUMPY_FLOAT_TYPE) / fac / sens[np.newaxis,:]
class IterRawData:
@ -200,7 +199,7 @@ class IterRawData:
# print(f'block: {block}, starto: {start_offset}, stopo {stop_offset}')
self.i += 1
return fa[block, start_offset:stop_offset, :][:, self.channels]
return fa[block, start_offset:stop_offset,:][:, self.channels]
class IterData(IterRawData):
@ -236,9 +235,6 @@ class Measurement:
# Full filepath
self.fn = fn
# Folder, Base filename + extension
self.folder, self.fn_base = os.path.split(fn)
# Open the h5 file in read-plus mode, to allow for changing the
# measurement comment.
with h5.File(fn, "r") as f:
@ -366,15 +362,23 @@ class Measurement:
def rename(self, newname: str):
"""
Try to rename the measurement file.
Args:
newname: New name, with or without extension
"""
_ , ext = os.path.splitext(newname)
# Add proper extension if new name is given without extension.
if ext != DOTMEXT:
newname = newname + DOTMEXT
newname_full = str(pathlib.Path(self.folder) / newname)
# Folder, Base filename + extension
folder, _ = os.path.split(self.fn)
newname_full = str(pathlib.Path(folder) / newname)
os.rename(self.fn, newname_full)
self.fn = newname_full
def genNewUUID(self):
"""
Create new UUID for measurement and store in file.
@ -420,7 +424,8 @@ class Measurement:
# Last resort, see if we can find the right measurement in the same folder
if m is None:
try:
m = Measurement.fromFolderWithUUID(required_uuid, self.folder, skip=[self.name])
folder, _ = os.path.split(self.fn)
m = Measurement.fromFolderWithUUID(required_uuid, folder, skip=[self.name])
logging.info('Found reference measurement in folder with correct UUID. Updating name of reference measurement')
# Update the measurement file name in the list, such that next time it
# can be opened just by its name.
@ -498,13 +503,14 @@ class Measurement:
raise RuntimeError(f'Measurement with UUID {uuid_str} could not be found.')
def setAttribute(self, attrname, value):
def setAttribute(self, attrname: str, value):
"""
Set an attribute in the measurement file, and keep a local copy in
memory for efficient accessing.
Args:
atrname
attrname: name of attribute, a string
value: the value. Should be anything that can be stored as an attribute in HDF5.
"""
with self.file("r+") as f:
# Update comment attribute in the file
@ -536,7 +542,8 @@ class Measurement:
@property
def name(self):
"""Returns filename base without extension."""
return os.path.splitext(self.fn_base)[0]
_, fn = os.path.split(self.fn)
return os.path.splitext(fn)[0]
@property
def channelNames(self):
@ -970,6 +977,22 @@ class Measurement:
wavfile.write(fn, int(self.samplerate), data.astype(newtype))
@staticmethod
def fromFile(fn):
"""
Try to open measurement from a given file name. First checks
whether the measurement is already open. Otherwise it might
happen that a Measurement object is created twice for the same backing file, which we do not allow.
"""
# See if the base part of the filename is referring to a file that is already open
with h5.File(fn, 'r') as f:
uuid = f.attrs['UUID']
if uuid in Measurement.uuid_s.keys():
return Measurement.uuid_s[uuid]
return Measurement(fn)
@staticmethod
def fromtxt(
fn,
@ -1048,8 +1071,8 @@ class Measurement:
sensitivity,
mfn,
timestamp=None,
qtys: List[SIQtys] = None,
channelNames: List[str] = None,
qtys: List[SIQtys]=None,
channelNames: List[str]=None,
force=False,
) -> Measurement:
"""
@ -1111,7 +1134,6 @@ class Measurement:
if len(qtys) != nchannels:
raise RuntimeError("Illegal length of qtys list given")
with h5.File(mfn, "w") as hf:
hf.attrs["samplerate"] = samplerate
hf.attrs["sensitivity"] = sensitivity

View File

@ -9,6 +9,7 @@ import numpy as np
from .lasp_atomic import Atomic
from .lasp_cpp import InDataHandler, StreamMgr
from .lasp_version import LASP_VERSION_MAJOR, LASP_VERSION_MINOR
import uuid
@dataclasses.dataclass
@ -139,6 +140,7 @@ class Recording:
f.attrs["blocksize"] = blocksize
f.attrs["sensitivity"] = [ch.sensitivity for ch in in_ch]
f.attrs["channelNames"] = [ch.name for ch in in_ch]
f.attrs["UUID"] = str(uuid.uuid1())
# Add the start delay here, as firstFrames() is called right after the
# constructor is called. time.time() returns a floating point