This page was generated from doc/examples/SOFA-file-access.ipynb. Interactive online version: Binder badge
[1]:
import sys
sys.path.insert(0, '../../src')
import sofa
print(sofa)
<module 'sofa' from '../../src/sofa/__init__.py'>
[2]:
import matplotlib.pyplot as plt
from mpl_toolkits import mplot3d
import numpy as np
%matplotlib inline
[3]:
def plot_coordinates(coords, title):
    x0 = coords
    n0 = coords
    fig = plt.figure(figsize=(15, 15))
    ax = fig.add_subplot(111, projection='3d')
    q = ax.quiver(x0[:, 0], x0[:, 1], x0[:, 2], n0[:, 0],
                  n0[:, 1], n0[:, 2], length=0.1)
    plt.xlabel('x (m)')
    plt.ylabel('y (m)')
    plt.title(title)
    return q

Open and plot HRTF

[4]:
HRTF_path = "MRT01.sofa"
HRTF = sofa.Database.open(HRTF_path)
HRTF.Metadata.dump()

# plot Source positions
source_positions = HRTF.Source.Position.get_values(system="cartesian")
plot_coordinates(source_positions, 'Source positions');

# plot Data.IR at M=5 for E=0
measurement = 5
emitter = 0
legend = []

t = np.arange(0,HRTF.Dimensions.N)*HRTF.Data.SamplingRate.get_values(indices={"M":measurement})

plt.figure(figsize=(15, 5))
for receiver in np.arange(HRTF.Dimensions.R):
    plt.plot(t, HRTF.Data.IR.get_values(indices={"M":measurement, "R":receiver, "E":emitter}))
    legend.append('Receiver {0}'.format(receiver))
plt.title('HRIR at M={0} for emitter {1}'.format(measurement, emitter))
plt.legend(legend)
plt.xlabel('$t$ in s')
plt.ylabel(r'$h(t)$')
plt.grid()

HRTF.close()
APIName: ARI SOFA API for Matlab/Octave
APIVersion: 1.0.2
ApplicationName: ITA-Toolbox
ApplicationVersion: 7
AuthorContact: rbo (@akustik.rwth-aachen.de)
Comment:
Conventions: SOFA
DataType: FIR
DatabaseName: ITA HRTF-database
DateCreated: 20-Oct-2016
DateModified: 2017-08-10 16:13:37
History: R. Bomhardt, M. de la Fuente Klein, and J. Fels: A high-resolution head-related transfer function and three-dimensional ear model database, Proceedings of Meetings on Acoustics 29, 050002 (2016)
License: Creative Commons Attribution-NonCommercial-ShareAlike 4.0 (CC BY-NC-SA 4.0)
ListenerShortName: MRT01
Organization: Institute of Technical Acoustics, RWTH Aachen University
Origin:
References: R. Bomhardt, M. de la Fuente Klein, and J. Fels: A high-resolution head-related transfer function and three-dimensional ear model database, Proceedings of Meetings on Acoustics 29, 050002 (2016)
RoomDescription: 11m x 5.9m x 4.5m
RoomType: free field
SOFAConventions: SimpleFreeFieldHRIR
SOFAConventionsVersion: 1.0
Title: ITA HRTF
Version: 1.0
../_images/examples_SOFA-file-access_4_1.png
../_images/examples_SOFA-file-access_4_2.png

Retrieving coordinates

Coordinates may be retrieved using one of three methods:

  • SpatialObject.get_values(...) returns the local coordinates as stored in the file
  • SpatialObject.get_global_values(...) returns the global coordinates by removing the position and rotation of the reference object (reference object for Emitters is Source, reference object for Receivers is Listener)
  • SpatialObject.get_relative_values(SpatialObject reference,...) returns the coordinates in the local coordinate system of the specified reference object by applying position and rotation of the reference to the global coordinates of the object, reference=None is equivalent to SpatialObject.get_global_values(...)
[5]:
HRTF_path = "MRT01.sofa"
HRTF = sofa.Database.open(HRTF_path)

print("Source positions 0 to 4 in spherical coordinates")
print(np.round(HRTF.Source.Position.get_values(indices={"M":slice(5)}, system="spherical", angle_unit="degree"),2))

print("Stationary local Emitter position")
print(np.round(HRTF.Emitter.Position.get_values(system="spherical", angle_unit="degree"),2))

print("Global Emitter positions 0 to 4")
print(np.round(HRTF.Emitter.Position.get_global_values(indices={"M":slice(5)}, system="spherical", angle_unit="degree"),2))

print("Stationary Listener position")
print(np.round(HRTF.Listener.Position.get_values(system="spherical", angle_unit="degree"),2))

print("Listener positions 0 to 4 relative to Emitter")
print(np.round(HRTF.Listener.Position.get_relative_values(HRTF.Emitter, indices={"M":slice(5)}, system="spherical", angle_unit="degree"),2))

HRTF.close()
Source positions 0 to 4 in spherical coordinates
[[ -0.   -66.24   1.  ]
 [ -0.   -61.2    1.  ]
 [ -0.   -56.16   1.  ]
 [ -0.   -51.12   1.  ]
 [ -0.   -46.08   1.  ]]
Stationary local Emitter position
[[[0.]
  [0.]
  [0.]]]
Global Emitter positions 0 to 4
[[[ -0.    -0.    -0.    -0.    -0.  ]
  [-66.24 -61.2  -56.16 -51.12 -46.08]
  [  1.     1.     1.     1.     1.  ]]]
Stationary Listener position
[[0. 0. 0.]]
Listener positions 0 to 4 relative to Emitter
[[[180.    66.24   1.  ]
  [180.    61.2    1.  ]
  [180.    56.16   1.  ]
  [180.    51.12   1.  ]
  [180.    46.08   1.  ]]]

Create new SimpleFreeFieldHRIR .sofa file

When creating a new .SOFA file, a convention must be provided by name. Providing dimensions at this point is possible, but not required.

[6]:
print(sofa.conventions.implemented())
['GeneralFIR', 'GeneralTF', 'SimpleFreeFieldHRIR', 'GeneralFIRE', 'MultiSpeakerBRIR', 'SimpleFreeFieldTF', 'SimpleFreeFieldSOS', 'SingleRoomDRIR']
[7]:
HRIR_path = "free_field_HRIR.sofa"
measurements = 5
data_length = 1000
max_string_length = 128

# we will add this one later
receivers = 2
[8]:
HRIR = sofa.Database.create(HRIR_path, "SimpleFreeFieldHRIR",
                            dimensions={"M": measurements, "N": data_length, "S": max_string_length})

Next, all spatial objects (Listener, Receiver, Source, Emitter) must be initialized to set which of their coordinates will stay fixed over the measurements, and which will vary. This information is provided in the fixed and variances list of the initialization method. Defining the Position is required for all spatial objects, and conventions can extend the requirements.

[9]:
try: HRIR.Listener.initialize()
except Exception as e: print("Initializing without Position error: {0}".format(e))

try: HRIR.Listener.initialize(fixed=["Position"])
except Exception as e: print("Initializing with incomplete convention requirements: {0}".format(e))

HRIR.Listener.initialize(fixed=["Position", "View", "Up"])
print("Successfully initialized Listener with fixed Position, View and Up.")
Initializing without Position error: Listener.initialize: Missing 'Position' in fixed or variances argument
Initializing with incomplete convention requirements: must have Listener Up and View
Successfully initialized Listener with fixed Position, View and Up.

It is also possible to initialize variables later on that were not required at the start by calling the initialize_coordinates method again. Attempting to initialize a coordinate that already exists is ignored.

[10]:
HRIR.Source.initialize(variances=["Position"])
HRIR.Source.initialize_coordinates(variances=["View"])

HRIR.Source.initialize_coordinates(fixed=["Position"]) # no change, message or output.

If the number of emitters and receivers was not defined by the convention or when the file was created, it can manually be defined by including the count keyword argument in initialize.

[11]:
HRIR.Receiver.initialize(fixed=["Position"], count=receivers)

try: HRIR.Emitter.initialize(fixed=["Position"], count=2)
except Exception as e: print("Initializing with Emitter count not allowed in convention error: {0}".format(e))
HRIR.Emitter.initialize(fixed=["Position"])
Initializing with Emitter count not allowed in convention error: must have 1 Emitter

Finally, we need to initialize the data type as well. This can only be done once the receiver count (and emitter count for FIRE) have been defined, so after the initialization of spatial objects or after a call to create that includes the relevant dimensions. Data that may be fixed or varying can be declared as varying in the variances list keyword argument.

The sample count N may be included at this point as the keyword argument sample_count if it is not already defined.

[12]:
print(HRIR.Data.Type)
HRIR.Data.initialize(variances=["Delay"])
FIR

The room data may be initialized at any point. As with any ProxyObject, additional SOFA attributes and variables may be created as well.

[13]:
HRIR.Room.Type = "shoebox"
HRIR.Room.initialize(variances=["CornerA", "CornerB"])

HRIR.Room.create_attribute("Location", "various recording locations")
HRIR.Room.create_variable("Temperature", ("M",))
HRIR.Room.Temperature.Units = "kelvin"
HRIR.Room.Temperature = 150
HRIR.Room.create_string_array("Description", ("M", "S"))

print(HRIR.Room.Location)
print(HRIR.Room.Temperature.get_values(), HRIR.Room.Temperature.Units)
various recording locations
[150. 150. 150. 150. 150.] kelvin

The file is now fully initialized and ready for use. You can take a look at a list of all attributes, variables and dimensions using the appropriate .dump() functions.

[14]:
print("Attributes and metadata")
HRIR.Metadata.dump()
Attributes and metadata
APIName: python-SOFA
APIVersion: 0.2
AuthorContact:
Conventions: SOFA
DataType: FIR
DatabaseName:
DateCreated: 2020-03-03 15:33:24
DateModified:
EmitterDescription:
License: No license provided, ask the author for permission
ListenerDescription:
ListenerShortName:
Organization:
ReceiverDescription:
RoomLocation: various recording locations
RoomType: shoebox
SOFAConventions: SimpleFreeFieldHRIR
SOFAConventionsVersion: 1.0
SourceDescription:
Title:
Version: 1.0
[15]:
print("Dimensions")
HRIR.Dimensions.dump()
Dimensions
M: 5
N: 1000
S: 128
I: 1
C: 3
R: 2
E: 1
[16]:
print("Variables")
HRIR.Variables.dump()
Variables
Data.Delay: ('M', 'R')
Data.IR: ('M', 'R', 'N')
Data.SamplingRate: ('I',)
EmitterPosition: ('E', 'C', 'I')
ListenerPosition: ('I', 'C')
ListenerUp: ('I', 'C')
ListenerView: ('I', 'C')
ReceiverPosition: ('R', 'C', 'I')
RoomCornerA: ('M', 'C')
RoomCornerB: ('M', 'C')
RoomDescription: ('M', 'S')
RoomTemperature: ('M',)
SourcePosition: ('M', 'C')
SourceView: ('M', 'C')
[17]:
HRIR.close()