nocturne-client 1.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- nocturne_client-1.0.0/PKG-INFO +65 -0
- nocturne_client-1.0.0/README.md +46 -0
- nocturne_client-1.0.0/nocturne_client/__init__.py +169 -0
- nocturne_client-1.0.0/nocturne_client.egg-info/PKG-INFO +65 -0
- nocturne_client-1.0.0/nocturne_client.egg-info/SOURCES.txt +9 -0
- nocturne_client-1.0.0/nocturne_client.egg-info/dependency_links.txt +1 -0
- nocturne_client-1.0.0/nocturne_client.egg-info/entry_points.txt +2 -0
- nocturne_client-1.0.0/nocturne_client.egg-info/requires.txt +1 -0
- nocturne_client-1.0.0/nocturne_client.egg-info/top_level.txt +1 -0
- nocturne_client-1.0.0/pyproject.toml +33 -0
- nocturne_client-1.0.0/setup.cfg +4 -0
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: nocturne-client
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Python client for the Nocturne bioacoustic species classifier (by Stratus Labs).
|
|
5
|
+
Author: Stratus Labs
|
|
6
|
+
License: Apache-2.0
|
|
7
|
+
Project-URL: Homepage, https://nocturne.runstratus.com
|
|
8
|
+
Project-URL: Model, https://huggingface.co/stratus-labs/nocturne-v1-teacher
|
|
9
|
+
Project-URL: Source, https://github.com/stratus-labs/nocturne-client
|
|
10
|
+
Keywords: bioacoustics,audio,classification,insects,amphibians,ecology
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
14
|
+
Classifier: Topic :: Multimedia :: Sound/Audio :: Analysis
|
|
15
|
+
Classifier: Topic :: Scientific/Engineering :: Bio-Informatics
|
|
16
|
+
Requires-Python: >=3.9
|
|
17
|
+
Description-Content-Type: text/markdown
|
|
18
|
+
Requires-Dist: requests>=2.28
|
|
19
|
+
|
|
20
|
+
# nocturne-client
|
|
21
|
+
|
|
22
|
+
Tiny Python client for [Nocturne](https://nocturne.runstratus.com) — the bioacoustic species classifier for insects, amphibians, and other non-bird wildlife from [Stratus Labs](https://huggingface.co/stratus-labs).
|
|
23
|
+
|
|
24
|
+
## Install
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
pip install nocturne-client
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Use
|
|
31
|
+
|
|
32
|
+
```python
|
|
33
|
+
from nocturne_client import Nocturne
|
|
34
|
+
|
|
35
|
+
n = Nocturne()
|
|
36
|
+
r = n.predict("cicada.wav", top_k=5, threshold=0.15)
|
|
37
|
+
for p in r.predictions:
|
|
38
|
+
print(f"{p.score:.3f} {p.species}")
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Batch:
|
|
42
|
+
|
|
43
|
+
```python
|
|
44
|
+
results = n.predict_batch(["clip1.wav", "clip2.wav", "clip3.wav"])
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
CLI:
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
nocturne-client predict clip.wav --top-k 5
|
|
51
|
+
nocturne-client health
|
|
52
|
+
nocturne-client vocab
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Environment
|
|
56
|
+
|
|
57
|
+
- `NOCTURNE_URL` — override the default endpoint (`https://nocturne.runstratus.com`) if you're self-hosting.
|
|
58
|
+
|
|
59
|
+
## Rate limits
|
|
60
|
+
|
|
61
|
+
The public endpoint is capped at 10 requests per minute per IP. The client retries automatically on 429 with linear backoff. For batch work at scale, self-host the model (weights on HuggingFace at [`stratus-labs/nocturne-v1-teacher`](https://huggingface.co/stratus-labs/nocturne-v1-teacher)) or contact Stratus Labs.
|
|
62
|
+
|
|
63
|
+
## License
|
|
64
|
+
|
|
65
|
+
Apache-2.0. Model weights are CC-BY-4.0 (see the model card).
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# nocturne-client
|
|
2
|
+
|
|
3
|
+
Tiny Python client for [Nocturne](https://nocturne.runstratus.com) — the bioacoustic species classifier for insects, amphibians, and other non-bird wildlife from [Stratus Labs](https://huggingface.co/stratus-labs).
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install nocturne-client
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Use
|
|
12
|
+
|
|
13
|
+
```python
|
|
14
|
+
from nocturne_client import Nocturne
|
|
15
|
+
|
|
16
|
+
n = Nocturne()
|
|
17
|
+
r = n.predict("cicada.wav", top_k=5, threshold=0.15)
|
|
18
|
+
for p in r.predictions:
|
|
19
|
+
print(f"{p.score:.3f} {p.species}")
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Batch:
|
|
23
|
+
|
|
24
|
+
```python
|
|
25
|
+
results = n.predict_batch(["clip1.wav", "clip2.wav", "clip3.wav"])
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
CLI:
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
nocturne-client predict clip.wav --top-k 5
|
|
32
|
+
nocturne-client health
|
|
33
|
+
nocturne-client vocab
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Environment
|
|
37
|
+
|
|
38
|
+
- `NOCTURNE_URL` — override the default endpoint (`https://nocturne.runstratus.com`) if you're self-hosting.
|
|
39
|
+
|
|
40
|
+
## Rate limits
|
|
41
|
+
|
|
42
|
+
The public endpoint is capped at 10 requests per minute per IP. The client retries automatically on 429 with linear backoff. For batch work at scale, self-host the model (weights on HuggingFace at [`stratus-labs/nocturne-v1-teacher`](https://huggingface.co/stratus-labs/nocturne-v1-teacher)) or contact Stratus Labs.
|
|
43
|
+
|
|
44
|
+
## License
|
|
45
|
+
|
|
46
|
+
Apache-2.0. Model weights are CC-BY-4.0 (see the model card).
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
"""Tiny Python client for the Nocturne v1 bioacoustic classifier API.
|
|
2
|
+
|
|
3
|
+
pip install nocturne-client
|
|
4
|
+
from nocturne_client import Nocturne
|
|
5
|
+
n = Nocturne() # points at https://nocturne.runstratus.com by default
|
|
6
|
+
print(n.predict("clip.wav", top_k=5))
|
|
7
|
+
"""
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import json
|
|
11
|
+
import os
|
|
12
|
+
import time
|
|
13
|
+
from dataclasses import dataclass
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
from typing import Iterable
|
|
16
|
+
|
|
17
|
+
import requests
|
|
18
|
+
|
|
19
|
+
__version__ = "1.0.0"
|
|
20
|
+
DEFAULT_URL = os.environ.get("NOCTURNE_URL", "https://nocturne.runstratus.com")
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@dataclass
|
|
24
|
+
class Prediction:
|
|
25
|
+
species: str
|
|
26
|
+
score: float
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@dataclass
|
|
30
|
+
class PredictionResponse:
|
|
31
|
+
filename: str
|
|
32
|
+
duration_seconds: float
|
|
33
|
+
chunks_analyzed: int
|
|
34
|
+
inference_seconds: float
|
|
35
|
+
device: str
|
|
36
|
+
model: str
|
|
37
|
+
predictions: list[Prediction]
|
|
38
|
+
|
|
39
|
+
@classmethod
|
|
40
|
+
def from_json(cls, j: dict) -> "PredictionResponse":
|
|
41
|
+
return cls(
|
|
42
|
+
filename=j["filename"],
|
|
43
|
+
duration_seconds=j.get("duration_seconds", 0.0),
|
|
44
|
+
chunks_analyzed=j.get("chunks_analyzed", 0),
|
|
45
|
+
inference_seconds=j.get("inference_seconds", 0.0),
|
|
46
|
+
device=j.get("device", ""),
|
|
47
|
+
model=j.get("model", ""),
|
|
48
|
+
predictions=[Prediction(**p) for p in j.get("predictions", [])],
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class Nocturne:
|
|
53
|
+
"""Client for the Nocturne bioacoustic species classifier."""
|
|
54
|
+
|
|
55
|
+
def __init__(self, base_url: str = DEFAULT_URL, timeout: float = 60.0,
|
|
56
|
+
max_retries: int = 3, retry_delay: float = 5.0):
|
|
57
|
+
self.base_url = base_url.rstrip("/")
|
|
58
|
+
self.timeout = timeout
|
|
59
|
+
self.max_retries = max_retries
|
|
60
|
+
self.retry_delay = retry_delay
|
|
61
|
+
self._session = requests.Session()
|
|
62
|
+
self._session.headers.update({"User-Agent": f"nocturne-client/{__version__}"})
|
|
63
|
+
|
|
64
|
+
def health(self) -> dict:
|
|
65
|
+
r = self._session.get(f"{self.base_url}/health", timeout=10)
|
|
66
|
+
r.raise_for_status()
|
|
67
|
+
return r.json()
|
|
68
|
+
|
|
69
|
+
def vocab(self) -> list[str]:
|
|
70
|
+
r = self._session.get(f"{self.base_url}/vocab", timeout=30)
|
|
71
|
+
r.raise_for_status()
|
|
72
|
+
return r.json().get("species", [])
|
|
73
|
+
|
|
74
|
+
def predict(self, audio: str | Path | bytes, top_k: int = 5,
|
|
75
|
+
threshold: float = 0.15) -> PredictionResponse:
|
|
76
|
+
"""Predict species for a single clip. Retries on 429 rate limits."""
|
|
77
|
+
params = {"top_k": top_k, "threshold": threshold}
|
|
78
|
+
for attempt in range(self.max_retries):
|
|
79
|
+
r = self._session.post(
|
|
80
|
+
f"{self.base_url}/predict", params=params,
|
|
81
|
+
files={"file": self._as_file(audio)}, timeout=self.timeout,
|
|
82
|
+
)
|
|
83
|
+
if r.status_code == 429:
|
|
84
|
+
if attempt < self.max_retries - 1:
|
|
85
|
+
time.sleep(self.retry_delay * (attempt + 1))
|
|
86
|
+
continue
|
|
87
|
+
r.raise_for_status()
|
|
88
|
+
return PredictionResponse.from_json(r.json())
|
|
89
|
+
raise RuntimeError(f"rate limited after {self.max_retries} retries")
|
|
90
|
+
|
|
91
|
+
def predict_batch(self, audios: Iterable[str | Path | bytes], top_k: int = 5,
|
|
92
|
+
threshold: float = 0.15, chunk_size: int = 20) -> list[PredictionResponse]:
|
|
93
|
+
"""Predict for a batch of clips. Batched to <=20/request per server cap."""
|
|
94
|
+
audios = list(audios)
|
|
95
|
+
results = []
|
|
96
|
+
for i in range(0, len(audios), chunk_size):
|
|
97
|
+
batch = audios[i : i + chunk_size]
|
|
98
|
+
files = [("files", self._as_file(a)) for a in batch]
|
|
99
|
+
r = self._session.post(
|
|
100
|
+
f"{self.base_url}/predict-batch",
|
|
101
|
+
params={"top_k": top_k, "threshold": threshold},
|
|
102
|
+
files=files, timeout=self.timeout * 3,
|
|
103
|
+
)
|
|
104
|
+
r.raise_for_status()
|
|
105
|
+
for item in r.json().get("results", []):
|
|
106
|
+
if "error" in item:
|
|
107
|
+
print(f"[nocturne-client] {item['filename']}: {item['error']}")
|
|
108
|
+
continue
|
|
109
|
+
results.append(PredictionResponse.from_json(item))
|
|
110
|
+
return results
|
|
111
|
+
|
|
112
|
+
def _as_file(self, audio):
|
|
113
|
+
if isinstance(audio, (str, Path)):
|
|
114
|
+
p = Path(audio)
|
|
115
|
+
return (p.name, open(p, "rb"), "application/octet-stream")
|
|
116
|
+
if isinstance(audio, (bytes, bytearray)):
|
|
117
|
+
return ("clip.wav", bytes(audio), "audio/wav")
|
|
118
|
+
raise TypeError(f"unsupported audio type: {type(audio)}")
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def _cli():
|
|
122
|
+
"""Command-line: nocturne-client predict clip.wav [--top-k 5] [--threshold 0.15]"""
|
|
123
|
+
import argparse
|
|
124
|
+
ap = argparse.ArgumentParser(prog="nocturne-client")
|
|
125
|
+
sub = ap.add_subparsers(dest="cmd", required=True)
|
|
126
|
+
|
|
127
|
+
p_pred = sub.add_parser("predict", help="Predict for one or more audio files.")
|
|
128
|
+
p_pred.add_argument("files", nargs="+")
|
|
129
|
+
p_pred.add_argument("--top-k", type=int, default=5)
|
|
130
|
+
p_pred.add_argument("--threshold", type=float, default=0.15)
|
|
131
|
+
p_pred.add_argument("--json", action="store_true", help="Emit raw JSON per file.")
|
|
132
|
+
|
|
133
|
+
sub.add_parser("health")
|
|
134
|
+
sub.add_parser("vocab")
|
|
135
|
+
|
|
136
|
+
args = ap.parse_args()
|
|
137
|
+
n = Nocturne()
|
|
138
|
+
|
|
139
|
+
if args.cmd == "health":
|
|
140
|
+
print(json.dumps(n.health(), indent=2))
|
|
141
|
+
return
|
|
142
|
+
if args.cmd == "vocab":
|
|
143
|
+
v = n.vocab()
|
|
144
|
+
print(f"{len(v)} species:")
|
|
145
|
+
for s in v[:50]:
|
|
146
|
+
print(f" {s}")
|
|
147
|
+
if len(v) > 50:
|
|
148
|
+
print(f" ... ({len(v)-50} more)")
|
|
149
|
+
return
|
|
150
|
+
if args.cmd == "predict":
|
|
151
|
+
if len(args.files) == 1:
|
|
152
|
+
r = n.predict(args.files[0], top_k=args.top_k, threshold=args.threshold)
|
|
153
|
+
if args.json:
|
|
154
|
+
print(json.dumps(r.__dict__, default=lambda o: o.__dict__, indent=2))
|
|
155
|
+
else:
|
|
156
|
+
print(f"{r.filename} ({r.duration_seconds}s, {r.inference_seconds}s inference):")
|
|
157
|
+
for p in r.predictions:
|
|
158
|
+
print(f" {p.score:.3f} {p.species}")
|
|
159
|
+
else:
|
|
160
|
+
results = n.predict_batch(args.files, top_k=args.top_k, threshold=args.threshold)
|
|
161
|
+
for r in results:
|
|
162
|
+
print(f"{r.filename}:")
|
|
163
|
+
for p in r.predictions[:5]:
|
|
164
|
+
print(f" {p.score:.3f} {p.species}")
|
|
165
|
+
print()
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
if __name__ == "__main__":
|
|
169
|
+
_cli()
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: nocturne-client
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Python client for the Nocturne bioacoustic species classifier (by Stratus Labs).
|
|
5
|
+
Author: Stratus Labs
|
|
6
|
+
License: Apache-2.0
|
|
7
|
+
Project-URL: Homepage, https://nocturne.runstratus.com
|
|
8
|
+
Project-URL: Model, https://huggingface.co/stratus-labs/nocturne-v1-teacher
|
|
9
|
+
Project-URL: Source, https://github.com/stratus-labs/nocturne-client
|
|
10
|
+
Keywords: bioacoustics,audio,classification,insects,amphibians,ecology
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
14
|
+
Classifier: Topic :: Multimedia :: Sound/Audio :: Analysis
|
|
15
|
+
Classifier: Topic :: Scientific/Engineering :: Bio-Informatics
|
|
16
|
+
Requires-Python: >=3.9
|
|
17
|
+
Description-Content-Type: text/markdown
|
|
18
|
+
Requires-Dist: requests>=2.28
|
|
19
|
+
|
|
20
|
+
# nocturne-client
|
|
21
|
+
|
|
22
|
+
Tiny Python client for [Nocturne](https://nocturne.runstratus.com) — the bioacoustic species classifier for insects, amphibians, and other non-bird wildlife from [Stratus Labs](https://huggingface.co/stratus-labs).
|
|
23
|
+
|
|
24
|
+
## Install
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
pip install nocturne-client
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Use
|
|
31
|
+
|
|
32
|
+
```python
|
|
33
|
+
from nocturne_client import Nocturne
|
|
34
|
+
|
|
35
|
+
n = Nocturne()
|
|
36
|
+
r = n.predict("cicada.wav", top_k=5, threshold=0.15)
|
|
37
|
+
for p in r.predictions:
|
|
38
|
+
print(f"{p.score:.3f} {p.species}")
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Batch:
|
|
42
|
+
|
|
43
|
+
```python
|
|
44
|
+
results = n.predict_batch(["clip1.wav", "clip2.wav", "clip3.wav"])
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
CLI:
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
nocturne-client predict clip.wav --top-k 5
|
|
51
|
+
nocturne-client health
|
|
52
|
+
nocturne-client vocab
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Environment
|
|
56
|
+
|
|
57
|
+
- `NOCTURNE_URL` — override the default endpoint (`https://nocturne.runstratus.com`) if you're self-hosting.
|
|
58
|
+
|
|
59
|
+
## Rate limits
|
|
60
|
+
|
|
61
|
+
The public endpoint is capped at 10 requests per minute per IP. The client retries automatically on 429 with linear backoff. For batch work at scale, self-host the model (weights on HuggingFace at [`stratus-labs/nocturne-v1-teacher`](https://huggingface.co/stratus-labs/nocturne-v1-teacher)) or contact Stratus Labs.
|
|
62
|
+
|
|
63
|
+
## License
|
|
64
|
+
|
|
65
|
+
Apache-2.0. Model weights are CC-BY-4.0 (see the model card).
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
nocturne_client/__init__.py
|
|
4
|
+
nocturne_client.egg-info/PKG-INFO
|
|
5
|
+
nocturne_client.egg-info/SOURCES.txt
|
|
6
|
+
nocturne_client.egg-info/dependency_links.txt
|
|
7
|
+
nocturne_client.egg-info/entry_points.txt
|
|
8
|
+
nocturne_client.egg-info/requires.txt
|
|
9
|
+
nocturne_client.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
requests>=2.28
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
nocturne_client
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "nocturne-client"
|
|
7
|
+
version = "1.0.0"
|
|
8
|
+
description = "Python client for the Nocturne bioacoustic species classifier (by Stratus Labs)."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = {text = "Apache-2.0"}
|
|
11
|
+
authors = [{name = "Stratus Labs"}]
|
|
12
|
+
requires-python = ">=3.9"
|
|
13
|
+
dependencies = ["requests>=2.28"]
|
|
14
|
+
keywords = ["bioacoustics", "audio", "classification", "insects", "amphibians", "ecology"]
|
|
15
|
+
classifiers = [
|
|
16
|
+
"Development Status :: 4 - Beta",
|
|
17
|
+
"Programming Language :: Python :: 3",
|
|
18
|
+
"License :: OSI Approved :: Apache Software License",
|
|
19
|
+
"Topic :: Multimedia :: Sound/Audio :: Analysis",
|
|
20
|
+
"Topic :: Scientific/Engineering :: Bio-Informatics",
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
[project.urls]
|
|
24
|
+
Homepage = "https://nocturne.runstratus.com"
|
|
25
|
+
Model = "https://huggingface.co/stratus-labs/nocturne-v1-teacher"
|
|
26
|
+
Source = "https://github.com/stratus-labs/nocturne-client"
|
|
27
|
+
|
|
28
|
+
[project.scripts]
|
|
29
|
+
nocturne-client = "nocturne_client:_cli"
|
|
30
|
+
|
|
31
|
+
[tool.setuptools.packages.find]
|
|
32
|
+
where = ["."]
|
|
33
|
+
include = ["nocturne_client*"]
|