Skip to content
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

Draft for a file format for rooms and scenes [WIP] #216

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions pyroomacoustics/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@
from . import bss
from . import denoise
from . import phase
from . import io

import warnings
warnings.warn(
Expand Down
1 change: 1 addition & 0 deletions pyroomacoustics/io/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from .reader import read_scene
164 changes: 164 additions & 0 deletions pyroomacoustics/io/reader.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
import json
from pathlib import Path

import numpy as np
from scipy.io import wavfile

from ..acoustics import OctaveBandsFactory
from ..parameters import Material
from ..room import Room, wall_factory


def parse_vertex(line, line_num):
assert line[0] == v

line = line.strip().split(" ")

if len(line) < 4 or len(line > 5):
raise ValueError("Malformed vertex on line {line_num}")

return np.array([float(line[i]) for i in range(3)])


def read_obj(filename):
with open(filename, "r") as f:
content = f.readlines()

# keep track of the faces to process later
vertices = []
unprocessed_faces = []

for no, line in enumerate(content):
if line[0] == "v":
vertices.append(parse_vertex(line, no))
elif line[0] == "f":
unprocessed_faces.append([no, line])

for no, line in faces:
pass


def read_room_json(filename, fs):

with open(filename, "r") as f:
content = json.load(f)

vertices = np.array(content["vertices"])

faces = []
materials = []
names = []
walls_args = []

for name, wall_info in content["walls"].items():
vertex_ids = np.array(wall_info["vertices"]) - 1
wall_vertices = vertices[vertex_ids].T

try:
mat_info = wall_info["material"]
if isinstance(mat_info, dict):
mat = Material(**mat_info)
elif isinstance(mat_info, list):
mat = Material(*mat_info)
else:
mat = Material(mat_info)
except KeyError:
mat = Material(energy_absorption=0.0)

walls_args.append([wall_vertices, mat, name])

octave_bands = OctaveBandsFactory(fs=fs)
materials = [a[1] for a in walls_args]
if not Material.all_flat(materials):
for mat in materials:
mat.resample(octave_bands)

walls = [
wall_factory(w, m.absorption_coeffs, m.scattering_coeffs, name=n)
for w, m, n in walls_args
]

return walls


def read_source(source, scene_parent_dir, fs_room):

if isinstance(source, list):
return {"position": source, "signal": np.zeros(1)}

elif isinstance(source, dict):
kwargs = {"position": source["loc"]}

if "signal" in source:
fs_audio, audio = wavfile.read(scene_parent_dir / source["signal"])

# convert to float if necessary
if audio.dtype == np.int16:
audio = audio / 2 ** 15

if audio.ndim == 2:
import warnings

warnings.warn(
"The audio file was multichannel. Only keeping channel 1."
)
audio = audio[:, 0]

if fs_audio != fs_room:
try:
import samplerate

fs_ratio = fs_room / fs_audio
audio = samplerate.resample(audio, fs_ratio, "sinc_best")
except ImportError:
raise ImportError(
"The samplerate package must be installed for"
" resampling of the signals."
)

kwargs["signal"] = audio

else:
# add a zero signal is the source is not active
kwargs["signal"] = np.zeros(1)

if "delay" in source:
kwargs["delay"] = source["delay"]

return kwargs

else:
raise TypeError("Unexpected type.")


def read_scene(filename):

filename = Path(filename)
parent_dir = filename.parent

with open(filename, "r") as f:
scene_info = json.load(f)

# the sampling rate
try:
fs = scene_info["samplerate"]
except KeyError:
fs = 16000

# read the room file
room_filename = parent_dir / scene_info["room"]
walls = read_room_json(room_filename, fs)

if "room_kwargs" not in scene_info:
scene_info["room_kwargs"] = {}

room = Room(walls, fs=fs, **scene_info["room_kwargs"])
for source in scene_info["sources"]:
room.add_source(**read_source(source, parent_dir, fs))
for mic in scene_info["microphones"]:
room.add_microphone(mic)

if "ray_tracing" in scene_info:
room.set_ray_tracing(**scene_info["ray_tracing"])

return room
4 changes: 2 additions & 2 deletions pyroomacoustics/parameters.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
* Scattering coefficients
* Air absorption
"""
import io
import io as _io
import json
import os

Expand Down Expand Up @@ -238,7 +238,7 @@ def from_speed(cls, c):
}


with io.open(_materials_database_fn, "r", encoding="utf8") as f:
with _io.open(_materials_database_fn, "r", encoding="utf8") as f:
materials_data = json.load(f)

center_freqs = materials_data["center_freqs"]
Expand Down
15 changes: 10 additions & 5 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,23 +12,22 @@
exec(f.read())

try:
from setuptools import setup, Extension
from setuptools import Extension, distutils, setup
from setuptools.command.build_ext import build_ext
from setuptools import distutils
except ImportError:
print("Setuptools unavailable. Falling back to distutils.")
import distutils
from distutils.command.build_ext import build_ext
from distutils.core import setup
from distutils.extension import Extension
from distutils.command.build_ext import build_ext


class get_pybind_include(object):
"""Helper class to determine the pybind11 include path

The purpose of this class is to postpone importing pybind11
until it is actually installed, so that the ``get_include()``
method can be invoked. """
method can be invoked."""

def __init__(self, user=False):
self.user = user
Expand Down Expand Up @@ -172,12 +171,18 @@ def build_extensions(self):
"pyroomacoustics.bss",
"pyroomacoustics.denoise",
"pyroomacoustics.phase",
"pyroomacoustics.io",
],
# Libroom C extension
ext_modules=ext_modules,
# Necessary to keep the source files
package_data={"pyroomacoustics": ["*.pxd", "*.pyx", "data/materials.json"]},
install_requires=["Cython", "numpy", "scipy>=0.18.0", "pybind11>=2.2",],
install_requires=[
"Cython",
"numpy",
"scipy>=0.18.0",
"pybind11>=2.2",
],
cmdclass={"build_ext": BuildExt}, # taken from pybind11 example
zip_safe=False,
test_suite="nose.collector",
Expand Down