From bca16becdf4be7ac4b1b15416932214e425663b5 Mon Sep 17 00:00:00 2001 From: Alexander Rodionov Date: Sun, 19 Mar 2023 21:10:13 +0300 Subject: [PATCH 01/30] iWIP --- .github/workflows/ci.yml | 2 +- .pre-commit-config.yaml | 57 +++++++++++++++++++++++++++++++++++ Dockerfile | 11 +++++++ Makefile | 9 ++++++ pitch_detectors/algorithms.py | 4 +-- pitch_detectors/config.py | 1 - pyproject.toml | 2 +- tests/algorithms_test.py | 1 + 8 files changed, 82 insertions(+), 5 deletions(-) create mode 100644 .pre-commit-config.yaml create mode 100644 Dockerfile create mode 100644 Makefile delete mode 100644 pitch_detectors/config.py diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6f086c3..737f281 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -8,7 +8,7 @@ jobs: strategy: fail-fast: false matrix: - python_version: ['3.8', '3.9', '3.10', '3.11'] + python_version: ['3.8', '3.9', '3.10'] steps: - uses: actions/checkout@v3 - uses: actions/setup-python@v4 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..f77768a --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,57 @@ +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.3.0 + hooks: + - id: check-added-large-files + - id: check-yaml + - id: check-json + - id: check-ast + - id: check-byte-order-marker + - id: check-builtin-literals + - id: check-case-conflict + - id: check-docstring-first + - id: debug-statements + - id: end-of-file-fixer + - id: mixed-line-ending + - id: trailing-whitespace + - id: check-merge-conflict + - id: detect-private-key + - id: double-quote-string-fixer + - id: name-tests-test + - id: requirements-txt-fixer + + - repo: https://github.com/asottile/add-trailing-comma + rev: v2.3.0 + hooks: + - id: add-trailing-comma + + - repo: https://github.com/asottile/pyupgrade + rev: v3.1.0 + hooks: + - id: pyupgrade + + - repo: https://github.com/pre-commit/mirrors-autopep8 + rev: v1.7.0 + hooks: + - id: autopep8 + + - repo: https://github.com/PyCQA/autoflake + rev: v1.7.6 + hooks: + - id: autoflake + + - repo: https://github.com/PyCQA/flake8 + rev: 5.0.4 + hooks: + - id: flake8 + + - repo: https://github.com/charliermarsh/ruff-pre-commit + rev: v0.0.254 + hooks: + - id: ruff + args: [--fix, --exit-non-zero-on-fix] + + # - repo: https://github.com/pre-commit/mirrors-mypy + # rev: v0.982 + # hooks: + # - id: mypy diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..9f4483c --- /dev/null +++ b/Dockerfile @@ -0,0 +1,11 @@ +FROM nvidia/cuda:11.2.2-cudnn8-devel-ubuntu20.04 + +# https://github.com/NVIDIA/nvidia-docker/wiki/Usage +# https://github.com/NVIDIA/nvidia-docker/issues/531 +ENV NVIDIA_DRIVER_CAPABILITIES compute,video,utility + +RUN apt-get update && apt-get install -y python3-pip + +RUN --mount=type=cache,target=/root/.cache/pip \ + pip install tensorflow + diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..b18e95d --- /dev/null +++ b/Makefile @@ -0,0 +1,9 @@ +.PHONY: build +build: + # https://pythonspeed.com/articles/docker-cache-pip-downloads/ + DOCKER_BUILDKIT=1 docker build --progress=plain -t tandav/pitch-detectors . + +.PHONY: test-tensorflow-gpu +test-tensorflow-gpu: build + docker run --rm -it --gpus all tandav/pitch-detectors \ + python3 -c "import tensorflow as tf; print(tf.config.list_physical_devices('GPU'))" diff --git a/pitch_detectors/algorithms.py b/pitch_detectors/algorithms.py index d36184f..cf2c5f2 100644 --- a/pitch_detectors/algorithms.py +++ b/pitch_detectors/algorithms.py @@ -1,6 +1,5 @@ import numpy as np -from pitch_detectors import config from pitch_detectors import util @@ -179,13 +178,14 @@ def __init__( self, a: np.ndarray, fs: int, confidence_threshold=0.8, expected_sample_rate: int = 16000, + spice_model_path = 'data/spice_model/', ): import resampy import tensorflow as tf import tensorflow_hub as hub a = resampy.resample(a, fs, expected_sample_rate) super().__init__(a, fs) - model = hub.load(config.spice_model_path) + model = hub.load(spice_model_path) model_output = model.signatures['serving_default'](tf.constant(a, tf.float32)) confidence = 1.0 - model_output['uncertainty'] f0 = self.output2hz(model_output['pitch'].numpy()) diff --git a/pitch_detectors/config.py b/pitch_detectors/config.py deleted file mode 100644 index bce54d0..0000000 --- a/pitch_detectors/config.py +++ /dev/null @@ -1 +0,0 @@ -spice_model_path = 'data/spice_model/' diff --git a/pyproject.toml b/pyproject.toml index 6519e96..f584437 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,7 +6,7 @@ authors = [ ] description = "collection of pitch detection algorithms with unified interface" readme = "README.md" -requires-python = ">=3.8" +requires-python = ">=3.8,<3.11" dependencies = [ "AMFM-decompy", "crepe", diff --git a/tests/algorithms_test.py b/tests/algorithms_test.py index d0222ab..3bf5ea2 100644 --- a/tests/algorithms_test.py +++ b/tests/algorithms_test.py @@ -14,6 +14,7 @@ def a_fs(rescale: float = 100000): assert a.dtype == np.float32 yield a, fs + @pytest.mark.parametrize('algorithm', ALGORITHMS) def test_detection(algorithm, a_fs): a, fs = a_fs From 3a586ece1588b6efa29cad4e33d3fb5536dd2578 Mon Sep 17 00:00:00 2001 From: Alexander Rodionov Date: Sun, 19 Mar 2023 21:15:03 +0300 Subject: [PATCH 02/30] pre-commit --- .pre-commit-config.yaml | 13 ++++--------- Dockerfile | 1 - pitch_detectors/algorithms.py | 4 ++-- tests/algorithms_test.py | 10 ++++++---- 4 files changed, 12 insertions(+), 16 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index f77768a..aeab0a9 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -40,16 +40,11 @@ repos: hooks: - id: autoflake - - repo: https://github.com/PyCQA/flake8 - rev: 5.0.4 - hooks: - - id: flake8 - - repo: https://github.com/charliermarsh/ruff-pre-commit - rev: v0.0.254 - hooks: - - id: ruff - args: [--fix, --exit-non-zero-on-fix] + rev: v0.0.254 + hooks: + - id: ruff + args: [--fix, --exit-non-zero-on-fix] # - repo: https://github.com/pre-commit/mirrors-mypy # rev: v0.982 diff --git a/Dockerfile b/Dockerfile index 9f4483c..026d42a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -8,4 +8,3 @@ RUN apt-get update && apt-get install -y python3-pip RUN --mount=type=cache,target=/root/.cache/pip \ pip install tensorflow - diff --git a/pitch_detectors/algorithms.py b/pitch_detectors/algorithms.py index cf2c5f2..cdd4166 100644 --- a/pitch_detectors/algorithms.py +++ b/pitch_detectors/algorithms.py @@ -127,7 +127,7 @@ def __init__( class Yaapt(PitchDetector): def __init__(self, a: np.ndarray, fs: int, hz_min: float = 75, hz_max: float = 600): import amfm_decompy.basic_tools as basic - import amfm_decompy.pYAAPT as pYAAPT + from amfm_decompy import pYAAPT super().__init__(a, fs, hz_min, hz_max) self.signal = basic.SignalObj(data=self.a, fs=self.fs) f0 = pYAAPT.yaapt(self.signal, f0_min=self.hz_min, f0_max=self.hz_max, frame_length=15) @@ -178,7 +178,7 @@ def __init__( self, a: np.ndarray, fs: int, confidence_threshold=0.8, expected_sample_rate: int = 16000, - spice_model_path = 'data/spice_model/', + spice_model_path='data/spice_model/', ): import resampy import tensorflow as tf diff --git a/tests/algorithms_test.py b/tests/algorithms_test.py index 3bf5ea2..7956f59 100644 --- a/tests/algorithms_test.py +++ b/tests/algorithms_test.py @@ -1,9 +1,11 @@ +from pathlib import Path + import numpy as np import pytest -from pitch_detectors.algorithms import ALGORITHMS -from scipy.io import wavfile -from pathlib import Path from dsplib.scale import minmax_scaler +from scipy.io import wavfile + +from pitch_detectors.algorithms import ALGORITHMS @pytest.fixture @@ -18,4 +20,4 @@ def a_fs(rescale: float = 100000): @pytest.mark.parametrize('algorithm', ALGORITHMS) def test_detection(algorithm, a_fs): a, fs = a_fs - p = algorithm(a, fs) + algorithm(a, fs) From 3339009b957790c6cba3b8fdbc8cb3a3818d67e0 Mon Sep 17 00:00:00 2001 From: Alexander Rodionov Date: Sun, 19 Mar 2023 21:25:11 +0300 Subject: [PATCH 03/30] wip --- .pre-commit-config.yaml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index aeab0a9..ac212b8 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -46,6 +46,11 @@ repos: - id: ruff args: [--fix, --exit-non-zero-on-fix] + - repo: https://github.com/PyCQA/pylint + rev: v2.17.0 + hooks: + - id: pylint + # - repo: https://github.com/pre-commit/mirrors-mypy # rev: v0.982 # hooks: From 3cf276c10e217c11f10fd647311950ff4ea60060 Mon Sep 17 00:00:00 2001 From: Alexander Rodionov Date: Sun, 19 Mar 2023 22:04:18 +0300 Subject: [PATCH 04/30] fix pylint --- .pre-commit-config.yaml | 1 + pitch_detectors/algorithms.py | 22 ++++++++++++--------- pyproject.toml | 36 ++++++++++++++++++++++++++++------- 3 files changed, 43 insertions(+), 16 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index ac212b8..e980ba6 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -50,6 +50,7 @@ repos: rev: v2.17.0 hooks: - id: pylint + additional_dependencies: ["pylint-per-file-ignores"] # - repo: https://github.com/pre-commit/mirrors-mypy # rev: v0.982 diff --git a/pitch_detectors/algorithms.py b/pitch_detectors/algorithms.py index cdd4166..9c9ea0d 100644 --- a/pitch_detectors/algorithms.py +++ b/pitch_detectors/algorithms.py @@ -13,6 +13,7 @@ def __init__(self, a: np.ndarray, fs: int, hz_min: float = 75, hz_max: float = 6 self.hz_max = hz_max self.seconds = len(a) / fs self.f0 = None + self.t = None def dict(self): return {'f0': util.nan_to_none(self.f0.tolist()), 't': self.t.tolist()} @@ -97,7 +98,8 @@ def __init__( import torch import torchcrepe if device is None: - torch.device('cuda:0' if torch.cuda.is_available() else 'cpu'), + device = 'cuda:0' if torch.cuda.is_available() else 'cpu' + torch.device(device) super().__init__(a, fs, hz_min, hz_max) if not torch.cuda.is_available(): @@ -175,7 +177,9 @@ class Spice(PitchDetector): use_gpu = True def __init__( - self, a: np.ndarray, fs: int, + self, + a: np.ndarray, + fs: int, confidence_threshold=0.8, expected_sample_rate: int = 16000, spice_model_path='data/spice_model/', @@ -194,18 +198,18 @@ def __init__( def output2hz( self, pitch_output: np.ndarray, - PT_OFFSET: float = 25.58, - PT_SLOPE: float = 63.07, - FMIN: float = 10.0, - BINS_PER_OCTAVE: float = 12.0, + pt_offset: float = 25.58, + pt_slope: float = 63.07, + fmin: float = 10.0, + bins_per_octave: float = 12.0, ) -> np.ndarray: """convert pitch from the model output [0.0, 1.0] range to absolute values in Hz.""" - cqt_bin = pitch_output * PT_SLOPE + PT_OFFSET - return FMIN * 2.0 ** (1.0 * cqt_bin / BINS_PER_OCTAVE) + cqt_bin = pitch_output * pt_slope + pt_offset + return fmin * 2.0 ** (1.0 * cqt_bin / bins_per_octave) class World(PitchDetector): - def __init__(self, a: np.ndarray, fs): + def __init__(self, a: np.ndarray, fs: int): import pyworld super().__init__(a, fs) f0, sp, ap = pyworld.wav2world(a.astype(float), fs) diff --git a/pyproject.toml b/pyproject.toml index f584437..84e212c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -135,17 +135,39 @@ force-single-line = true # ============================================================================== +[tool.pylint.MASTER] +load-plugins=[ + "pylint_per_file_ignores", +] + +[tool.pylint.BASIC] +good-names = [ + "a", + "fs", + "f0", + "t", + "x", + "pm", + "sp", + "ap", +] + [tool.pylint.messages-control] disable = [ - "C0321","C3001","C0116","C0301","C0103","C0115","C0114", - "W1514", - "W0401", # wildcard import - "W0614", - "W1113", - "R0903", - "E0401", + "missing-function-docstring", + "missing-class-docstring", + "missing-module-docstring", + "line-too-long", + "import-outside-toplevel", + "unused-variable", + "too-many-arguments", + "import-error", ] +[tool.pylint-per-file-ignores] +"/tests/" = "redefined-outer-name" + + # ============================================================================== [tool.autopep8] From a19a224f7522ca9f0c2e6f62190a758b35337cc1 Mon Sep 17 00:00:00 2001 From: Alexander Rodionov Date: Sun, 19 Mar 2023 22:08:07 +0300 Subject: [PATCH 05/30] fix ruff --- pyproject.toml | 7 ------- 1 file changed, 7 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 84e212c..ff619a7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -116,14 +116,7 @@ extend-select = [ ] ignore = [ "E501", # line too long - "E731", - "E701", - "E702", - "F403", # star imports - "F405", # star imports - "B008", "PLR0913", - "TCH003", ] [tool.ruff.per-file-ignores] From 80bcfa55d5a4444954b94bb114b52f1647521868 Mon Sep 17 00:00:00 2001 From: Alexander Rodionov Date: Sun, 19 Mar 2023 22:17:06 +0300 Subject: [PATCH 06/30] fix mypy --- .pre-commit-config.yaml | 8 ++++---- pitch_detectors/algorithms.py | 22 +++++++++++++--------- pyproject.toml | 2 +- 3 files changed, 18 insertions(+), 14 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index e980ba6..973d0d5 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -52,7 +52,7 @@ repos: - id: pylint additional_dependencies: ["pylint-per-file-ignores"] - # - repo: https://github.com/pre-commit/mirrors-mypy - # rev: v0.982 - # hooks: - # - id: mypy + - repo: https://github.com/pre-commit/mirrors-mypy + rev: v0.982 + hooks: + - id: mypy diff --git a/pitch_detectors/algorithms.py b/pitch_detectors/algorithms.py index 9c9ea0d..1c903c5 100644 --- a/pitch_detectors/algorithms.py +++ b/pitch_detectors/algorithms.py @@ -12,14 +12,18 @@ def __init__(self, a: np.ndarray, fs: int, hz_min: float = 75, hz_max: float = 6 self.hz_min = hz_min self.hz_max = hz_max self.seconds = len(a) / fs - self.f0 = None - self.t = None - - def dict(self): + self.f0: np.ndarray | None = None + self.t: np.ndarray | None = None + + def dict(self) -> dict[str, list[float | None]]: + if self.f0 is None: + raise ValueError('f0 must be not None') + if self.t is None: + raise ValueError('t must be not None') return {'f0': util.nan_to_none(self.f0.tolist()), 't': self.t.tolist()} @classmethod - def name(cls): + def name(cls) -> str: return cls.__class__.__name__ @@ -92,8 +96,8 @@ class TorchCrepe(PitchDetector): def __init__( self, a: np.ndarray, fs: int, hz_min: float = 75, hz_max: float = 600, confidence_threshold: float = 0.8, - batch_size=2048, - device=None, + batch_size: int = 2048, + device: str | None = None, ): import torch import torchcrepe @@ -180,9 +184,9 @@ def __init__( self, a: np.ndarray, fs: int, - confidence_threshold=0.8, + confidence_threshold: float = 0.8, expected_sample_rate: int = 16000, - spice_model_path='data/spice_model/', + spice_model_path: str = 'data/spice_model/', ): import resampy import tensorflow as tf diff --git a/pyproject.toml b/pyproject.toml index ff619a7..53a8a3b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -91,7 +91,7 @@ warn_unreachable = true warn_unused_configs = true warn_unused_ignores = true -[[tool.mypy.overrides]] +[tool.mypy.overrides] module = ["tests.*"] disallow_untyped_defs = false From cd99e552e1c12700ddff5f1f1cf8448071d4f4ab Mon Sep 17 00:00:00 2001 From: Alexander Rodionov Date: Sun, 19 Mar 2023 22:31:56 +0300 Subject: [PATCH 07/30] fix pyright --- .pre-commit-config.yaml | 5 +++++ pitch_detectors/algorithms.py | 10 +++++----- pyproject.toml | 6 ++++++ 3 files changed, 16 insertions(+), 5 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 973d0d5..683c5a6 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -56,3 +56,8 @@ repos: rev: v0.982 hooks: - id: mypy + + - repo: https://github.com/RobertCraigie/pyright-python + rev: v1.1.299 + hooks: + - id: pyright diff --git a/pitch_detectors/algorithms.py b/pitch_detectors/algorithms.py index 1c903c5..560e404 100644 --- a/pitch_detectors/algorithms.py +++ b/pitch_detectors/algorithms.py @@ -24,7 +24,7 @@ def dict(self) -> dict[str, list[float | None]]: @classmethod def name(cls) -> str: - return cls.__class__.__name__ + return cls.__name__ class PraatAC(PitchDetector): @@ -165,10 +165,10 @@ def __init__(self, a: np.ndarray, fs: int, hz_min: float = 75, hz_max: float = 6 class Reaper(PitchDetector): def __init__(self, a: np.ndarray, fs: int, hz_min: float = 75, hz_max: float = 600): - import dsplib.scale import pyreaper + from dsplib.scale import minmax_scaler int16_info = np.iinfo(np.int16) - a = dsplib.scale.minmax_scaler(a, np.min(a), np.max(a), int16_info.min, int16_info.max).round().astype(np.int16) + a = minmax_scaler(a, np.min(a), np.max(a), int16_info.min, int16_info.max).round().astype(np.int16) super().__init__(a, fs, hz_min, hz_max) pm_times, pm, f0_times, f0, corr = pyreaper.reaper(self.a, fs=self.fs, minf0=self.hz_min, maxf0=self.hz_max, frame_period=0.01) f0[f0 == -1] = np.nan @@ -226,9 +226,9 @@ class TorchYin(PitchDetector): def __init__(self, a: np.ndarray, fs: int, hz_min: float = 75, hz_max: float = 600): import torch import torchyin - a = torch.from_numpy(a) super().__init__(a, fs, hz_min, hz_max) - f0 = torchyin.estimate(self.a, sample_rate=self.fs, pitch_min=self.hz_min, pitch_max=self.hz_max) + _a = torch.from_numpy(a) + f0 = torchyin.estimate(_a, sample_rate=self.fs, pitch_min=self.hz_min, pitch_max=self.hz_max) f0[f0 == 0] = np.nan self.f0 = f0[:-1] self.t = np.linspace(0, self.seconds, f0.shape[0])[1:] diff --git a/pyproject.toml b/pyproject.toml index 53a8a3b..5658b6f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -169,3 +169,9 @@ recursive = true aggressive = 3 # ============================================================================== + +[tool.pyright] +venvPath = "/home/tandav/.virtualenvs" +venv = "pitch-detectors" + +# ============================================================================== From 88c6338ba20c56e1a6c92322baec9298c9e3fa60 Mon Sep 17 00:00:00 2001 From: Alexander Rodionov Date: Mon, 20 Mar 2023 00:18:11 +0300 Subject: [PATCH 08/30] fix docker base image (todo find latest working) --- Dockerfile | 28 ++++++++++++++++++++++++++-- Makefile | 12 ++++++++++++ pitch_detectors/algorithms.py | 10 ++++++---- pitch_detectors/util.py | 5 +++-- pyproject.toml | 2 +- 5 files changed, 48 insertions(+), 9 deletions(-) diff --git a/Dockerfile b/Dockerfile index 026d42a..6a3c411 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,10 +1,34 @@ -FROM nvidia/cuda:11.2.2-cudnn8-devel-ubuntu20.04 +# FROM nvidia/cuda:11.2.2-cudnn8-devel-ubuntu20.04 +FROM nvidia/cuda:11.7.0-cudnn8-devel-ubuntu22.04 # https://github.com/NVIDIA/nvidia-docker/wiki/Usage # https://github.com/NVIDIA/nvidia-docker/issues/531 ENV NVIDIA_DRIVER_CAPABILITIES compute,video,utility -RUN apt-get update && apt-get install -y python3-pip +# RUN apt-get update && apt-get install -y python3-pip +RUN apt-get update && \ + DEBIAN_FRONTEND=noninteractive apt-get install -y software-properties-common && \ + add-apt-repository -y ppa:deadsnakes/ppa && \ + apt-get install -y python3.10-venv libsndfile-dev libasound-dev portaudio19-dev + +# - apt-get install -y software-properties-common curl git openjdk-8-jdk make libgomp1 +# - add-apt-repository -y ppa:deadsnakes/ppa +# - apt-get install -y python3.7-venv python3.8-venv python3.9-venv python3.10-venv + + +# https://pythonspeed.com/articles/activate-virtualenv-dockerfile/ +ENV VIRTUAL_ENV=/venv +RUN python3.10 -m venv $VIRTUAL_ENV +ENV PATH="$VIRTUAL_ENV/bin:$PATH" RUN --mount=type=cache,target=/root/.cache/pip \ pip install tensorflow + +WORKDIR /app +COPY pyproject.toml /app/ +RUN --mount=type=cache,target=/root/.cache/pip \ + pip install --upgrade pip setuptools wheel && \ + pip install .[dev] + +# COPY libmv /app/libmv +# COPY scripts /app/scripts diff --git a/Makefile b/Makefile index b18e95d..2a2566a 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,19 @@ build: # https://pythonspeed.com/articles/docker-cache-pip-downloads/ DOCKER_BUILDKIT=1 docker build --progress=plain -t tandav/pitch-detectors . +.PHONY: push +push: + docker push tandav/pitch-detectors + .PHONY: test-tensorflow-gpu test-tensorflow-gpu: build docker run --rm -it --gpus all tandav/pitch-detectors \ python3 -c "import tensorflow as tf; print(tf.config.list_physical_devices('GPU'))" + +.PHONY: test +test: build + docker run --rm -it --gpus all \ + -v $$PWD/pitch_detectors:/app/pitch_detectors \ + -v $$PWD/tests:/app/tests \ + tandav/pitch-detectors \ + pytest -v diff --git a/pitch_detectors/algorithms.py b/pitch_detectors/algorithms.py index 560e404..4888e25 100644 --- a/pitch_detectors/algorithms.py +++ b/pitch_detectors/algorithms.py @@ -1,3 +1,5 @@ +import typing as tp + import numpy as np from pitch_detectors import util @@ -12,10 +14,10 @@ def __init__(self, a: np.ndarray, fs: int, hz_min: float = 75, hz_max: float = 6 self.hz_min = hz_min self.hz_max = hz_max self.seconds = len(a) / fs - self.f0: np.ndarray | None = None - self.t: np.ndarray | None = None + self.f0: tp.Optional[np.ndarray] = None + self.t: tp.Optional[np.ndarray] = None - def dict(self) -> dict[str, list[float | None]]: + def dict(self) -> dict[str, list[tp.Optional[float]]]: if self.f0 is None: raise ValueError('f0 must be not None') if self.t is None: @@ -97,7 +99,7 @@ class TorchCrepe(PitchDetector): def __init__( self, a: np.ndarray, fs: int, hz_min: float = 75, hz_max: float = 600, confidence_threshold: float = 0.8, batch_size: int = 2048, - device: str | None = None, + device: tp.Optional[str] = None, ): import torch import torchcrepe diff --git a/pitch_detectors/util.py b/pitch_detectors/util.py index 4fd2276..f353def 100644 --- a/pitch_detectors/util.py +++ b/pitch_detectors/util.py @@ -1,9 +1,10 @@ import math +import typing as tp -def nan_to_none(x: list[float]) -> list[float | None]: +def nan_to_none(x: list[float]) -> list[tp.Optional[float]]: return [None if math.isnan(v) else v for v in x] -def none_to_nan(x: list[float | None]) -> list[float]: +def none_to_nan(x: list[tp.Optional[float]]) -> list[float]: return [float('nan') if v is None else v for v in x] diff --git a/pyproject.toml b/pyproject.toml index 5658b6f..241bf13 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,7 +10,7 @@ requires-python = ">=3.8,<3.11" dependencies = [ "AMFM-decompy", "crepe", - "dsplib==0.7.0", + "dsplib==0.7.2", "librosa", "numpy", "praat-parselmouth==0.4.1", From 4a5cf669184d6cf6f3798b1aeadff6181977080c Mon Sep 17 00:00:00 2001 From: Alexander Rodionov Date: Mon, 20 Mar 2023 10:30:45 +0300 Subject: [PATCH 09/30] WIP --- Dockerfile | 18 +++--------------- Makefile | 11 +++++++---- README.md | 28 ++++++++++++++++------------ tests/algorithms_test.py | 17 ++++++++++++----- tests/gpu_test.py | 16 ++++++++++++++++ 5 files changed, 54 insertions(+), 36 deletions(-) create mode 100644 tests/gpu_test.py diff --git a/Dockerfile b/Dockerfile index 6a3c411..6a18286 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,34 +1,22 @@ -# FROM nvidia/cuda:11.2.2-cudnn8-devel-ubuntu20.04 -FROM nvidia/cuda:11.7.0-cudnn8-devel-ubuntu22.04 +# FROM nvidia/cuda:11.7.0-cudnn8-devel-ubuntu22.04 +FROM nvidia/cuda:12.0.1-cudnn8-devel-ubuntu22.04 # https://github.com/NVIDIA/nvidia-docker/wiki/Usage # https://github.com/NVIDIA/nvidia-docker/issues/531 ENV NVIDIA_DRIVER_CAPABILITIES compute,video,utility -# RUN apt-get update && apt-get install -y python3-pip RUN apt-get update && \ DEBIAN_FRONTEND=noninteractive apt-get install -y software-properties-common && \ add-apt-repository -y ppa:deadsnakes/ppa && \ apt-get install -y python3.10-venv libsndfile-dev libasound-dev portaudio19-dev -# - apt-get install -y software-properties-common curl git openjdk-8-jdk make libgomp1 -# - add-apt-repository -y ppa:deadsnakes/ppa -# - apt-get install -y python3.7-venv python3.8-venv python3.9-venv python3.10-venv - - # https://pythonspeed.com/articles/activate-virtualenv-dockerfile/ ENV VIRTUAL_ENV=/venv RUN python3.10 -m venv $VIRTUAL_ENV ENV PATH="$VIRTUAL_ENV/bin:$PATH" -RUN --mount=type=cache,target=/root/.cache/pip \ - pip install tensorflow - WORKDIR /app -COPY pyproject.toml /app/ +COPY pyproject.toml . RUN --mount=type=cache,target=/root/.cache/pip \ pip install --upgrade pip setuptools wheel && \ pip install .[dev] - -# COPY libmv /app/libmv -# COPY scripts /app/scripts diff --git a/Makefile b/Makefile index 2a2566a..0493929 100644 --- a/Makefile +++ b/Makefile @@ -1,15 +1,18 @@ .PHONY: build build: # https://pythonspeed.com/articles/docker-cache-pip-downloads/ - DOCKER_BUILDKIT=1 docker build --progress=plain -t tandav/pitch-detectors . + # DOCKER_BUILDKIT=1 docker build --progress=plain -t tandav/pitch-detectors:cuda11.7.0-cudnn8-devel-ubuntu22.04 . + DOCKER_BUILDKIT=1 docker build --progress=plain -t tandav/pitch-detectors:cuda12.0.1-cudnn8-devel-ubuntu22.04 . .PHONY: push push: - docker push tandav/pitch-detectors + # docker push tandav/pitch-detectors:cuda11.7.0-cudnn8-devel-ubuntu22.04 + docker push tandav/pitch-detectors:cuda11.7.0-cudnn8-devel-ubuntu22.04 + docker push tandav/pitch-detectors:latest .PHONY: test-tensorflow-gpu test-tensorflow-gpu: build - docker run --rm -it --gpus all tandav/pitch-detectors \ + docker run --rm -it --gpus all tandav/pitch-detectors:cuda11.7.0-cudnn8-devel-ubuntu22.04 \ python3 -c "import tensorflow as tf; print(tf.config.list_physical_devices('GPU'))" .PHONY: test @@ -17,5 +20,5 @@ test: build docker run --rm -it --gpus all \ -v $$PWD/pitch_detectors:/app/pitch_detectors \ -v $$PWD/tests:/app/tests \ - tandav/pitch-detectors \ + tandav/pitch-detectors:cuda11.7.0-cudnn8-devel-ubuntu22.04 \ pytest -v diff --git a/README.md b/README.md index 12163f5..f4c0528 100644 --- a/README.md +++ b/README.md @@ -2,18 +2,22 @@ collection of pitch detection algorithms with unified interface ## list of algorithms -1. PraatAC [cpu] -1. PraatCC [cpu] -1. PraatSHS [cpu] -1. Pyin [cpu] -1. Reaper [cpu] -1. Yaapt [cpu] -1. Rapt [cpu] -1. World [cpu] -1. TorchYin [cpu] -1. Crepe [cpu, gpu] -1. TorchCrepe [cpu, gpu] -1. Swipe [cpu, gpu] + +| algorithm | cpu | gpu | +|------------|-----|-----| +| [PraatAC](https://parselmouth.readthedocs.io/en/stable/api_reference.html#parselmouth.Sound.to_pitch_ac) | ✓ | | +| [PraatCC](https://parselmouth.readthedocs.io/en/stable/api_reference.html#parselmouth.Sound.to_pitch_cc) | ✓ | | +| [PraatSHS](https://parselmouth.readthedocs.io/en/stable/api_reference.html#parselmouth.Sound.to_pitch_shs) | ✓ | | +| [Pyin](https://librosa.org/doc/latest/generated/librosa.pyin.html) | ✓ | | +| [Reaper](https://github.com/r9y9/pyreaper) | ✓ | | +| [Yaapt](http://bjbschmitt.github.io/AMFM_decompy/pYAAPT.html#amfm_decompy.pYAAPT.yaapt) | ✓ | | +| [Rapt](https://pysptk.readthedocs.io/en/stable/generated/pysptk.sptk.rapt.html) | ✓ | | +| [Swipe](https://pysptk.readthedocs.io/en/stable/generated/pysptk.sptk.swipe.html) | ✓ | ✓ | +| [World](https://github.com/JeremyCCHsu/Python-Wrapper-for-World-Vocoder) | ✓ | | +| [TorchYin](https://github.com/brentspell/torch-yin) | ✓ | | +| [Crepe](https://github.com/marl/crepe) | ✓ | ✓ | +| [TorchCrepe](https://github.com/maxrmorrison/torchcrepe) | ✓ | ✓ | + ## additional features - robust (vote-based + median) averaging of pitch diff --git a/tests/algorithms_test.py b/tests/algorithms_test.py index 7956f59..644ab21 100644 --- a/tests/algorithms_test.py +++ b/tests/algorithms_test.py @@ -1,3 +1,4 @@ +import dataclasses from pathlib import Path import numpy as np @@ -8,16 +9,22 @@ from pitch_detectors.algorithms import ALGORITHMS +@dataclasses.dataclass +class Record: + a: np.ndarray + fs: int + + @pytest.fixture -def a_fs(rescale: float = 100000): +def record(rescale: float = 100000): """audio and fs""" fs, a = wavfile.read(Path(__file__).parent / 'data' / 'b1a5da49d564a7341e7e1327aa3f229a.wav') a = minmax_scaler(a, a.min(), a.max(), -rescale, rescale).astype(np.float32) assert a.dtype == np.float32 - yield a, fs + return Record(a, fs) +@pytest.mark.filterwarnings('ignore:pkg_resources') @pytest.mark.parametrize('algorithm', ALGORITHMS) -def test_detection(algorithm, a_fs): - a, fs = a_fs - algorithm(a, fs) +def test_detection(algorithm, record): + algorithm(record.a, record.fs) diff --git a/tests/gpu_test.py b/tests/gpu_test.py new file mode 100644 index 0000000..1e8d3b3 --- /dev/null +++ b/tests/gpu_test.py @@ -0,0 +1,16 @@ +import subprocess + +import tensorflow as tf +import torch + + +def test_nvidia_smi(): + subprocess.check_call('nvidia-smi') + + +def test_tensorflow(): + assert tf.config.experimental.list_physical_devices('GPU') + + +def test_pytorch(): + assert torch.cuda.is_available() From ca1bc5826131b94474ab727a0f7b621d4109b687 Mon Sep 17 00:00:00 2001 From: Alexander Rodionov Date: Mon, 20 Mar 2023 10:33:08 +0300 Subject: [PATCH 10/30] fix deprecation pkg_resources warnings --- tests/algorithms_test.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/algorithms_test.py b/tests/algorithms_test.py index 644ab21..aedfd57 100644 --- a/tests/algorithms_test.py +++ b/tests/algorithms_test.py @@ -24,7 +24,8 @@ def record(rescale: float = 100000): return Record(a, fs) -@pytest.mark.filterwarnings('ignore:pkg_resources') +@pytest.mark.filterwarnings('ignore:pkg_resources is deprecated as an API') +@pytest.mark.filterwarnings('ignore:Deprecated call to `pkg_resources.declare_namespace') @pytest.mark.parametrize('algorithm', ALGORITHMS) def test_detection(algorithm, record): algorithm(record.a, record.fs) From 5e4e1a396c5c4f38511b1cda707e11d939e65c22 Mon Sep 17 00:00:00 2001 From: Alexander Rodionov Date: Mon, 20 Mar 2023 11:42:46 +0300 Subject: [PATCH 11/30] wip --- Dockerfile | 5 +++-- Makefile | 9 ++------- README.md | 4 ++-- pyproject.toml | 1 + tests/algorithms_test.py | 1 + tests/gpu_test.py | 4 ++++ 6 files changed, 13 insertions(+), 11 deletions(-) diff --git a/Dockerfile b/Dockerfile index 6a18286..7a24a48 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,6 @@ -# FROM nvidia/cuda:11.7.0-cudnn8-devel-ubuntu22.04 -FROM nvidia/cuda:12.0.1-cudnn8-devel-ubuntu22.04 +# FROM nvidia/cuda:11.7.0-cudnn8-devel-ubuntu22.04 - SUCCESS +# FROM nvidia/cuda:12.0.1-cudnn8-devel-ubuntu22.04 - FAIL +FROM nvidia/cuda:11.8.0-cudnn8-devel-ubuntu22.04 # https://github.com/NVIDIA/nvidia-docker/wiki/Usage # https://github.com/NVIDIA/nvidia-docker/issues/531 diff --git a/Makefile b/Makefile index 0493929..0b3a3cd 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ build: # https://pythonspeed.com/articles/docker-cache-pip-downloads/ # DOCKER_BUILDKIT=1 docker build --progress=plain -t tandav/pitch-detectors:cuda11.7.0-cudnn8-devel-ubuntu22.04 . - DOCKER_BUILDKIT=1 docker build --progress=plain -t tandav/pitch-detectors:cuda12.0.1-cudnn8-devel-ubuntu22.04 . + DOCKER_BUILDKIT=1 docker build --progress=plain -t tandav/pitch-detectors:12.8.0-cudnn8-devel-ubuntu22.04 . .PHONY: push push: @@ -10,15 +10,10 @@ push: docker push tandav/pitch-detectors:cuda11.7.0-cudnn8-devel-ubuntu22.04 docker push tandav/pitch-detectors:latest -.PHONY: test-tensorflow-gpu -test-tensorflow-gpu: build - docker run --rm -it --gpus all tandav/pitch-detectors:cuda11.7.0-cudnn8-devel-ubuntu22.04 \ - python3 -c "import tensorflow as tf; print(tf.config.list_physical_devices('GPU'))" - .PHONY: test test: build docker run --rm -it --gpus all \ -v $$PWD/pitch_detectors:/app/pitch_detectors \ -v $$PWD/tests:/app/tests \ - tandav/pitch-detectors:cuda11.7.0-cudnn8-devel-ubuntu22.04 \ + tandav/pitch-detectors:12.8.0-cudnn8-devel-ubuntu22.04 \ pytest -v diff --git a/README.md b/README.md index f4c0528..ae8bfe3 100644 --- a/README.md +++ b/README.md @@ -11,10 +11,10 @@ collection of pitch detection algorithms with unified interface | [Pyin](https://librosa.org/doc/latest/generated/librosa.pyin.html) | ✓ | | | [Reaper](https://github.com/r9y9/pyreaper) | ✓ | | | [Yaapt](http://bjbschmitt.github.io/AMFM_decompy/pYAAPT.html#amfm_decompy.pYAAPT.yaapt) | ✓ | | -| [Rapt](https://pysptk.readthedocs.io/en/stable/generated/pysptk.sptk.rapt.html) | ✓ | | -| [Swipe](https://pysptk.readthedocs.io/en/stable/generated/pysptk.sptk.swipe.html) | ✓ | ✓ | | [World](https://github.com/JeremyCCHsu/Python-Wrapper-for-World-Vocoder) | ✓ | | | [TorchYin](https://github.com/brentspell/torch-yin) | ✓ | | +| [Rapt](https://pysptk.readthedocs.io/en/stable/generated/pysptk.sptk.rapt.html) | ✓ | | +| [Swipe](https://pysptk.readthedocs.io/en/stable/generated/pysptk.sptk.swipe.html) | ✓ | ✓ | | [Crepe](https://github.com/marl/crepe) | ✓ | ✓ | | [TorchCrepe](https://github.com/maxrmorrison/torchcrepe) | ✓ | ✓ | diff --git a/pyproject.toml b/pyproject.toml index 241bf13..b9f3be0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -32,6 +32,7 @@ dev = [ "bumpver", "pre-commit", "pytest", + "pytest-order", ] [project.urls] diff --git a/tests/algorithms_test.py b/tests/algorithms_test.py index aedfd57..97426df 100644 --- a/tests/algorithms_test.py +++ b/tests/algorithms_test.py @@ -24,6 +24,7 @@ def record(rescale: float = 100000): return Record(a, fs) +@pytest.mark.order(3) @pytest.mark.filterwarnings('ignore:pkg_resources is deprecated as an API') @pytest.mark.filterwarnings('ignore:Deprecated call to `pkg_resources.declare_namespace') @pytest.mark.parametrize('algorithm', ALGORITHMS) diff --git a/tests/gpu_test.py b/tests/gpu_test.py index 1e8d3b3..4b1729c 100644 --- a/tests/gpu_test.py +++ b/tests/gpu_test.py @@ -1,16 +1,20 @@ import subprocess +import pytest import tensorflow as tf import torch +@pytest.mark.order(0) def test_nvidia_smi(): subprocess.check_call('nvidia-smi') +@pytest.mark.order(1) def test_tensorflow(): assert tf.config.experimental.list_physical_devices('GPU') +@pytest.mark.order(2) def test_pytorch(): assert torch.cuda.is_available() From 06321c0522df0ac4c40907ec1f3d1c781d37c78f Mon Sep 17 00:00:00 2001 From: Alexander Rodionov Date: Mon, 20 Mar 2023 16:20:21 +0300 Subject: [PATCH 12/30] wip --- Dockerfile | 8 ++++++ Makefile | 5 ++-- README.md | 15 ++++++++---- pitch_detectors/algorithms.py | 46 ++++++++++++++++++++++++----------- pyproject.toml | 1 + 5 files changed, 54 insertions(+), 21 deletions(-) diff --git a/Dockerfile b/Dockerfile index 7a24a48..0d9e7f0 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,3 +1,9 @@ +FROM alpine/curl as downloader +RUN curl -L https://tfhub.dev/google/spice/2?tf-hub-format=compressed --output spice_2.tar.gz && \ + mkdir /spice_model && \ + tar xvf spice_2.tar.gz --directory /spice_model && \ + rm spice_2.tar.gz + # FROM nvidia/cuda:11.7.0-cudnn8-devel-ubuntu22.04 - SUCCESS # FROM nvidia/cuda:12.0.1-cudnn8-devel-ubuntu22.04 - FAIL FROM nvidia/cuda:11.8.0-cudnn8-devel-ubuntu22.04 @@ -6,6 +12,8 @@ FROM nvidia/cuda:11.8.0-cudnn8-devel-ubuntu22.04 # https://github.com/NVIDIA/nvidia-docker/issues/531 ENV NVIDIA_DRIVER_CAPABILITIES compute,video,utility +COPY --from=downloader /spice_model /spice_model + RUN apt-get update && \ DEBIAN_FRONTEND=noninteractive apt-get install -y software-properties-common && \ add-apt-repository -y ppa:deadsnakes/ppa && \ diff --git a/Makefile b/Makefile index 0b3a3cd..8503ad0 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ build: # https://pythonspeed.com/articles/docker-cache-pip-downloads/ # DOCKER_BUILDKIT=1 docker build --progress=plain -t tandav/pitch-detectors:cuda11.7.0-cudnn8-devel-ubuntu22.04 . - DOCKER_BUILDKIT=1 docker build --progress=plain -t tandav/pitch-detectors:12.8.0-cudnn8-devel-ubuntu22.04 . + DOCKER_BUILDKIT=1 docker build --progress=plain -t tandav/pitch-detectors:11.8.0-cudnn8-devel-ubuntu22.04 . .PHONY: push push: @@ -13,7 +13,8 @@ push: .PHONY: test test: build docker run --rm -it --gpus all \ + -e PITCH_DETECTORS_ERROR_GPU_NOT_AVAILABLE=true \ -v $$PWD/pitch_detectors:/app/pitch_detectors \ -v $$PWD/tests:/app/tests \ - tandav/pitch-detectors:12.8.0-cudnn8-devel-ubuntu22.04 \ + tandav/pitch-detectors:11.8.0-cudnn8-devel-ubuntu22.04 \ pytest -v diff --git a/README.md b/README.md index ae8bfe3..af50c45 100644 --- a/README.md +++ b/README.md @@ -14,18 +14,23 @@ collection of pitch detection algorithms with unified interface | [World](https://github.com/JeremyCCHsu/Python-Wrapper-for-World-Vocoder) | ✓ | | | [TorchYin](https://github.com/brentspell/torch-yin) | ✓ | | | [Rapt](https://pysptk.readthedocs.io/en/stable/generated/pysptk.sptk.rapt.html) | ✓ | | -| [Swipe](https://pysptk.readthedocs.io/en/stable/generated/pysptk.sptk.swipe.html) | ✓ | ✓ | +| [Swipe](https://pysptk.readthedocs.io/en/stable/generated/pysptk.sptk.swipe.html) | ✓ | | | [Crepe](https://github.com/marl/crepe) | ✓ | ✓ | | [TorchCrepe](https://github.com/maxrmorrison/torchcrepe) | ✓ | ✓ | +| [Spice](https://ai.googleblog.com/2019/11/spice-self-supervised-pitch-estimation.html) | ✓ | ✓ | -## additional features -- robust (vote-based + median) averaging of pitch -- json import/export - ## install ```bash pip install pitch-detectors ``` ## usage + +```python +from pitch_detectors +``` + +## additional features +- robust (vote-based + median) averaging of pitch +- json import/export diff --git a/pitch_detectors/algorithms.py b/pitch_detectors/algorithms.py index 4888e25..ea8c3c6 100644 --- a/pitch_detectors/algorithms.py +++ b/pitch_detectors/algorithms.py @@ -1,3 +1,4 @@ +import os import typing as tp import numpy as np @@ -16,6 +17,12 @@ def __init__(self, a: np.ndarray, fs: int, hz_min: float = 75, hz_max: float = 6 self.seconds = len(a) / fs self.f0: tp.Optional[np.ndarray] = None self.t: tp.Optional[np.ndarray] = None + if ( + os.environ.get('PITCH_DETECTORS_ERROR_GPU_NOT_AVAILABLE') and + self.use_gpu and + not self.gpu_available() + ): + raise ConnectionError(f'gpu must be available for {self.name()} algorithm') def dict(self) -> dict[str, list[tp.Optional[float]]]: if self.f0 is None: @@ -28,6 +35,25 @@ def dict(self) -> dict[str, list[tp.Optional[float]]]: def name(cls) -> str: return cls.__name__ + def gpu_available(self) -> bool: + return False + + +class TensorflowGPU: + use_gpu = True + + def gpu_available(self) -> bool: + import tensorflow as tf + return bool(tf.config.experimental.list_physical_devices('GPU')) + + +class TorchGPU: + use_gpu = True + + def gpu_available(self) -> bool: + import torch + return torch.cuda.is_available() + class PraatAC(PitchDetector): def __init__(self, a: np.ndarray, fs: int, hz_min: float = 75, hz_max: float = 600): @@ -75,17 +101,13 @@ def __init__(self, a: np.ndarray, fs: int, hz_min: float = 75, hz_max: float = 6 self.t = np.linspace(0, self.seconds, f0.shape[0]) -class Crepe(PitchDetector): - use_gpu = True - +class Crepe(TensorflowGPU, PitchDetector): def __init__(self, a: np.ndarray, fs: int, hz_min: float = 75, hz_max: float = 600, confidence_threshold: float = 0.8): import crepe import tensorflow as tf super().__init__(a, fs, hz_min, hz_max) gpus = tf.config.experimental.list_physical_devices('GPU') - if not gpus: - raise RuntimeError('Crepe requires a GPU') for gpu in gpus: tf.config.experimental.set_memory_growth(gpu, True) @@ -93,9 +115,7 @@ def __init__(self, a: np.ndarray, fs: int, hz_min: float = 75, hz_max: float = 6 self.f0[self.confidence < confidence_threshold] = np.nan -class TorchCrepe(PitchDetector): - use_gpu = True - +class TorchCrepe(TorchGPU, PitchDetector): def __init__( self, a: np.ndarray, fs: int, hz_min: float = 75, hz_max: float = 600, confidence_threshold: float = 0.8, batch_size: int = 2048, @@ -178,17 +198,14 @@ def __init__(self, a: np.ndarray, fs: int, hz_min: float = 75, hz_max: float = 6 self.t = f0_times -class Spice(PitchDetector): - """https://ai.googleblog.com/2019/11/spice-self-supervised-pitch-estimation.html""" - use_gpu = True - +class Spice(TensorflowGPU, PitchDetector): def __init__( self, a: np.ndarray, fs: int, confidence_threshold: float = 0.8, expected_sample_rate: int = 16000, - spice_model_path: str = 'data/spice_model/', + spice_model_path: str = '/spice_model', ): import resampy import tensorflow as tf @@ -249,6 +266,7 @@ def __init__(self, a: np.ndarray, fs: int, hz_min: float = 75, hz_max: float = 6 Rapt, World, TorchYin, + Spice, ) cpu_algorithms = ( @@ -266,7 +284,7 @@ def __init__(self, a: np.ndarray, fs: int, hz_min: float = 75, hz_max: float = 6 gpu_algorithms = ( 'Crepe', 'TorchCrepe', - 'Swipe', + 'Spice', ) algorithms = cpu_algorithms + gpu_algorithms diff --git a/pyproject.toml b/pyproject.toml index b9f3be0..c7c7781 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -156,6 +156,7 @@ disable = [ "unused-variable", "too-many-arguments", "import-error", + "too-few-public-methods", ] [tool.pylint-per-file-ignores] From 19d5326fa6dc253de9bd210acab7b0101513325e Mon Sep 17 00:00:00 2001 From: Alexander Rodionov Date: Mon, 20 Mar 2023 16:35:48 +0300 Subject: [PATCH 13/30] wip --- .github/workflows/ci.yml | 96 ++++++++++++++++++++++++---------------- Makefile | 5 +-- pyproject.toml | 1 + 3 files changed, 61 insertions(+), 41 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 737f281..e2fece0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -3,46 +3,66 @@ name: ci on: push jobs: - test: - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - python_version: ['3.8', '3.9', '3.10'] + build: + runs-on: [self-hosted, gpu] steps: - uses: actions/checkout@v3 - - uses: actions/setup-python@v4 - with: - python-version: ${{ matrix.python_version }} - - name: install dependencies - run: python3 -m pip install .[dev] + - name: test gpu is available + run: nvidia-smi - - name: test - run: pytest --cov pitch_detectors --cov-fail-under=90 + - name: build image + run: make build - publish-to-pypi-and-github-release: - if: "startsWith(github.ref, 'refs/tags/')" - runs-on: ubuntu-latest - needs: test - steps: - - uses: actions/checkout@master - - name: Set up Python - uses: actions/setup-python@v4 - with: - python-version: '3.11' - - - name: Install pypa/build - run: python -m pip install --upgrade setuptools build twine - - - name: Build a source tarball and wheel - run: python -m build . - - - name: - env: - TWINE_USERNAME: __token__ - TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }} - run: python -m twine upload dist/* - - - name: Github Release - uses: softprops/action-gh-release@v1 + - name: test image + run: make test + + # - name: push image + # if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }} + # run: make push + + +# jobs: +# test: +# runs-on: ubuntu-latest +# strategy: +# fail-fast: false +# matrix: +# python_version: ['3.8', '3.9', '3.10'] +# steps: +# - uses: actions/checkout@v3 +# - uses: actions/setup-python@v4 +# with: +# python-version: ${{ matrix.python_version }} + +# - name: install dependencies +# run: python3 -m pip install .[dev] + +# - name: test +# run: pytest --cov pitch_detectors --cov-fail-under=90 + + # publish-to-pypi-and-github-release: + # if: "startsWith(github.ref, 'refs/tags/')" + # runs-on: ubuntu-latest + # needs: test + # steps: + # - uses: actions/checkout@master + # - name: Set up Python + # uses: actions/setup-python@v4 + # with: + # python-version: '3.11' + + # - name: Install pypa/build + # run: python -m pip install --upgrade setuptools build twine + + # - name: Build a source tarball and wheel + # run: python -m build . + + # - name: + # env: + # TWINE_USERNAME: __token__ + # TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }} + # run: python -m twine upload dist/* + + # - name: Github Release + # uses: softprops/action-gh-release@v1 diff --git a/Makefile b/Makefile index 8503ad0..20dc6be 100644 --- a/Makefile +++ b/Makefile @@ -6,8 +6,7 @@ build: .PHONY: push push: - # docker push tandav/pitch-detectors:cuda11.7.0-cudnn8-devel-ubuntu22.04 - docker push tandav/pitch-detectors:cuda11.7.0-cudnn8-devel-ubuntu22.04 + docker push tandav/pitch-detectors:11.8.0-cudnn8-devel-ubuntu22.04 docker push tandav/pitch-detectors:latest .PHONY: test @@ -17,4 +16,4 @@ test: build -v $$PWD/pitch_detectors:/app/pitch_detectors \ -v $$PWD/tests:/app/tests \ tandav/pitch-detectors:11.8.0-cudnn8-devel-ubuntu22.04 \ - pytest -v + pytest -v --cov pitch_detectors --cov-fail-under 90 diff --git a/pyproject.toml b/pyproject.toml index c7c7781..89cfaac 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -33,6 +33,7 @@ dev = [ "pre-commit", "pytest", "pytest-order", + "pytest-cov", ] [project.urls] From deab447f6621eeb6ee8c5dc3e6ad09aa221c6bcc Mon Sep 17 00:00:00 2001 From: Alexander Rodionov Date: Mon, 20 Mar 2023 16:37:54 +0300 Subject: [PATCH 14/30] wip --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 20dc6be..fb0e428 100644 --- a/Makefile +++ b/Makefile @@ -11,7 +11,7 @@ push: .PHONY: test test: build - docker run --rm -it --gpus all \ + docker run --rm -t --gpus all \ -e PITCH_DETECTORS_ERROR_GPU_NOT_AVAILABLE=true \ -v $$PWD/pitch_detectors:/app/pitch_detectors \ -v $$PWD/tests:/app/tests \ From f30bff430c0a53921c46430f8b21d5505910c057 Mon Sep 17 00:00:00 2001 From: Alexander Rodionov Date: Mon, 20 Mar 2023 16:57:03 +0300 Subject: [PATCH 15/30] wip --- .github/workflows/ci.yml | 5 ++++- Makefile | 11 ++++++++++- pitch_detectors/algorithms.py | 4 +--- tests/gpu_test.py | 4 ++++ 4 files changed, 19 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e2fece0..2bebb69 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,9 +14,12 @@ jobs: - name: build image run: make build - - name: test image + - name: test run: make test + - name: test-no-gpu + run: make test-no-gpu + # - name: push image # if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }} # run: make push diff --git a/Makefile b/Makefile index fb0e428..6dfd323 100644 --- a/Makefile +++ b/Makefile @@ -12,7 +12,16 @@ push: .PHONY: test test: build docker run --rm -t --gpus all \ - -e PITCH_DETECTORS_ERROR_GPU_NOT_AVAILABLE=true \ + -e PITCH_DETECTORS_GPU=true \ + -v $$PWD/pitch_detectors:/app/pitch_detectors \ + -v $$PWD/tests:/app/tests \ + tandav/pitch-detectors:11.8.0-cudnn8-devel-ubuntu22.04 \ + pytest -v --cov pitch_detectors --cov-fail-under 90 + +.PHONY: test-no-gpu +test-no-gpu: build + docker run --rm -t \ + -e PITCH_DETECTORS_GPU=false \ -v $$PWD/pitch_detectors:/app/pitch_detectors \ -v $$PWD/tests:/app/tests \ tandav/pitch-detectors:11.8.0-cudnn8-devel-ubuntu22.04 \ diff --git a/pitch_detectors/algorithms.py b/pitch_detectors/algorithms.py index ea8c3c6..3a3da20 100644 --- a/pitch_detectors/algorithms.py +++ b/pitch_detectors/algorithms.py @@ -18,7 +18,7 @@ def __init__(self, a: np.ndarray, fs: int, hz_min: float = 75, hz_max: float = 6 self.f0: tp.Optional[np.ndarray] = None self.t: tp.Optional[np.ndarray] = None if ( - os.environ.get('PITCH_DETECTORS_ERROR_GPU_NOT_AVAILABLE') and + os.environ.get('PITCH_DETECTORS_GPU') == 'true' and self.use_gpu and not self.gpu_available() ): @@ -128,8 +128,6 @@ def __init__( torch.device(device) super().__init__(a, fs, hz_min, hz_max) - if not torch.cuda.is_available(): - raise RuntimeError('TorchCrepe requires a GPU') f0, confidence = torchcrepe.predict( torch.from_numpy(a[np.newaxis, ...]), diff --git a/tests/gpu_test.py b/tests/gpu_test.py index 4b1729c..71814fe 100644 --- a/tests/gpu_test.py +++ b/tests/gpu_test.py @@ -1,3 +1,4 @@ +import os import subprocess import pytest @@ -6,15 +7,18 @@ @pytest.mark.order(0) +@pytest.mark.skipif(os.environ.get('PITCH_DETECTORS_GPU') == 'false', reason='gpu is not used') def test_nvidia_smi(): subprocess.check_call('nvidia-smi') @pytest.mark.order(1) +@pytest.mark.skipif(os.environ.get('PITCH_DETECTORS_GPU') == 'false', reason='gpu is not used') def test_tensorflow(): assert tf.config.experimental.list_physical_devices('GPU') @pytest.mark.order(2) +@pytest.mark.skipif(os.environ.get('PITCH_DETECTORS_GPU') == 'false', reason='gpu is not used') def test_pytorch(): assert torch.cuda.is_available() From 14ea6c428bf432341ec0af1b6714a756c1af6422 Mon Sep 17 00:00:00 2001 From: Alexander Rodionov Date: Mon, 20 Mar 2023 17:15:47 +0300 Subject: [PATCH 16/30] disable python cache for tests --- Makefile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Makefile b/Makefile index 6dfd323..dfefc40 100644 --- a/Makefile +++ b/Makefile @@ -12,6 +12,7 @@ push: .PHONY: test test: build docker run --rm -t --gpus all \ + -e PYTHONDONTWRITEBYTECODE=1 \ -e PITCH_DETECTORS_GPU=true \ -v $$PWD/pitch_detectors:/app/pitch_detectors \ -v $$PWD/tests:/app/tests \ @@ -21,6 +22,7 @@ test: build .PHONY: test-no-gpu test-no-gpu: build docker run --rm -t \ + -e PYTHONDONTWRITEBYTECODE=1 \ -e PITCH_DETECTORS_GPU=false \ -v $$PWD/pitch_detectors:/app/pitch_detectors \ -v $$PWD/tests:/app/tests \ From 44da33d5c767e971ceeb8564301e1c23505e77b8 Mon Sep 17 00:00:00 2001 From: Alexander Rodionov Date: Mon, 20 Mar 2023 17:34:54 +0300 Subject: [PATCH 17/30] fix typing --- pitch_detectors/algorithms.py | 9 ++++----- pitch_detectors/util.py | 5 ++--- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/pitch_detectors/algorithms.py b/pitch_detectors/algorithms.py index 3a3da20..7ed78c1 100644 --- a/pitch_detectors/algorithms.py +++ b/pitch_detectors/algorithms.py @@ -1,5 +1,4 @@ import os -import typing as tp import numpy as np @@ -15,8 +14,8 @@ def __init__(self, a: np.ndarray, fs: int, hz_min: float = 75, hz_max: float = 6 self.hz_min = hz_min self.hz_max = hz_max self.seconds = len(a) / fs - self.f0: tp.Optional[np.ndarray] = None - self.t: tp.Optional[np.ndarray] = None + self.f0: np.ndarray | None = None + self.t: np.ndarray | None = None if ( os.environ.get('PITCH_DETECTORS_GPU') == 'true' and self.use_gpu and @@ -24,7 +23,7 @@ def __init__(self, a: np.ndarray, fs: int, hz_min: float = 75, hz_max: float = 6 ): raise ConnectionError(f'gpu must be available for {self.name()} algorithm') - def dict(self) -> dict[str, list[tp.Optional[float]]]: + def dict(self) -> dict[str, list[float | None]]: if self.f0 is None: raise ValueError('f0 must be not None') if self.t is None: @@ -119,7 +118,7 @@ class TorchCrepe(TorchGPU, PitchDetector): def __init__( self, a: np.ndarray, fs: int, hz_min: float = 75, hz_max: float = 600, confidence_threshold: float = 0.8, batch_size: int = 2048, - device: tp.Optional[str] = None, + device: str | None = None, ): import torch import torchcrepe diff --git a/pitch_detectors/util.py b/pitch_detectors/util.py index f353def..4fd2276 100644 --- a/pitch_detectors/util.py +++ b/pitch_detectors/util.py @@ -1,10 +1,9 @@ import math -import typing as tp -def nan_to_none(x: list[float]) -> list[tp.Optional[float]]: +def nan_to_none(x: list[float]) -> list[float | None]: return [None if math.isnan(v) else v for v in x] -def none_to_nan(x: list[tp.Optional[float]]) -> list[float]: +def none_to_nan(x: list[float | None]) -> list[float]: return [float('nan') if v is None else v for v in x] From d302da6a19db1c08f3bcc6f457cd7b0858fb3918 Mon Sep 17 00:00:00 2001 From: Alexander Rodionov Date: Tue, 21 Mar 2023 10:39:21 +0300 Subject: [PATCH 18/30] wip --- Dockerfile | 6 ++++++ Makefile | 22 ++++++++++++++++------ pitch_detectors/evaluation.py | 16 ++++++++++++++++ pitch_detectors/util.py | 11 +++++++++++ pyproject.toml | 3 +++ tests/algorithms_test.py | 10 +++------- 6 files changed, 55 insertions(+), 13 deletions(-) create mode 100644 pitch_detectors/evaluation.py diff --git a/Dockerfile b/Dockerfile index 0d9e7f0..2d7a04f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -26,6 +26,12 @@ ENV PATH="$VIRTUAL_ENV/bin:$PATH" WORKDIR /app COPY pyproject.toml . + RUN --mount=type=cache,target=/root/.cache/pip \ pip install --upgrade pip setuptools wheel && \ pip install .[dev] + +COPY pitch_detectors /app/pitch_detectors +COPY tests /app/tests +RUN --mount=type=cache,target=/root/.cache/pip \ + pip install .[dev] diff --git a/Makefile b/Makefile index dfefc40..1db0493 100644 --- a/Makefile +++ b/Makefile @@ -12,19 +12,29 @@ push: .PHONY: test test: build docker run --rm -t --gpus all \ - -e PYTHONDONTWRITEBYTECODE=1 \ -e PITCH_DETECTORS_GPU=true \ - -v $$PWD/pitch_detectors:/app/pitch_detectors \ - -v $$PWD/tests:/app/tests \ tandav/pitch-detectors:11.8.0-cudnn8-devel-ubuntu22.04 \ pytest -v --cov pitch_detectors --cov-fail-under 90 + # -e PYTHONDONTWRITEBYTECODE=1 \ + # -v $$PWD/tests:/app/tests \ + # -v $$PWD/pitch_detectors:/app/pitch_detectors \ .PHONY: test-no-gpu test-no-gpu: build docker run --rm -t \ - -e PYTHONDONTWRITEBYTECODE=1 \ -e PITCH_DETECTORS_GPU=false \ - -v $$PWD/pitch_detectors:/app/pitch_detectors \ - -v $$PWD/tests:/app/tests \ tandav/pitch-detectors:11.8.0-cudnn8-devel-ubuntu22.04 \ pytest -v --cov pitch_detectors --cov-fail-under 90 + # -e PYTHONDONTWRITEBYTECODE=1 \ + # -v $$PWD/tests:/app/tests \ + # -v $$PWD/pitch_detectors:/app/pitch_detectors \ + +.PHONY: evaluation +evaluation: build + docker run --rm -t --gpus all \ + -e PYTHONDONTWRITEBYTECODE=1 \ + -e PITCH_DETECTORS_GPU=true \ + -v /home/tandav/Downloads/MIR-1K:/app/MIR-1K \ + tandav/pitch-detectors:11.8.0-cudnn8-devel-ubuntu22.04 \ + python pitch_detectors/evaluation.py + # -v $$PWD/pitch_detectors:/app/pitch_detectors \ diff --git a/pitch_detectors/evaluation.py b/pitch_detectors/evaluation.py new file mode 100644 index 0000000..9d8bf09 --- /dev/null +++ b/pitch_detectors/evaluation.py @@ -0,0 +1,16 @@ +from pathlib import Path + +from pitch_detectors.algorithms import ALGORITHMS + +MIR_1K_DIR = Path('MIR-1K') +WAV_DIR = MIR_1K_DIR / 'Wavfile' + + +def main(): + for wav_path in WAV_DIR.glob('*.wav'): + for algorithm in ALGORITHMS: + print(wav_path.name, algorithm) + + +if __name__ == '__main__': + main() diff --git a/pitch_detectors/util.py b/pitch_detectors/util.py index 4fd2276..5a8cbaa 100644 --- a/pitch_detectors/util.py +++ b/pitch_detectors/util.py @@ -1,4 +1,9 @@ import math +from pathlib import Path + +import numpy as np +from dsplib.scale import minmax_scaler +from scipy.io import wavfile def nan_to_none(x: list[float]) -> list[float | None]: @@ -7,3 +12,9 @@ def nan_to_none(x: list[float]) -> list[float | None]: def none_to_nan(x: list[float | None]) -> list[float]: return [float('nan') if v is None else v for v in x] + + +def load_wav(path: Path | str, rescale: float = 100000): + fs, a = wavfile.read(path) + a = minmax_scaler(a, a.min(), a.max(), -rescale, rescale).astype(np.float32) + return fs, a diff --git a/pyproject.toml b/pyproject.toml index 89cfaac..b47759e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -25,6 +25,7 @@ dependencies = [ "torch", "torch-yin", "torchcrepe", + # "tqdm", ] [project.optional-dependencies] @@ -34,6 +35,8 @@ dev = [ "pytest", "pytest-order", "pytest-cov", + # "mir_eval", + # "resampy", ] [project.urls] diff --git a/tests/algorithms_test.py b/tests/algorithms_test.py index 97426df..89e4305 100644 --- a/tests/algorithms_test.py +++ b/tests/algorithms_test.py @@ -3,9 +3,8 @@ import numpy as np import pytest -from dsplib.scale import minmax_scaler -from scipy.io import wavfile +from pitch_detectors import util from pitch_detectors.algorithms import ALGORITHMS @@ -16,11 +15,8 @@ class Record: @pytest.fixture -def record(rescale: float = 100000): - """audio and fs""" - fs, a = wavfile.read(Path(__file__).parent / 'data' / 'b1a5da49d564a7341e7e1327aa3f229a.wav') - a = minmax_scaler(a, a.min(), a.max(), -rescale, rescale).astype(np.float32) - assert a.dtype == np.float32 +def record(): + fs, a = util.load_wav(Path(__file__).parent / 'data' / 'b1a5da49d564a7341e7e1327aa3f229a.wav') return Record(a, fs) From ec5ce2ca51a2e64c040fefa4f3c3ffdc5d1ff84d Mon Sep 17 00:00:00 2001 From: Alexander Rodionov Date: Tue, 21 Mar 2023 10:46:54 +0300 Subject: [PATCH 19/30] add comment about python3.10 in README --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index af50c45..8556a11 100644 --- a/README.md +++ b/README.md @@ -34,3 +34,4 @@ from pitch_detectors ## additional features - robust (vote-based + median) averaging of pitch - json import/export +- all agorithms tested on python3.10, this is recommended python version to use From b16c557449faed6f9bed9d294725ebbce1fb4357 Mon Sep 17 00:00:00 2001 From: Alexander Rodionov Date: Tue, 21 Mar 2023 10:54:52 +0300 Subject: [PATCH 20/30] use image variable in Makefile --- .gitignore | 1 + Makefile | 22 +++++++--------------- 2 files changed, 8 insertions(+), 15 deletions(-) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..fa65608 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +*.ipynb diff --git a/Makefile b/Makefile index 1db0493..80b21a9 100644 --- a/Makefile +++ b/Makefile @@ -1,40 +1,32 @@ +IMAGE = tandav/pitch-detectors:11.8.0-cudnn8-devel-ubuntu22.04 + .PHONY: build build: - # https://pythonspeed.com/articles/docker-cache-pip-downloads/ - # DOCKER_BUILDKIT=1 docker build --progress=plain -t tandav/pitch-detectors:cuda11.7.0-cudnn8-devel-ubuntu22.04 . - DOCKER_BUILDKIT=1 docker build --progress=plain -t tandav/pitch-detectors:11.8.0-cudnn8-devel-ubuntu22.04 . + DOCKER_BUILDKIT=1 docker build --progress=plain -t $(IMAGE) . .PHONY: push push: - docker push tandav/pitch-detectors:11.8.0-cudnn8-devel-ubuntu22.04 + docker push $(IMAGE) docker push tandav/pitch-detectors:latest .PHONY: test test: build docker run --rm -t --gpus all \ -e PITCH_DETECTORS_GPU=true \ - tandav/pitch-detectors:11.8.0-cudnn8-devel-ubuntu22.04 \ + $(IMAGE) \ pytest -v --cov pitch_detectors --cov-fail-under 90 - # -e PYTHONDONTWRITEBYTECODE=1 \ - # -v $$PWD/tests:/app/tests \ - # -v $$PWD/pitch_detectors:/app/pitch_detectors \ .PHONY: test-no-gpu test-no-gpu: build docker run --rm -t \ -e PITCH_DETECTORS_GPU=false \ - tandav/pitch-detectors:11.8.0-cudnn8-devel-ubuntu22.04 \ + $(IMAGE) \ pytest -v --cov pitch_detectors --cov-fail-under 90 - # -e PYTHONDONTWRITEBYTECODE=1 \ - # -v $$PWD/tests:/app/tests \ - # -v $$PWD/pitch_detectors:/app/pitch_detectors \ .PHONY: evaluation evaluation: build docker run --rm -t --gpus all \ - -e PYTHONDONTWRITEBYTECODE=1 \ -e PITCH_DETECTORS_GPU=true \ -v /home/tandav/Downloads/MIR-1K:/app/MIR-1K \ - tandav/pitch-detectors:11.8.0-cudnn8-devel-ubuntu22.04 \ + $(IMAGE) \ python pitch_detectors/evaluation.py - # -v $$PWD/pitch_detectors:/app/pitch_detectors \ From 4fe6a314bfd07af8f3e4dfc4467a482c13a96860 Mon Sep 17 00:00:00 2001 From: Alexander Rodionov Date: Tue, 21 Mar 2023 12:35:19 +0300 Subject: [PATCH 21/30] evaluation wip --- pitch_detectors/evaluation.py | 77 +++++++++++++++++++++++++++++++++-- pyproject.toml | 5 +-- 2 files changed, 75 insertions(+), 7 deletions(-) diff --git a/pitch_detectors/evaluation.py b/pitch_detectors/evaluation.py index 9d8bf09..918c826 100644 --- a/pitch_detectors/evaluation.py +++ b/pitch_detectors/evaluation.py @@ -1,15 +1,84 @@ from pathlib import Path -from pitch_detectors.algorithms import ALGORITHMS +import mir_eval +import numpy as np +from dsplib.scale import minmax_scaler +from scipy.io import wavfile + +from pitch_detectors import algorithms MIR_1K_DIR = Path('MIR-1K') WAV_DIR = MIR_1K_DIR / 'Wavfile' +def midi_to_freq(midi_n, ref_frequency=440.0): + if (midi_n == 0): + return 0 + else: + return ref_frequency * 2**((midi_n - 69) / 12) + + +def load_f0_true(wav_path: Path, seconds: float): + pitch_label_dir = wav_path.parent.parent / 'PitchLabel' + f0_path = (pitch_label_dir / wav_path.stem).with_suffix('.pv') + f0 = [] + with open(f0_path) as f: + for line in f: + line = line.strip() + if line == '0': + f0.append(float('nan')) + else: + f0.append(midi_to_freq(float(line))) # todoo fix + f0 = np.array(f0) + t = np.arange(0.02, seconds - 0.02, 0.02) + assert t.shape == f0.shape + return t, f0 + + +def resample_f0( + pitch: algorithms.PitchDetector, + t_resampled: np.ndarray, +): + f0_resampled = np.full_like(t_resampled, fill_value=np.nan) + notna_slices = np.ma.clump_unmasked(np.ma.masked_invalid(pitch.f0)) + for sl in notna_slices: + t_slice = pitch.t[sl] + f0_slice = pitch.f0[sl] + t_start, t_stop = t_slice[0], t_slice[-1] + mask = (t_start < t_resampled) & (t_resampled < t_stop) + t_interp = t_resampled[mask] + f0_interp = np.interp(t_interp, t_slice, f0_slice) + f0_resampled[mask] = f0_interp + return f0_resampled + + +def raw_pitch_accuracy( + ref_f0, + est_f0, + cent_tolerance=50, +): + ref_voicing = np.isfinite(ref_f0) + est_voicing = np.isfinite(est_f0) + ref_cent = mir_eval.melody.hz2cents(ref_f0) + est_cent = mir_eval.melody.hz2cents(est_f0) + return mir_eval.melody.raw_pitch_accuracy(ref_voicing, ref_cent, est_voicing, est_cent, cent_tolerance) + + def main(): - for wav_path in WAV_DIR.glob('*.wav'): - for algorithm in ALGORITHMS: - print(wav_path.name, algorithm) + with open(MIR_1K_DIR / 'evaluation.csv', 'w') as f: + for wav_path in sorted(WAV_DIR.glob('*.wav')): + fs, a = wavfile.read(wav_path) + seconds = len(a) / fs + a = a[:, 1].astype(np.float32) + rescale = 100000 + a = minmax_scaler(a, a.min(), a.max(), -rescale, rescale).astype(np.float32) + t_true, f0_true = load_f0_true(wav_path, seconds) + for algorithm in algorithms.ALGORITHMS: + pitch = algorithm(a, fs) + f0 = resample_f0(pitch, t_resampled=t_true) + score = raw_pitch_accuracy(f0_true, f0) + print(wav_path.name, algorithm.name(), score, sep=',') + print(wav_path.name, algorithm.name(), score, sep=',', file=f) if __name__ == '__main__': diff --git a/pyproject.toml b/pyproject.toml index b47759e..acac62d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -25,7 +25,6 @@ dependencies = [ "torch", "torch-yin", "torchcrepe", - # "tqdm", ] [project.optional-dependencies] @@ -35,8 +34,8 @@ dev = [ "pytest", "pytest-order", "pytest-cov", - # "mir_eval", - # "resampy", + "mir_eval", + "resampy", ] [project.urls] From c6faec6ddb8f2f62a0b91010bb0a864bbf68ff4f Mon Sep 17 00:00:00 2001 From: Alexander Rodionov Date: Tue, 21 Mar 2023 13:07:33 +0300 Subject: [PATCH 22/30] [WIP] evaluation --- pitch_detectors/algorithms.py | 5 +++-- pitch_detectors/evaluation.py | 9 +++++---- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/pitch_detectors/algorithms.py b/pitch_detectors/algorithms.py index 7ed78c1..ec564cb 100644 --- a/pitch_detectors/algorithms.py +++ b/pitch_detectors/algorithms.py @@ -212,8 +212,9 @@ def __init__( model = hub.load(spice_model_path) model_output = model.signatures['serving_default'](tf.constant(a, tf.float32)) confidence = 1.0 - model_output['uncertainty'] - f0 = self.output2hz(model_output['pitch'].numpy()) - f0[confidence < confidence_threshold] = np.nan + self.f0 = self.output2hz(model_output['pitch'].numpy()) + self.f0[confidence < confidence_threshold] = np.nan + self.t = np.linspace(0, self.seconds, self.f0.shape[0]) def output2hz( self, diff --git a/pitch_detectors/evaluation.py b/pitch_detectors/evaluation.py index 918c826..62e87c0 100644 --- a/pitch_detectors/evaluation.py +++ b/pitch_detectors/evaluation.py @@ -30,8 +30,9 @@ def load_f0_true(wav_path: Path, seconds: float): else: f0.append(midi_to_freq(float(line))) # todoo fix f0 = np.array(f0) - t = np.arange(0.02, seconds - 0.02, 0.02) - assert t.shape == f0.shape + # t = np.arange(0.02, seconds - 0.02, 0.02) + # assert t.shape == f0.shape + t = np.linspace(0.02, seconds, len(f0)) return t, f0 @@ -77,8 +78,8 @@ def main(): pitch = algorithm(a, fs) f0 = resample_f0(pitch, t_resampled=t_true) score = raw_pitch_accuracy(f0_true, f0) - print(wav_path.name, algorithm.name(), score, sep=',') - print(wav_path.name, algorithm.name(), score, sep=',', file=f) + print(wav_path.stem, algorithm.name(), score, sep=',') + print(wav_path.stem, algorithm.name(), score, sep=',', file=f) if __name__ == '__main__': From c387724cfbf5061bd6d46a457e3df1332a412dd9 Mon Sep 17 00:00:00 2001 From: Alexander Rodionov Date: Tue, 21 Mar 2023 13:23:06 +0300 Subject: [PATCH 23/30] add tqdm progressbars --- pitch_detectors/evaluation.py | 10 ++++++---- pyproject.toml | 1 + 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/pitch_detectors/evaluation.py b/pitch_detectors/evaluation.py index 62e87c0..c1188ac 100644 --- a/pitch_detectors/evaluation.py +++ b/pitch_detectors/evaluation.py @@ -1,5 +1,5 @@ from pathlib import Path - +import tqdm import mir_eval import numpy as np from dsplib.scale import minmax_scaler @@ -67,18 +67,20 @@ def raw_pitch_accuracy( def main(): with open(MIR_1K_DIR / 'evaluation.csv', 'w') as f: - for wav_path in sorted(WAV_DIR.glob('*.wav')): + t = tqdm.tqdm(sorted(WAV_DIR.glob('*.wav'))) + for wav_path in t: fs, a = wavfile.read(wav_path) seconds = len(a) / fs a = a[:, 1].astype(np.float32) rescale = 100000 a = minmax_scaler(a, a.min(), a.max(), -rescale, rescale).astype(np.float32) t_true, f0_true = load_f0_true(wav_path, seconds) - for algorithm in algorithms.ALGORITHMS: + for algorithm in tqdm.tqdm(algorithms.ALGORITHMS, leave=False): pitch = algorithm(a, fs) f0 = resample_f0(pitch, t_resampled=t_true) score = raw_pitch_accuracy(f0_true, f0) - print(wav_path.stem, algorithm.name(), score, sep=',') + # print(wav_path.stem, algorithm.name(), score, sep=',') + t.set_description(f'{wav_path.stem} {algorithm.name()} {score}') print(wav_path.stem, algorithm.name(), score, sep=',', file=f) diff --git a/pyproject.toml b/pyproject.toml index acac62d..c2e0b87 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -35,6 +35,7 @@ dev = [ "pytest-order", "pytest-cov", "mir_eval", + "tqdm", "resampy", ] From ad60077c1fc40425c95ee84e1fd779b26b092357 Mon Sep 17 00:00:00 2001 From: Alexander Rodionov Date: Tue, 21 Mar 2023 19:45:52 +0300 Subject: [PATCH 24/30] write evaluation metrics to redis --- Makefile | 5 ++++ pitch_detectors/evaluation.py | 46 +++++++++++++++++++++-------------- pyproject.toml | 3 +++ 3 files changed, 36 insertions(+), 18 deletions(-) diff --git a/Makefile b/Makefile index 80b21a9..1369e41 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,8 @@ IMAGE = tandav/pitch-detectors:11.8.0-cudnn8-devel-ubuntu22.04 +include .env +export + .PHONY: build build: DOCKER_BUILDKIT=1 docker build --progress=plain -t $(IMAGE) . @@ -27,6 +30,8 @@ test-no-gpu: build evaluation: build docker run --rm -t --gpus all \ -e PITCH_DETECTORS_GPU=true \ + -e REDIS_URL=$$REDIS_URL \ -v /home/tandav/Downloads/MIR-1K:/app/MIR-1K \ $(IMAGE) \ python pitch_detectors/evaluation.py + # env diff --git a/pitch_detectors/evaluation.py b/pitch_detectors/evaluation.py index c1188ac..1e0ba64 100644 --- a/pitch_detectors/evaluation.py +++ b/pitch_detectors/evaluation.py @@ -1,8 +1,13 @@ +import os +import time from pathlib import Path -import tqdm + import mir_eval import numpy as np +import tqdm from dsplib.scale import minmax_scaler +from musiclib.pitch import Pitch +from redis import Redis from scipy.io import wavfile from pitch_detectors import algorithms @@ -19,6 +24,7 @@ def midi_to_freq(midi_n, ref_frequency=440.0): def load_f0_true(wav_path: Path, seconds: float): + p = Pitch() pitch_label_dir = wav_path.parent.parent / 'PitchLabel' f0_path = (pitch_label_dir / wav_path.stem).with_suffix('.pv') f0 = [] @@ -28,7 +34,7 @@ def load_f0_true(wav_path: Path, seconds: float): if line == '0': f0.append(float('nan')) else: - f0.append(midi_to_freq(float(line))) # todoo fix + f0.append(p.note_i_to_hz(float(line))) f0 = np.array(f0) # t = np.arange(0.02, seconds - 0.02, 0.02) # assert t.shape == f0.shape @@ -66,22 +72,26 @@ def raw_pitch_accuracy( def main(): - with open(MIR_1K_DIR / 'evaluation.csv', 'w') as f: - t = tqdm.tqdm(sorted(WAV_DIR.glob('*.wav'))) - for wav_path in t: - fs, a = wavfile.read(wav_path) - seconds = len(a) / fs - a = a[:, 1].astype(np.float32) - rescale = 100000 - a = minmax_scaler(a, a.min(), a.max(), -rescale, rescale).astype(np.float32) - t_true, f0_true = load_f0_true(wav_path, seconds) - for algorithm in tqdm.tqdm(algorithms.ALGORITHMS, leave=False): - pitch = algorithm(a, fs) - f0 = resample_f0(pitch, t_resampled=t_true) - score = raw_pitch_accuracy(f0_true, f0) - # print(wav_path.stem, algorithm.name(), score, sep=',') - t.set_description(f'{wav_path.stem} {algorithm.name()} {score}') - print(wav_path.stem, algorithm.name(), score, sep=',', file=f) + redis = Redis.from_url(os.environ['REDIS_URL'], decode_responses=True) + t = tqdm.tqdm(sorted(WAV_DIR.glob('*.wav'))) + for wav_path in t: + fs, a = wavfile.read(wav_path) + seconds = len(a) / fs + a = a[:, 1].astype(np.float32) + rescale = 100000 + a = minmax_scaler(a, a.min(), a.max(), -rescale, rescale).astype(np.float32) + t_true, f0_true = load_f0_true(wav_path, seconds) + for algorithm in tqdm.tqdm(algorithms.ALGORITHMS, leave=False): + pitch = algorithm(a, fs) + f0 = resample_f0(pitch, t_resampled=t_true) + score = raw_pitch_accuracy(f0_true, f0) + t.set_description(f'{wav_path.stem} {algorithm.name()} {score}') + redis.hset( + f'pitch_detectors:evaluation:{algorithm.name()}:{wav_path.stem}', mapping={ + 'raw_pitch_accuracy': score, + 'timestamp': int(time.time() * 1000), + }, + ) if __name__ == '__main__': diff --git a/pyproject.toml b/pyproject.toml index c2e0b87..262b9c9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -37,6 +37,9 @@ dev = [ "mir_eval", "tqdm", "resampy", + "redis", + "musiclib", + "python-dotenv", ] [project.urls] From 1145bcf2c786736f21b41137da27222e63d6d50f Mon Sep 17 00:00:00 2001 From: Alexander Rodionov Date: Tue, 21 Mar 2023 21:22:09 +0300 Subject: [PATCH 25/30] update evaluation --- pitch_detectors/evaluation.py | 60 ++++++++++++++++++++++++----------- 1 file changed, 41 insertions(+), 19 deletions(-) diff --git a/pitch_detectors/evaluation.py b/pitch_detectors/evaluation.py index 1e0ba64..fa45579 100644 --- a/pitch_detectors/evaluation.py +++ b/pitch_detectors/evaluation.py @@ -1,3 +1,4 @@ +import argparse import os import time from pathlib import Path @@ -71,28 +72,49 @@ def raw_pitch_accuracy( return mir_eval.melody.raw_pitch_accuracy(ref_voicing, ref_cent, est_voicing, est_cent, cent_tolerance) -def main(): - redis = Redis.from_url(os.environ['REDIS_URL'], decode_responses=True) +def evaluate_one( + redis: Redis, + algorithm: algorithms.PitchDetector, + wav_path: Path, +): + key = f'pitch_detectors:evaluation:{algorithm.name()}:{wav_path.stem}' + if redis.exists(key): + return key + fs, a = wavfile.read(wav_path) + seconds = len(a) / fs + a = a[:, 1].astype(np.float32) + rescale = 100000 + a = minmax_scaler(a, a.min(), a.max(), -rescale, rescale).astype(np.float32) + t_true, f0_true = load_f0_true(wav_path, seconds) + pitch = algorithm(a, fs) + f0 = resample_f0(pitch, t_resampled=t_true) + score = raw_pitch_accuracy(f0_true, f0) + redis.hset( + key, mapping={ + 'raw_pitch_accuracy': score, + 'timestamp': int(time.time() * 1000), + }, + ) + return key + + +def evaluate_all(redis: Redis): t = tqdm.tqdm(sorted(WAV_DIR.glob('*.wav'))) for wav_path in t: - fs, a = wavfile.read(wav_path) - seconds = len(a) / fs - a = a[:, 1].astype(np.float32) - rescale = 100000 - a = minmax_scaler(a, a.min(), a.max(), -rescale, rescale).astype(np.float32) - t_true, f0_true = load_f0_true(wav_path, seconds) for algorithm in tqdm.tqdm(algorithms.ALGORITHMS, leave=False): - pitch = algorithm(a, fs) - f0 = resample_f0(pitch, t_resampled=t_true) - score = raw_pitch_accuracy(f0_true, f0) - t.set_description(f'{wav_path.stem} {algorithm.name()} {score}') - redis.hset( - f'pitch_detectors:evaluation:{algorithm.name()}:{wav_path.stem}', mapping={ - 'raw_pitch_accuracy': score, - 'timestamp': int(time.time() * 1000), - }, - ) + key = evaluate_one(redis, algorithm, wav_path) + t.set_description(key) if __name__ == '__main__': - main() + parser = argparse.ArgumentParser() + parser.add_argument('--algorithm', type=str) + parser.add_argument('--file', type=str) + args = parser.parse_args() + if (args.algorithm is None) ^ (args.file is None): + raise ValueError('you must specify both algorithm and file or neither') + redis = Redis.from_url(os.environ['REDIS_URL'], decode_responses=True) + if args.algorithm is not None and args.file is not None: + evaluate_one(redis, algorithm=getattr(algorithms, args.algorithm), wav_path=WAV_DIR / args.file) + raise SystemExit(0) + evaluate_all(redis) From bbbb3c001694714a03a7562777de8f9da7f239ee Mon Sep 17 00:00:00 2001 From: Alexander Rodionov Date: Tue, 21 Mar 2023 22:41:29 +0300 Subject: [PATCH 26/30] wip --- .github/workflows/ci.yml | 83 ++++++++---------- Dockerfile | 7 +- Makefile | 5 +- README.md | 52 ++++++----- data/b1a5da49d564a7341e7e1327aa3f229a.png | Bin 0 -> 22240 bytes .../b1a5da49d564a7341e7e1327aa3f229a.wav | Bin tests/algorithms_test.py | 2 +- 7 files changed, 75 insertions(+), 74 deletions(-) create mode 100644 data/b1a5da49d564a7341e7e1327aa3f229a.png rename {tests/data => data}/b1a5da49d564a7341e7e1327aa3f229a.wav (100%) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2bebb69..58647cc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -20,52 +20,43 @@ jobs: - name: test-no-gpu run: make test-no-gpu - # - name: push image - # if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }} - # run: make push - - -# jobs: -# test: -# runs-on: ubuntu-latest -# strategy: -# fail-fast: false -# matrix: -# python_version: ['3.8', '3.9', '3.10'] -# steps: -# - uses: actions/checkout@v3 -# - uses: actions/setup-python@v4 -# with: -# python-version: ${{ matrix.python_version }} - -# - name: install dependencies -# run: python3 -m pip install .[dev] - -# - name: test -# run: pytest --cov pitch_detectors --cov-fail-under=90 - - # publish-to-pypi-and-github-release: - # if: "startsWith(github.ref, 'refs/tags/')" - # runs-on: ubuntu-latest - # needs: test - # steps: - # - uses: actions/checkout@master - # - name: Set up Python - # uses: actions/setup-python@v4 - # with: - # python-version: '3.11' - - # - name: Install pypa/build - # run: python -m pip install --upgrade setuptools build twine +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-python@v4 + with: + python-version: '3.10' - # - name: Build a source tarball and wheel - # run: python -m build . + - name: install dependencies + run: python3 -m pip install .[dev] - # - name: - # env: - # TWINE_USERNAME: __token__ - # TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }} - # run: python -m twine upload dist/* + - name: test + run: pytest --cov pitch_detectors - # - name: Github Release - # uses: softprops/action-gh-release@v1 + publish-to-pypi-and-github-release: + if: "startsWith(github.ref, 'refs/tags/')" + runs-on: ubuntu-latest + needs: test + steps: + - uses: actions/checkout@master + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: '3.11' + + - name: Install pypa/build + run: python -m pip install --upgrade setuptools build twine + + - name: Build a source tarball and wheel + run: python -m build . + + - name: + env: + TWINE_USERNAME: __token__ + TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }} + run: python -m twine upload dist/* + + - name: Github Release + uses: softprops/action-gh-release@v1 diff --git a/Dockerfile b/Dockerfile index 2d7a04f..8d8b8ff 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,8 +4,6 @@ RUN curl -L https://tfhub.dev/google/spice/2?tf-hub-format=compressed --output s tar xvf spice_2.tar.gz --directory /spice_model && \ rm spice_2.tar.gz -# FROM nvidia/cuda:11.7.0-cudnn8-devel-ubuntu22.04 - SUCCESS -# FROM nvidia/cuda:12.0.1-cudnn8-devel-ubuntu22.04 - FAIL FROM nvidia/cuda:11.8.0-cudnn8-devel-ubuntu22.04 # https://github.com/NVIDIA/nvidia-docker/wiki/Usage @@ -33,5 +31,6 @@ RUN --mount=type=cache,target=/root/.cache/pip \ COPY pitch_detectors /app/pitch_detectors COPY tests /app/tests -RUN --mount=type=cache,target=/root/.cache/pip \ - pip install .[dev] +COPY data /app/data +# RUN --mount=type=cache,target=/root/.cache/pip \ +# pip install .[dev] diff --git a/Makefile b/Makefile index 1369e41..28720e0 100644 --- a/Makefile +++ b/Makefile @@ -17,14 +17,14 @@ test: build docker run --rm -t --gpus all \ -e PITCH_DETECTORS_GPU=true \ $(IMAGE) \ - pytest -v --cov pitch_detectors --cov-fail-under 90 + pytest -v --cov pitch_detectors .PHONY: test-no-gpu test-no-gpu: build docker run --rm -t \ -e PITCH_DETECTORS_GPU=false \ $(IMAGE) \ - pytest -v --cov pitch_detectors --cov-fail-under 90 + pytest -v --cov pitch_detectors .PHONY: evaluation evaluation: build @@ -34,4 +34,3 @@ evaluation: build -v /home/tandav/Downloads/MIR-1K:/app/MIR-1K \ $(IMAGE) \ python pitch_detectors/evaluation.py - # env diff --git a/README.md b/README.md index 8556a11..0aefec2 100644 --- a/README.md +++ b/README.md @@ -2,25 +2,28 @@ collection of pitch detection algorithms with unified interface ## list of algorithms + +| algorithm | cpu | gpu | accuracy [1] | +|------------------------------------------------------------------------------------------------------------|-----|-----|--------------| +| [PraatAC](https://parselmouth.readthedocs.io/en/stable/api_reference.html#parselmouth.Sound.to_pitch_ac) | ✓ | | 0.880 | +| [PraatCC](https://parselmouth.readthedocs.io/en/stable/api_reference.html#parselmouth.Sound.to_pitch_cc) | ✓ | | 0.893 | +| [PraatSHS](https://parselmouth.readthedocs.io/en/stable/api_reference.html#parselmouth.Sound.to_pitch_shs) | ✓ | | 0.618 | +| [Pyin](https://librosa.org/doc/latest/generated/librosa.pyin.html) | ✓ | | 0.886 | +| [Reaper](https://github.com/r9y9/pyreaper) | ✓ | | 0.826 | +| [Yaapt](http://bjbschmitt.github.io/AMFM_decompy/pYAAPT.html#amfm_decompy.pYAAPT.yaapt) | ✓ | | 0.759 | +| [World](https://github.com/JeremyCCHsu/Python-Wrapper-for-World-Vocoder) | ✓ | | 0.873 | +| [TorchYin](https://github.com/brentspell/torch-yin) | ✓ | | 0.886 | +| [Rapt](https://pysptk.readthedocs.io/en/stable/generated/pysptk.sptk.rapt.html) | ✓ | | 0.859 | +| [Swipe](https://pysptk.readthedocs.io/en/stable/generated/pysptk.sptk.swipe.html) | ✓ | | 0.871 | +| [Crepe](https://github.com/marl/crepe) | ✓ | ✓ | 0.802 | +| [TorchCrepe](https://github.com/maxrmorrison/torchcrepe) | ✓ | ✓ | 0.817 | +| [Spice](https://ai.googleblog.com/2019/11/spice-self-supervised-pitch-estimation.html) | ✓ | ✓ | 0.908 | + -| algorithm | cpu | gpu | -|------------|-----|-----| -| [PraatAC](https://parselmouth.readthedocs.io/en/stable/api_reference.html#parselmouth.Sound.to_pitch_ac) | ✓ | | -| [PraatCC](https://parselmouth.readthedocs.io/en/stable/api_reference.html#parselmouth.Sound.to_pitch_cc) | ✓ | | -| [PraatSHS](https://parselmouth.readthedocs.io/en/stable/api_reference.html#parselmouth.Sound.to_pitch_shs) | ✓ | | -| [Pyin](https://librosa.org/doc/latest/generated/librosa.pyin.html) | ✓ | | -| [Reaper](https://github.com/r9y9/pyreaper) | ✓ | | -| [Yaapt](http://bjbschmitt.github.io/AMFM_decompy/pYAAPT.html#amfm_decompy.pYAAPT.yaapt) | ✓ | | -| [World](https://github.com/JeremyCCHsu/Python-Wrapper-for-World-Vocoder) | ✓ | | -| [TorchYin](https://github.com/brentspell/torch-yin) | ✓ | | -| [Rapt](https://pysptk.readthedocs.io/en/stable/generated/pysptk.sptk.rapt.html) | ✓ | | -| [Swipe](https://pysptk.readthedocs.io/en/stable/generated/pysptk.sptk.swipe.html) | ✓ | | -| [Crepe](https://github.com/marl/crepe) | ✓ | ✓ | -| [TorchCrepe](https://github.com/maxrmorrison/torchcrepe) | ✓ | ✓ | -| [Spice](https://ai.googleblog.com/2019/11/spice-self-supervised-pitch-estimation.html) | ✓ | ✓ | - +- [1] accuracy is mean [raw pitch accuracy](http://craffel.github.io/mir_eval/#mir_eval.melody.raw_pitch_accuracy) on 1000 samples of [MIR-1K](https://www.kaggle.com/datasets/datongmuyuyi/mir1k) dataset ## install +all agorithms tested on python3.10, this is recommended python version to use ```bash pip install pitch-detectors ``` @@ -28,10 +31,19 @@ pip install pitch-detectors ## usage ```python -from pitch_detectors +from scipy.io import wavfile +from pitch_detectors import algorithms +import matplotlib.pyplot as plt + +fs, a = wavfile.read('data/b1a5da49d564a7341e7e1327aa3f229a.wav') +pitch = algorithms.Crepe(a, fs) +plt.plot(pitch.t, pitch.f0) +plt.show() ``` +![Alt text](data/b1a5da49d564a7341e7e1327aa3f229a.png) + + ## additional features -- robust (vote-based + median) averaging of pitch -- json import/export -- all agorithms tested on python3.10, this is recommended python version to use +- [ ] robust (vote-based + median) averaging of pitch +- [ ] json import/export diff --git a/data/b1a5da49d564a7341e7e1327aa3f229a.png b/data/b1a5da49d564a7341e7e1327aa3f229a.png new file mode 100644 index 0000000000000000000000000000000000000000..632d896d566794fe678c5519f4eaf80fbe683377 GIT binary patch literal 22240 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 Kpz1}DU;YlUdAt$; literal 0 HcmV?d00001 diff --git a/tests/data/b1a5da49d564a7341e7e1327aa3f229a.wav b/data/b1a5da49d564a7341e7e1327aa3f229a.wav similarity index 100% rename from tests/data/b1a5da49d564a7341e7e1327aa3f229a.wav rename to data/b1a5da49d564a7341e7e1327aa3f229a.wav diff --git a/tests/algorithms_test.py b/tests/algorithms_test.py index 89e4305..f45cd6f 100644 --- a/tests/algorithms_test.py +++ b/tests/algorithms_test.py @@ -16,7 +16,7 @@ class Record: @pytest.fixture def record(): - fs, a = util.load_wav(Path(__file__).parent / 'data' / 'b1a5da49d564a7341e7e1327aa3f229a.wav') + fs, a = util.load_wav(Path(__file__).parent.parent / 'data' / 'b1a5da49d564a7341e7e1327aa3f229a.wav') return Record(a, fs) From c8b5cb1cc131dfa9d7f1ad9debc61228f49481fb Mon Sep 17 00:00:00 2001 From: Alexander Rodionov Date: Tue, 21 Mar 2023 22:42:32 +0300 Subject: [PATCH 27/30] ci --- .github/workflows/ci.yml | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 58647cc..a207d87 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -20,21 +20,6 @@ jobs: - name: test-no-gpu run: make test-no-gpu -jobs: - test: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - uses: actions/setup-python@v4 - with: - python-version: '3.10' - - - name: install dependencies - run: python3 -m pip install .[dev] - - - name: test - run: pytest --cov pitch_detectors - publish-to-pypi-and-github-release: if: "startsWith(github.ref, 'refs/tags/')" runs-on: ubuntu-latest From d8e2bc6676fc4d0d53cc70c425e5329574ec75d9 Mon Sep 17 00:00:00 2001 From: Alexander Rodionov Date: Tue, 21 Mar 2023 22:43:07 +0300 Subject: [PATCH 28/30] _ --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a207d87..11f43dd 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -3,7 +3,7 @@ name: ci on: push jobs: - build: + test: runs-on: [self-hosted, gpu] steps: - uses: actions/checkout@v3 From 8d2bca7e671f9cfc892c138f600f15e60e455fac Mon Sep 17 00:00:00 2001 From: Alexander Rodionov Date: Tue, 21 Mar 2023 23:00:17 +0300 Subject: [PATCH 29/30] wip --- Dockerfile | 10 ++++------ Makefile | 4 +--- pyproject.toml | 3 +++ 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/Dockerfile b/Dockerfile index 8d8b8ff..3d842d5 100644 --- a/Dockerfile +++ b/Dockerfile @@ -24,13 +24,11 @@ ENV PATH="$VIRTUAL_ENV/bin:$PATH" WORKDIR /app COPY pyproject.toml . +COPY README.md . +COPY pitch_detectors /app/pitch_detectors +COPY tests /app/tests +COPY data /app/data RUN --mount=type=cache,target=/root/.cache/pip \ pip install --upgrade pip setuptools wheel && \ pip install .[dev] - -COPY pitch_detectors /app/pitch_detectors -COPY tests /app/tests -COPY data /app/data -# RUN --mount=type=cache,target=/root/.cache/pip \ -# pip install .[dev] diff --git a/Makefile b/Makefile index 28720e0..a247d51 100644 --- a/Makefile +++ b/Makefile @@ -1,8 +1,5 @@ IMAGE = tandav/pitch-detectors:11.8.0-cudnn8-devel-ubuntu22.04 -include .env -export - .PHONY: build build: DOCKER_BUILDKIT=1 docker build --progress=plain -t $(IMAGE) . @@ -28,6 +25,7 @@ test-no-gpu: build .PHONY: evaluation evaluation: build + eval "$$(cat .env)"; \ docker run --rm -t --gpus all \ -e PITCH_DETECTORS_GPU=true \ -e REDIS_URL=$$REDIS_URL \ diff --git a/pyproject.toml b/pyproject.toml index 262b9c9..f662a62 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -54,6 +54,9 @@ issues = "https://github.com/tandav/pitch-detectors/issues" requires = ["setuptools"] build-backend = "setuptools.build_meta" +[tool.setuptools] +packages = ["pitch_detectors"] + # ============================================================================== [tool.bumpver] From 7fa4eaddf7e8b42861caf575774f0d434f770f60 Mon Sep 17 00:00:00 2001 From: Alexander Rodionov Date: Tue, 21 Mar 2023 23:29:49 +0300 Subject: [PATCH 30/30] fix pre-commit --- .pre-commit-config.yaml | 1 + pitch_detectors/algorithms.py | 10 +++------ pitch_detectors/evaluation.py | 40 +++++++++++++++-------------------- pitch_detectors/util.py | 2 +- pyproject.toml | 6 +++++- 5 files changed, 27 insertions(+), 32 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 683c5a6..86b32fa 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -56,6 +56,7 @@ repos: rev: v0.982 hooks: - id: mypy + additional_dependencies: [types-redis] - repo: https://github.com/RobertCraigie/pyright-python rev: v1.1.299 diff --git a/pitch_detectors/algorithms.py b/pitch_detectors/algorithms.py index ec564cb..d010b66 100644 --- a/pitch_detectors/algorithms.py +++ b/pitch_detectors/algorithms.py @@ -14,8 +14,8 @@ def __init__(self, a: np.ndarray, fs: int, hz_min: float = 75, hz_max: float = 6 self.hz_min = hz_min self.hz_max = hz_max self.seconds = len(a) / fs - self.f0: np.ndarray | None = None - self.t: np.ndarray | None = None + self.f0: np.ndarray + self.t: np.ndarray if ( os.environ.get('PITCH_DETECTORS_GPU') == 'true' and self.use_gpu and @@ -24,10 +24,6 @@ def __init__(self, a: np.ndarray, fs: int, hz_min: float = 75, hz_max: float = 6 raise ConnectionError(f'gpu must be available for {self.name()} algorithm') def dict(self) -> dict[str, list[float | None]]: - if self.f0 is None: - raise ValueError('f0 must be not None') - if self.t is None: - raise ValueError('t must be not None') return {'f0': util.nan_to_none(self.f0.tolist()), 't': self.t.tolist()} @classmethod @@ -51,7 +47,7 @@ class TorchGPU: def gpu_available(self) -> bool: import torch - return torch.cuda.is_available() + return torch.cuda.is_available() # type: ignore class PraatAC(PitchDetector): diff --git a/pitch_detectors/evaluation.py b/pitch_detectors/evaluation.py index fa45579..cc81bca 100644 --- a/pitch_detectors/evaluation.py +++ b/pitch_detectors/evaluation.py @@ -17,21 +17,14 @@ WAV_DIR = MIR_1K_DIR / 'Wavfile' -def midi_to_freq(midi_n, ref_frequency=440.0): - if (midi_n == 0): - return 0 - else: - return ref_frequency * 2**((midi_n - 69) / 12) - - -def load_f0_true(wav_path: Path, seconds: float): +def load_f0_true(wav_path: Path, seconds: float) -> tuple[np.ndarray, np.ndarray]: p = Pitch() pitch_label_dir = wav_path.parent.parent / 'PitchLabel' f0_path = (pitch_label_dir / wav_path.stem).with_suffix('.pv') f0 = [] with open(f0_path) as f: - for line in f: - line = line.strip() + for _line in f: + line = _line.strip() if line == '0': f0.append(float('nan')) else: @@ -46,12 +39,12 @@ def load_f0_true(wav_path: Path, seconds: float): def resample_f0( pitch: algorithms.PitchDetector, t_resampled: np.ndarray, -): +) -> np.ndarray: f0_resampled = np.full_like(t_resampled, fill_value=np.nan) notna_slices = np.ma.clump_unmasked(np.ma.masked_invalid(pitch.f0)) - for sl in notna_slices: - t_slice = pitch.t[sl] - f0_slice = pitch.f0[sl] + for slice_ in notna_slices: + t_slice = pitch.t[slice_] + f0_slice = pitch.f0[slice_] t_start, t_stop = t_slice[0], t_slice[-1] mask = (t_start < t_resampled) & (t_resampled < t_stop) t_interp = t_resampled[mask] @@ -61,22 +54,23 @@ def resample_f0( def raw_pitch_accuracy( - ref_f0, - est_f0, - cent_tolerance=50, -): + ref_f0: np.ndarray, + est_f0: np.ndarray, + cent_tolerance: float = 50, +) -> float: ref_voicing = np.isfinite(ref_f0) est_voicing = np.isfinite(est_f0) ref_cent = mir_eval.melody.hz2cents(ref_f0) est_cent = mir_eval.melody.hz2cents(est_f0) - return mir_eval.melody.raw_pitch_accuracy(ref_voicing, ref_cent, est_voicing, est_cent, cent_tolerance) + score: float = mir_eval.melody.raw_pitch_accuracy(ref_voicing, ref_cent, est_voicing, est_cent, cent_tolerance) + return score def evaluate_one( - redis: Redis, - algorithm: algorithms.PitchDetector, + redis: Redis[str], + algorithm: type[algorithms.PitchDetector], wav_path: Path, -): +) -> str: key = f'pitch_detectors:evaluation:{algorithm.name()}:{wav_path.stem}' if redis.exists(key): return key @@ -98,7 +92,7 @@ def evaluate_one( return key -def evaluate_all(redis: Redis): +def evaluate_all(redis: Redis[str]) -> None: t = tqdm.tqdm(sorted(WAV_DIR.glob('*.wav'))) for wav_path in t: for algorithm in tqdm.tqdm(algorithms.ALGORITHMS, leave=False): diff --git a/pitch_detectors/util.py b/pitch_detectors/util.py index 5a8cbaa..c19979c 100644 --- a/pitch_detectors/util.py +++ b/pitch_detectors/util.py @@ -14,7 +14,7 @@ def none_to_nan(x: list[float | None]) -> list[float]: return [float('nan') if v is None else v for v in x] -def load_wav(path: Path | str, rescale: float = 100000): +def load_wav(path: Path | str, rescale: float = 100000) -> tuple[int, np.ndarray]: fs, a = wavfile.read(path) a = minmax_scaler(a, a.min(), a.max(), -rescale, rescale).astype(np.float32) return fs, a diff --git a/pyproject.toml b/pyproject.toml index f662a62..4821013 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -102,7 +102,7 @@ warn_unreachable = true warn_unused_configs = true warn_unused_ignores = true -[tool.mypy.overrides] +[[tool.mypy.overrides]] module = ["tests.*"] disallow_untyped_defs = false @@ -154,6 +154,8 @@ good-names = [ "pm", "sp", "ap", + "f", + "p", ] [tool.pylint.messages-control] @@ -167,6 +169,8 @@ disable = [ "too-many-arguments", "import-error", "too-few-public-methods", + "unspecified-encoding", + "redefined-outer-name", ] [tool.pylint-per-file-ignores]