Download code/paya/inputs.py from yeabwang/Paya: direct link, hf CLI and curl.
- Browser
- Download file 5.92 kB
-
https://huggingface.co/yeabwang/Paya/resolve/main/code/paya/inputs.py
- Command line
-
hf download hf://yeabwang/Paya/code/paya/inputs.py
-
curl -L -o inputs.py https://huggingface.co/yeabwang/Paya/resolve/main/code/paya/inputs.py
5.92 kB
| """Validate native signal samples, required metadata and separate text inputs.""" | |
| from collections.abc import Mapping | |
| from dataclasses import dataclass | |
| import math | |
| from numbers import Real | |
| import numpy as np | |
| from .vendor import signal | |
| TYPES = ("eeg", "ieeg", "ecg", "ppg") | |
| SIGNAL_CONDITIONS = ("raw", "processed") | |
| class UnsupportedSignalError(ValueError): | |
| """The supplied measurements cannot satisfy a supported preparation.""" | |
| class Channel: | |
| samples: np.ndarray | |
| sensor_type: str | |
| rate: float | |
| start: float | |
| metadata: dict | |
| def positive_number(value, name): | |
| if isinstance(value, (bool, np.bool_)) or not isinstance(value, Real): | |
| raise ValueError(f"{name} must be a real number, not a string or boolean") | |
| value = float(value) | |
| if not math.isfinite(value) or value <= 0: | |
| raise ValueError(f"{name} must be finite and positive") | |
| return value | |
| def validate_text(question, criteria): | |
| if not isinstance(question, str) or not question.strip(): | |
| raise ValueError("question must be a nonempty string") | |
| if not isinstance(criteria, list) or not 2 <= len(criteria) <= 64: | |
| raise ValueError("criteria must be an ordered list of 2–64 strings") | |
| if any(not isinstance(c, str) or not c.strip() for c in criteria): | |
| raise ValueError("criteria entries must be nonempty strings") | |
| if len({c.strip() for c in criteria}) != len(criteria): | |
| raise ValueError("criteria entries must be distinct") | |
| def validate_state(state, max_channels=signal.MAX_CHANNELS, max_samples=signal.MAX_SAMPLES): | |
| if not isinstance(state, Mapping) or not isinstance(state.get("signals"), list): | |
| raise ValueError("state must contain a signals list") | |
| if not 1 <= len(state["signals"]) <= max_channels: | |
| raise ValueError(f"Supply 1–{max_channels} physical channels") | |
| channels = [] | |
| for entry in state["signals"]: | |
| if not isinstance(entry, Mapping) or not isinstance(entry.get("metadata"), Mapping): | |
| raise ValueError("Each signal needs samples and a metadata object") | |
| meta = dict(entry["metadata"]) | |
| kind = meta.get("sensor_type") | |
| if not isinstance(kind, str) or kind.strip().lower() not in TYPES: | |
| raise ValueError(f"metadata.sensor_type must be one of {TYPES}") | |
| kind = kind.strip().lower() | |
| rate = positive_number(meta.get("sampling_rate_hz"), "metadata.sampling_rate_hz") | |
| duration = positive_number(meta.get("duration_s"), "metadata.duration_s") | |
| condition = meta.get("signal_condition", "raw") | |
| if not isinstance(condition, str) or condition not in SIGNAL_CONDITIONS: | |
| raise ValueError("metadata.signal_condition must be raw or processed") | |
| # Units and lead labels are outside the input contract; values retain their supplied scale. | |
| meta.pop("unit", None) | |
| meta.pop("lead", None) | |
| if "powerline_hz" in meta: | |
| powerline = meta["powerline_hz"] | |
| if isinstance(powerline, (bool, np.bool_)) or not isinstance(powerline, Real) or powerline not in (0, 50, 60): | |
| raise ValueError("metadata.powerline_hz must be numeric 0, 50, or 60") | |
| raw = np.asarray(entry.get("samples")) | |
| if raw.ndim != 1 or raw.dtype.kind not in "iuf" or not signal.MIN_SAMPLES <= raw.size <= max_samples: | |
| raise ValueError(f"samples must contain {signal.MIN_SAMPLES}–{max_samples} real numeric measurements") | |
| values = raw.astype(np.float64) | |
| if not np.isfinite(values).all(): | |
| raise ValueError("Samples must be finite; missing intervals cannot be filled silently") | |
| with np.errstate(over="ignore", invalid="ignore"): | |
| values = values.astype(np.float32) | |
| if not np.isfinite(values).all(): | |
| raise ValueError("Samples exceed the float32 waveform representation") | |
| start = meta.get("start_time_s", 0.0) | |
| if isinstance(start, (bool, np.bool_)) or not isinstance(start, Real) or not math.isfinite(start): | |
| raise ValueError("metadata.start_time_s must be finite") | |
| measured_duration = len(values)/rate | |
| if not math.isfinite(measured_duration) or not math.isclose( | |
| duration, measured_duration, rel_tol=1e-12, abs_tol=1e-9): | |
| raise ValueError("metadata.duration_s disagrees with samples and sampling rate") | |
| if "timestamps_s" in meta: | |
| ts = np.asarray(meta["timestamps_s"], dtype=np.float64) | |
| if ts.shape != values.shape or not np.isfinite(ts).all(): | |
| raise ValueError("Timestamps must be finite and match samples") | |
| if not np.allclose(np.diff(ts), 1/rate, rtol=0.01, atol=1e-9): | |
| raise ValueError("Timestamps disagree with the declared uniform sampling rate") | |
| if not math.isclose(ts[0], start, rel_tol=0, abs_tol=max(1e-9, 0.01/rate)): | |
| raise ValueError("Timestamps disagree with start_time_s") | |
| for field in ("placement", "reference"): | |
| if field in meta and (not isinstance(meta[field], str) or not meta[field].strip()): | |
| raise ValueError(f"metadata.{field} must be a nonempty string when supplied") | |
| meta.update(sensor_type=kind, sampling_rate_hz=rate, duration_s=duration, signal_condition=condition) | |
| channels.append(Channel(values, kind, rate, float(start), meta)) | |
| first = channels[0] | |
| for c in channels[1:]: | |
| if c.rate != first.rate or len(c.samples) != len(first.samples): | |
| raise ValueError("All channels must have the same sampling rate and sample count for one encoder input") | |
| if abs(c.start-first.start) > max(1e-9, 0.01/first.rate): | |
| raise ValueError("Channels must have the same start_time_s") | |
| return channels | |