angleforge / src /edge_impulse.py
eoinedge's picture
Upload folder using huggingface_hub
f2ec79c verified
Raw
History Blame Contribute Delete
3.73 kB
"""Upload a generated image dataset directly to an Edge Impulse project.
Uses the Edge Impulse ingestion REST API with a project API key. Image
filenames follow the ``label.<id>.jpg`` convention so Edge Impulse assigns
labels from the filename prefix automatically.
"""
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 = 120
_BATCH_SIZE = 20
_IMAGE_EXTS = (".jpg", ".jpeg", ".png", ".bmp")
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:
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 _content_type(path: Path) -> str:
return "image/png" if path.suffix.lower() == ".png" else "image/jpeg"
def _upload_batch(category: str, api_key: str, 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 paths:
handle = path.open("rb")
handles.append(handle)
files.append(("data", (path.name, handle, _content_type(path))))
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(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 ``edge_impulse_upload/{training,testing}`` images 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 split, category in (("training", "training"), ("testing", "testing")):
split_dir = base / split
if not split_dir.exists():
continue
imgs = sorted(p for p in split_dir.iterdir() if p.suffix.lower() in _IMAGE_EXTS)
if not imgs:
continue
log(f"Uploading {len(imgs)} {category} image(s) to Edge Impulse...")
for start in range(0, len(imgs), _BATCH_SIZE):
batch = imgs[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