wakeforge / src /edge_impulse.py
eoinedge's picture
Upload folder using huggingface_hub
5838d6a verified
Raw
History Blame Contribute Delete
3.91 kB
"""Upload a generated dataset directly to an Edge Impulse project.
Uses the Edge Impulse ingestion REST API with a project API key
(Project → Dashboard → Keys). WAV filenames use the ``label.<id>.wav``
convention, so Edge Impulse auto-assigns labels from the filename prefix.
Ingestion endpoints:
POST https://ingestion.edgeimpulse.com/api/training/files
POST https://ingestion.edgeimpulse.com/api/testing/files
"""
from __future__ import annotations
from dataclasses import dataclass, field
from pathlib import Path
from typing import Callable, List, Optional
import requests
_INGESTION_URL = "https://ingestion.edgeimpulse.com/api/{category}/files"
_TIMEOUT = 60
_BATCH_SIZE = 25
ProgressFn = Callable[[str], None]
@dataclass
class UploadResult:
uploaded: int = 0
failed: int = 0
errors: List[str] = field(default_factory=list)
def verify_api_key(api_key: str) -> bool:
"""Return True if the ingestion API accepts the key.
A key with no files still returns a non-auth error, so we treat any
non-401/403 response as "key looks usable".
"""
if not api_key or not api_key.strip():
return False
try:
response = requests.post(
_INGESTION_URL.format(category="training"),
headers={"x-api-key": api_key.strip()},
timeout=_TIMEOUT,
)
except requests.RequestException:
return False
return response.status_code not in (401, 403)
def _upload_batch(
category: str,
api_key: str,
wav_paths: List[Path],
allow_duplicates: bool,
) -> tuple[int, Optional[str]]:
headers = {"x-api-key": api_key}
if not allow_duplicates:
headers["x-disallow-duplicates"] = "1"
files = []
handles = []
try:
for path in wav_paths:
handle = path.open("rb")
handles.append(handle)
files.append(("data", (path.name, handle, "audio/wav")))
response = requests.post(
_INGESTION_URL.format(category=category),
headers=headers,
files=files,
timeout=_TIMEOUT,
)
finally:
for handle in handles:
handle.close()
if response.status_code == 200:
return len(wav_paths), None
return 0, f"HTTP {response.status_code}: {response.text[:200]}"
def upload_dataset(
dataset_dir: str,
api_key: str,
allow_duplicates: bool = False,
progress: Optional[ProgressFn] = None,
) -> UploadResult:
"""Upload the ``edge_impulse_upload/{training,testing}`` WAVs to a project."""
def log(message: str) -> None:
if progress:
progress(message)
else:
print(message)
api_key = (api_key or "").strip()
if not api_key:
raise ValueError("An Edge Impulse API key is required to upload.")
base = Path(dataset_dir) / "edge_impulse_upload"
result = UploadResult()
for src_split, category in (("training", "training"), ("testing", "testing")):
split_dir = base / src_split
if not split_dir.exists():
continue
wavs = sorted(split_dir.glob("*.wav"))
if not wavs:
continue
log(f"Uploading {len(wavs)} {category} file(s) to Edge Impulse...")
for start in range(0, len(wavs), _BATCH_SIZE):
batch = wavs[start:start + _BATCH_SIZE]
uploaded, error = _upload_batch(category, api_key, batch, allow_duplicates)
if error is None:
result.uploaded += uploaded
log(f" {category}: {result.uploaded} uploaded")
else:
result.failed += len(batch)
result.errors.append(f"{category} batch @ {start}: {error}")
log(f" WARNING: {category} batch failed: {error}")
log(f"Edge Impulse upload complete: {result.uploaded} uploaded, {result.failed} failed.")
return result