-
Notifications
You must be signed in to change notification settings - Fork 0
Isis standard cb #104
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Isis standard cb #104
Changes from all commits
Commits
Show all changes
23 commits
Select commit
Hold shift + click to select a range
6e0f5d0
add isis_standard_callbacks
rerpha a3fe798
end of day
rerpha 586ffa2
working, though doesnt return any results
rerpha e606ad1
wip - use class decorator
rerpha 2eea499
not working yet, but something like this might work?
rerpha 777e467
backing up
rerpha 01bc3da
wip
rerpha de2daa0
callbacks working - add thread event to wait for fix and ax
rerpha 247d79a
add peak stats toggle, allow passing in ax
rerpha 8fb0884
add some checks before adding callbacks
rerpha f5e2768
make properties for live_fit and peak_stats, add some more sanitising
rerpha 9425836
add some tests
rerpha a35a8a6
Merge remote-tracking branch 'origin/main' into isis_standard_cb
rerpha 219245d
make docs easier to follow
rerpha 5571bef
more tests
rerpha 8be1104
get coverage to 100% by testing __call__()
rerpha 77dc586
ignore none possibility in dae_scan.py
rerpha 35d3855
review comments - make x and y mandatory, make props raise rather tha…
rerpha c7be9fe
add livefit on its own to subs if not show_fit_on_plot
rerpha b69dd38
add live fit logger
rerpha 5c9419d
specify live fit logger output in dae_scan.py2
rerpha c9fe8c4
review comments - make kw only, sort out fit callback (remove toggle …
rerpha d8eec06
Merge branch 'main' into isis_standard_cb
rerpha File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1 +1,234 @@ | ||
| """Bluesky callbacks which may be attached to the RunEngine.""" | ||
|
|
||
| import logging | ||
| import threading | ||
| from collections.abc import Generator | ||
| from os import PathLike | ||
| from pathlib import Path | ||
| from typing import Any, Callable | ||
|
|
||
| import bluesky.preprocessors as bpp | ||
| import matplotlib.pyplot as plt | ||
| from bluesky.callbacks import CallbackBase, LiveFitPlot, LiveTable | ||
| from bluesky.callbacks.fitting import PeakStats | ||
| from bluesky.callbacks.mpl_plotting import QtAwareCallback | ||
| from bluesky.utils import Msg, make_decorator | ||
| from event_model import RunStart | ||
| from matplotlib.axes import Axes | ||
|
|
||
| from ibex_bluesky_core.callbacks.file_logger import ( | ||
| DEFAULT_PATH as DEFAULT_PATH_HRF, | ||
| ) | ||
| from ibex_bluesky_core.callbacks.file_logger import ( | ||
| HumanReadableFileCallback, | ||
| ) | ||
| from ibex_bluesky_core.callbacks.fitting import FitMethod, LiveFit | ||
| from ibex_bluesky_core.callbacks.fitting.livefit_logger import ( | ||
| DEFAULT_PATH as DEFAULT_PATH_LFL, | ||
| ) | ||
| from ibex_bluesky_core.callbacks.fitting.livefit_logger import ( | ||
| LiveFitLogger, | ||
| ) | ||
| from ibex_bluesky_core.callbacks.plotting import LivePlot | ||
|
|
||
| logger = logging.getLogger(__name__) | ||
|
|
||
| # ruff: noqa: PLR0913, PLR0912, PLR0917 | ||
|
|
||
|
|
||
| class ISISCallbacks: | ||
| """ISIS standard callbacks for use within plans.""" | ||
|
|
||
| def __init__( | ||
| self, | ||
| *, | ||
| x: str, | ||
| y: str, | ||
| yerr: str | None = None, | ||
| measured_fields: list[str] | None = None, | ||
| add_table_cb: bool = True, | ||
| fields_for_live_table: list[str] | None = None, | ||
| add_human_readable_file_cb: bool = True, | ||
| fields_for_hr_file: list[str] | None = None, | ||
| human_readable_file_output_dir: str | PathLike[str] | None = None, | ||
rerpha marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| add_plot_cb: bool = True, | ||
| ax: Axes | None = None, | ||
| fit: FitMethod | None = None, | ||
| show_fit_on_plot: bool = True, | ||
| add_peak_stats: bool = True, | ||
| add_live_fit_logger: bool = True, | ||
| live_fit_logger_output_dir: str | PathLike[str] | None = None, | ||
| live_fit_logger_postfix: str = "isc", | ||
| ) -> None: | ||
| """A collection of ISIS standard callbacks for use within plans. | ||
|
|
||
| By default, this adds: | ||
|
|
||
| - HumanReadableFileCallback | ||
|
|
||
| - LiveTable | ||
|
|
||
| - PeakStats | ||
|
|
||
| - LiveFit | ||
|
|
||
| - LiveFitPlot | ||
|
|
||
| - LivePlot | ||
|
|
||
| Results can be accessed from the `live_fit` and `peak_stats` properties. | ||
|
|
||
| This is to be used as a member and then as a decorator if results are needed ie:: | ||
|
|
||
| def dae_scan_plan(): | ||
| ... | ||
| icc = ISISCallbacks( | ||
| x=block.name, | ||
| y=reducer.intensity.name, | ||
| yerr=reducer.intensity_stddev.name, | ||
| fit=Linear.fit(), | ||
| ... | ||
| ) | ||
| ... | ||
|
|
||
| @icc | ||
| def _inner(): | ||
| yield from ... | ||
| ... | ||
| print(icc.live_fit.result.fit_report()) | ||
| print(f"COM: {icc.peak_stats['com']}") | ||
|
|
||
| Args: | ||
| x: The signal name to use for X within plots and fits. | ||
| y: The signal name to use for Y within plots and fits. | ||
| yerr: The signal name to use for the Y uncertainty within plots and fits. | ||
| measured_fields: the fields to use for both the live table and human-readable file. | ||
| add_table_cb: whether to add a table callback. | ||
| fields_for_live_table: the fields to measure for the live table (in addition to `measured_fields`). | ||
| add_human_readable_file_cb: whether to add a human-readable file callback. | ||
| fields_for_hr_file: the fields to measure for the human-readable file (in addition to `measured_fields`). | ||
| human_readable_file_output_dir: the output directory for human-readable files. can be blank and will default. | ||
| add_plot_cb: whether to add a plot callback. | ||
| ax: An optional axes object to use for plotting. | ||
| fit: The fit method to use when fitting. | ||
| show_fit_on_plot: whether to show fit on plot. | ||
| add_peak_stats: whether to add a peak stats callback. | ||
| add_live_fit_logger: whether to add a live fit logger. | ||
| live_fit_logger_output_dir: the output directory for live fit logger. | ||
| live_fit_logger_postfix: the postfix to add to live fit logger. | ||
| """ # noqa | ||
| self._subs = [] | ||
| self._peak_stats = None | ||
| self._live_fit = None | ||
| if measured_fields is None: | ||
| measured_fields = [] | ||
| if fields_for_live_table is None: | ||
| fields_for_live_table = [] | ||
| if fields_for_hr_file is None: | ||
| fields_for_hr_file = [] | ||
|
|
||
| measured_fields.append(x) | ||
| measured_fields.append(y) | ||
| if yerr is not None: | ||
| measured_fields.append(yerr) | ||
|
|
||
| if add_human_readable_file_cb: | ||
| combined_hr_fields = measured_fields + fields_for_hr_file | ||
| self._subs.append( | ||
| HumanReadableFileCallback( | ||
| fields=combined_hr_fields, | ||
| output_dir=Path(human_readable_file_output_dir) | ||
| if human_readable_file_output_dir | ||
| else DEFAULT_PATH_HRF, | ||
| ), | ||
| ) | ||
|
|
||
| if add_table_cb: | ||
| combined_lt_fields = measured_fields + fields_for_live_table | ||
| self._subs.append( | ||
| LiveTable(combined_lt_fields), | ||
| ) | ||
|
|
||
| if add_peak_stats: | ||
| self._peak_stats = PeakStats(x=x, y=y) | ||
| self._subs.append(self._peak_stats) | ||
|
|
||
| if (add_plot_cb or show_fit_on_plot) and not ax: | ||
| logger.debug("No axis provided, creating a new one") | ||
| fig, ax, exc, result = None, None, None, None | ||
| done_event = threading.Event() | ||
|
|
||
| class _Cb(QtAwareCallback): | ||
| def start(self, doc: RunStart) -> None: | ||
| nonlocal result, exc, fig, ax | ||
| try: | ||
| plt.close("all") | ||
| fig, ax = plt.subplots() | ||
| finally: | ||
| done_event.set() | ||
|
|
||
| cb = _Cb() | ||
| cb("start", {"time": 0, "uid": ""}) | ||
| done_event.wait(10.0) | ||
|
|
||
| if fit is not None: | ||
| self._live_fit = LiveFit(fit, y=y, x=x, yerr=yerr) | ||
| if show_fit_on_plot: | ||
| self._subs.append(LiveFitPlot(livefit=self._live_fit, ax=ax)) | ||
| else: | ||
| self._subs.append(self._live_fit) | ||
| if add_live_fit_logger: | ||
| self._subs.append( | ||
| LiveFitLogger( | ||
| livefit=self._live_fit, | ||
| x=x, | ||
| y=y, | ||
| yerr=yerr, | ||
| output_dir=DEFAULT_PATH_LFL | ||
| if live_fit_logger_output_dir is None | ||
| else live_fit_logger_output_dir, | ||
| postfix=live_fit_logger_postfix, | ||
| ) | ||
| ) | ||
|
|
||
| if add_plot_cb or show_fit_on_plot: | ||
| self._subs.append( | ||
| LivePlot( | ||
| y=y, | ||
| x=x, | ||
| marker="x", | ||
| linestyle="none", | ||
| ax=ax, | ||
| yerr=yerr, | ||
| ) | ||
| ) | ||
|
|
||
| @property | ||
| def live_fit(self) -> LiveFit: | ||
| """The live fit object containing fitting results.""" | ||
| if self._live_fit is None: | ||
| raise ValueError("live_fit was not added as a callback.") | ||
| return self._live_fit | ||
|
|
||
| @property | ||
| def peak_stats(self) -> PeakStats: | ||
| """The peak stats object containing statistics ie. centre of mass.""" | ||
| if self._peak_stats is None: | ||
| raise ValueError("peak stats was not added as a callback.") | ||
| return self._peak_stats | ||
|
|
||
| @property | ||
| def subs(self) -> list[CallbackBase]: | ||
| """The list of subscribed callbacks.""" | ||
| return self._subs | ||
|
|
||
| def _icbc_wrapper(self, plan: Generator[Msg, None, None]) -> Generator[Msg, None, None]: | ||
| @bpp.subs_decorator(self.subs) | ||
| def _inner() -> Generator[Msg, None, None]: | ||
| return (yield from plan) | ||
|
|
||
| return (yield from _inner()) | ||
|
|
||
| def __call__(self, f: Callable[..., Any]) -> Callable[..., Any]: | ||
| """Make a decorator to wrap the plan and subscribe to all callbacks.""" | ||
| return make_decorator(self._icbc_wrapper)()(f) | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.