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.
@@ -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,2 @@
1
+ [console_scripts]
2
+ nocturne-client = nocturne_client:_cli
@@ -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*"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+