-
Notifications
You must be signed in to change notification settings - Fork 1
Implementation of classes 'Device' and 'MeasurementChain' #2
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
Open
al-de
wants to merge
22
commits into
pyfar:master
Choose a base branch
from
pyfar-seminar:master
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
22 commits
Select commit
Hold shift + click to select a range
47fb7aa
first implementation of a measurement chain and demonstration in mest…
al-de b17de9f
Implemented feedback
al-de 2a0b1fe
add propertys & setter for Device class
al-de f1bd7eb
changed device summation to time domain with oaconvolve
al-de cf70788
moved docstring from MeasurementChain.add_device to Device.__init__
al-de 660fa11
fixed inconstistent varibale names in MeasurementChain.add_device
al-de 1be2d84
set default parameter MeasurementChain.__init__(devices=None) to avoi…
al-de 5c9189a
first implementation of tests for mestopy
al-de 7245949
add docstrings to tests
al-de a1b7e53
added init structure
f-brinkmann 6e912b3
Update docstring of Device
al-de 59f4e82
Update docstring of Device
al-de c648e49
Update d
al-de 20f7be2
Update docstring of MeasurementChain
al-de f12ef06
Update docstring of MeasurementChain
al-de 6990b3c
Update ValueError in case Devices samplingrates do not agree with Mea…
al-de 346632f
Update docstring of Device poperty freq
al-de be50803
Update docstring of Device and resolve some linting problems
al-de 39afff3
add check of sampling rates, when a list of devices is used to init M…
al-de ee55705
add conftest.py with a pytest.fixture for flat_freq signal stub
al-de 2cc5a58
Merge branch 'master' into enhancement/init
al-de 5eed12b
Merge pull request #1 from pyfar-seminar/enhancement/init
al-de File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,5 +1,9 @@ | ||
| """Top-level package for mestopy.""" | ||
|
|
||
| __author__ = """The pyfar developer""" | ||
| __email__ = 'marco.berzborn@akustik.rwth-aachen.de' | ||
| __email__ = 'info@pyfar.org' | ||
| __version__ = '0.0.1' | ||
|
|
||
| from .mestopy import (Device, MeasurementChain) | ||
|
|
||
| __all__ = ['Device', 'MeasurementChain'] |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1 +1,289 @@ | ||
| """Main module.""" | ||
| from scipy.signal import oaconvolve | ||
| from pyfar import Signal | ||
|
|
||
|
|
||
| # Class to generate ref-Objects, that can bei part of the MeasurementChain | ||
| class Device(object): | ||
| """Class for device in MeasurementChain. | ||
|
|
||
| This class holds methods and properties of a device in the | ||
| 'MeasurementChain' class. A device can be e.g., a sound card or a | ||
| pre-amplifier, described by a frequency response and/or sensitivity. | ||
| """ | ||
|
|
||
| def __init__(self, name, data=None, sens=1, unit=None): | ||
| """Init Device with data. | ||
|
|
||
| Attributes | ||
| ---------- | ||
| name : str | ||
| Name of the device. | ||
| data : Signal, None, optional | ||
| Signal data that reprensets the inversed frequency response of | ||
| the device. The default is None, in this case a perfect flat | ||
| frequency response is assumed and only sensitivity as a factor | ||
| is applied. | ||
| Caution: Avoid large gains in the frequency responses because | ||
| they will boost measurement noise and might cause numerical | ||
| instabilities. One possibility to avoid this is to use | ||
| regularized inversion. | ||
| sens : float, optional | ||
| Sensitivity of the device as a factor. If neither device_data nor | ||
| sens is given, add_device generates a device that has no effect to | ||
| the measurement chain as it has no frequency response and a | ||
| sesitivity (factor) default of 1. | ||
| unit : str, optional | ||
| The phyiscal unit of the device, e.g., mV/Pa. | ||
| """ | ||
| self.name = name | ||
| self.data = data | ||
| self.sens = sens | ||
| self.unit = unit | ||
|
|
||
| @property | ||
| def name(self): | ||
| """The name of the device""" | ||
| return self._name | ||
|
|
||
| @name.setter | ||
| def name(self, name): | ||
| if not isinstance(name, str): | ||
| raise ValueError('Device name must be string.') | ||
| else: | ||
| self._name = name | ||
|
|
||
| @property | ||
| def data(self): | ||
| """The freqeuncy dependent data, representing the device.""" | ||
| return self._data | ||
|
|
||
| @data.setter | ||
| def data(self, data): | ||
| if not isinstance(data, (Signal, type(None))): | ||
| raise TypeError('Input data must be type Signal or None.') | ||
| else: | ||
| self._data = data | ||
|
|
||
| @property | ||
| def sens(self): | ||
| """The sensitivity of the device.""" | ||
| return self._sens | ||
|
|
||
| @sens.setter | ||
| def sens(self, sens): | ||
| if not isinstance(sens, (int, float)): | ||
| raise ValueError('Sensitivity must be a number (int or float).') | ||
| else: | ||
| self._sens = sens | ||
|
|
||
| @property | ||
| def unit(self): | ||
| """The unit of the sensitivity.""" | ||
| return self._unit | ||
|
|
||
| @unit.setter | ||
| def unit(self, unit): | ||
| if not (isinstance(unit, str) or unit is None): | ||
| raise ValueError('Unit of sensitivity must be string or None.') | ||
| else: | ||
| self._unit = unit | ||
|
|
||
| @property | ||
| def freq(self): | ||
| """Return the inverted frequency multiplied by the sensitivity as a signal, | ||
| or the sensitivity as scalar, when the device has no frequency | ||
| response. | ||
| """ | ||
| if self.data is not None: | ||
| return self.data * self.sens | ||
| else: | ||
| return self.sens | ||
|
|
||
| def __repr__(self): | ||
| """String representation of Device class.""" | ||
| if self.data is None: | ||
| repr_string = ( | ||
| f"{self.name} defined by " | ||
| f"sensitivity={self.sens} unit={self.unit}\n") | ||
| else: | ||
| repr_string = ( | ||
| f"{self.name} defined by {self.data.n_bins} freq-bins, " | ||
| f"sensitivity={self.sens} unit={self.unit}\n") | ||
| return repr_string | ||
|
|
||
|
|
||
| # Class for MeasurementChain as frame for Devices | ||
| class MeasurementChain(object): | ||
| """Class for complete measurement chain. | ||
|
|
||
| This class holds methods and properties of all devices in the | ||
| measurement chain. It can include a single or multiple objects of | ||
| the Device class. | ||
| """ | ||
|
|
||
| def __init__(self, | ||
| sampling_rate, | ||
| sound_device=None, | ||
| devices=None, | ||
| comment=None): | ||
| """Init measurement chain with sampling rate. | ||
|
|
||
| Attributes | ||
| ---------- | ||
| sampling_rate : double | ||
| Sampling rate in Hertz. | ||
| sound_device : int | ||
| Number to identify the sound device used. The default is None. | ||
| devices : list | ||
| A list of Device objects. The default is an empty list. | ||
| comment : str | ||
| A comment related to the measurement chain. The default is None. | ||
| """ | ||
| self.sampling_rate = sampling_rate | ||
| self.sound_device = sound_device | ||
| self.comment = comment | ||
| if isinstance(devices, type(None)): | ||
| self.devices = [] | ||
| else: | ||
| for dev in devices: | ||
| if not isinstance(dev, Device): | ||
| raise TypeError('Input data must be type Device.') | ||
| if dev.data is None: | ||
| continue | ||
| if not dev.data.sampling_rate == self.sampling_rate: | ||
| raise ValueError("Sampling rate of device does not agree " | ||
| "with the measurement chain.") | ||
| self.devices = devices | ||
| self._freq() | ||
|
|
||
| def _find_device_index(self, name): | ||
| """Private method to find the index of a given device name.""" | ||
| for i, dev in enumerate(self.devices): | ||
| if dev.name == name: | ||
| return i | ||
| raise ValueError(f"device {name} not found") | ||
|
|
||
| def _freq(self): | ||
| """Private method to calculate the frequency response of the complete | ||
| measurement chain and save it to the private attribute _resp.""" | ||
| if self.devices == []: | ||
| resp = 1.0 | ||
| else: | ||
| resp = [[1.0]] | ||
| for dev in self.devices: | ||
| if isinstance(dev.freq, Signal): | ||
| resp = oaconvolve(resp, dev.freq.time) | ||
| else: | ||
| resp = oaconvolve(resp, [[dev.freq]]) | ||
| resp = Signal(resp, self.sampling_rate, domain='time') | ||
| resp.domain = 'freq' | ||
| self._resp = resp | ||
|
|
||
| def add_device(self, | ||
| name, | ||
| data=None, | ||
| sens=1, | ||
| unit=None | ||
| ): | ||
| """Adds a new device to the measurement chain. | ||
|
|
||
| Refer to the documentation of Device class. | ||
|
|
||
| Attributes | ||
| ---------- | ||
| name : str | ||
| data : pyfar.Signal, optional | ||
| sens : float, optional | ||
| unit : str, optional | ||
| """ | ||
| # check if device_data is type Signal or None | ||
| if not isinstance(data, (Signal, type(None))): | ||
| raise TypeError('Input data must be type Signal or None.') | ||
| # check if there are no devices in measurement chain | ||
| if not self.devices == []: | ||
| # check if sampling_rate of new device and MeasurementChain | ||
| # is the same | ||
| if data is not None: | ||
| if not self.sampling_rate == data.sampling_rate: | ||
| raise ValueError("Sampling rate of the new device does" | ||
| "not agree with the measurement chain.") | ||
| # add device to chain | ||
| new_device = Device(name, data=data, | ||
| sens=sens, unit=unit) | ||
| self.devices.append(new_device) | ||
| self._freq() | ||
|
|
||
| def list_devices(self): | ||
| """Returns a list of names of all devices in the measurement chain. | ||
| """ | ||
| # list all ref-objects in chain | ||
| device_names = [] | ||
| for dev in self.devices: | ||
| name = dev.name | ||
| device_names.append(name) | ||
| return device_names | ||
|
|
||
| def remove_device(self, num): | ||
| """Removes a single device from the measurement chain, | ||
| by name or number. | ||
|
|
||
| Attributes | ||
| ---------- | ||
| num : int or str | ||
| Identifier for device to remove. Device can be found by name as | ||
| string or by number in device list as int. | ||
| """ | ||
| # remove ref-object in chain position num | ||
| if isinstance(num, int): | ||
| self.devices.pop(num) | ||
| # remove ref-object in chain by name | ||
| elif isinstance(num, str): | ||
| self.remove_device(self._find_device_index(num)) | ||
| else: | ||
| raise TypeError("device to remove must be int or str") | ||
| self._freq() | ||
|
|
||
| # reset complete ref-object-list | ||
| def reset_devices(self): | ||
| """Resets the list of devices in the measurement chain. | ||
| Other global parameters such as sampling rate or sound device of the | ||
| measurement chain remain unchanged. | ||
| """ | ||
| self.devices = [] | ||
| self._freq() | ||
|
|
||
| # get the freq-response of specific device in measurement chain | ||
| def device_freq(self, num): | ||
| """Returns the frequency response of a single device from the | ||
| measurement chain, by name or number. | ||
|
|
||
| Attributes | ||
| ---------- | ||
| num : int or str | ||
| Identifier for device, can be name as string or by number | ||
| in device list as int. | ||
| """ | ||
| if isinstance(num, int): | ||
| return self.devices[num].freq | ||
| elif isinstance(num, str): | ||
| return self.device_freq(self._find_device_index(num)) | ||
| else: | ||
| raise TypeError("Device must be called by int or str.") | ||
|
|
||
| # get the freq-response of whole measurement chain as pyfar.Signal | ||
| @property | ||
| def freq(self): | ||
| """Returns the frequency response of the complete measurement chain. | ||
| All devices (frequency response and sensitivity) are considered. | ||
| """ | ||
| return self._resp | ||
|
|
||
| def __repr__(self): | ||
| """String representation of MeasurementChain class. | ||
| """ | ||
| repr_string = ( | ||
| f"measurement chain with {len(self.devices)} devices " | ||
| f"@ {self.sampling_rate} Hz sampling rate.\n") | ||
| for i, dev in enumerate(self.devices): | ||
| repr_string = f"{repr_string}# {i:{2}}: {dev}" | ||
| return repr_string | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is also checked in Device and could be removed here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When removing this check and passing something else then None or a Signal as data to add_device, the sampling-rate-check
if not self.sampling_rate == data.sampling_rate:would raise an AttributeError, as the data has no attribute 'sampling_rate'. My thought was to raise a more meaningful error, but I'm not sure whether this is the best solution.