diff --git a/README.rst b/README.rst index 41c0a04..6b08bd5 100644 --- a/README.rst +++ b/README.rst @@ -25,6 +25,7 @@ The following external libraries are required: - `NumPy`_ - `SciPy`_ +- `Pysofaconventions`_ - `Plotly`_ (for plotting) @@ -36,8 +37,8 @@ For performance and convenience reasons we highly recommend to use Once installed, you can use the following steps to create a new environment with the *sfa* toolbox. -#. Create a new environment: - ``conda create --name sfa python numpy scipy plotly`` +#. Create new Conda environment from the specified requirements: + ``conda env create --file environment.yml`` #. Activate the environment: ``source activate sfa`` @@ -77,7 +78,7 @@ course also simply download the examples and run them locally! Exp1: Ideal plane wave -~~~~~~~~~~~~~~~~~~~~~ +~~~~~~~~~~~~~~~~~~~~~~ Ideal unity plane wave simulation and 3D plot. @@ -103,18 +104,10 @@ a cardioid mic. .. _AE3_img: https://nbviewer.jupyter.org/github/AppliedAcousticsChalmers/sound_field_analysis-py/blob/master/examples/Exp2_MeasuredWave.ipynb -Exp3: Import data in SOFA format -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -The provided example loading a SOFA_ file is outdated. We recommend using the -`pysofaconventions `_ -package. See repository for examples and install instructions. - - Exp4: Binaural rendering ^^^^^^^^^^^^^^^^^^^^^^^^ -Render a spherical microphone array measurement for binaural reproduction. +Render a spherical microphone array measurement for binaural reproduction. The example shows examples for loading miro or `SOFA`_ files. `View interactively on nbviewer `__ @@ -122,6 +115,14 @@ Render a spherical microphone array measurement for binaural reproduction. Version history --------------- +*2019-07-30 V0.9* + * Implement SOFA import + * Update Exp4 to contain SOFA import + * Delete obsolete Exp3 + * Add named tuple HRIRSignal + * Implement cart2sph and sph2cart utility functions + * Add conda environment file for convenient installation of required packages + *2019-07-11 V0.8* * Implement Spherical Harmonics coefficients tapering * Adaption of associated Spherical Head Filter @@ -137,7 +138,7 @@ Version history * Implement Spherical Head Filter * Implement Spherical Fourier Transform using pseudo-inverse * Extract real time capable Spatial Fourier Transform - * Outsource reversed m index function (Exp. 4) + * Outsource reversed m index function (Exp4) References @@ -153,6 +154,7 @@ The Lebedev grid generation was adapted from an implementation by `Richard P. Mu .. _[1]: #references .. _NumPy: http://www.numpy.org .. _SciPy: http://www.scipy.org +.. _Pysofaconventions: https://github.com/andresperezlopez/pysofaconventions .. _Plotly: https://plot.ly/python/ .. _Conda: https://www.continuum.io/downloads .. _conda-forge: https://conda-forge.github.io diff --git a/environment.yml b/environment.yml new file mode 100755 index 0000000..65739f8 --- /dev/null +++ b/environment.yml @@ -0,0 +1,12 @@ +name: sfa +channels: + - defaults +dependencies: + - python + - numpy + - scipy + - plotly + - pip + - pip: + - pysofaconventions >= 0.1.5 + - -e . diff --git a/examples/Exp3_Import_SOFA.ipynb b/examples/Exp3_Import_SOFA.ipynb deleted file mode 100644 index fdb76c0..0000000 --- a/examples/Exp3_Import_SOFA.ipynb +++ /dev/null @@ -1,240 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": { - "collapsed": true - }, - "source": [ - "# Read .sofa HRIR\n", - "\n", - "SOFA (Spatially Oriented Format for Acoustics) files (https://www.sofaconventions.org) are HDF5 containers that hold data like Head-Related Impulse Responses (HRIRs), Binaural Room Impulse Responses (BRIRs) or Directional Impulse Responses (DRIRs, array impulse responses).\n", - "\n", - "Additionally, they hold important meta information and are a great way to archive such recording data. Unfortunately, there currently is no Python API available (You may find at Matlab/Octave and C++ implementation here: https://www.sofaconventions.org/mediawiki/index.php/Software_and_APIs).\n", - "\n", - "Luckily, sofa files can be read using the netCDF4 package, the extracted impulse responses can then be saved into a format that is (currently) easier to work with like numpy's .npy. Note that this is much less optimized for filesize!\n", - "\n", - "## Dependencies\n", - "This example mainly relies on the **netCDF4** package to read in the sofa format. sound_field_analysis is only used for some format definitions.\n", - "\n", - "To be able to install netCDF4, having the packages hdf5 and netcdf installed was necessary. On OSX this worked by:\n", - "\n", - "```\n", - "brew install hdf5\n", - "brew install netcdf\n", - "pip install netCDF4```" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [ - { - "ename": "ImportError", - "evalue": "dlopen(/anaconda3/lib/python3.6/site-packages/netCDF4/_netCDF4.cpython-36m-darwin.so, 2): Symbol not found: _clock_gettime\n Referenced from: /anaconda3/lib/python3.6/site-packages/netCDF4/.dylibs/libcurl.4.dylib (which was built for Mac OS X 10.13)\n Expected in: /usr/lib/libSystem.B.dylib\n in /anaconda3/lib/python3.6/site-packages/netCDF4/.dylibs/libcurl.4.dylib", - "output_type": "error", - "traceback": [ - "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", - "\u001b[0;31mImportError\u001b[0m Traceback (most recent call last)", - "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m()\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[0;32mimport\u001b[0m \u001b[0mos\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 2\u001b[0;31m \u001b[0;32mfrom\u001b[0m \u001b[0mnetCDF4\u001b[0m \u001b[0;32mimport\u001b[0m \u001b[0mDataset\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 3\u001b[0m \u001b[0;32mimport\u001b[0m \u001b[0mnumpy\u001b[0m \u001b[0;32mas\u001b[0m \u001b[0mnp\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 4\u001b[0m \u001b[0;32mimport\u001b[0m \u001b[0msys\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 5\u001b[0m \u001b[0msys\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mpath\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0minsert\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;36m0\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m'../../sound_field_analysis-py'\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0;32m/anaconda3/lib/python3.6/site-packages/netCDF4/__init__.py\u001b[0m in \u001b[0;36m\u001b[0;34m()\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[0;31m# init for netCDF4. package\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 2\u001b[0m \u001b[0;31m# Docstring comes from extension module _netCDF4.\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 3\u001b[0;31m \u001b[0;32mfrom\u001b[0m \u001b[0;34m.\u001b[0m\u001b[0m_netCDF4\u001b[0m \u001b[0;32mimport\u001b[0m \u001b[0;34m*\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 4\u001b[0m \u001b[0;31m# Need explicit imports for names beginning with underscores\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 5\u001b[0m \u001b[0;32mfrom\u001b[0m \u001b[0;34m.\u001b[0m\u001b[0m_netCDF4\u001b[0m \u001b[0;32mimport\u001b[0m \u001b[0m__doc__\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0m__pdoc__\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0;31mImportError\u001b[0m: dlopen(/anaconda3/lib/python3.6/site-packages/netCDF4/_netCDF4.cpython-36m-darwin.so, 2): Symbol not found: _clock_gettime\n Referenced from: /anaconda3/lib/python3.6/site-packages/netCDF4/.dylibs/libcurl.4.dylib (which was built for Mac OS X 10.13)\n Expected in: /usr/lib/libSystem.B.dylib\n in /anaconda3/lib/python3.6/site-packages/netCDF4/.dylibs/libcurl.4.dylib" - ] - } - ], - "source": [ - "import os\n", - "from netCDF4 import Dataset\n", - "import numpy as np\n", - "import sys\n", - "sys.path.insert(0, '../')\n", - "from sound_field_analysis import io" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Load .sofa file\n", - "Plenty of sofa files are listed at https://www.sofaconventions.org/mediawiki/index.php/Files\n", - "In this example, the \"mit_kemar_large_pinna.sofa\" was used." - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "\n", - "root group (NETCDF4 data model, file format HDF5):\n", - " Conventions: SOFA\n", - " Version: 0.6\n", - " SOFAConventions: SimpleFreeFieldHRIR\n", - " SOFAConventionsVersion: 0.4\n", - " APIName: ARI SOFA API for Matlab/Octave\n", - " APIVersion: 0.4.3\n", - " ApplicationName: ARI converter\n", - " ApplicationVersion: \n", - " AuthorContact: piotr@majdak.com\n", - " Comment: Converted by Piotr Majdak, Acoustics Research Institute, OeAW\n", - " DataType: FIR\n", - " History: Converted from the MIT database\n", - " License: This HRTF data is provided without any usage restrictions. It is requested that you cite Gardner and Martin (1995) when using this data for research or commercial applications.\n", - " Organization: MIT Media Lab\n", - " References: Gardner, W. G., and Martin, K. D. (1995). \"HRTF measurements of a KEMAR,\" J Acoust Soc Am 97, 3907-3908.\n", - " RoomType: free field\n", - " Origin: http://sound.media.mit.edu/resources/KEMAR.html\n", - " DateCreated: 1999-11-16 20:01:54\n", - " DateModified: 2014-05-22 09:14:59\n", - " Title: HRTF\n", - " DatabaseName: MIT\n", - " ListenerShortName: KEMAR, large pinna\n", - " dimensions(sizes): I(1), C(3), R(2), E(1), N(512), M(710)\n", - " variables(dimensions): float64 \u001b[4mListenerPosition\u001b[0m(I,C), float64 \u001b[4mReceiverPosition\u001b[0m(R,C,I), float64 \u001b[4mSourcePosition\u001b[0m(M,C), float64 \u001b[4mEmitterPosition\u001b[0m(E,C,I), float64 \u001b[4mListenerUp\u001b[0m(I,C), float64 \u001b[4mListenerView\u001b[0m(I,C), float64 \u001b[4mData.IR\u001b[0m(M,R,N), float64 \u001b[4mData.SamplingRate\u001b[0m(I), float64 \u001b[4mData.Delay\u001b[0m(I,R)\n", - " groups: " - ] - }, - "execution_count": 2, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "sofa_file_name = 'sofa/mit_kemar_large_pinna.sofa'\n", - "sofa_file = Dataset(sofa_file_name, 'r', format='NETCDF4')\n", - "print('sofa_file: ' + str(sofa_file))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## SOFA content\n", - "If everything went correctly, you should see a description of the selection .sofa file above.\n", - "\n", - "Generally, HRIR / BRIR sets will have R=2 receiver positions (left and right ear) at I=1 listener position (usually 0,0,0), with impulse responses of M source positions (with E=1 emitter position, usually 0,0,0) of length N.\n", - "\n", - "\n", - "Other data such as array recordings can have several receiver positions for a single source position and the script below needs to be adjusted accordingly.\n", - "\n", - "As an example, the ear distance is calculated as the difference between the y coordinates of the left and the right ear." - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "SourcePosition: \n", - "float64 SourcePosition(M, C)\n", - " Type: spherical\n", - " Units: degree, degree, meter\n", - "unlimited dimensions: \n", - "current shape = (710, 3)\n", - "filling on, default _FillValue of 9.969209968386869e+36 used\n", - "\n", - "Data.IR: \n", - "float64 Data.IR(M, R, N)\n", - "unlimited dimensions: \n", - "current shape = (710, 2, 512)\n", - "filling on, default _FillValue of 9.969209968386869e+36 used\n", - "\n", - "Ear distance: [ 0.18] m\n" - ] - } - ], - "source": [ - "print('SourcePosition: ' + str(sofa_file['SourcePosition']))\n", - "print('Data.IR: ' + str(sofa_file['Data.IR']))\n", - "print('Ear distance: ' + str(sofa_file['ReceiverPosition'][1, 1] - sofa_file['ReceiverPosition'][0, 1]) + ' m')" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": {}, - "outputs": [], - "source": [ - "# extract IRs\n", - "HRIRs_l = np.squeeze(sofa_file['Data.IR'][:, 0, :])\n", - "HRIRs_r = np.squeeze(sofa_file['Data.IR'][:, 1, :])\n", - "Az, El, R = np.squeeze(np.hsplit(sofa_file['SourcePosition'][:], 3))\n", - "fs = sofa_file['Data.SamplingRate'][:][0]" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [ - "sofa_file.close()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Save as npy file\n", - "To now write the data into the handy binary .npy format, we could simply call np.save().\n", - "\n", - "Specifically for the sound_field_analysis toolbox, we convert the angles into radiants (and elevation to colatitude), create an io.ArraySignal and finally save that." - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": {}, - "outputs": [], - "source": [ - "azimuth = Az / 180 * np.pi\n", - "colatitude = np.pi / 2 - El / 180 * np.pi\n", - "\n", - "hrir_full_l = io.ArraySignal(io.TimeSignal(HRIRs_l, fs), io.SphericalGrid(azimuth, colatitude, R))\n", - "hrir_full_r = io.ArraySignal(io.TimeSignal(HRIRs_r, fs), io.SphericalGrid(azimuth, colatitude, R))" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "metadata": {}, - "outputs": [], - "source": [ - "np.save(os.path.splitext(os.path.basename(sofa_file_name))[0] + '_L', hrir_full_l)\n", - "np.save(os.path.splitext(os.path.basename(sofa_file_name))[0] + '_R', hrir_full_r)" - ] - } - ], - "metadata": { - "anaconda-cloud": {}, - "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.6.4" - } - }, - "nbformat": 4, - "nbformat_minor": 1 -} diff --git a/setup.py b/setup.py index c247e49..6f0b0b3 100644 --- a/setup.py +++ b/setup.py @@ -1,34 +1,34 @@ -from setuptools import setup - -version = open("sound_field_analysis/_version.py").readlines()[-1].split()[-1].strip("\"'") - -setup( - name='sound_field_analysis', - version=version, - description='Analyze, visualize and process sound field data recorded by spherical microphone arrays.', - url='https://github.com/AppliedAcousticsChalmers/sound_field_analysis-py/', - author='QU Lab / Christoph Hohnerlein', - author_email='christoph.hohnerlein@qu.tu-berlin.de', - license='GPLv3', - classifiers=[ - 'Development Status :: 4 - Beta', - 'Intended Audience :: Science/Research', - 'Topic :: Multimedia :: Sound/Audio', - 'License :: OSI Approved :: GNU General Public License v3 (GPLv3)', - 'Programming Language :: Python :: 3', - ], - keywords='sound field analysis spherical microphone array', - install_requires=[ - 'scipy', - 'numpy', - ], - - package_data={ - 'examples': ['examples'], - }, - extras_require={ - 'plotting': ["plotly"], - 'sofa_import': ["netCDF4"], # see Exp3_Import_SOFA.py description for installation - }, - packages=['sound_field_analysis'], -) +from setuptools import setup + +version = open("sound_field_analysis/_version.py").readlines()[-1].split()[-1].strip("\"'") + +setup( + name='sound_field_analysis', + version=version, + description='Analyze, visualize and process sound field data recorded by spherical microphone arrays.', + url='https://github.com/AppliedAcousticsChalmers/sound_field_analysis-py/', + author='QU Lab / Christoph Hohnerlein', + author_email='christoph.hohnerlein@qu.tu-berlin.de', + license='GPLv3', + classifiers=[ + 'Development Status :: 4 - Beta', + 'Intended Audience :: Science/Research', + 'Topic :: Multimedia :: Sound/Audio', + 'License :: OSI Approved :: GNU General Public License v3 (GPLv3)', + 'Programming Language :: Python :: 3', + ], + keywords='sound field analysis spherical microphone array', + install_requires=[ + 'scipy', + 'numpy', + 'pysofaconventions', + ], + + package_data={ + 'examples': ['examples'], + }, + extras_require={ + 'plotting': ["plotly"], + }, + packages=['sound_field_analysis'], +) diff --git a/sound_field_analysis/_version.py b/sound_field_analysis/_version.py index 1e05753..d28b1cf 100644 --- a/sound_field_analysis/_version.py +++ b/sound_field_analysis/_version.py @@ -1,2 +1,2 @@ """Version information.""" -__version__ = '0.8.4' +__version__ = '0.9.0' diff --git a/sound_field_analysis/io.py b/sound_field_analysis/io.py index 6910c6c..34032bf 100755 --- a/sound_field_analysis/io.py +++ b/sound_field_analysis/io.py @@ -271,7 +271,7 @@ def read_SOFA_file(file_name): Tuple containing a TimeSignal `signal`, SphericalGrid `grid`, TimeSignal 'center_signal', ArrayConfiguration `configuration` and the air temperature - sofa_signal : HRIRSignal + sofa_signal : HRIRSignal Tuple containing the TimeSignals 'l' for the left, and 'r' for the right ear, SphericalGrid `grid`, TimeSignal 'center_signal' Notes @@ -281,12 +281,7 @@ def read_SOFA_file(file_name): * Up to now, importing 'SimpleFreeFieldHRIR' and 'SingleRoomDRIR' are provided only. """ - # check if package 'pysofaconventions' is available - try: - import pysofaconventions as sofa - except ImportError: - print('Could not found pysofaconventions. Could not load SOFA file') - return None + import pysofaconventions as sofa def print_sofa_infos(SOFA_convention): print(f'\n --> samplerate: {SOFA_convention.getSamplingRate()[0]:.0f} Hz' \