Skip to content
Merged
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
18 changes: 5 additions & 13 deletions audinterface/core/feature.py
Original file line number Diff line number Diff line change
Expand Up @@ -545,7 +545,7 @@ def process_index(

if cache_root is not None:
cache_root = audeer.mkdir(cache_root)
hash = audformat.utils.hash(index)
hash = audformat.utils.hash(index, strict=True)
cache_path = os.path.join(cache_root, f"{hash}.pkl")

if cache_path and os.path.exists(cache_path):
Expand Down Expand Up @@ -731,7 +731,7 @@ def _reshape_3d(self, features: np.ndarray | pd.Series):
n_frames = features.shape[1]
else:
raise RuntimeError(
f"Cannot determine feature shape from " f"{features.shape}, ",
f"Cannot determine feature shape from {features.shape}, ",
f"when expected shape is "
f"({self.num_channels, self.num_features, -1}).",
)
Expand All @@ -743,17 +743,11 @@ def _reshape_3d(self, features: np.ndarray | pd.Series):
# assert channels and features have expected length
if n_channels != self.num_channels:
raise RuntimeError(
f"Number of channels must be"
f" {self.num_channels}, "
f"not "
f"{n_channels}."
f"Number of channels must be {self.num_channels}, not {n_channels}."
)
if n_features != self.num_features:
raise RuntimeError(
f"Number of features must be "
f"{self.num_features}, "
f"not "
f"{n_features}."
f"Number of features must be {self.num_features}, not {n_features}."
)

# reshape features to (channels, features, frames)
Expand Down Expand Up @@ -857,9 +851,7 @@ def _values_to_frame(
features = features.reshape(new_shape).T

if n_frames > 1 and self.win_dur is None:
raise RuntimeError(
f"Got " f"{n_frames} " f"frames, but 'win_dur' is not set."
)
raise RuntimeError(f"Got {n_frames} frames, but 'win_dur' is not set.")

return features

Expand Down
2 changes: 1 addition & 1 deletion audinterface/core/process.py
Original file line number Diff line number Diff line change
Expand Up @@ -603,7 +603,7 @@ def process_index(

if cache_root is not None:
cache_root = audeer.mkdir(cache_root)
hash = audformat.utils.hash(index)
hash = audformat.utils.hash(index, strict=True)
cache_path = os.path.join(cache_root, f"{hash}.pkl")

if cache_path and os.path.exists(cache_path):
Expand Down
86 changes: 86 additions & 0 deletions tests/test_feature.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,20 @@ def segment(signal, sampling_rate):
SEGMENT = audinterface.Segment(process_func=segment)


def index_durations(index: pd.Index, root: str = None) -> pd.Series:
r"""Get series with duration of all index elements."""
if len(index) == 0:
return pd.Series(index=index)
segmented_index = audformat.utils.to_segmented_index(
index, allow_nat=False, root=root
)
starts = segmented_index.get_level_values(audformat.define.IndexField.START)
ends = segmented_index.get_level_values(audformat.define.IndexField.END)
durations = (ends - starts).to_series()
durations.index = index
return durations


def feature_extractor(signal, _):
return np.ones((NUM_CHANNELS, NUM_FEATURES))

Expand Down Expand Up @@ -531,6 +545,78 @@ def test_process_index(tmpdir, preserve_index):
pd.testing.assert_frame_equal(df, df_cached)


@pytest.mark.parametrize(
"index,durations",
[
(audformat.filewise_index([f"f{i}.wav" for i in range(10)]), [1.0] * 10),
(
audformat.segmented_index(
files=[f"f{i}.wav" for i in range(5)],
starts=[0, 0, 1, 1, 2],
ends=[1, 1, 2, 2, 3],
),
[1.0, 2.0, 3.0, 3.0, 3.0],
),
],
)
@pytest.mark.parametrize("preserve_index", [False, True])
def test_process_index_order(tmpdir, index, durations, preserve_index):
# Ensure the returned index is as expected
# when the same index but different order have already been cached
# https://github.com/audeering/audinterface/issues/203
cache_root = os.path.join(tmpdir, "cache")
feature = audinterface.Feature(
feature_names="mean",
process_func=mean,
)
# Create signals for the index
sampling_rate = 8000
signals = [
np.random.uniform(-1.0, 1.0, (1, int(duration * sampling_rate)))
for duration in durations
]
root = str(tmpdir.mkdir("wav"))
files = []
is_segmented_index = audformat.is_segmented_index(index)
for i, signal in enumerate(signals):
if is_segmented_index:
file, _, _ = index[i]
else:
file = index[i]
path = os.path.join(root, file)
af.write(path, signal, sampling_rate)
files.append(file)
segment_durations = index_durations(index, root=root)

# Run process once with caching
df = feature.process_index(index, root=root, cache_root=cache_root)

# Run process again but on reverse index
reverse_index = index[::-1]
reverse_durations = segment_durations[::-1]
df_reverse = feature.process_index(
reverse_index, root=root, cache_root=cache_root, preserve_index=preserve_index
)
# Make sure the resulting reverse features are as expected
if preserve_index or is_segmented_index:
expected_index = reverse_index
else:
expected_index = audformat.segmented_index(
files=files[::-1],
starts=[0] * len(reverse_index),
ends=reverse_durations.values,
)
expected_df = pd.DataFrame(
index=expected_index, data={col: df[col].values[::-1] for col in df.columns}
)
pd.testing.assert_frame_equal(df_reverse, expected_df)

# Make sure there is one cache file for the original index
# and one file for the reversed index
files_in_cache = audeer.list_file_names(cache_root)
assert len(files_in_cache) == 2


@pytest.mark.parametrize(
"process_func, applies_sliding_window, num_feat, signal, start, end, "
"is_mono, expected",
Expand Down
100 changes: 98 additions & 2 deletions tests/test_process.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,20 @@ def signal_modification(signal, sampling_rate, subtract=False):
return signal


def index_durations(index: pd.Index, root: str = None) -> pd.Series:
r"""Get series with duration of all index elements."""
if len(index) == 0:
return pd.Series(index=index)
segmented_index = audformat.utils.to_segmented_index(
index, allow_nat=False, root=root
)
starts = segmented_index.get_level_values(audformat.define.IndexField.START)
ends = segmented_index.get_level_values(audformat.define.IndexField.END)
durations = (ends - starts).to_series()
durations.index = index
return durations


@pytest.mark.parametrize(
"process_func, segment, signal, sampling_rate, start, end, keep_nat, "
"channels, mixdown, expected_output",
Expand Down Expand Up @@ -390,7 +404,7 @@ def test_process_file(


@pytest.mark.parametrize(
"process_func, num_files, signal, sampling_rate, starts, ends, " "expected_output",
"process_func, num_files, signal, sampling_rate, starts, ends, expected_output",
[
(
signal_duration,
Expand Down Expand Up @@ -710,6 +724,88 @@ def test_process_index(tmpdir, num_workers, multiprocessing, preserve_index):
pd.testing.assert_series_equal(y, y_cached)


@pytest.mark.parametrize(
"index,durations",
[
(audformat.filewise_index([f"f{i}.wav" for i in range(10)]), [1.0] * 10),
(
audformat.segmented_index(
files=[f"f{i}.wav" for i in range(5)],
starts=[0, 0, 1, 1, 2],
ends=[1, 1, 2, 2, 3],
),
[1.0, 2.0, 3.0, 3.0, 3.0],
),
],
)
@pytest.mark.parametrize("preserve_index", [False, True])
def test_process_index_order(tmpdir, index, durations, preserve_index):
# Ensure the returned index is as expected
# when the same index but different order have already been cached
# https://github.com/audeering/audinterface/issues/203
cache_root = os.path.join(tmpdir, "cache")
process = audinterface.Process(
process_func=None,
sampling_rate=None,
resample=False,
verbose=False,
)
# Create signals for the index
sampling_rate = 8000
signals = [
np.random.uniform(-1.0, 1.0, (1, int(duration * sampling_rate)))
for duration in durations
]
root = str(tmpdir.mkdir("wav"))
files = []
is_segmented_index = audformat.is_segmented_index(index)
for i, signal in enumerate(signals):
if is_segmented_index:
file, _, _ = index[i]
else:
file = index[i]
path = os.path.join(root, file)
af.write(path, signal, sampling_rate)
files.append(file)
segment_durations = index_durations(index, root=root)

# Run process once with caching
process.process_index(index, root=root, cache_root=cache_root)

# Run process again but on reverse index
reverse_index = index[::-1]
reverse_durations = segment_durations[::-1]
y_reverse = process.process_index(
reverse_index, root=root, cache_root=cache_root, preserve_index=preserve_index
)
# Make sure the index is as expected
if preserve_index or is_segmented_index:
expected_index = reverse_index
else:
expected_index = audformat.segmented_index(
files=files[::-1],
starts=[0] * len(reverse_index),
ends=reverse_durations.values,
)
pd.testing.assert_index_equal(y_reverse.index, expected_index)
Comment thread
hagenw marked this conversation as resolved.
# Make sure the files contain the expected signals
for idx, value in y_reverse.items():
if not preserve_index or is_segmented_index:
path, start, end = idx
else:
path = idx
start = end = None
signal, sampling_rate = audinterface.utils.read_audio(
path, start=start, end=end, root=root
)
np.testing.assert_equal(signal, value)

# Make sure there is one cache file for the original index
# and one file for the reversed index
files_in_cache = audeer.list_file_names(cache_root)
assert len(files_in_cache) == 2


def test_process_index_filewise_end_times(tmpdir):
# Ensure the resulting segmented index
# returned by audinterface.process_index()
Expand Down Expand Up @@ -1138,7 +1234,7 @@ def test_process_signal_from_index(


@pytest.mark.parametrize(
"process_func, signal, sampling_rate, min_signal_dur, " "max_signal_dur, expected",
"process_func, signal, sampling_rate, min_signal_dur, max_signal_dur, expected",
[
(
None,
Expand Down
Loading