diff --git a/mestopy/__init__.py b/mestopy/__init__.py index 1e7566e..cb226bb 100644 --- a/mestopy/__init__.py +++ b/mestopy/__init__.py @@ -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'] diff --git a/mestopy/mestopy.py b/mestopy/mestopy.py index dd0b80e..69ab300 100644 --- a/mestopy/mestopy.py +++ b/mestopy/mestopy.py @@ -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 diff --git a/mestopy_demo.ipynb b/mestopy_demo.ipynb new file mode 100644 index 0000000..3529f1d --- /dev/null +++ b/mestopy_demo.ipynb @@ -0,0 +1,242 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Mestopy Demo-Notebook\n" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "# import modules\n", + "import pyfar\n", + "import numpy as np\n", + "\n", + "# import from mestopy\n", + "from mestopy.mestopy import MeasurementChain" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Demo: basic functionality of Measurement Chain" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "# initialise measurement chain @fs=48000\n", + "fs = 48000\n", + "chain = MeasurementChain(fs)" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "output_type": "stream", + "name": "stderr", + "text": [ + "/home/alex/Git/pyfar-develop/pyfar/signal.py:507: UserWarning: Number of time samples not given, assuming an even number of samples from the number of frequency bins.\n", + " warnings.warn(\n", + "measurement chain with 4 devices @ 48000 Hz sampling rate.\n", + "# 0: Device 1 defined by 24000 freq-bins, sensitivity=1.0 unit=None\n", + "# 1: Device 1 defined by 24000 freq-bins, sensitivity=1.0 unit=None\n", + "# 2: Device 2 defined by 24000 freq-bins, sensitivity=2.0 unit=None\n", + "# 3: Device 3 defined by 24000 freq-bins, sensitivity=0.5 unit=None\n", + "\n", + "['Device 1', 'Device 1', 'Device 2', 'Device 3']\n", + "['Device 1', 'Device 2', 'Device 3']\n", + "freq domain energy Signal:\n", + "(1,) channels with 143992 samples @ 48000 Hz sampling rate and none FFT normalization\n", + "\n", + "freq domain energy Signal:\n", + "(1,) channels with 47998 samples @ 48000 Hz sampling rate and none FFT normalization\n", + "\n" + ] + } + ], + "source": [ + "# reset MeasurementChain refs, in case you run this cell multiple times\n", + "chain.reset_devices()\n", + "\n", + "# generate a flat signal\n", + "sig = pyfar.Signal(np.full(int(fs/2), 1.0), fs, domain='freq')\n", + "\n", + "# add these signals as devices to the measurement chain; attention: Device 1 is added twice by \"mistake\"\n", + "chain.add_device('Device 1', sig, sens=1.0) \n", + "chain.add_device('Device 1', sig, sens=1.0)\n", + "chain.add_device('Device 2', sig, sens=2.0)\n", + "chain.add_device('Device 3', sig, sens=0.5)\n", + "\n", + "# print measurement chain __repr__\n", + "print(chain)\n", + "\n", + "# print names of devices in measurement chain\n", + "print(chain.list_devices())\n", + "\n", + "# remove double added Device 1 from measurement chain\n", + "chain.remove_device(1)\n", + "\n", + "# print names of devices in measurement chain after removing\n", + "print(chain.list_devices())\n", + "\n", + "# get the freq-response of whole measurement chain as pyfar.Signal\n", + "print(chain.freq)\n", + "\n", + "# get the freq-response of specific device in measurement chain as pyfar.Signal\n", + "print(chain.device_freq(1))" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "output_type": "execute_result", + "data": { + "text/plain": [ + "" + ] + }, + "metadata": {}, + "execution_count": 4 + }, + { + "output_type": "display_data", + "data": { + "text/plain": "
", + "image/svg+xml": "\n\n\n\n \n \n \n \n 2021-03-12T20:07:06.798012\n image/svg+xml\n \n \n Matplotlib v3.3.4, https://matplotlib.org/\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n", + "image/png": "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P/6o9PR0jRo1SqNHj9YXX3zhk33nzZunO+64Q3fffbdzrK59s7OzlZCQoJEjR2rHjh1en8+d+vTT3zVJ3vXUk3q87aevHmNt/X7wwQd14MCBeu9ZX656Hax6zvtlrxtaT2299tVj9Obnd9OmTXrqqacafM66ePvz621NjS6gQkND9cgjj2j9+vXKzs7W22+/raKiIq1cuVK9e/fWu+++q969e2vlypUBq+nNN9/U9ddf7/zaF7U888wz6tevn9555x3n/r7Yd/jw4Vq6dOlFY672LSoqUm5urtatW6elS5dq4cKFstvtXp+zLt72MxA1SZ731NN6vOmnLx9jbf0OFle9DrZf9rqhauu1r3jz8xsI/n4+bnQBdfXVV6tr166SpMsvv1ydOnWSzWZTXl6ehg0bJkkaNmyYPv7444DUc+zYMX366adKSEhwjjW0llOnTukf//iHRowYIUlq3ry5Wrdu7ZPHePPNN+uKK664aMzVvnl5eRo8eLCsVqs6dOigqKgoFRYWen3Ounjbz0DU5E1PPanH23768jHW1u/zzp07p4yMDGVlZdVrb2+56nWw6pFq73VD6nHV64bseSFvfn4vtH37dk2aNEkVFRX1Oq8rDXk+9qSmRhdQFyorK9OhQ4fUo0cPff/997r66qsl/fxNO3HiREBqWLx4saZMmSKLxeIca2gtpaWlCg8P17x585SYmKjMzEz99NNPfnuMrva12Wy65pprnOsiIyMvekLxNU/6GYiavOmpJ/V4289APEa73a7Zs2erY8eOSklJ8enenriw18Gsp7ZeN6QeV71uyJ7uuHte+Oijj7Ry5Uq98MILCg8P99l5f8mb52NPa2q0AVVZWan09HRNnz5drVq1CkoNn3zyia688kp169bNp/va7XYdOnRIo0eP1po1a3TZZZcF9LK9Lr/8QfaVhvTTlzX5oqe1Pdk1tJ++/r4vWLBA0dHRSkpK8um+nqit18Gop65e17eeunodjMe4b98+rVq1Ss8//7zLK2lf8Obn15uaGmVAnT17Vunp6fr973+v22+/XZJ05ZVXqry8XJJUXl6utm3b+r2OgoIC5efna/jw4Zo1a5b27NmjOXPmNLiWyMhIRUZGOv91OWjQIB08eNBvj9HVvpGRkTp27Jhznc1mU0REhE/OeSFv+unvmrztqSf1eNvPQHzfb7rpJu3bt0/V1dU+3ded2nodrHpc9boh9bjqdUP2dKeu54UOHTqosrJShw8f9uk5L+Tt87E3NTW6gHI4HHriiSd0/fXXa/z48c7xgQMHavPmzZKkzZs3a+DAgX6v5aGHHlJOTo42bdqk+fPn63e/+50yMzMbXMvVV1+ta665Rt99950kaffu3ercubPfHqOrfePi4pSbm6uamhqVlpaqpKREMTExPjnned720981edtTT+rxtp+B+L6PGDFC/fr104wZM3T27Fmf7u2Kq14Hqx5XvW5IPa563ZA93anreaFdu3ZatGiRMjIy9M033/jsnOfV5/nYm5oaXUAVFBQoJydHe/bsUWJiohITE7V9+3bde++92rVrl+666y7t2rVLEydODFqNvqglLS1Nc+bM0dixY/Wvf/1LkyZN8sm+M2fO1KRJk1RcXKyhQ4fqvffec7lvdHS04uPjNWbMGD388MNKT09XSEiI1+esi7f9DERNtWloPd7005ePsbZ+nzd+/Hh17dpVc+fO1blz5+q1vzdc9TpY9bhT33pq63VD9zzPm5/f8zp16qTMzEzNmDFD//nPf7w+Z13q+3zsaU183AYAwEiN7goKANA0EFAAACMRUAAAIxFQAAAjEVAAACMRUMD/9O7d2/lW2cTERJWVlQW7JJ84cOCAnn76aa+OGT58+EX3SNu7d6/zbt9AoIQGuwDAFC1atNCaNWtqnXM4HHI4HGrWrPH9m6579+7q3r17sMsAvEZAAS6UlZVpypQp6tWrl7744gstXrxYW7du1QcffKCamhrddtttSk5OliS9+uqrev/999WuXTuFh4erW7dumjBhgh588EFNmzZN3bt3V0VFhSZMmKBNmzbJbrfrxRdf1L59+1RTU6MxY8Zo1KhR2rt3r/7yl78oPDxc33zzjbp166bMzExZLBYVFhZq8eLF+umnn9S8eXO9/PLLmjp1qtLS0nTjjTdKku677z49/vjj+u1vf+t8HHv37tUbb7yh559/XsuXL9fRo0dVWlqqY8eO6Z577tHYsWO9+r5MmTLFeRub0tJSpaWlOe9cDfgSAQX8T3V1tRITEyVJ1157rR599FEVFxcrIyNDM2bM0M6dO1VSUqJVq1bJ4XDo0Ucf1eeff67LLrtMubm5WrNmjc6ePavx48e7vdnshg0bdPnll+v1119XTU2NkpKS1KdPH0nSoUOHtG7dOkVERCgpKUkFBQWKiYnRzJkztWDBAsXExOjUqVNq0aKFEhIStHnzZt14440qLi7WmTNnLgqn2hQXF2vZsmWqrKx0fqheaOivnwqSk5Odd7CorKxUp06dJElLliyRJH311VeaN2+ebr31Vm++zYDHCCjgf375K76ysjK1b99esbGxkqSdO3dq586dGjdunCQ5b3hZWVmp2267TWFhYZJ+vpeeOzt37tTXX3+tDz/8UNLPnyNUUlKi0NBQxcTEOD9u44YbblBZWZlatWqlq666ynlPvvN3jI6Pj9crr7yiqVOnauPGjR5dyfTv319Wq1VWq1Vt27bVf//734s+3uO85cuXOz8K4fxV2HkVFRWaO3eunnzyyaB9mgAufQQUUIfzoSP9/DrUxIkTNWrUqIvWrFmzxuXHYYSGhjrvuXbhXawdDofS0tLUt2/fi9bv3btXVqvV+XWzZs1kt9vlcDhqPUdYWJhuueUWffzxx/rggw/0+uuvu31Mte3vDbvdrpkzZ+r+++9Xly5dvDoW8Ebje8UXCJK+fftq48aNqqyslPTzx2B8//33uvnmm/XRRx+pqqpKp0+f1ieffOI8pn379s6PW9i2bdtFe61fv955V+vi4mLnB9vVplOnTiovL3d+su7p06edxyYkJOiZZ55R9+7d1aZNG98+6Fq8+OKL6tKli4YMGeL3c6Fp4woK8FCfPn307bffOu9O3bJlS2VmZqpr16664447lJiYqPbt26tnz57OYyZMmKAZM2YoJydHvXr1co4nJCToyJEjGjdunBwOh9q2bavFixe7PHfz5s21YMECPf3006qurlaLFi2UlZWl0NBQdevWTZdffrmGDx/ut8d+odWrV6tz587O1+uSk5MD8vE2aHq4mzngY8uXL1fLli01YcKEgJzv+PHjSk5O1vr16xvl2+ABV/i/GWjENm/erIkTJyolJYVwwiWHKygAgJH4JxcAwEgEFADASAQUAMBIBBQAwEgEFADASP8Pn6aMjIP9RicAAAAASUVORK5CYII=\n" + }, + "metadata": {} + } + ], + "source": [ + "# plot freq-response of whole measurement chain\n", + "fig1 = pyfar.plot.line.freq(chain.freq)\n", + "\n", + "# plot freq-response of element 1 of the measurement chain\n", + "pyfar.plot.line.freq(chain.device_freq(2))" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "['Device 1', 'Device 2', 'Device 3']\n['Device 1', 'Device 2']\n" + ] + }, + { + "output_type": "execute_result", + "data": { + "text/plain": [ + "" + ] + }, + "metadata": {}, + "execution_count": 5 + }, + { + "output_type": "display_data", + "data": { + "text/plain": "
", + "image/svg+xml": "\n\n\n\n \n \n \n \n 2021-03-12T20:07:07.478433\n image/svg+xml\n \n \n Matplotlib v3.3.4, https://matplotlib.org/\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n", + "image/png": "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a3S3r81mU3l5uWud3W5XRESEX855Pl/6GeiafO2pN/X42s9g/L3/9re/1f79+1VTU+PXfT1pqNdG1eOu102px12vm7KnJ439XOjWrZuqqqp05MgRv57zfL7+PPalpmYXUE6nU0899ZSuueYaJSYmusaHDBmiLVu2SJK2bNmiIUOGBLyWhx56SDk5Odq8ebPmzZunm266SXPnzm1yLZ06dVLnzp313XffSZL27Nmja6+9NmCv0d2+sbGxys3NVW1trUpLS1VSUqKoqCi/nLOer/0MdE2+9tSbenztZzD+3kePHq3BgwdrxowZqqur8+ve7rjrtVH1uOt1U+px1+um7OlJYz8XunTpooULFyozM1PffPON385Z72J+HvtSU7MLqIKCAuXk5Gjv3r1KSEhQQkKCduzYoXvuuUe7d+/WXXfdpd27d2vy5MmG1eiPWtLT0/Xkk09q4sSJ+te//qUpU6b4Zd+ZM2dqypQpKi4u1ogRI/TBBx+43TcyMlJxcXEaP368Hn74YWVkZCgkJMTnczbG134Go6aGNLUeX/rpz9fYUL/rJSYmqnfv3po9e7bOnj17Ufv7wl2vjarHk4utp6FeN3XPer58/9br2bOn5s6dqxkzZug///mPz+dszMX+PPa2Jh63AQAwpWZ3BQUAaBkIKACAKRFQAABTIqAAAKZEQAEATImAAv5nwIABro/KJiQkqKyszOiS/OLgwYN69tlnfTpm1KhRF9wjbd++fa67fQPBEmp0AYBZtGnTRmvXrm1wzul0yul0qlWr5vdvur59+6pv375GlwH4jIAC3CgrK9PUqVN144036ssvv9SiRYu0bds2ffjhh6qtrdVtt92mlJQUSdJrr72mv/71r+rSpYvCw8PVp08fJSUl6YEHHtC0adPUt29fVVZWKikpSZs3b5bD4dCyZcu0f/9+1dbWavz48Ro7dqz27dunV155ReHh4frmm2/Up08fzZ07VxaLRYWFhVq0aJFOnz6t1q1b6+WXX9Yjjzyi9PR0XX/99ZKke++9V48//rh+85vfuF7Hvn379MYbb+jFF1/UihUrdOzYMZWWlqq8vFx33323Jk6c6NPfy9SpU123sSktLVV6errrztWAPxFQwP/U1NQoISFBknTVVVfpscceU3FxsTIzMzVjxgzt2rVLJSUlWr16tZxOpx577DF98cUXuuyyy5Sbm6u1a9eqrq5OiYmJHm82u3HjRl1++eV6/fXXVVtbq+TkZA0cOFCSdPjwYa1fv14RERFKTk5WQUGBoqKiNHPmTM2fP19RUVE6deqU2rRpo/j4eG3ZskXXX3+9iouLdebMmQvCqSHFxcVavny5qqqqXA/VCw395Y+ClJQU1x0sqqqq1LNnT0nSkiVLJEn//Oc/NWfOHN16662+/DUDXiOggP/5+a/4ysrK1LVrV0VHR0uSdu3apV27dmnSpEmS5LrhZVVVlW677TaFhYVJOncvPU927dqlr7/+Wh999JGkc88RKikpUWhoqKKiolyP27juuutUVlamdu3a6Ve/+pXrnnz1d4yOi4vTq6++qkceeUSbNm3y6krmlltukdVqldVqVceOHfXf//73gsd71FuxYoXrUQj1V2H1KisrNXv2bD399NOGPU0Alz4CCmhEfehI596Hmjx5ssaOHXvBmrVr17p9HEZoaKjrnmvn38Xa6XQqPT1dgwYNumD9vn37ZLVaXV+3atVKDodDTqezwXOEhYXp5ptv1ieffKIPP/xQr7/+usfX1ND+vnA4HJo5c6buu+8+9erVy6djAV80v3d8AYMMGjRImzZtUlVVlaRzj8H4/vvvdcMNN+jjjz9WdXW1fvrpJ3366aeuY7p27ep63ML27dsv2GvDhg2uu1oXFxe7HmzXkJ49e6qiosL1ZN2ffvrJdWx8fLyee+459e3bVx06dPDvi27AsmXL1KtXLw0fPjzg50LLxhUU4KWBAwfq22+/dd2dum3btpo7d6569+6tO+64QwkJCeratav69+/vOiYpKUkzZsxQTk6ObrzxRtd4fHy8jh49qkmTJsnpdKpjx45atGiR23O3bt1a8+fP17PPPquamhq1adNGWVlZCg0NVZ8+fXT55Zdr1KhRAXvt51uzZo2uvfZa1/t1KSkpQXm8DVoe7mYO+NmKFSvUtm1bJSUlBeV8x48fV0pKijZs2NAsPwYPuMP/moFmbMuWLZo8ebJSU1MJJ1xyuIICAJgS/+QCAJgSAQUAMCUCCgBgSgQUAMCUCCgAgCn9P2lrREvt3FYrAAAAAElFTkSuQmCC\n" + }, + "metadata": {} + } + ], + "source": [ + "# remove devices from measurement chain by name\n", + "print(chain.list_devices())\n", + "chain.remove_device('Device 3')\n", + "print(chain.list_devices())\n", + "\n", + "# plot freq-response of whole measurement chain\n", + "fig3 = pyfar.plot.line.freq(chain.device_freq('Device 1'))\n", + "# invert ref-response and plot again\n", + "pyfar.plot.line.freq(chain.freq)" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "measurement chain with 3 devices @ 48000 Hz sampling rate.\n# 0: Device 1 defined by 24000 freq-bins, sensitivity=1.0 unit=None\n# 1: Device 2 defined by 24000 freq-bins, sensitivity=2.0 unit=None\n# 2: new device defined by sensitivity=1 unit=None\n\n" + ] + } + ], + "source": [ + "chain.add_device('new device')\n", + "print(chain)" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "freq domain energy Signal:\n(1,) channels with 95995 samples @ 48000 Hz sampling rate and none FFT normalization\n\n1.0\n" + ] + } + ], + "source": [ + "# print freq of chain, it is a Signal, because there are devices with frequency responses\n", + "print(chain.freq)\n", + "\n", + "# create measurement chain with no devices\n", + "chain2 = MeasurementChain(48000)\n", + "# print freq, it is a scalar of 1, because there are no devices\n", + "print(chain2.freq)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.8-final" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} \ No newline at end of file diff --git a/tests/__init__.py b/tests/__init__.py deleted file mode 100644 index 172d321..0000000 --- a/tests/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""Unit test package for mestopy.""" diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..fbc618c --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,19 @@ +import pytest +from pyfar.testing.stub_utils import signal_stub + + +@pytest.fixture +def flat_freq(): + """Flat frequency signal stub. + + Returns + ------- + signal : Signal + Stub of flat freq signal + """ + time = [1, 0, 0, 0, 0, 0, 0, 0] + freq = [1, 1, 1, 1] + sampling_rate = 44100 + fft_norm = 'none' + signal = signal_stub(time, freq, sampling_rate, fft_norm) + return signal diff --git a/tests/test_mestopy.py b/tests/test_mestopy.py index accfdeb..b7fd9f3 100644 --- a/tests/test_mestopy.py +++ b/tests/test_mestopy.py @@ -2,23 +2,176 @@ """Tests for `mestopy` package.""" -import pytest +from mestopy import Device, MeasurementChain +from pyfar import Signal +import numpy.testing as npt -from mestopy import mestopy +def test_Device_init(): + """Test to init Device without optinal parameters.""" + dev = Device('dev1') + assert isinstance(dev, Device) -@pytest.fixture -def response(): - """Sample pytest fixture. - See more at: http://doc.pytest.org/en/latest/fixture.html - """ - # import requests - # return requests.get('https://github.com/mberz/cookiecutter-pypackage') +def test_Device_default_parameter(): + """Test the default attributes after + init Device without optinal parameters.""" + dev = Device('dev1') + assert dev.name == 'dev1' + assert dev.data is None + assert dev.sens == 1 + assert dev.unit is None -def test_content(response): - """Sample pytest test function with the pytest fixture as an argument.""" - # from bs4 import BeautifulSoup - # assert 'GitHub' in BeautifulSoup(response.content).title.string +def test_Device_name(): + """Test name setter and getter of Device class.""" + dev = Device('dev1') + assert dev.name == 'dev1' + dev.name = 'dev2' + assert dev.name == 'dev2' + + +def test_Device_data(flat_freq): + """Test data setter and getter of Device class + with signal_stub.""" + dev = Device('dev1') + assert dev.data is None + dev.data = flat_freq + assert isinstance(dev.data, Signal) + assert dev.data == flat_freq + + +def test_Device_sens(): + """Test sens setter and getter of Device class.""" + dev = Device('dev1') + assert dev.sens == 1 + dev.sens = 0.3 + assert dev.sens == 0.3 + + +def test_Device_unit(): + """Test unit setter and getter of Device class.""" + dev = Device('dev1') + assert dev.unit is None + dev.unit = 'mV/Pa' + assert dev.unit == 'mV/Pa' + + +def test_Device_freq(flat_freq): + """Test freq getter of Device init with and without a Signal.""" + dev = Device('dev1') + assert dev.freq == 1 + dev = Device('dev1', flat_freq) + assert isinstance(dev.freq, type(flat_freq * 1.0)) + assert dev.freq == dev.data * dev.sens + + +def test_MeasurementChain_init(): + """Test to init MeasurementChain without optinal parameters.""" + chain = MeasurementChain(44100) + assert isinstance(chain, MeasurementChain) + + +def test_MeasurementChain_default_paramerter(): + """Test the default attributes after init + MeasurementChain without optinal parameters.""" + chain = MeasurementChain(44100) + assert chain.devices == [] + assert chain.freq == 1.0 + assert chain.sampling_rate == 44100 + assert chain.sound_device is None + assert chain.comment is None + + +def test_MeasurementChain_add_device(): + """Test add_device method of MeasurementChain class.""" + chain = MeasurementChain(44100) + assert len(chain.devices) == 0 + chain.add_device('dev1') + assert len(chain.devices) == 1 + chain.add_device('dev2') + assert len(chain.devices) == 2 + + +def test_MeasurementChain__find_device_index(): + """Test private _find_device_index method of + MeasurementChain class.""" + chain = MeasurementChain(44100) + chain.add_device('dev1') + chain.add_device('dev2') + chain.add_device('dev3') + assert chain._find_device_index('dev2') == 1 + + +def test_MeasurementChain__freq(): + """Test private _freq method of MeasurementChain class.""" + chain = MeasurementChain(44100) + assert chain._resp == 1.0 + chain.add_device('dev1', sens=2.0) + assert isinstance(chain._resp, Signal) + assert chain._resp.n_samples == 1 + assert chain._resp.time[0] == 2.0 + + +def test_MeasurementChain_list_devices(): + """Test list_devices method of MeasurementChain class.""" + chain = MeasurementChain(44100) + assert chain.list_devices() == [] + chain.add_device('dev1') + chain.add_device('dev2') + chain.add_device('dev3') + assert chain.list_devices() == ['dev1', 'dev2', 'dev3'] + + +def test_MeasurementChain_remove_device(): + """Test remove_device method of MeasurementChain class. + Test with nuber and name as argument.""" + chain = MeasurementChain(44100) + chain.add_device('dev1') + chain.add_device('dev2') + chain.add_device('dev3') + # by number + chain.remove_device(0) + assert chain.list_devices() == ['dev2', 'dev3'] + # by name + chain.remove_device('dev3') + assert chain.list_devices() == ['dev2'] + + +def test_MeasurementChain_reset_devices(): + """Test reset_devices method of MeasurementChain class.""" + chain = MeasurementChain(44100) + chain.add_device('dev1') + chain.add_device('dev2') + chain.add_device('dev3') + chain.reset_devices() + assert chain.list_devices() == [] + + +def test_MeasurementChain_device_freq(): + """Test device_freq method of MeasurementChain class with + devices init with only sens, but no freq-data. + Test with nuber and name as argument.""" + chain = MeasurementChain(44100) + chain.add_device('dev1', sens=1.0) + chain.add_device('dev2', sens=2.0) + chain.add_device('dev3', sens=3.0) + # by number + assert chain.device_freq(1) == 2.0 + # by name + assert chain.device_freq('dev3') == 3.0 + + +def test_MeasurementChain_freq(): + """Test freq getter of MeasurementChain class with pyfar.Signal.""" + freq = [1, 1, 1, 1] + sampling_rate = 44100 + signal = Signal(freq, sampling_rate, domain='freq') + chain = MeasurementChain(sampling_rate) + sens1 = 2 + sens2 = 3 + chain.add_device('dev1', sens=sens1) + chain.add_device('dev2', data=signal, sens=sens2) + assert chain.freq.n_bins == 4 + npt.assert_equal(chain.freq.freq, signal.freq*sens1*sens2)