Markers and measurements
A marker is a named point in time. A measurement spans two markers on a
channel and publishes numbers about what happens between them: duration,
frequency, edge count. Markers are reached through workspace.markers,
measurements through workspace.measures.
Measurements are computed on the server and anchored to their markers: move a marker, and the numbers are recomputed.
add, move, remove​
- Python
- NodeJS
- Rust
- C
first = workspace.markers.add(sample=0) # returns the marker id
second = workspace.markers.add(sample=250_000)
workspace.markers.move_to(first, sample=1_000)
workspace.markers.remove(first)
for marker in workspace.markers.all():
marker.id
marker.label # "M1", "M2"... assigned by the server
marker.sample
marker.trigger # True for the marker the trigger placed
const first = await workspace.markers.add(0); // returns the marker id
const second = await workspace.markers.add(250_000);
workspace.markers.move_to(first, 1_000);
workspace.markers.remove(first); // takes its measurements with it
for (const marker of workspace.markers.all()) {
marker.id;
marker.label; // "M1", "M2"... assigned by the server
marker.sample;
}
let first = workspace.markers().add(0).await?; // returns the marker id
let second = workspace.markers().add(250_000).await?;
workspace.markers().move_to(first, 1_000)?;
workspace.markers().remove(first)?; // takes its measurements with it
for marker in workspace.markers().all() {
println!("{} at {}", marker.label, marker.sample); // "M1 at 0"
}
uint32_t first = ss_marker_add(workspace, 0); /* 0 on failure */
uint32_t second = ss_marker_add(workspace, 250000);
ss_marker_move(workspace, first, 1000);
ss_marker_remove(workspace, first); /* takes its measurements with it */
/* Reading them is a batch: read, count, copy, free. */
ss_markers *batch = ss_markers_read(workspace);
size_t count = ss_markers_count(batch);
ss_marker *markers = malloc(sizeof *markers * count);
ss_markers_copy(batch, 0, markers, count);
for (size_t i = 0; i < count; i += 1) {
printf("%s at %lld\n", markers[i].label, (long long)markers[i].sample);
}
free(markers);
ss_markers_free(batch); /* every `label` above dies here */
A measurement is anchored to two markers. Remove one of them and the measurement goes with it.
measure​
Creates a measurement between two markers on a channel. Computation starts immediately.
- Python
- NodeJS
- Rust
- C
measure_id = workspace.measures.add(
first, second,
channel=0,
kinds=["time", "frequency", "edges"],
time_precision=3,
freq_precision=3,
)
// Computation starts immediately; wait() below blocks for the results.
const measureId = await workspace.measures.add(first, second, {
channel: 0,
kinds: ['time', 'frequency', 'edges'],
time_precision: 3,
freq_precision: 3,
});
use scanastudio_client::Precision;
// Computation starts immediately; wait() below blocks for the results.
// Precision::default() is 3 and 3.
let measure_id = workspace.measures()
.add(first, second, 0, &["time", "frequency", "edges"], Precision::default())
.await?;
/* NULL kinds means the server's own set. The last two are the precisions
asked for on times and frequencies; 255 means exact. */
uint32_t measure = ss_measure_add(workspace, first, second, 0,
"[\"time\", \"frequency\", \"edges\"]", 3, 3);
if (!measure) fprintf(stderr, "measure: %s\n", ss_last_error());
/* What this server understands, as a JSON array. */
char *kinds = ss_measure_kinds();
if (kinds) { printf("%s\n", kinds); ss_string_free(kinds); }
measures.between​
The same thing without placing the markers yourself: give two sample indices and the SDK creates the markers for you.
- Python
- NodeJS
- Rust
- C
measure_id = workspace.measures.between(
0, workspace.capture.last_sample,
channel=0,
kinds=["frequency", "duty"],
)
// Places the two markers for you, then measures between them.
const measureId = await workspace.measures.between(
0, workspace.capture.last_sample,
{ channel: 0, kinds: ['frequency', 'duty'] },
);
// Places the two markers for you, then measures between them.
let measure_id = workspace.measures()
.between(0, last, 0, &["frequency", "duty"], Precision::default())
.await?;
/* Places the two markers for you, then measures between them. */
uint32_t measure = ss_measure_between(workspace, 0, ss_capture_last_sample(workspace), 0,
"[\"frequency\", \"duty\"]", 3, 3);
The measurement kinds​
kinds is a list drawn from this vocabulary, exported as the constant
MEASURE_KINDS in Python, NodeJS and Rust:
time frequency samples edges duty min max average rms
The server currently does not treat these nine as distinct measurements. It recognises two cases:
| You ask for | You get back |
|---|---|
A list containing edges | edges alone — the number of edges on the channel between the markers |
| Anything else, or nothing | The standard timing set: time_total, freq_avg, freq_min, freq_max |
So ["time"], ["duty"], ["rms"] and [] all return the same four results,
and ["time", "edges"] returns only edges.
The names in a result are not the names you asked for. Read
result.kind rather than assuming it echoes your request.
The result kinds you will actually see:
| Result kind | What it is |
|---|---|
time_total | Duration between the two markers, in seconds. |
freq_avg | Mean frequency of the signal over that span, in hertz. |
freq_min | Lowest instantaneous frequency in the span. |
freq_max | Highest instantaneous frequency in the span. |
edges | Number of edges on the channel between the markers. |
Ask for ["edges"] when you want an edge count, and leave kinds alone when
you want timing. Requesting the other seven names is accepted and harmless, but
does not currently change what comes back.
wait and results​
Measurements are computed asynchronously. wait blocks until nothing is still
computing, and hands back the results.
- Python
- NodeJS
- Rust
- C
for measure in workspace.measures.wait(timeout=60.0):
measure.id
measure.channel
measure.marker_a, measure.marker_b
measure.computing # False once it is settled
for result in measure.results:
print(result.kind, result.value) # "time" 0.000123
// Blocks until nothing is still computing.
for (const measure of await workspace.measures.wait(60_000)) {
measure.marker_a;
measure.marker_b;
measure.computing; // false once it is settled
for (const result of measure.results) {
console.log(result.kind, result.value); // "time_total" 0.02
}
}
// Blocks until nothing is still computing.
for measure in workspace.measures().wait(Duration::from_secs(60)).await? {
for result in &measure.results {
println!("{} = {}", result.kind, result.value); // "time_total = 0.02"
}
}
/* Blocks until nothing is still computing, then reads. ss_measures_read
takes them as they stand, without waiting. */
ss_measures *batch = ss_measures_wait(workspace, 60.0);
if (!batch) fail("measures");
for (size_t i = 0; i < ss_measures_count(batch); i += 1) {
ss_measure row;
if (ss_measures_copy(batch, i, &row, 1) != 1) break;
ss_measure_value *values = malloc(sizeof *values * row.value_count);
ptrdiff_t got = ss_measure_values_copy(batch, i, 0, values, row.value_count);
for (ptrdiff_t v = 0; v < got; v += 1) {
printf("%s = %g\n", values[v].kind, values[v].value);
}
free(values);
}
ss_measures_free(batch); /* every `kind` above dies here */
The results are in SI units, and unrounded: time_precision and
freq_precision say what was asked for, and formatting is yours to do.
Example output. One measurement asked for the standard timing set and one
asked for edges, over the same 500 000-sample window:
time_total 0.02
freq_avg 51480.173999
freq_min 19638.648861
freq_max 107758.62069
edges 101.0
Values are plain numbers in SI units: seconds for time_total, hertz for the
three frequencies. The result names are not the kind names you asked for.
measures() returns the current list without waiting, if you would rather poll
computing yourself.
result.value is a plain number in SI units (seconds, hertz). The precision
settings control how ScanaStudio displays the value; they do not round what
you read here.
set_kinds​
Changes what an existing measurement publishes, without recreating it.
- Python
- NodeJS
- Rust
- C
workspace.measures.set_kinds(measure_id, ["time", "samples"], time_precision=6)
// Changes what an existing measurement publishes, without recreating it.
workspace.measures.set_kinds(measureId, ['time', 'samples'], { time_precision: 6 });
// Rust has no keyword arguments, so the precisions are a builder.
workspace.measures().set_kinds(
measure_id,
&["time", "samples"],
Precision::new().time(6),
)?;
/* The last two arguments are the precisions, on times and frequencies. */
ss_measure_set_kinds(workspace, measure, "[\"time\", \"samples\"]", 6, 3);
/* Moving the whole measurement, both markers and its channel.
-1 keeps the channel it was measured on. */
ss_measure_move(workspace, measure, 0, 500000, -1);
Clearing​
- Python
- NodeJS
- Rust
- C
workspace.measures.remove(measure_id) # one measurement, markers stay
workspace.measures.clear() # every measurement, markers stay
workspace.markers.clear() # everything: markers and measurements
workspace.measures.remove(measureId); // one measurement, markers stay
workspace.measures.clear(); // every measurement, markers stay
workspace.markers.clear(); // everything
workspace.measures().remove(measure_id)?; // one measurement, markers stay
workspace.measures().clear()?; // every measurement, markers stay
workspace.markers().clear()?; // every marker, and their measurements
ss_measure_remove(workspace, measure); /* one measurement, markers stay */
ss_measures_clear(workspace); /* every measurement, markers stay */
ss_markers_clear(workspace); /* every marker, and their measurements */