| Title: | Governed Real-Time Streams for Physiological Data |
|---|---|
| Description: | Provides validated stream metadata, source and sink contracts, a bounded single-producer/single-consumer ring buffer, deterministic in-process loopback streams, capability-gated Lab Streaming Layer inlets and outlets, marker-event capture, governed Extensible Data Format read/write, and conversion of buffered physiological samples to PhysioExperiment objects, with explicit robust clock-offset, drift, jitter, multi-stream synchronization, transactional causal processing pipelines, explicit backpressure, monotonic latency measurement, and governed loopback, MQTT, and TTL stimulation-command outlets with arm, dead-man, maximum, duplicate-ID, and refractory interlocks, plus causal EMG-onset, EEG-ERD, and phase-target detectors bound to commit-before-stimulation closed-loop session control and provenance, with a bounded live biofeedback scope and marker-anchored video-time synchronization. |
| Authors: | Yusuke Matsui [aut, cre] |
| Maintainer: | Yusuke Matsui <[email protected]> |
| License: | MIT + file LICENSE |
| Version: | 0.9.4 |
| Built: | 2026-10-03 14:00:53 UTC |
| Source: | https://github.com/x-biosignal/PhysioStream |
Every trigger opens disarmed. Dead-man expiry disarms locally and requires a new explicit arm with a different session ID within the open lifecycle. Emergency stop latches locally even if the transport stop attempt fails. These software gates do not replace device hardware limits or an independent emergency stop.
armTrigger(trigger, session_id, now_ns = NULL) triggerHeartbeat(trigger, now_ns = NULL) disarmTrigger(trigger, reason = "caller", now_ns = NULL) emergencyStop(trigger, reason = "caller", now_ns = NULL)armTrigger(trigger, session_id, now_ns = NULL) triggerHeartbeat(trigger, now_ns = NULL) disarmTrigger(trigger, reason = "caller", now_ns = NULL) emergencyStop(trigger, reason = "caller", now_ns = NULL)
trigger |
A |
session_id |
Unique non-empty session identifier. |
now_ns |
Optional exact process-local monotonic nanosecond value. |
reason |
Bounded non-sensitive reason code. |
The trigger, invisibly.
trigger <- loopbackTrigger(allowed_channels = "left", max_intensity = 20, intensity_unit = "mA", max_duration_ms = 500, refractory_ms = 0, deadman_ms = 1000) triggerOpen(trigger) armTrigger(trigger, "session-1", now_ns = 0) triggerState(trigger)$arm$statustrigger <- loopbackTrigger(allowed_channels = "left", max_intensity = 20, intensity_unit = "mA", max_duration_ms = 500, refractory_ms = 0, deadman_ms = 1000) triggerOpen(trigger) armTrigger(trigger, "session-1", now_ns = 0) triggerState(trigger)$arm$status
The operation uses direct-form-II-transposed second-order sections followed
by a per-channel rolling root-mean-square. It is a descriptor for
onChunk() and performs no processing until registered and stepped.
bandpassRmsOp( sos, window_samples, warmup = c("partial", "complete"), name = "bandpass_rms" )bandpassRmsOp( sos, window_samples, warmup = c("partial", "complete"), name = "bandpass_rms" )
sos |
Finite second-order-section matrix with columns
|
window_samples |
Exact positive RMS window length. |
warmup |
Whether to emit partial-window RMS values or only rows after a complete window exists. |
name |
Default operation name. |
A PipelineOperation descriptor.
# A pass-through SOS keeps the example self-contained; use # PhysioPreprocess::sosDesign() for a real band definition. sos <- matrix(c(1, 0, 0, 1, 0, 0), 1L, 6L, dimnames = list(NULL, c("b0", "b1", "b2", "a0", "a1", "a2"))) op <- bandpassRmsOp(sos, window_samples = 4L) pipeline <- streamPipeline(chunk_size = 8L) onChunk(pipeline, op) x <- matrix(sin(seq_len(16)), 8, 2, dimnames = list(NULL, c("C3", "C4"))) pipelineEnqueue(pipeline, x, ingest_time_ns = 0) pipelineStep(pipeline)$results[[1]]$output$samples# A pass-through SOS keeps the example self-contained; use # PhysioPreprocess::sosDesign() for a real band definition. sos <- matrix(c(1, 0, 0, 1, 0, 0), 1L, 6L, dimnames = list(NULL, c("b0", "b1", "b2", "a0", "a1", "a2"))) op <- bandpassRmsOp(sos, window_samples = 4L) pipeline <- streamPipeline(chunk_size = 8L) onChunk(pipeline, op) x <- matrix(sin(seq_len(16)), 8, 2, dimnames = list(NULL, c("C3", "C4"))) pipelineEnqueue(pipeline, x, ingest_time_ns = 0) pipelineStep(pipeline)$results[[1]]$output$samples
Start, step, and stop a biofeedback runtime
biofeedbackStart(scope) biofeedbackStep(scope, max_chunks = 16L) biofeedbackStop(scope)biofeedbackStart(scope) biofeedbackStep(scope, max_chunks = 16L) biofeedbackStop(scope)
scope |
A |
max_chunks |
Exact per-step work bound. |
Lifecycle functions return scope invisibly.
biofeedbackStep() returns a plain update summary.
info <- streamInfo("demo", type = "EEG", channel_names = c("left", "right"), nominal_srate = 100, channel_units = c("uV", "uV")) source <- streamOpen(loopbackSource(info, capacity = 4096L)) scope <- biofeedbackScope(source, update_hz = 20, window_seconds = 1, max_points = 64L, launch = FALSE) biofeedbackStart(scope) loopbackFeed(source, matrix(as.double(1:40), 20, 2), seq_len(20) / 100) invisible(biofeedbackStep(scope)) biofeedbackStop(scope) biofeedbackState(scope)$lifecycleinfo <- streamInfo("demo", type = "EEG", channel_names = c("left", "right"), nominal_srate = 100, channel_units = c("uV", "uV")) source <- streamOpen(loopbackSource(info, capacity = 4096L)) scope <- biofeedbackScope(source, update_hz = 20, window_seconds = 1, max_points = 64L, launch = FALSE) biofeedbackStart(scope) loopbackFeed(source, matrix(as.double(1:40), 20, 2), seq_len(20) / 100) invisible(biofeedbackStep(scope)) biofeedbackStop(scope) biofeedbackState(scope)$lifecycle
Inspect a biofeedback frame or portable state
biofeedbackFrame(scope) biofeedbackState(scope)biofeedbackFrame(scope) biofeedbackState(scope)
scope |
A |
A deep plain-list copy, or NULL before the first frame.
info <- streamInfo("demo", type = "EEG", channel_names = c("left", "right"), nominal_srate = 100, channel_units = c("uV", "uV")) source <- loopbackSource(info, capacity = 256L) scope <- biofeedbackScope(source, source_lifecycle = "own", launch = FALSE) biofeedbackState(scope)$lifecycle biofeedbackFrame(scope)info <- streamInfo("demo", type = "EEG", channel_names = c("left", "right"), nominal_srate = 100, channel_units = c("uV", "uV")) source <- loopbackSource(info, capacity = 256L) scope <- biofeedbackScope(source, source_lifecycle = "own", launch = FALSE) biofeedbackState(scope)$lifecycle biofeedbackFrame(scope)
Construction is side-effect-free. With launch = FALSE, the returned
runtime is advanced synchronously with biofeedbackStart() and
biofeedbackStep(). Shiny is required only for launch = TRUE.
biofeedbackScope( source, pipeline = NULL, channels = NULL, derived = list(), window_seconds = 10, update_hz = 20, max_points = 2000L, gain = 1, threshold = NULL, target_range = NULL, source_lifecycle = c("borrow", "own"), launch = interactive(), host = "127.0.0.1", port = NULL, browser = interactive(), clock = NULL )biofeedbackScope( source, pipeline = NULL, channels = NULL, derived = list(), window_seconds = 10, update_hz = 20, max_points = 2000L, gain = 1, threshold = NULL, target_range = NULL, source_lifecycle = c("borrow", "own"), launch = interactive(), host = "127.0.0.1", port = NULL, browser = interactive(), clock = NULL )
source |
A regular-rate numeric |
pipeline |
Optional source-backed |
channels |
Exact source channel names to display. |
derived |
Strictly named derived/external trace descriptors. |
window_seconds |
Positive visible signal-time window. |
update_hz |
Requested display update rate. |
max_points |
Maximum plotted representatives per trace. |
gain |
Positive display-only gain. |
threshold |
Optional finite display threshold. |
target_range |
Optional increasing display target range. |
source_lifecycle |
Whether the scope borrows an open source or owns its open/close lifecycle. |
launch |
Whether to run the installed Shiny app immediately. |
host |
Local Shiny bind host. |
port |
Optional exact TCP port. |
browser |
Whether Shiny should launch a browser. |
clock |
Optional monotonic nanosecond clock for deterministic tests. |
A BiofeedbackScope when launch = FALSE, otherwise the result of
shiny::runApp().
# Construction is device-free; Shiny is required only for launch = TRUE. info <- streamInfo("demo", type = "EEG", channel_names = c("left", "right"), nominal_srate = 100, channel_units = c("uV", "uV")) source <- loopbackSource(info, capacity = 256L) scope <- biofeedbackScope(source, source_lifecycle = "own", launch = FALSE) biofeedbackState(scope)$lifecycle# Construction is device-free; Shiny is required only for launch = TRUE. info <- streamInfo("demo", type = "EEG", channel_names = c("left", "right"), nominal_srate = 100, channel_units = c("uV", "uV")) source <- loopbackSource(info, capacity = 256L) scope <- biofeedbackScope(source, source_lifecycle = "own", launch = FALSE) biofeedbackState(scope)$lifecycle
This is the sink used by downstream live metric adapters. It updates only declared external display traces and has no pipeline or stimulation effect.
biofeedbackUpdate( scope, values, timestamp, names = base::names(values), units = NULL, sequence = NULL )biofeedbackUpdate( scope, values, timestamp, names = base::names(values), units = NULL, sequence = NULL )
scope |
A running |
values |
Finite named values. |
timestamp |
One signal-domain timestamp. |
names |
Exact names corresponding to |
units |
Optional exact units corresponding to |
sequence |
Optional exact contiguous update sequence. |
An immutable plain receipt.
info <- streamInfo("demo", type = "EEG", channel_names = c("left", "right"), nominal_srate = 100, channel_units = c("uV", "uV")) source <- streamOpen(loopbackSource(info, capacity = 4096L)) scope <- biofeedbackScope(source, derived = list( score = list(type = "external", unit = "ratio", gain = 1) ), launch = FALSE) biofeedbackStart(scope) receipt <- biofeedbackUpdate(scope, c(score = 0.8), timestamp = 1, units = "ratio", sequence = 0) receipt$names biofeedbackStop(scope)info <- streamInfo("demo", type = "EEG", channel_names = c("left", "right"), nominal_srate = 100, channel_units = c("uV", "uV")) source <- streamOpen(loopbackSource(info, capacity = 4096L)) scope <- biofeedbackScope(source, derived = list( score = list(type = "external", unit = "ratio", gain = 1) ), launch = FALSE) biofeedbackStart(scope) receipt <- biofeedbackUpdate(scope, c(score = 0.8), timestamp = 1, units = "ratio", sequence = 0) receipt$names biofeedbackStop(scope)
Fits recorded clock offsets under the sign convention
master_time = device_time + offset(device_time). Fits are centered within
each reset segment.
clockOffset( clock_times, clock_values, method = c("huber", "ols", "ransac"), origin = c("median", "first"), segments = NULL, huber_k = 1.345, ransac_threshold = NULL, ransac_trials = 200L, seed = 1L )clockOffset( clock_times, clock_values, method = c("huber", "ols", "ransac"), origin = c("median", "first"), segments = NULL, huber_k = 1.345, ransac_threshold = NULL, ransac_trials = 200L, seed = 1L )
clock_times |
Finite non-decreasing device-clock observation times. |
clock_values |
Finite observed offsets in seconds. |
method |
Exact fitting method. |
origin |
Exact centering rule. |
segments |
Optional positive labels or zero-based inclusive ranges. |
huber_k |
Positive Huber tuning constant. |
ransac_threshold |
Optional positive inlier threshold in seconds. |
ransac_trials |
Positive number of deterministic RANSAC trials. |
seed |
Non-negative deterministic RANSAC seed. |
A serializable StreamClockModel.
device <- seq(0, 10, by = 0.5) offset <- -0.4 + 0.005 * (device - median(device)) model <- clockOffset(device, offset, method = "ols") model$segments$drift_ppmdevice <- seq(0, 10, by = 0.5) offset <- -0.4 + 0.005 * (device - median(device)) model <- clockOffset(device, offset, method = "ols") model$segments$drift_ppm
The controller is synchronous and terminal. Start explicitly opens and arms its trigger. Step processes one committed chunk and then dispatches due actions. Flush dispatches due delayed actions without ingesting samples. Stop and emergency stop suppress pending actions and close the trigger.
closedLoopStart(controller, session_id, now_ns = NULL) closedLoopStep(controller, samples, timestamps = NULL, now_ns = NULL) closedLoopFlush(controller, now_ns = NULL) closedLoopStop(controller, reason = "caller", now_ns = NULL) closedLoopEmergencyStop(controller, reason = "caller", now_ns = NULL)closedLoopStart(controller, session_id, now_ns = NULL) closedLoopStep(controller, samples, timestamps = NULL, now_ns = NULL) closedLoopFlush(controller, now_ns = NULL) closedLoopStop(controller, reason = "caller", now_ns = NULL) closedLoopEmergencyStop(controller, reason = "caller", now_ns = NULL)
controller |
A |
session_id |
New bounded trigger session identifier. |
now_ns |
Optional exact test monotonic nanosecond value. |
samples |
Finite sample-by-channel chunk. |
timestamps |
Optional signal-domain timestamps. |
reason |
Bounded non-sensitive stop reason. |
Start/stop functions return controller invisibly. Step and flush
return bounded plain result lists.
set.seed(1) pipeline <- streamPipeline(chunk_size = 2048L) trigger <- loopbackTrigger(allowed_channels = "left", max_intensity = 20, intensity_unit = "mA", max_duration_ms = 500, refractory_ms = 0, deadman_ms = 1000) detector <- emgOnsetOp("emg", sampling_rate = 1000, baseline_samples = 30, rms_window_samples = 5) controller <- closedLoop(pipeline, trigger, detector, intensity = 2, stim_channel = "left", duration_ms = 10) closedLoopStart(controller, "demo-session", now_ns = 0) emg <- rnorm(100, sd = 0.08) result <- closedLoopStep(controller, matrix(emg, ncol = 1, dimnames = list(NULL, "emg")), timestamps = (seq_along(emg) - 1) / 1000, now_ns = 1e6) length(result$actions) closedLoopStop(controller, now_ns = 2e6)set.seed(1) pipeline <- streamPipeline(chunk_size = 2048L) trigger <- loopbackTrigger(allowed_channels = "left", max_intensity = 20, intensity_unit = "mA", max_duration_ms = 500, refractory_ms = 0, deadman_ms = 1000) detector <- emgOnsetOp("emg", sampling_rate = 1000, baseline_samples = 30, rms_window_samples = 5) controller <- closedLoop(pipeline, trigger, detector, intensity = 2, stim_channel = "left", duration_ms = 10) closedLoopStart(controller, "demo-session", now_ns = 0) emg <- rnorm(100, sd = 0.08) result <- closedLoopStep(controller, matrix(emg, ncol = 1, dimnames = list(NULL, "emg")), timestamps = (seq_along(emg) - 1) / 1000, now_ns = 1e6) length(result$actions) closedLoopStop(controller, now_ns = 2e6)
Returned values are deep portable copies. They contain no runtime object, trigger credential, patient identifier, or raw physiological samples.
closedLoopState(controller) closedLoopLog(controller)closedLoopState(controller) closedLoopLog(controller)
controller |
A |
closedLoopState() returns the sealed state; closedLoopLog()
returns its bounded structured session log.
pipeline <- streamPipeline(chunk_size = 2048L) trigger <- loopbackTrigger(allowed_channels = "left", max_intensity = 20, intensity_unit = "mA", max_duration_ms = 500, refractory_ms = 0, deadman_ms = 1000) detector <- emgOnsetOp("emg", sampling_rate = 1000, baseline_samples = 30, rms_window_samples = 5) controller <- closedLoop(pipeline, trigger, detector, intensity = 2, stim_channel = "left", duration_ms = 10) closedLoopState(controller)$lifecycle$status closedLoopLog(controller)pipeline <- streamPipeline(chunk_size = 2048L) trigger <- loopbackTrigger(allowed_channels = "left", max_intensity = 20, intensity_unit = "mA", max_duration_ms = 500, refractory_ms = 0, deadman_ms = 1000) detector <- emgOnsetOp("emg", sampling_rate = 1000, baseline_samples = 30, rms_window_samples = 5) controller <- closedLoop(pipeline, trigger, detector, intensity = 2, stim_channel = "left", duration_ms = 10) closedLoopState(controller)$lifecycle$status closedLoopLog(controller)
closedLoop() is side-effect free with respect to the trigger transport. It
registers one governed detector operation in the supplied pipeline and owns
that graph and trigger for a terminal session lifecycle. Detector events are
decoded only after pipelineStep() commits; callbacks never stimulate.
closedLoop( pipeline, trigger, detector, intensity, stim_channel, duration_ms, delay_ms = 0, event_refractory_ms = 0, pending_capacity = 128L, log_capacity = 4096L, clock = NULL )closedLoop( pipeline, trigger, detector, intensity, stim_channel, duration_ms, delay_ms = 0, event_refractory_ms = 0, pending_capacity = 128L, log_capacity = 4096L, clock = NULL )
pipeline |
An empty-queue |
trigger |
A closed, disarmed |
detector |
An |
intensity, stim_channel, duration_ms
|
Explicit stimulation dose fields. |
delay_ms |
Detection-commit to attempt delay. Positive delays require a
later step or |
event_refractory_ms |
Controller-level detection refractory period. |
pending_capacity |
Maximum delayed actions. |
log_capacity |
Maximum retained audit records and detector identities. |
clock |
Optional deterministic monotonic clock, for loopback tests. |
A closed ClosedLoopController.
pipeline <- streamPipeline(chunk_size = 2048L) trigger <- loopbackTrigger(allowed_channels = "left", max_intensity = 20, intensity_unit = "mA", max_duration_ms = 500, refractory_ms = 0, deadman_ms = 1000) detector <- emgOnsetOp("emg", sampling_rate = 1000, baseline_samples = 30, rms_window_samples = 5) controller <- closedLoop(pipeline, trigger, detector, intensity = 2, stim_channel = "left", duration_ms = 10) closedLoopState(controller)$lifecycle$statuspipeline <- streamPipeline(chunk_size = 2048L) trigger <- loopbackTrigger(allowed_channels = "left", max_intensity = 20, intensity_unit = "mA", max_duration_ms = 500, refractory_ms = 0, deadman_ms = 1000) detector <- emgOnsetOp("emg", sampling_rate = 1000, baseline_samples = 30, rms_window_samples = 5) controller <- closedLoop(pipeline, trigger, detector, intensity = 2, stim_channel = "left", duration_ms = 10) closedLoopState(controller)$lifecycle$status
The bounded log records detection, queue, and acknowledgement evidence. Acknowledgement is explicitly not a physical-delivery claim. Raw samples and trigger runtime values are excluded.
closedLoopProvenance(controller, x, input_assay = NA_character_)closedLoopProvenance(controller, x, input_assay = NA_character_)
controller |
A stopped |
x |
A |
input_assay |
Exact existing assay name or |
A modified PhysioExperiment with one provenance activity.
pipeline <- streamPipeline(chunk_size = 2048L) trigger <- loopbackTrigger(allowed_channels = "left", max_intensity = 20, intensity_unit = "mA", max_duration_ms = 500, refractory_ms = 0, deadman_ms = 1000) detector <- emgOnsetOp("emg", sampling_rate = 1000, baseline_samples = 30, rms_window_samples = 5) controller <- closedLoop(pipeline, trigger, detector, intensity = 2, stim_channel = "left", duration_ms = 10) closedLoopStart(controller, "demo-session", now_ns = 0) closedLoopStep(controller, matrix(rnorm(100, sd = 0.08), ncol = 1, dimnames = list(NULL, "emg")), now_ns = 1e6) closedLoopStop(controller, now_ns = 2e6) pe <- PhysioExperiment(assays = list(raw = matrix(as.double(1:20), 10, 2)), samplingRate = 100) result <- closedLoopProvenance(controller, pe, "raw") length(S4Vectors::metadata(result)$provenance)pipeline <- streamPipeline(chunk_size = 2048L) trigger <- loopbackTrigger(allowed_channels = "left", max_intensity = 20, intensity_unit = "mA", max_duration_ms = 500, refractory_ms = 0, deadman_ms = 1000) detector <- emgOnsetOp("emg", sampling_rate = 1000, baseline_samples = 30, rms_window_samples = 5) controller <- closedLoop(pipeline, trigger, detector, intensity = 2, stim_channel = "left", duration_ms = 10) closedLoopStart(controller, "demo-session", now_ns = 0) closedLoopStep(controller, matrix(rnorm(100, sd = 0.08), ncol = 1, dimnames = list(NULL, "emg")), now_ns = 1e6) closedLoopStop(controller, now_ns = 2e6) pe <- PhysioExperiment(assays = list(raw = matrix(as.double(1:20), 10, 2)), samplingRate = 100) result <- closedLoopProvenance(controller, pe, "raw") length(S4Vectors::metadata(result)$provenance)
This changes timestamps only. It never interpolates or reorders samples.
dejitter( timestamps, nominal_srate, segments = NULL, anchor = c("least_squares", "first"), max_residual_seconds = Inf )dejitter( timestamps, nominal_srate, segments = NULL, anchor = c("least_squares", "first"), max_residual_seconds = Inf )
timestamps |
Finite non-decreasing timestamps. |
nominal_srate |
Positive nominal sampling rate. |
segments |
Optional positive labels or zero-based inclusive ranges. |
anchor |
Exact grid anchoring rule. |
max_residual_seconds |
Maximum permitted absolute jitter residual. |
Regular timestamps with a plain dejitter diagnostics attribute.
raw <- c(10.001, 10.010, 10.021, 10.029) regular <- dejitter(raw, nominal_srate = 100) as.numeric(regular)raw <- c(10.001, 10.010, 10.021, 10.029) regular <- dejitter(raw, nominal_srate = 100) as.numeric(regular)
Apply a governed stream-clock model
driftCorrect( timestamps, model, segments = NULL, extrapolate = c("bounded", "error"), max_extrapolation_seconds = 30 )driftCorrect( timestamps, model, segments = NULL, extrapolate = c("bounded", "error"), max_extrapolation_seconds = 30 )
timestamps |
Finite device-clock timestamps. |
model |
A valid |
segments |
Optional positive labels or zero-based inclusive ranges. |
extrapolate |
Exact extrapolation policy. |
max_extrapolation_seconds |
Non-negative bounded extrapolation limit. |
Corrected timestamps with a plain clock_correction attribute.
device <- seq(0, 10, by = 0.5) offset <- -0.4 + 0.005 * (device - median(device)) model <- clockOffset(device, offset, method = "ols") corrected <- driftCorrect(seq(0, 10, by = 1), model) as.numeric(corrected)device <- seq(0, 10, by = 0.5) offset <- -0.4 + 0.005 * (device - median(device)) model <- clockOffset(device, offset, method = "ols") corrected <- driftCorrect(seq(0, 10, by = 1), model) as.numeric(corrected)
The detector uses a trailing RMS envelope, a frozen warm-up baseline, and
explicit enter/release hysteresis. It is a pure onChunk() operation:
stimulation is attempted only by closedLoopStep() after pipeline commit.
Explicit signal-timestamp intervals must remain within five percent of the
declared sampling interval.
emgOnsetOp( channel, sampling_rate, baseline_samples, rms_window_samples, enter_z = 5, release_z = 2, min_on_samples = 3L, min_off_samples = 3L, min_baseline_sd = 1e-08 )emgOnsetOp( channel, sampling_rate, baseline_samples, rms_window_samples, enter_z = 5, release_z = 2, min_on_samples = 3L, min_off_samples = 3L, min_baseline_sd = 1e-08 )
channel |
Exact input channel name. |
sampling_rate |
Sampling rate in hertz. |
baseline_samples |
Number of complete-window envelope samples used for the frozen baseline. |
rms_window_samples |
Trailing causal RMS window length. |
enter_z, release_z
|
Baseline-standardized enter/release thresholds. |
min_on_samples, min_off_samples
|
Consecutive confirmation counts. |
min_baseline_sd |
Minimum accepted baseline scale. |
A governed PipelineOperation detector descriptor.
set.seed(1) emg <- c(rnorm(80, sd = 0.08), rep(1.5, 12), rnorm(30, sd = 0.08)) detector <- emgOnsetOp("emg", sampling_rate = 1000, baseline_samples = 50, rms_window_samples = 8) pipeline <- streamPipeline(chunk_size = length(emg)) onChunk(pipeline, detector) pipelineEnqueue(pipeline, matrix(emg, ncol = 1, dimnames = list(NULL, "emg")), seq_along(emg) / 1000) length(pipelineStep(pipeline)$results[[1]]$events)set.seed(1) emg <- c(rnorm(80, sd = 0.08), rep(1.5, 12), rnorm(30, sd = 0.08)) detector <- emgOnsetOp("emg", sampling_rate = 1000, baseline_samples = 50, rms_window_samples = 8) pipeline <- streamPipeline(chunk_size = length(emg)) onChunk(pipeline, detector) pipelineEnqueue(pipeline, matrix(emg, ncol = 1, dimnames = list(NULL, "emg")), seq_along(emg) / 1000) length(pipelineStep(pipeline)$results[[1]]$events)
A fixed causal biquad and trailing power window estimate band-power change from a frozen baseline. The result is an ERD proxy, not a clinical movement-intention diagnosis. Explicit signal-timestamp intervals must remain within five percent of the declared sampling interval.
erdIntentOp( channel, sampling_rate, band = c(8, 13), baseline_samples, power_window_samples, enter_percent = -30, release_percent = -15, min_on_samples = 3L, min_off_samples = 3L, min_baseline_power = 1e-12 )erdIntentOp( channel, sampling_rate, band = c(8, 13), baseline_samples, power_window_samples, enter_percent = -30, release_percent = -15, min_on_samples = 3L, min_off_samples = 3L, min_baseline_power = 1e-12 )
channel |
Exact input channel name. |
sampling_rate |
Sampling rate in hertz. |
band |
Two strictly increasing band edges in hertz. |
baseline_samples |
Number of complete power windows in the baseline. |
power_window_samples |
Trailing causal mean-square window length. |
enter_percent, release_percent
|
ERD enter/release percentages. |
min_on_samples, min_off_samples
|
Consecutive confirmation counts. |
min_baseline_power |
Minimum accepted frozen baseline power. |
A governed PipelineOperation detector descriptor.
detector <- erdIntentOp("eeg", sampling_rate = 1000, band = c(8, 13), baseline_samples = 40, power_window_samples = 10) class(detector)detector <- erdIntentOp("eeg", sampling_rate = 1000, band = c(8, 13), baseline_samples = 40, power_window_samples = 10) class(detector)
For forgetting = 1, the processor retains Chan-Golub-LeVeque sufficient
statistics and is batch-equivalent without retaining samples. Smaller
forgetting factors produce an explicitly exponentially weighted covariance.
incrementalPCA(n_components, n_features = NULL, forgetting = 1, center = TRUE)incrementalPCA(n_components, n_features = NULL, forgetting = 1, center = TRUE)
n_components |
Number of retained components. |
n_features |
Optional feature count to bind at construction. |
forgetting |
Per-sample covariance forgetting factor in |
center |
Whether to estimate and subtract a running mean. |
A mutable IncrementalPCA streaming processor.
set.seed(1) latent <- matrix(rnorm(300), ncol = 3) samples <- latent %*% matrix(rnorm(12), 3, 4) pca <- incrementalPCA(n_components = 2L) result <- update(pca, samples) result$diagnostics$explained_varianceset.seed(1) latent <- matrix(rnorm(300), ncol = 3) samples <- latent %*% matrix(rnorm(12), 3, 4) pca <- incrementalPCA(n_components = 2L) result <- update(pca, samples) result$diagnostics$explained_variance
Adaptive filters estimate the part of each signal channel linearly predictable from its paired reference. The returned residual is not inherently a clean physiological signal; that interpretation requires a caller-owned reference-noise assumption.
lmsFilter(n_taps, step_size, leakage = 0, initial_weights = NULL)lmsFilter(n_taps, step_size, leakage = 0, initial_weights = NULL)
n_taps |
Exact adaptive-filter order. |
step_size |
Positive LMS step size. |
leakage |
Weight leakage in |
initial_weights |
Optional finite |
A mutable LMSFilter streaming processor.
# Cancel a reference-correlated component from a short synthetic signal. set.seed(1) reference <- sin(2 * pi * 5 * seq_len(200) / 100) signal <- 0.4 * reference + rnorm(200, sd = 0.05) filt <- lmsFilter(n_taps = 4L, step_size = 0.05) result <- update(filt, matrix(signal, ncol = 1), reference = matrix(reference, ncol = 1)) str(result$output)# Cancel a reference-correlated component from a short synthetic signal. set.seed(1) reference <- sin(2 * pi * 5 * seq_len(200) / 100) signal <- 0.4 * reference + rnorm(200, sd = 0.05) filt <- lmsFilter(n_taps = 4L, step_size = 0.05) result <- update(filt, matrix(signal, ncol = 1), reference = matrix(reference, ncol = 1)) str(result$output)
Feed a loopback source
loopbackFeed(x, samples, timestamps)loopbackFeed(x, samples, timestamps)
x |
An open |
samples |
A finite sample-by-channel real matrix. |
timestamps |
Strictly increasing finite timestamps. |
x, invisibly. The native buffer is modified by reference.
info <- streamInfo("demo", type = "EEG", channel_names = c("C3", "C4"), nominal_srate = 100) src <- streamOpen(loopbackSource(info, capacity = 32L)) loopbackFeed(src, matrix(as.double(1:6), 3, 2), c(0.01, 0.02, 0.03)) streamPull(src)$countinfo <- streamInfo("demo", type = "EEG", channel_names = c("C3", "C4"), nominal_srate = 100) src <- streamOpen(loopbackSource(info, capacity = 32L)) loopbackFeed(src, matrix(as.double(1:6), 3, 2), c(0.01, 0.02, 0.03)) streamPull(src)$count
Construct a loopback source
loopbackSource(info, capacity = 1024L)loopbackSource(info, capacity = 1024L)
info |
Numeric |
capacity |
Exact positive ring capacity. |
A closed-over loopback source in the "created" state.
info <- streamInfo("demo", type = "EEG", channel_names = c("C3", "C4"), nominal_srate = 100) src <- streamOpen(loopbackSource(info, capacity = 32L)) streamState(src)info <- streamInfo("demo", type = "EEG", channel_names = c("C3", "C4"), nominal_srate = 100) src <- streamOpen(loopbackSource(info, capacity = 32L)) streamState(src)
Deterministic in-process stream source
bufferLive native ring buffer.
info <- streamInfo("demo", type = "EEG", channel_names = c("C3", "C4"), nominal_srate = 100) src <- loopbackSource(info, capacity = 16L) is(src, "LoopbackSource")info <- streamInfo("demo", type = "EEG", channel_names = c("C3", "C4"), nominal_srate = 100) src <- loopbackSource(info, capacity = 16L) is(src, "LoopbackSource")
The loopback backend records commands and timing without opening a device or network connection. It exercises the same arm, dead-man, maximum, duplicate ID, and refractory gates as live transports.
loopbackTrigger( allowed_channels, max_intensity, intensity_unit, max_duration_ms, refractory_ms, deadman_ms, audit_capacity = 4096L, clock = NULL )loopbackTrigger( allowed_channels, max_intensity, intensity_unit, max_duration_ms, refractory_ms, deadman_ms, audit_capacity = 4096L, clock = NULL )
allowed_channels |
Unique allowed channel identifiers. |
max_intensity |
Positive scalar or exactly channel-named maxima. |
intensity_unit |
Explicit device intensity unit; no conversion occurs. |
max_duration_ms |
Positive scalar or channel-named duration maxima. |
refractory_ms |
Non-negative scalar or channel-named refractory times. |
deadman_ms |
Positive heartbeat expiry interval. |
audit_capacity |
Bounded command/audit capacity. |
clock |
Optional deterministic monotonic clock for tests. |
A closed, disarmed LoopbackTrigger.
trigger <- loopbackTrigger(allowed_channels = "left", max_intensity = 20, intensity_unit = "mA", max_duration_ms = 500, refractory_ms = 0, deadman_ms = 1000) triggerState(trigger)$lifecycle$statustrigger <- loopbackTrigger(allowed_channels = "left", max_intensity = 20, intensity_unit = "mA", max_duration_ms = 500, refractory_ms = 0, deadman_ms = 1000) triggerState(trigger)$lifecycle$status
The default probe is conservative and does not initialize Python. Explicit transport operations initialize only the already configured reticulate interpreter; no function installs or selects an environment.
lslAvailable(backend = c("auto", "pylsl"), initialize = FALSE)lslAvailable(backend = c("auto", "pylsl"), initialize = FALSE)
backend |
Exact backend, currently |
initialize |
Whether the configured Python interpreter may be initialized to verify that pylsl and liblsl load. |
One logical value.
# Reports FALSE unless pylsl and liblsl are installed and loadable. lslAvailable()# Reports FALSE unless pylsl and liblsl are installed and loadable. lslAvailable()
Report the configured LSL backend
lslBackendInfo(backend = c("auto", "pylsl"))lslBackendInfo(backend = c("auto", "pylsl"))
backend |
Exact backend, currently |
A serializable named list of backend and runtime versions.
# Resolving the backend version requires pylsl/liblsl to be installed. if (lslAvailable()) str(lslBackendInfo())# Resolving the backend version requires pylsl/liblsl to be installed. if (lslAvailable()) str(lslBackendInfo())
Construction validates R state only. Assign the result of streamOpen() to
open the already configured backend.
lslInlet( info, capacity = 4096L, max_chunk = 1024L, recover = TRUE, processing = c("none", "clocksync"), marker_capacity = 4096L, backend = getOption("PhysioStream.lsl_backend", "auto") )lslInlet( info, capacity = 4096L, max_chunk = 1024L, recover = TRUE, processing = c("none", "clocksync"), marker_capacity = 4096L, backend = getOption("PhysioStream.lsl_backend", "auto") )
info |
A resolved |
capacity |
Numeric ring capacity. |
max_chunk |
Maximum rows per backend pull. |
recover |
Whether liblsl may recover by non-empty source id. |
processing |
Exact timestamp processing mode. |
marker_capacity |
String marker queue capacity. |
backend |
Exact backend, currently |
An LSLInlet in state "created".
# Requires a running LSL stream on the local network. if (lslAvailable()) { found <- lslResolveStreams(timeout = 0.2) if (length(found)) { inlet <- streamOpen(lslInlet(found[[1]])) streamClose(inlet) } }# Requires a running LSL stream on the local network. if (lslAvailable()) { found <- lslResolveStreams(timeout = 0.2) if (length(found)) { inlet <- streamOpen(lslInlet(found[[1]])) streamClose(inlet) } }
Live backend and buffer state are intentionally reference-like and do not survive serialization.
runtimePrivate live backend state.
bufferNumeric ring buffer or bounded marker queue.
max_chunkMaximum rows requested per pull.
recoverWhether liblsl source recovery is enabled.
processingExact timestamp processing mode.
marker_capacityMarker queue capacity.
# Concrete inlet objects come from lslInlet(); see ?lslInlet. isVirtualClass("LSLInlet")# Concrete inlet objects come from lslInlet(); see ?lslInlet. isVirtualClass("LSLInlet")
Marker timestamps remain in the LSL clock domain unless an explicit
time_origin is subtracted. Cross-stream alignment is handled separately.
lslMarkerEvents(x, n = NULL, consume = FALSE, time_origin = 0, type = NULL)lslMarkerEvents(x, n = NULL, consume = FALSE, time_origin = 0, type = NULL)
x |
An open string/irregular-rate |
n |
Optional newest marker count. |
consume |
Whether to consume the complete oldest selection. |
time_origin |
Finite scalar subtracted from LSL timestamps. |
type |
Optional event type; defaults to the stream type. |
A valid PhysioExperiment::PhysioEvents.
# Requires an open marker inlet bound to a live LSL marker stream. if (lslAvailable()) { found <- lslResolveStreams(property = "type", value = "Markers", timeout = 0.2) if (length(found)) { inlet <- streamOpen(lslInlet(found[[1]])) events <- lslMarkerEvents(inlet) streamClose(inlet) } }# Requires an open marker inlet bound to a live LSL marker stream. if (lslAvailable()) { found <- lslResolveStreams(property = "type", value = "Markers", timeout = 0.2) if (length(found)) { inlet <- streamOpen(lslInlet(found[[1]])) events <- lslMarkerEvents(inlet) streamClose(inlet) } }
Construct an LSL outlet
lslOutlet( info, chunk_size = 0L, max_buffered = 360L, backend = getOption("PhysioStream.lsl_backend", "auto") )lslOutlet( info, chunk_size = 0L, max_buffered = 360L, backend = getOption("PhysioStream.lsl_backend", "auto") )
info |
A valid numeric regular-rate or string irregular-rate
|
chunk_size |
Exact non-negative LSL chunk preference. |
max_buffered |
Exact positive LSL sender buffer bound. |
backend |
Exact backend, currently |
An LSLOutlet in state "created".
# Publishing requires the LSL runtime (pylsl/liblsl). if (lslAvailable()) { info <- streamInfo("demo", type = "EEG", channel_names = c("C3", "C4"), nominal_srate = 100) outlet <- streamOpen(lslOutlet(info)) lslPush(outlet, matrix(as.double(1:4), 2, 2)) streamClose(outlet) }# Publishing requires the LSL runtime (pylsl/liblsl). if (lslAvailable()) { info <- streamInfo("demo", type = "EEG", channel_names = c("C3", "C4"), nominal_srate = 100) outlet <- streamOpen(lslOutlet(info)) lslPush(outlet, matrix(as.double(1:4), 2, 2)) streamClose(outlet) }
Lab Streaming Layer outlet
runtimePrivate live backend state.
chunk_sizePreferred LSL chunk size.
max_bufferedLSL sender buffer bound.
# Concrete outlet objects come from lslOutlet(); see ?lslOutlet. isVirtualClass("LSLOutlet")# Concrete outlet objects come from lslOutlet(); see ?lslOutlet. isVirtualClass("LSLOutlet")
Pull one LSL chunk into an inlet buffer
lslPull(x, max_samples = x@max_chunk, timeout = 0)lslPull(x, max_samples = x@max_chunk, timeout = 0)
x |
An open |
max_samples |
Exact number of samples requested, at most the inlet chunk bound. |
timeout |
Finite non-negative backend timeout. |
A serializable pull summary. Payload remains in the bounded inlet buffer or marker queue.
# Requires an open inlet bound to a live LSL stream. if (lslAvailable()) { found <- lslResolveStreams(timeout = 0.2) if (length(found)) { inlet <- streamOpen(lslInlet(found[[1]])) summary <- lslPull(inlet, max_samples = 32) streamClose(inlet) } }# Requires an open inlet bound to a live LSL stream. if (lslAvailable()) { found <- lslResolveStreams(timeout = 0.2) if (length(found)) { inlet <- streamOpen(lslInlet(found[[1]])) summary <- lslPull(inlet, max_samples = 32) streamClose(inlet) } }
Push samples or markers through an LSL outlet
lslPush(x, samples, timestamps = NULL, pushthrough = TRUE)lslPush(x, samples, timestamps = NULL, pushthrough = TRUE)
x |
An open |
samples |
A strict sample-by-channel numeric or character matrix. |
timestamps |
Optional explicit timestamp per row. |
pushthrough |
Whether the final backend operation flushes the chunk. |
A serializable push summary.
# Requires the LSL runtime (pylsl/liblsl). if (lslAvailable()) { info <- streamInfo("demo", type = "EEG", channel_names = c("C3", "C4"), nominal_srate = 100) outlet <- streamOpen(lslOutlet(info)) lslPush(outlet, matrix(as.double(1:4), 2, 2)) streamClose(outlet) }# Requires the LSL runtime (pylsl/liblsl). if (lslAvailable()) { info <- streamInfo("demo", type = "EEG", channel_names = c("C3", "C4"), nominal_srate = 100) outlet <- streamOpen(lslOutlet(info)) lslPush(outlet, matrix(as.double(1:4), 2, 2)) streamClose(outlet) }
pylsl resolution returns short descriptors. PhysioStream therefore opens a bounded, non-recovering metadata inlet for each result, retrieves its full descriptor, and closes the temporary inlet before returning. No samples are pulled.
lslResolveStreams( property = NULL, value = NULL, minimum = 0L, timeout = 1, backend = getOption("PhysioStream.lsl_backend", "auto") )lslResolveStreams( property = NULL, value = NULL, minimum = 0L, timeout = 1, backend = getOption("PhysioStream.lsl_backend", "auto") )
property, value
|
Either both |
minimum |
Exact minimum number of descriptors required. |
timeout |
Finite non-negative resolver timeout in seconds. |
backend |
Exact backend, currently |
A list of validated StreamInfo objects in backend order.
# Requires a running LSL network with at least one stream. if (lslAvailable()) { streams <- lslResolveStreams(timeout = 0.2) }# Requires a running LSL network with at least one stream. if (lslAvailable()) { streams <- lslResolveStreams(timeout = 0.2) }
The default benchmark processes 60 seconds of deterministic 32-channel, 512-Hz data in 32-sample chunks through the compiled causal bandpass plus rolling-RMS operation. Input generation and warmup are outside the measured interval. Results describe empirical performance on the recorded hardware, not a hard real-time guarantee.
measureLatency( pipeline = NULL, n_channels = 32L, sampling_rate = 512, hop_samples = 32L, duration_s = 60, warmup_chunks = 64L, seed = 1L )measureLatency( pipeline = NULL, n_channels = 32L, sampling_rate = 512, hop_samples = 32L, duration_s = 60, warmup_chunks = 64L, seed = 1L )
pipeline |
Optional empty |
n_channels |
Exact positive channel count. |
sampling_rate |
Finite positive sampling rate. |
hop_samples |
Exact positive samples per chunk. |
duration_s |
Finite positive duration whose sample count is divisible
by |
warmup_chunks |
Exact non-negative warmup chunk count. |
seed |
Exact non-negative local random seed. |
A plain benchmark result with summary, latency rows, pipeline state, hardware, configuration, and reference hash.
# Tiny deterministic benchmark; sampling_rate * duration_s must divide # evenly by hop_samples. bench <- measureLatency(n_channels = 2L, sampling_rate = 100, hop_samples = 10L, duration_s = 0.5, warmup_chunks = 1L, seed = 1L) bench$summary$count# Tiny deterministic benchmark; sampling_rate * duration_s must divide # evenly by hop_samples. bench <- measureLatency(n_channels = 2L, sampling_rate = 100, hop_samples = 10L, duration_s = 0.5, warmup_chunks = 1L, seed = 1L) bench$summary$count
The conservative availability probe does not select or install Python. Initialization is explicit and uses only the interpreter already configured for reticulate.
mqttAvailable(initialize = FALSE) mqttBackendInfo()mqttAvailable(initialize = FALSE) mqttBackendInfo()
initialize |
Whether the configured Python interpreter may be initialized to verify pinned Paho. |
mqttAvailable() returns one logical value.
mqttBackendInfo() returns a plain capability list.
# Reports FALSE unless the pinned Paho MQTT backend is installed. mqttAvailable()# Reports FALSE unless the pinned Paho MQTT backend is installed. mqttAvailable()
Uses MQTT v5, Paho callback API VERSION2, a caller-thread manual network
loop, QoS 0 or 1, and retain = FALSE. QoS 1 can duplicate a command, so the
receiver must deduplicate exact command IDs. No exactly-once or physical
delivery claim is made.
mqttTrigger( host, topic, client_id, allowed_channels, max_intensity, intensity_unit, max_duration_ms, refractory_ms, deadman_ms, port = 8883L, qos = 1L, tls = c("required", "disabled"), allow_insecure_localhost = FALSE, username = NULL, password = NULL, connect_timeout_ms = 5000, publish_timeout_ms = 1000, audit_capacity = 4096L, clock = NULL )mqttTrigger( host, topic, client_id, allowed_channels, max_intensity, intensity_unit, max_duration_ms, refractory_ms, deadman_ms, port = 8883L, qos = 1L, tls = c("required", "disabled"), allow_insecure_localhost = FALSE, username = NULL, password = NULL, connect_timeout_ms = 5000, publish_timeout_ms = 1000, audit_capacity = 4096L, clock = NULL )
host |
Broker hostname or IP literal. |
topic |
Concrete publish topic without wildcards. |
client_id |
Bounded MQTT client identifier. |
allowed_channels |
Unique allowed channel identifiers. |
max_intensity |
Positive scalar or exactly channel-named maxima. |
intensity_unit |
Explicit device intensity unit; no conversion occurs. |
max_duration_ms |
Positive scalar or channel-named duration maxima. |
refractory_ms |
Non-negative scalar or channel-named refractory times. |
deadman_ms |
Positive heartbeat expiry interval. |
port |
Integer broker port. |
qos |
Exact MQTT QoS, 0 or 1. |
tls |
Exact |
allow_insecure_localhost |
Permit plaintext only for a loopback host. |
username |
Optional broker username, excluded from portable state. |
password |
Optional broker password, excluded from portable state. |
connect_timeout_ms |
Positive bounded connect timeout. |
publish_timeout_ms |
Positive bounded acknowledgement timeout. |
audit_capacity |
Bounded command/audit capacity. |
clock |
Optional deterministic monotonic clock for tests. |
TLS is required by default. Plaintext is restricted to an explicitly acknowledged loopback-only test broker.
A closed, disarmed MqttTrigger.
## Not run: # Requires a reachable MQTT broker; see loopbackTrigger() for an # offline equivalent with the same interlocks. trigger <- mqttTrigger(host = "127.0.0.1", port = 1883, topic = "stim/commands", allowed_channels = "left", max_intensity = 20, intensity_unit = "mA", max_duration_ms = 500, refractory_ms = 0, deadman_ms = 1000) ## End(Not run)## Not run: # Requires a reachable MQTT broker; see loopbackTrigger() for an # offline equivalent with the same interlocks. trigger <- mqttTrigger(host = "127.0.0.1", port = 1883, topic = "stim/commands", allowed_channels = "left", max_intensity = 20, intensity_unit = "mA", max_duration_ms = 500, refractory_ms = 0, deadman_ms = 1000) ## End(Not run)
Stateful normalized LMS adaptive filter
nlmsFilter( n_taps, step_size = 0.5, epsilon = 1e-08, leakage = 0, initial_weights = NULL )nlmsFilter( n_taps, step_size = 0.5, epsilon = 1e-08, leakage = 0, initial_weights = NULL )
n_taps |
Exact adaptive-filter order. |
step_size |
Positive normalized step size; values in |
epsilon |
Positive denominator regularizer. |
leakage |
Weight leakage in |
initial_weights |
Optional finite |
A mutable NLMSFilter streaming processor.
set.seed(1) reference <- sin(2 * pi * 8 * seq_len(200) / 100) signal <- 0.3 * reference + rnorm(200, sd = 0.05) filt <- nlmsFilter(n_taps = 4L, step_size = 0.5) result <- update(filt, matrix(signal, ncol = 1), reference = matrix(reference, ncol = 1)) tail(result$output)set.seed(1) reference <- sin(2 * pi * 8 * seq_len(200) / 100) signal <- 0.3 * reference + rnorm(200, sd = 0.05) filt <- nlmsFilter(n_taps = 4L, step_size = 0.5) result <- update(filt, matrix(signal, ncol = 1), reference = matrix(reference, ncol = 1)) tail(result$output)
The callback is invoked as callback(chunk, state, context) and must return
a list containing output, state, events, and diagnostics. Operation
states are committed only after the full graph succeeds.
onChunk(pipeline, callback, state = NULL, name = NULL, kind = "filter")onChunk(pipeline, callback, state = NULL, name = NULL, kind = "filter")
pipeline |
A |
callback |
A callback function or a |
state |
Initial bounded plain-list state for a custom callback. |
name |
Unique non-empty operation name. |
kind |
Exact operation kind. |
pipeline, invisibly.
sos <- matrix(c(1, 0, 0, 1, 0, 0), 1L, 6L, dimnames = list(NULL, c("b0", "b1", "b2", "a0", "a1", "a2"))) pipeline <- streamPipeline(chunk_size = 8L) onChunk(pipeline, bandpassRmsOp(sos, window_samples = 4L)) length(pipelineState(pipeline)$operations)sos <- matrix(c(1, 0, 0, 1, 0, 0), 1L, 6L, dimnames = list(NULL, c("b0", "b1", "b2", "a0", "a1", "a2"))) pipeline <- streamPipeline(chunk_size = 8L) onChunk(pipeline, bandpassRmsOp(sos, window_samples = 4L)) length(pipelineState(pipeline)$operations)
The processor combines exponentially weighted whitening with a natural gradient update and symmetric decorrelation. Reported components have a deterministic order and sign, but physiological source identity is not inferred. Only complete update blocks are emitted; an incomplete block is retained with its timestamps and sequence identity until a later update.
onlineICA( n_components = NULL, n_features = NULL, learning_rate = 0.01, forgetting = 0.995, nonlinearity = c("tanh", "extended"), block_size = 1L, orthogonalize_every = 1L, seed = 1L )onlineICA( n_components = NULL, n_features = NULL, learning_rate = 0.01, forgetting = 0.995, nonlinearity = c("tanh", "extended"), block_size = 1L, orthogonalize_every = 1L, seed = 1L )
n_components |
Optional number of sources; defaults to all features. |
n_features |
Optional feature count to bind at construction. |
learning_rate |
Positive natural-gradient step size. |
forgetting |
Per-sample whitening forgetting factor in |
nonlinearity |
Exact |
block_size |
Exact positive update block size. |
orthogonalize_every |
Exact positive number of blocks between symmetric decorrelations. |
seed |
Exact integer used only for deterministic initialization. |
A mutable OnlineICA streaming processor.
set.seed(1) sources <- cbind(sin(seq_len(200) / 5), sign(sin(seq_len(200) / 7))) samples <- sources %*% t(matrix(c(1, 0.4, 0.3, 1), 2, 2)) ica <- onlineICA(n_components = 2L, block_size = 10L) result <- update(ica, samples) dim(result$output)set.seed(1) sources <- cbind(sin(seq_len(200) / 5), sign(sin(seq_len(200) / 7))) samples <- sources %*% t(matrix(c(1, 0.4, 0.3, 1), 2, 2)) ica <- onlineICA(n_components = 2L, block_size = 10L) result <- update(ica, samples) dim(result$output)
The detector fits a sinusoid at a fixed frequency using only the trailing
window ending at each current sample. closedLoop() binds its configured
detection-to-stimulation delay before registration so the event reports the
predicted phase at the due time. Explicit signal-timestamp intervals must
remain within five percent of the declared sampling interval.
phaseTargetOp( channel, sampling_rate, frequency, target_degrees, window_cycles = 3, tolerance_degrees = 20, min_amplitude = 0, min_fit = 0.8 )phaseTargetOp( channel, sampling_rate, frequency, target_degrees, window_cycles = 3, tolerance_degrees = 20, min_amplitude = 0, min_fit = 0.8 )
channel |
Exact input channel name. |
sampling_rate |
Sampling rate in hertz. |
frequency |
Target oscillation frequency in hertz. |
target_degrees |
Requested phase in |
window_cycles |
Number of trailing cycles in the causal fit. |
tolerance_degrees |
Maximum accepted predicted signed phase error. |
min_amplitude |
Minimum fitted oscillation amplitude. |
min_fit |
Minimum coefficient of determination for the sinusoid fit. |
A governed PipelineOperation detector descriptor.
sr <- 500 t <- seq_len(800) / sr eeg <- 1.7 + 2.5 * cos(2 * pi * 9 * t + 0.7) detector <- phaseTargetOp("eeg", sampling_rate = sr, frequency = 9, target_degrees = 90) pipeline <- streamPipeline(chunk_size = length(eeg)) onChunk(pipeline, detector) pipelineEnqueue(pipeline, matrix(eeg, ncol = 1, dimnames = list(NULL, "eeg")), t) length(pipelineStep(pipeline)$results[[1]]$events)sr <- 500 t <- seq_len(800) / sr eeg <- 1.7 + 2.5 * cos(2 * pi * 9 * t + 0.7) detector <- phaseTargetOp("eeg", sampling_rate = sr, frequency = 9, target_degrees = 90) pipeline <- streamPipeline(chunk_size = length(eeg)) onChunk(pipeline, detector) pipelineEnqueue(pipeline, matrix(eeg, ncol = 1, dimnames = list(NULL, "eeg")), t) length(pipelineStep(pipeline)$results[[1]]$events)
Inspect or reset governed pipeline state
pipelineState(pipeline) pipelineReset(pipeline, keep_operations = TRUE)pipelineState(pipeline) pipelineReset(pipeline, keep_operations = TRUE)
pipeline |
A |
keep_operations |
Whether reset retains the registered graph and resets each operation to its initial state. |
pipelineState() returns a deep plain list; pipelineReset()
returns the pipeline invisibly.
pipeline <- streamPipeline(chunk_size = 8L) pipelineState(pipeline)$configuration$chunk_size pipelineReset(pipeline)pipeline <- streamPipeline(chunk_size = 8L) pipelineState(pipeline)$configuration$chunk_size pipelineReset(pipeline)
Full-queue behavior is governed by the pipeline's exact backpressure policy. Empty chunks are no-ops and consume neither capacity nor sequence identity.
pipelineEnqueue(pipeline, samples, timestamps = NULL, ingest_time_ns = NULL)pipelineEnqueue(pipeline, samples, timestamps = NULL, ingest_time_ns = NULL)
pipeline |
A |
samples |
Finite sample-by-channel values. |
timestamps |
Optional strictly increasing row-matched timestamps. |
ingest_time_ns |
Optional process-local monotonic nanosecond stamp. |
pipeline, invisibly.
pipeline <- streamPipeline(chunk_size = 4L) x <- matrix(1:8, 4L, 2L, dimnames = list(NULL, c("a", "b"))) pipelineEnqueue(pipeline, x, c(1, 2, 3, 4)) pipelineState(pipeline)$channel_namespipeline <- streamPipeline(chunk_size = 4L) x <- matrix(1:8, 4L, 2L, dimnames = list(NULL, c("a", "b"))) pipelineEnqueue(pipeline, x, c(1, 2, 3, 4)) pipelineState(pipeline)$channel_names
The source must already be open. The function never starts a background thread and stops on an empty pull rather than busy-spinning.
pipelineRun(pipeline, max_chunks = Inf, timeout = 0)pipelineRun(pipeline, max_chunks = Inf, timeout = 0)
pipeline |
A source-backed |
max_chunks |
Maximum chunks to process; |
timeout |
Finite non-negative overall run time in seconds. Zero means no wall-time limit for the current call. |
A plain list of processed results and stop diagnostics.
info <- streamInfo("demo", type = "EEG", channel_names = "C3", nominal_srate = 100) src <- streamOpen(loopbackSource(info, capacity = 64L)) loopbackFeed(src, matrix(sin(seq_len(16)), 16, 1), seq_len(16) / 100) sos <- matrix(c(1, 0, 0, 1, 0, 0), 1L, 6L, dimnames = list(NULL, c("b0", "b1", "b2", "a0", "a1", "a2"))) pipeline <- streamPipeline(source = src, chunk_size = 8L) onChunk(pipeline, bandpassRmsOp(sos, window_samples = 4L)) run <- pipelineRun(pipeline) run$n_processedinfo <- streamInfo("demo", type = "EEG", channel_names = "C3", nominal_srate = 100) src <- streamOpen(loopbackSource(info, capacity = 64L)) loopbackFeed(src, matrix(sin(seq_len(16)), 16, 1), seq_len(16) / 100) sos <- matrix(c(1, 0, 0, 1, 0, 0), 1L, 6L, dimnames = list(NULL, c("b0", "b1", "b2", "a0", "a1", "a2"))) pipeline <- streamPipeline(source = src, chunk_size = 8L) onChunk(pipeline, bandpassRmsOp(sos, window_samples = 4L)) run <- pipelineRun(pipeline) run$n_processed
Process queued chunks synchronously
pipelineStep(pipeline, n = 1L)pipelineStep(pipeline, n = 1L)
pipeline |
A |
n |
Maximum whole chunks to process. |
A plain list containing per-chunk results and counts. Each result
includes event_sources, a plain operation identity parallel to events.
sos <- matrix(c(1, 0, 0, 1, 0, 0), 1L, 6L, dimnames = list(NULL, c("b0", "b1", "b2", "a0", "a1", "a2"))) pipeline <- streamPipeline(chunk_size = 8L) onChunk(pipeline, bandpassRmsOp(sos, window_samples = 4L)) x <- matrix(sin(seq_len(16)), 8, 2, dimnames = list(NULL, c("C3", "C4"))) pipelineEnqueue(pipeline, x, ingest_time_ns = 0) pipelineStep(pipeline)$n_processedsos <- matrix(c(1, 0, 0, 1, 0, 0), 1L, 6L, dimnames = list(NULL, c("b0", "b1", "b2", "a0", "a1", "a2"))) pipeline <- streamPipeline(chunk_size = 8L) onChunk(pipeline, bandpassRmsOp(sos, window_samples = 4L)) x <- matrix(sin(seq_len(16)), 8, 2, dimnames = list(NULL, c("C3", "C4"))) pipelineEnqueue(pipeline, x, ingest_time_ns = 0) pipelineStep(pipeline)$n_processed
processorState() returns a deep, portable copy of the numeric processor
state. processorReset() clears learned and delay state, increments the
reset counter, and can retain the bound channel identity.
processorState(object, ...) processorReset(object, keep_channels = TRUE, ...)processorState(object, ...) processorReset(object, keep_channels = TRUE, ...)
object |
A |
... |
Reserved for methods. |
keep_channels |
Whether to retain channel identity across reset. |
processorState() returns a plain list. processorReset() returns
object invisibly.
filt <- lmsFilter(n_taps = 3L, step_size = 0.05) update(filt, matrix(1:8, 4L, 2L), reference = matrix(1:4, 4L, 1L)) processorState(filt)$n_samples processorReset(filt) processorState(filt)$n_samplesfilt <- lmsFilter(n_taps = 3L, step_size = 0.05) update(filt, matrix(1:8, 4L, 2L), reference = matrix(1:4, 4L, 1L)) processorState(filt)$n_samples processorReset(filt) processorState(filt)$n_samples
XDF timestamps are authoritative in rowData(x)$xdf_time; the regular
streamTimeIndex() grid of the returned multi-rate container is only a
nominal convenience for jittered or irregular streams. Files and converted
payloads are also bounded by a private 512 MiB in-memory ceiling even when
max_file_bytes is larger.
readXDF( path, streams = NULL, synchronize = TRUE, dejitter = TRUE, keep_raw_timestamps = TRUE, max_file_bytes = 2^31 - 1, backend = getOption("PhysioStream.xdf_backend", "auto") )readXDF( path, streams = NULL, synchronize = TRUE, dejitter = TRUE, keep_raw_timestamps = TRUE, max_file_bytes = 2^31 - 1, backend = getOption("PhysioStream.xdf_backend", "auto") )
path |
Existing local |
streams |
Optional unique stream ids or unambiguous stream names. |
synchronize |
Whether pyxdf clock synchronization is applied. |
dejitter |
Whether pyxdf timestamp de-jittering is applied. |
keep_raw_timestamps |
Whether a second uncorrected pass is retained. |
max_file_bytes |
Exact positive file-size ceiling. |
backend |
Exact configured backend. |
A valid PhysioExperiment::MultiPhysioExperiment.
# Reading XDF requires the pyxdf backend (via reticulate). if (xdfAvailable()) { path <- system.file("extdata", "xdf-minimal.xdf", package = "PhysioStream") container <- readXDF(path) }# Reading XDF requires the pyxdf backend (via reticulate). if (xdfAvailable()) { path <- system.file("extdata", "xdf-minimal.xdf", package = "PhysioStream") container <- readXDF(path) }
Pull or inspect buffered samples
ringPull(x, n = ringFill(x)) ringPeek(x, n = ringFill(x), from = "oldest")ringPull(x, n = ringFill(x)) ringPeek(x, n = ringFill(x), from = "oldest")
x |
A live |
n |
Non-negative exact number of rows. Requests larger than the current fill return all available rows. |
from |
Exact peek side, either |
A named list with samples, timestamps, sequence, count, and
stats_after.
info <- streamInfo("demo", type = "EEG", channel_names = c("C3", "C4"), nominal_srate = 100) buffer <- ringBuffer(info, capacity = 8L) ringPush(buffer, matrix(as.double(1:6), 3, 2), c(0.01, 0.02, 0.03)) ringPeek(buffer, 2L)$samples ringPull(buffer)$sequenceinfo <- streamInfo("demo", type = "EEG", channel_names = c("C3", "C4"), nominal_srate = 100) buffer <- ringBuffer(info, capacity = 8L) ringPush(buffer, matrix(as.double(1:6), 3, 2), c(0.01, 0.02, 0.03)) ringPeek(buffer, 2L)$samples ringPull(buffer)$sequence
Ring-buffer capacity and occupancy
ringCapacity(x) ringFill(x) ringStats(x)ringCapacity(x) ringFill(x) ringStats(x)
x |
A live |
ringCapacity() and ringFill() return integer scalars.
ringStats() returns a serializable named list.
info <- streamInfo("demo", type = "EEG", channel_names = c("C3", "C4"), nominal_srate = 100) buffer <- ringBuffer(info, capacity = 8L) ringPush(buffer, matrix(as.double(1:6), 3, 2), c(0.01, 0.02, 0.03)) ringCapacity(buffer) ringFill(buffer) ringStats(buffer)$fillinfo <- streamInfo("demo", type = "EEG", channel_names = c("C3", "C4"), nominal_srate = 100) buffer <- ringBuffer(info, capacity = 8L) ringPush(buffer, matrix(as.double(1:6), 3, 2), c(0.01, 0.02, 0.03)) ringCapacity(buffer) ringFill(buffer) ringStats(buffer)$fill
Construct a native ring buffer
ringBuffer(info, capacity)ringBuffer(info, capacity)
info |
Numeric |
capacity |
Exact positive sample capacity. |
A live RingBuffer.
info <- streamInfo("demo", type = "EEG", channel_names = c("C3", "C4"), nominal_srate = 100) buffer <- ringBuffer(info, capacity = 16L) ringCapacity(buffer)info <- streamInfo("demo", type = "EEG", channel_names = c("C3", "C4"), nominal_srate = 100) buffer <- ringBuffer(info, capacity = 16L) ringCapacity(buffer)
Copying a RingBuffer wrapper aliases the same native buffer. The native
queue is not serialized; a restored wrapper fails on its first operation.
ptrProtected native pointer.
infoImmutable stream metadata.
capacityFixed sample capacity.
schema_versionWrapper schema.
info <- streamInfo("demo", type = "EEG", channel_names = c("C3", "C4"), nominal_srate = 100) buffer <- ringBuffer(info, capacity = 8L) ringPush(buffer, matrix(as.double(1:4), 2, 2), c(0.01, 0.02)) ringFill(buffer)info <- streamInfo("demo", type = "EEG", channel_names = c("C3", "C4"), nominal_srate = 100) buffer <- ringBuffer(info, capacity = 8L) ringPush(buffer, matrix(as.double(1:4), 2, 2), c(0.01, 0.02)) ringFill(buffer)
The push is transactional. When unread rows are overwritten, the oldest rows are dropped and counted. Matrices are always sample by channel.
ringPush(x, samples, timestamps)ringPush(x, samples, timestamps)
x |
A live |
samples |
A finite real matrix, sample by channel. |
timestamps |
Strictly increasing finite timestamps. |
Updated serializable ring statistics, invisibly.
info <- streamInfo("demo", type = "EEG", channel_names = c("C3", "C4"), nominal_srate = 100) buffer <- ringBuffer(info, capacity = 8L) ringPush(buffer, matrix(as.double(1:4), 2, 2), c(0.01, 0.02)) ringFill(buffer)info <- streamInfo("demo", type = "EEG", channel_names = c("C3", "C4"), nominal_srate = 100) buffer <- ringBuffer(info, capacity = 8L) ringPush(buffer, matrix(as.double(1:4), 2, 2), c(0.01, 0.02)) ringFill(buffer)
Reset clears unread rows and the last-timestamp constraint while preserving lifetime counters and monotonic sequence identity.
ringReset(x)ringReset(x)
x |
A live |
Updated serializable ring statistics, invisibly.
info <- streamInfo("demo", type = "EEG", channel_names = c("C3", "C4"), nominal_srate = 100) buffer <- ringBuffer(info, capacity = 8L) ringPush(buffer, matrix(as.double(1:4), 2, 2), c(0.01, 0.02)) ringReset(buffer) ringFill(buffer)info <- streamInfo("demo", type = "EEG", channel_names = c("C3", "C4"), nominal_srate = 100) buffer <- ringBuffer(info, capacity = 8L) ringPush(buffer, matrix(as.double(1:4), 2, 2), c(0.01, 0.02)) ringReset(buffer) ringFill(buffer)
Stateful recursive-least-squares adaptive filter
rlsFilter(n_taps, forgetting = 0.99, delta = 1, initial_weights = NULL)rlsFilter(n_taps, forgetting = 0.99, delta = 1, initial_weights = NULL)
n_taps |
Exact adaptive-filter order. |
forgetting |
Forgetting factor in |
delta |
Positive initial inverse-covariance scale. |
initial_weights |
Optional finite |
A mutable RLSFilter streaming processor.
set.seed(1) reference <- cos(2 * pi * 6 * seq_len(150) / 100) signal <- 0.5 * reference + rnorm(150, sd = 0.05) filt <- rlsFilter(n_taps = 3L, forgetting = 0.995) result <- update(filt, matrix(signal, ncol = 1), reference = matrix(reference, ncol = 1)) length(result$output)set.seed(1) reference <- cos(2 * pi * 6 * seq_len(150) / 100) signal <- 0.5 * reference + rnorm(150, sd = 0.05) filt <- rlsFilter(n_taps = 3L, forgetting = 0.995) result <- update(filt, matrix(signal, ncol = 1), reference = matrix(reference, ncol = 1)) length(result$output)
Validation, arm/dead-man, channel maximum, duration, duplicate-ID, and refractory gates run before transport invocation. Once invocation begins, command identity and refractory time are conservatively reserved even when acknowledgement is unknown. Commands are never retried automatically.
sendStim(trigger, intensity, channel, duration_ms, command_id, now_ns = NULL)sendStim(trigger, intensity, channel, duration_ms, command_id, now_ns = NULL)
trigger |
A |
intensity |
Finite non-negative intensity in the configured unit. |
channel |
Exact configured channel identifier. |
duration_ms |
Finite positive command duration in milliseconds. |
command_id |
Unique bounded command identifier. |
now_ns |
Optional exact process-local monotonic nanosecond value. |
MQTT acknowledgement means broker/client acceptance; TTL acknowledgement
means adapter completion. Neither status proves physical stimulation.
Receipts and audit records bind lifecycle and arm generations, validation,
attempt, and acknowledgement times, and bounded error class/code. A caller
interrupt after transport invocation first finalizes an unknown audit
record and then re-signals the interrupt.
A deep plain-list command receipt.
trigger <- loopbackTrigger(allowed_channels = "left", max_intensity = 20, intensity_unit = "mA", max_duration_ms = 500, refractory_ms = 0, deadman_ms = 1000) triggerOpen(trigger) armTrigger(trigger, "session-1", now_ns = 0) receipt <- sendStim(trigger, intensity = 2.5, channel = "left", duration_ms = 125, command_id = "cmd-1", now_ns = 10) receipt$statustrigger <- loopbackTrigger(allowed_channels = "left", max_intensity = 20, intensity_unit = "mA", max_duration_ms = 500, refractory_ms = 0, deadman_ms = 1000) triggerOpen(trigger) armTrigger(trigger, "session-1", now_ns = 0) receipt <- sendStim(trigger, intensity = 2.5, channel = "left", duration_ms = 125, command_id = "cmd-1", now_ns = 10) receipt$status
Sliding short-time Fourier transform
slidingSTFT( sampling_rate, window_samples, hop_samples, n_fft = window_samples, window = c("hann", "hamming", "rectangular"), detrend = c("mean", "none"), output = c("power", "complex"), bands = NULL, causal_filter = NULL, n_features = NULL )slidingSTFT( sampling_rate, window_samples, hop_samples, n_fft = window_samples, window = c("hann", "hamming", "rectangular"), detrend = c("mean", "none"), output = c("power", "complex"), bands = NULL, causal_filter = NULL, n_features = NULL )
sampling_rate |
Positive samples per second. |
window_samples |
Exact frame length. |
hop_samples |
Exact frame advance, no larger than the window. |
n_fft |
Exact FFT length, at least the frame length. |
window |
Exact governed window definition. |
detrend |
Per-frame mean removal or no detrending. |
output |
Return one-sided density power or normalized complex coefficients whose squared modulus equals that density. |
bands |
Optional uniquely named two-column Hz matrix. |
causal_filter |
Optional independent PhysioPreprocess::StreamFilter objects or factory. |
n_features |
Optional channel count to bind at construction. |
A mutable SlidingSTFT streaming processor.
sr <- 100 signal <- sin(2 * pi * 10 * seq_len(200) / sr) stft <- slidingSTFT(sr, window_samples = 64L, hop_samples = 16L, output = "power") result <- update(stft, signal) dim(result$output)sr <- 100 signal <- sin(2 * pi * 10 * seq_len(200) / sr) stft <- slidingSTFT(sr, window_samples = 64L, hop_samples = 16L, output = "power") result <- update(stft, signal) dim(result$output)
Stream metadata accessors
streamName(x) streamType(x) streamChannels(x) streamRate(x) streamDtype(x)streamName(x) streamType(x) streamChannels(x) streamRate(x) streamDtype(x)
x |
A |
The requested metadata field.
info <- streamInfo("eeg-demo", type = "EEG", channel_names = c("C3", "C4"), nominal_srate = 250, dtype = "float32") streamName(info) streamType(info) streamChannels(info) streamRate(info) streamDtype(info)info <- streamInfo("eeg-demo", type = "EEG", channel_names = c("C3", "C4"), nominal_srate = 250, dtype = "float32") streamName(info) streamType(info) streamChannels(info) streamRate(info) streamDtype(info)
Transport packages extend these generics. Base virtual endpoints provide no implicit transport.
streamOpen(x, ...) streamClose(x, ...) streamPull(x, ...) streamPush(x, ...) streamState(x) ## S4 method for signature 'StreamEndpoint' streamState(x) ## S4 method for signature 'StreamEndpoint' streamOpen(x, ...) ## S4 method for signature 'StreamEndpoint' streamClose(x, ...) ## S4 method for signature 'StreamEndpoint' streamPull(x, ...) ## S4 method for signature 'StreamEndpoint' streamPush(x, ...) ## S4 method for signature 'LoopbackSource' streamOpen(x, ...) ## S4 method for signature 'LoopbackSource' streamClose(x, ...) ## S4 method for signature 'LoopbackSource' streamPull(x, ...) ## S4 method for signature 'LoopbackSource' streamPush(x, ...) ## S4 method for signature 'LSLInlet' streamState(x) ## S4 method for signature 'LSLOutlet' streamState(x) ## S4 method for signature 'LSLInlet' streamOpen(x, timeout = 2, ...) ## S4 method for signature 'LSLOutlet' streamOpen(x, ...) ## S4 method for signature 'LSLInlet' streamClose(x, ...) ## S4 method for signature 'LSLOutlet' streamClose(x, ...) ## S4 method for signature 'LSLInlet' streamPull(x, ...) ## S4 method for signature 'LSLOutlet' streamPush(x, ...)streamOpen(x, ...) streamClose(x, ...) streamPull(x, ...) streamPush(x, ...) streamState(x) ## S4 method for signature 'StreamEndpoint' streamState(x) ## S4 method for signature 'StreamEndpoint' streamOpen(x, ...) ## S4 method for signature 'StreamEndpoint' streamClose(x, ...) ## S4 method for signature 'StreamEndpoint' streamPull(x, ...) ## S4 method for signature 'StreamEndpoint' streamPush(x, ...) ## S4 method for signature 'LoopbackSource' streamOpen(x, ...) ## S4 method for signature 'LoopbackSource' streamClose(x, ...) ## S4 method for signature 'LoopbackSource' streamPull(x, ...) ## S4 method for signature 'LoopbackSource' streamPush(x, ...) ## S4 method for signature 'LSLInlet' streamState(x) ## S4 method for signature 'LSLOutlet' streamState(x) ## S4 method for signature 'LSLInlet' streamOpen(x, timeout = 2, ...) ## S4 method for signature 'LSLOutlet' streamOpen(x, ...) ## S4 method for signature 'LSLInlet' streamClose(x, ...) ## S4 method for signature 'LSLOutlet' streamClose(x, ...) ## S4 method for signature 'LSLInlet' streamPull(x, ...) ## S4 method for signature 'LSLOutlet' streamPush(x, ...)
x |
A stream endpoint. |
... |
Backend-specific arguments. |
timeout |
Optional finite non-negative LSL inlet open timeout. |
Backend-specific output.
# Drive the lifecycle on a device-free loopback source. info <- streamInfo("demo", type = "EEG", channel_names = c("C3", "C4"), nominal_srate = 100) src <- streamOpen(loopbackSource(info, capacity = 16L)) streamState(src) loopbackFeed(src, matrix(as.double(1:4), 2, 2), c(0.01, 0.02)) streamPull(src)$samples src <- streamClose(src) streamState(src)# Drive the lifecycle on a device-free loopback source. info <- streamInfo("demo", type = "EEG", channel_names = c("C3", "C4"), nominal_srate = 100) src <- streamOpen(loopbackSource(info, capacity = 16L)) streamState(src) loopbackFeed(src, matrix(as.double(1:4), 2, 2), c(0.01, 0.02)) streamPull(src)$samples src <- streamClose(src) streamState(src)
Virtual stream endpoint
infoImmutable StreamInfo.
stateExact lifecycle state.
auditAppend-only state transition records.
# StreamEndpoint is virtual; a loopback source is a concrete endpoint. info <- streamInfo("demo", type = "EEG", channel_names = "C3", nominal_srate = 100) src <- loopbackSource(info) is(src, "StreamEndpoint") streamState(src)# StreamEndpoint is virtual; a loopback source is a concrete endpoint. info <- streamInfo("demo", type = "EEG", channel_names = "C3", nominal_srate = 100) src <- loopbackSource(info) is(src, "StreamEndpoint") streamState(src)
With a character first argument, constructs validated stream metadata. With
a stream endpoint or ring buffer, retrieves its immutable StreamInfo.
streamInfo(name, ...) ## S4 method for signature 'character' streamInfo( name, type, channel_names, nominal_srate, dtype = c("float64", "float32", "int32", "int16", "int8", "string"), source_id = "", clock_domain = "local", channel_units = NULL, metadata = list(), ... ) ## S4 method for signature 'StreamInfo' streamInfo(name, ...) ## S4 method for signature 'StreamEndpoint' streamInfo(name, ...) ## S4 method for signature 'RingBuffer' streamInfo(name, ...)streamInfo(name, ...) ## S4 method for signature 'character' streamInfo( name, type, channel_names, nominal_srate, dtype = c("float64", "float32", "int32", "int16", "int8", "string"), source_id = "", clock_domain = "local", channel_units = NULL, metadata = list(), ... ) ## S4 method for signature 'StreamInfo' streamInfo(name, ...) ## S4 method for signature 'StreamEndpoint' streamInfo(name, ...) ## S4 method for signature 'RingBuffer' streamInfo(name, ...)
name |
A non-empty stream name, or a stream object when used as an accessor. |
... |
Reserved for methods. |
type |
A non-empty stream type. |
channel_names |
Unique non-empty channel labels. |
nominal_srate |
A finite nominal sampling rate greater than or equal to zero. |
dtype |
Exact storage type. |
source_id |
Optional stable source identifier. |
clock_domain |
Non-empty clock-domain label. |
channel_units |
Optional units matching |
metadata |
Named recursively serializable metadata, at most 1 MiB. |
A StreamInfo object, or an endpoint's StreamInfo.
info <- streamInfo("eeg-demo", type = "EEG", channel_names = c("C3", "Cz", "C4"), nominal_srate = 250, channel_units = rep("uV", 3)) info streamChannels(info)info <- streamInfo("eeg-demo", type = "EEG", channel_names = c("C3", "Cz", "C4"), nominal_srate = 250, channel_units = rep("uV", 3)) info streamChannels(info)
Validated stream metadata
## S4 method for signature 'StreamInfo' show(object)## S4 method for signature 'StreamInfo' show(object)
object |
A |
name,type,source_id,clock_domainScalar stream identity strings.
n_channelsExact positive channel count.
channel_names,channel_unitsChannel labels and optional units.
nominal_srateNominal samples per second; zero denotes an irregular stream reserved for later transports.
dtypeExact storage type.
metadataNamed recursively serializable metadata.
schema_versionMetadata schema identifier.
info <- streamInfo("eeg-demo", type = "EEG", channel_names = c("C3", "C4"), nominal_srate = 250) infoinfo <- streamInfo("eeg-demo", type = "EEG", channel_names = c("C3", "C4"), nominal_srate = 250) info
A pipeline owns a bounded FIFO of whole chunks and an ordered callback
graph. Processing occurs synchronously on the caller's R thread. The runtime
environment is not portable; use pipelineState() for a hashed plain-state
snapshot.
streamPipeline( source = NULL, chunk_size = 32L, queue_capacity = 64L, backpressure = c("error", "drop_oldest", "drop_newest"), latency_budget_ms = 50 )streamPipeline( source = NULL, chunk_size = 32L, queue_capacity = 64L, backpressure = c("error", "drop_oldest", "drop_newest"), latency_budget_ms = 50 )
source |
Optional |
chunk_size |
Maximum samples per enqueued chunk. |
queue_capacity |
Maximum number of whole chunks in the ingress FIFO. |
backpressure |
Exact full-queue policy. |
latency_budget_ms |
Finite positive empirical latency budget. |
A StreamPipeline environment.
pipeline <- streamPipeline(chunk_size = 8L) pipelineState(pipeline)$configuration$chunk_sizepipeline <- streamPipeline(chunk_size = 8L) pipelineState(pipeline)$configuration$chunk_size
Transport backends extend this class and the stream-operation generics.
# StreamSink is virtual; transport outlets such as lslOutlet() extend it. isVirtualClass("StreamSink")# StreamSink is virtual; transport outlets such as lslOutlet() extend it. isVirtualClass("StreamSink")
Original timestamps and monotonic stream sequence identity are retained.
Selection is non-consuming unless consume = TRUE; consuming a newest
subset while older unread rows remain is rejected.
streamSnapshot( x, n = NULL, duration_seconds = NULL, consume = FALSE, assay_name = "stream" )streamSnapshot( x, n = NULL, duration_seconds = NULL, consume = FALSE, assay_name = "stream" )
x |
An open buffered |
n |
Optional number of newest rows. |
duration_seconds |
Optional duration of the newest closed timestamp interval. |
consume |
Whether to consume the selected oldest contiguous unread rows. |
assay_name |
Non-empty assay name. |
A valid PhysioExperiment::PhysioExperiment.
info <- streamInfo("demo", type = "EEG", channel_names = c("C3", "C4"), nominal_srate = 100) src <- streamOpen(loopbackSource(info, capacity = 32L)) loopbackFeed(src, matrix(sin(seq_len(10)), 5, 2), c(0.01, 0.02, 0.03, 0.04, 0.05)) pe <- streamSnapshot(src) dim(pe)info <- streamInfo("demo", type = "EEG", channel_names = c("C3", "C4"), nominal_srate = 100) src <- streamOpen(loopbackSource(info, capacity = 32L)) loopbackFeed(src, matrix(sin(seq_len(10)), 5, 2), c(0.01, 0.02, 0.03, 0.04, 0.05)) pe <- streamSnapshot(src) dim(pe)
Transport backends extend this class and the stream-operation generics.
# StreamSource is virtual; loopbackSource() returns a concrete instance. info <- streamInfo("demo", type = "EEG", channel_names = "C3", nominal_srate = 100) is(loopbackSource(info), "StreamSource")# StreamSource is virtual; loopbackSource() returns a concrete instance. info <- streamInfo("demo", type = "EEG", channel_names = "C3", nominal_srate = 100) is(loopbackSource(info), "StreamSource")
Returns stored diagnostics for a synchronized container. For an
unsynchronized container, models or shared events are evaluated through
syncStreams() on a copy and only the diagnostics table is returned.
syncDiagnostics(x, models = NULL, shared_events = NULL)syncDiagnostics(x, models = NULL, shared_events = NULL)
x |
A |
models |
Optional uniquely named |
shared_events |
Optional exact |
One plain data-frame row per stream/reset segment.
## Not run: # Requires a multi-stream container (MultiPhysioExperiment), e.g. from # readXDF(). diagnostics <- syncDiagnostics(container) ## End(Not run)## Not run: # Requires a multi-stream container (MultiPhysioExperiment), e.g. from # readXDF(). diagnostics <- syncDiagnostics(container) ## End(Not run)
Applies explicit clock models or recorded XDF/shared-event evidence without interpolating sample values. Authoritative raw and master-domain timestamps are appended to each stream's row data.
syncStreams( x, master = NULL, models = NULL, shared_events = NULL, dejitter = FALSE, max_residual_seconds = NULL )syncStreams( x, master = NULL, models = NULL, shared_events = NULL, dejitter = FALSE, max_residual_seconds = NULL )
x |
A |
master |
Exact master stream key, or |
models |
Optional uniquely named |
shared_events |
Optional exact |
dejitter |
Whether to append a nominal-grid timestamp column. |
max_residual_seconds |
Optional non-negative residual gate. |
A synchronized PhysioExperiment::MultiPhysioExperiment.
## Not run: # Requires a multi-stream container (MultiPhysioExperiment), e.g. from # readXDF(); `master` names the reference stream key. synced <- syncStreams(container, master = "eeg") ## End(Not run)## Not run: # Requires a multi-stream container (MultiPhysioExperiment), e.g. from # readXDF(); `master` names the reference stream key. synced <- syncStreams(container, master = "eeg") ## End(Not run)
Trigger constructors are side-effect free. triggerOpen() explicitly opens
the configured transport and leaves it disarmed. triggerClose() performs a
bounded best-effort stop/deassert before closing. Software interlocks are
additional safeguards only; independently fail-safe stimulation hardware is
required.
triggerOpen(trigger) triggerClose(trigger)triggerOpen(trigger) triggerClose(trigger)
trigger |
A |
The trigger, invisibly.
trigger <- loopbackTrigger(allowed_channels = "left", max_intensity = 20, intensity_unit = "mA", max_duration_ms = 500, refractory_ms = 0, deadman_ms = 1000) triggerOpen(trigger) triggerState(trigger)$lifecycle$status triggerClose(trigger)trigger <- loopbackTrigger(allowed_channels = "left", max_intensity = 20, intensity_unit = "mA", max_duration_ms = 500, refractory_ms = 0, deadman_ms = 1000) triggerOpen(trigger) triggerState(trigger)$lifecycle$status triggerClose(trigger)
The returned deep plain-list snapshot contains no connection, Python object, callback, or credential. Runtime environments are not portable.
triggerState(trigger)triggerState(trigger)
trigger |
A |
A sealed plain list.
trigger <- loopbackTrigger(allowed_channels = "left", max_intensity = 20, intensity_unit = "mA", max_duration_ms = 500, refractory_ms = 0, deadman_ms = 1000) triggerOpen(trigger) triggerState(trigger)$arm$statustrigger <- loopbackTrigger(allowed_channels = "left", max_intensity = 20, intensity_unit = "mA", max_duration_ms = 500, refractory_ms = 0, deadman_ms = 1000) triggerOpen(trigger) triggerState(trigger)$arm$status
TTL transport is an adapter boundary, not a vendor stimulator protocol. Serial mode writes a versioned ASCII frame to a cooperating independently fail-safe adapter. Callback mode requires one atomic timed pulse operation; PhysioStream does not synthesize pulse timing with an R sleep or busy loop. Loopback mode records the exact frame without hardware.
ttlTrigger( transport = c("serial", "callback", "loopback"), allowed_channels, max_intensity, intensity_unit, max_duration_ms, refractory_ms, deadman_ms, port = NULL, baud = 115200L, line = 1L, pulse_width_ms = 5, write_timeout_ms = 100, writer = NULL, audit_capacity = 4096L, clock = NULL )ttlTrigger( transport = c("serial", "callback", "loopback"), allowed_channels, max_intensity, intensity_unit, max_duration_ms, refractory_ms, deadman_ms, port = NULL, baud = 115200L, line = 1L, pulse_width_ms = 5, write_timeout_ms = 100, writer = NULL, audit_capacity = 4096L, clock = NULL )
transport |
Exact |
allowed_channels |
Unique allowed channel identifiers. |
max_intensity |
Positive scalar or exactly channel-named maxima. |
intensity_unit |
Explicit device intensity unit; no conversion occurs. |
max_duration_ms |
Positive scalar or channel-named duration maxima. |
refractory_ms |
Non-negative scalar or channel-named refractory times. |
deadman_ms |
Positive heartbeat expiry interval. |
port |
Serial port path for serial mode. |
baud |
Positive integer serial baud rate. |
line |
Positive integer adapter line identifier. |
pulse_width_ms |
Positive pulse width requested from the adapter. |
write_timeout_ms |
Positive bounded serial write timeout. |
writer |
Callback implementing atomic |
audit_capacity |
Bounded command/audit capacity. |
clock |
Optional deterministic monotonic clock for tests. |
Opening some serial ports can momentarily affect RTS/DTR control lines. Hardware must remain fail-safe despite port open, process failure, malformed frames, and communication loss.
A closed, disarmed TtlTrigger.
## Not run: # Serial transport requires a device exposing a TTL line; see # loopbackTrigger() for an offline equivalent with the same interlocks. trigger <- ttlTrigger(transport = "serial", port = "/dev/ttyUSB0", allowed_channels = "left", max_intensity = 20, intensity_unit = "mA", max_duration_ms = 500, refractory_ms = 0, deadman_ms = 1000) ## End(Not run)## Not run: # Serial transport requires a device exposing a TTL line; see # loopbackTrigger() for an offline equivalent with the same interlocks. trigger <- ttlTrigger(transport = "serial", port = "/dev/ttyUSB0", allowed_channels = "left", max_intensity = 20, intensity_unit = "mA", max_duration_ms = 500, refractory_ms = 0, deadman_ms = 1000) ## End(Not run)
Map between synchronized signal and video time
videoSyncTime(sync, signal_time) signalSyncTime(sync, video_time)videoSyncTime(sync, signal_time) signalSyncTime(sync, video_time)
sync |
A validated |
signal_time |
Finite signal-domain times. |
video_time |
Finite media times. |
A numeric vector preserving names.
sync <- videoSync(c(a = 100, b = 110, c = 120), c(a = 1, b = 11, c = 21), frame_rate = 30, method = "offset", clock_domain = "demo") videoSyncTime(sync, c(x = 105, y = 115)) signalSyncTime(sync, c(x = 6, y = 16))sync <- videoSync(c(a = 100, b = 110, c = 120), c(a = 1, b = 11, c = 21), frame_rate = 30, method = "offset", clock_domain = "demo") videoSyncTime(sync, c(x = 105, y = 115)) signalSyncTime(sync, c(x = 6, y = 16))
The mapping convention is
video_seconds = intercept + rate * signal_seconds. Evaluation uses an
equivalent centered representation to retain precision for large clocks.
videoSync( signal_time, video_time, frame_rate, method = c("offset", "affine"), clock_domain, tolerance_frames = 1, max_rate_error_ppm = 5000 )videoSync( signal_time, video_time, frame_rate, method = c("offset", "affine"), clock_domain, tolerance_frames = 1, max_rate_error_ppm = 5000 )
signal_time |
Strictly increasing signal-domain marker times. |
video_time |
Strictly increasing corresponding media times. |
frame_rate |
Positive media frames per second. |
method |
Exact offset-only or affine fit. |
clock_domain |
Exact signal clock-domain label. |
tolerance_frames |
Maximum accepted anchor residual in frames. |
max_rate_error_ppm |
Maximum affine rate deviation from one. |
A sealed portable VideoSync.
sync <- videoSync( signal_time = c(a = 100, b = 110, c = 120), video_time = c(a = 1, b = 11, c = 21), frame_rate = 30, method = "offset", clock_domain = "demo") videoSyncTime(sync, c(x = 105))sync <- videoSync( signal_time = c(a = 100, b = 110, c = 120), video_time = c(a = 1, b = 11, c = 21), frame_rate = 30, method = "offset", clock_domain = "demo") videoSyncTime(sync, c(x = 105))
launch = FALSE returns a side-effect-free BiofeedbackScope carrying a
validated video descriptor. Local media are copied and exposed only when the
Shiny viewer starts.
videoSyncViewer( source, video, sync, pipeline = NULL, channels = NULL, window_seconds = 10, update_hz = 20, max_points = 2000L, source_lifecycle = c("borrow", "own"), launch = interactive(), host = "127.0.0.1", port = NULL, browser = interactive(), clock = NULL )videoSyncViewer( source, video, sync, pipeline = NULL, channels = NULL, window_seconds = 10, update_hz = 20, max_points = 2000L, source_lifecycle = c("borrow", "own"), launch = interactive(), host = "127.0.0.1", port = NULL, browser = interactive(), clock = NULL )
source |
A regular-rate numeric |
video |
Local mp4/webm/ogg path, HTTPS URL, or |
sync |
A |
pipeline |
Optional source-backed |
channels |
Exact source channel names to display. |
window_seconds |
Positive visible signal-time window. |
update_hz |
Requested display update rate. |
max_points |
Maximum plotted representatives per trace. |
source_lifecycle |
Whether the scope borrows an open source or owns its open/close lifecycle. |
launch |
Whether to run the installed Shiny app immediately. |
host |
Local Shiny bind host. |
port |
Optional exact TCP port. |
browser |
Whether Shiny should launch a browser. |
clock |
Optional monotonic nanosecond clock for deterministic tests. |
A BiofeedbackScope or the result of shiny::runApp().
## Not run: # Requires a Shiny session and a local video file for display. info <- streamInfo("demo", type = "EEG", channel_names = c("left", "right"), nominal_srate = 100, channel_units = c("uV", "uV")) source <- streamOpen(loopbackSource(info, capacity = 256L)) sync <- videoSync(c(a = 100, b = 110, c = 120), c(a = 1, b = 11, c = 21), frame_rate = 30, method = "offset", clock_domain = "local") viewer <- videoSyncViewer(source, "session.mp4", sync, launch = FALSE) ## End(Not run)## Not run: # Requires a Shiny session and a local video file for display. info <- streamInfo("demo", type = "EEG", channel_names = c("left", "right"), nominal_srate = 100, channel_units = c("uV", "uV")) source <- streamOpen(loopbackSource(info, capacity = 256L)) sync <- videoSync(c(a = 100, b = 110, c = 120), c(a = 1, b = 11, c = 21), frame_rate = 30, method = "offset", clock_domain = "local") viewer <- videoSyncViewer(source, "session.mp4", sync, launch = FALSE) ## End(Not run)
Complete windows are converted to one-sided power spectral density and
combined as alpha * newest + (1 - alpha) * previous.
welchOnline( sampling_rate, window_samples, hop_samples = floor(window_samples/2), n_fft = window_samples, alpha = 0.1, window = c("hann", "hamming", "rectangular"), detrend = c("mean", "none"), bands = NULL, causal_filter = NULL, n_features = NULL )welchOnline( sampling_rate, window_samples, hop_samples = floor(window_samples/2), n_fft = window_samples, alpha = 0.1, window = c("hann", "hamming", "rectangular"), detrend = c("mean", "none"), bands = NULL, causal_filter = NULL, n_features = NULL )
sampling_rate |
Positive samples per second. |
window_samples |
Exact frame length. |
hop_samples |
Exact frame advance, no larger than the window. |
n_fft |
Exact FFT length, at least the frame length. |
alpha |
Weight assigned to the newest periodogram. |
window |
Exact governed window definition. |
detrend |
Per-frame mean removal or no detrending. |
bands |
Optional uniquely named two-column Hz matrix. |
causal_filter |
Optional independent PhysioPreprocess::StreamFilter objects or factory. |
n_features |
Optional channel count to bind at construction. |
A mutable OnlineWelch streaming processor.
sr <- 100 signal <- sin(2 * pi * 10 * seq_len(256) / sr) welch <- welchOnline(sr, window_samples = 64L, hop_samples = 32L) result <- update(welch, signal) dim(result$output)sr <- 100 signal <- sin(2 * pi * 10 * seq_len(256) / sr) welch <- welchOnline(sr, window_samples = 64L, hop_samples = 32L) result <- update(welch, signal) dim(result$output)
The writer implements the governed XDF 1.0 subset natively and does not require Python. Output is scanned before an atomic same-directory rename.
writeXDF( x, path, overwrite = FALSE, chunk_samples = 256L, timestamps = c("output", "raw") )writeXDF( x, path, overwrite = FALSE, chunk_samples = 256L, timestamps = c("output", "raw") )
x |
One PhysioExperiment::MultiPhysioExperiment or PhysioExperiment::PhysioExperiment. |
path |
Destination |
overwrite |
Whether an existing regular destination may be replaced. |
chunk_samples |
Exact positive samples per XDF Samples chunk. |
timestamps |
Exact timestamp domain, |
The normalized output path, invisibly.
## Not run: # Writing XDF requires the pyxdf backend and a multi-stream container, # e.g. one returned by readXDF(). writeXDF(container, tempfile(fileext = ".xdf")) ## End(Not run)## Not run: # Writing XDF requires the pyxdf backend and a multi-stream container, # e.g. one returned by readXDF(). writeXDF(container, tempfile(fileext = ".xdf")) ## End(Not run)
The default probe is conservative and does not initialize Python. Explicit read operations initialize only the already configured reticulate interpreter. PhysioStream never installs or selects a Python environment.
xdfAvailable(backend = c("auto", "pyxdf"), initialize = FALSE)xdfAvailable(backend = c("auto", "pyxdf"), initialize = FALSE)
backend |
Exact backend, currently |
initialize |
Whether the configured Python interpreter may be initialized to verify pyxdf and NumPy. |
One non-missing logical value.
# Reports FALSE unless the pyxdf backend is installed. xdfAvailable()# Reports FALSE unless the pyxdf backend is installed. xdfAvailable()
Report the configured XDF backend
xdfBackendInfo(backend = c("auto", "pyxdf"))xdfBackendInfo(backend = c("auto", "pyxdf"))
backend |
Exact backend, currently |
A serializable named list of backend and runtime versions.
# Resolving the backend version requires the pyxdf backend. if (xdfAvailable()) str(xdfBackendInfo())# Resolving the backend version requires the pyxdf backend. if (xdfAvailable()) str(xdfBackendInfo())