Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 13 additions & 5 deletions MNeuEventLib/plotting.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,20 @@ def plot_sample_log(data: Data, log_name: str):
fig, ax = plt.subplots()
ax.set_title(sample_log['name'])
ax.set_xlabel("Time (seconds)")
if units := sample_log['unit'] == "":
unit_string = ""
else:
unit_string = f"({units})"
units = sample_log['unit']
unit_string = "" if units == "" else f"({units})"
ax.set_ylabel(f"Value {unit_string}")
ax.plot(sample_log['time'], sample_log['value'])

values = sample_log['value']
if isinstance(values, list):
# a string log comes back as a list of str rather than an array, so
# we need to plot it differently
categories = list(dict.fromkeys(values))
ax.plot(sample_log['time'], values, drawstyle="steps-post", marker="o")
ax.set_yticks(categories)
ax.set_ylabel("Value")
else:
ax.plot(sample_log['time'], values)

plt.show()

Expand Down
34 changes: 34 additions & 0 deletions docs/source/how-to/filtering.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,40 @@
"data.remove_log_filter(\"temperature filter 2\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Filtering text sample logs\n",
"\n",
"Not every sample log holds numbers. Some record the state of a piece of equipment as text, for example a cryostat writing `\"IDLE\"`, `\"RUNNING\"` and `\"PAUSED\"` as the run progresses. A range makes no sense for these, so they are filtered on an exact value instead, using `add_string_log_filter`. It takes three arguments:\n",
"- `name`: A name label for the filter, unique across all sample log filters.\n",
"- `log`: The sample log that the filter applies to.\n",
"- `value`: The value the log must hold for data to be kept.\n",
"\n",
"```python\n",
"data.add_string_log_filter(\"running only\", \"status\", \"RUNNING\")\n",
"```\n",
"\n",
"This keeps only the data taken while the `status` log held `\"RUNNING\"`, and discards everything else. Matching ignores case, so `\"running\"` and `\"Running\"` select the same data.\n",
"\n",
"So a log reading\n",
"\n",
"| time | value |\n",
"| ---- | ----- |\n",
"| 0s | IDLE |\n",
"| 3s | RUNNING |\n",
"| 10s | PAUSED |\n",
"\n",
"filtered on `\"RUNNING\"` keeps the data from 3s to 10s.\n",
"\n",
"Text and numeric filters cannot be swapped: `add_log_filter` on a text log, or `add_string_log_filter` on a numeric one, raises an error saying so. Asking for a value the log never takes is also an error, which lists the values that *are* present - otherwise the filter would match nothing and you would silently get the whole dataset back.\n",
"\n",
"Text log filters are changed with `edit_string_log_filter`, which takes the name of the filter and new values for `log` and/or `value`; anything we don't give is left as it is. For example, `data.edit_string_log_filter(\"running only\", value=\"PAUSED\")` would make our filter keep the data from 10s onwards instead. `edit_log_filter` can't change the value of a text filter, and `edit_string_log_filter` can't edit a numeric one.\n",
"\n",
"Text log filters are removed with `remove_log_filter`, just like numeric ones.\n"
]
},
{
"cell_type": "markdown",
"id": "1383b7e2-cd11-4a83-a86c-6c4f98a889b0",
Expand Down
6 changes: 5 additions & 1 deletion src/batch/functions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,7 @@ pub fn slog_range(name: String, log: String, start: f64, end: f64, step: f64) ->
mod tests {
use super::*;
use crate::consts::ToNanoseconds;
use crate::filters::LogPredicate;

/// The bounds of every filter set's single time filter, in order of
/// filter set.
Expand All @@ -173,7 +174,10 @@ mod tests {
.map(|filters| {
assert_eq!(filters.sample_log_filters.len(), 1);
let filter = &filters.sample_log_filters[name];
(filter.lower.unwrap(), filter.upper.unwrap())
match &filter.predicate {
LogPredicate::Range { lower, upper } => (lower.unwrap(), upper.unwrap()),
other => panic!("expected a range predicate, got {other:?}"),
}
})
.collect()
}
Expand Down
Loading
Loading