diff --git a/examples/files/california_config.yml b/examples/files/california_config.yml new file mode 100644 index 0000000..15cdc15 --- /dev/null +++ b/examples/files/california_config.yml @@ -0,0 +1,63 @@ +model: + kwargs: + model_file: examples/files/california_regression.pt + model_class: lume_model.pytorch.PyTorchModel + model_info: examples/files/california_model_info.json + output_format: + type: variable + requirements: + torch: 1.12 + +input_variables: + MedInc: + default: 3.7857346534729004 + range: + - 0.4999000132083893 + - 15.000100135803223 + type: scalar + HouseAge: + default: 29.282135009765625 + range: + - 1.0 + - 52.0 + type: scalar + AveRooms: + default: 5.4074907302856445 + range: + - 0.8461538553237915 + - 141.90908813476562 + type: scalar + AveBedrms: + default: 1.1071722507476807 + range: + - 0.375 + - 34.06666564941406 + type: scalar + Population: + default: 1437.0687255859375 + range: + - 3.0 + - 28566.0 + type: scalar + AveOccup: + default: 3.035413980484009 + range: + - 0.692307710647583 + - 599.7142944335938 + type: scalar + Latitude: + default: 35.28323745727539 + range: + - 32.65999984741211 + - 41.95000076293945 + type: scalar + Longitude: + default: -119.11573028564453 + range: + - -124.3499984741211 + - -114.30999755859375 + type: scalar + +output_variables: + MedHouseVal: + type: scalar diff --git a/examples/files/california_epics_config.yml b/examples/files/california_epics_config.yml new file mode 100644 index 0000000..9ef05c2 --- /dev/null +++ b/examples/files/california_epics_config.yml @@ -0,0 +1,38 @@ +input_variables: + MedInc: + pvname: MedInc + protocol: pva + serve: true + HouseAge: + pvname: HouseAge + protocol: pva + serve: true + AveRooms: + pvname: AveRooms + protocol: pva + serve: true + AveBedrms: + pvname: AveBedrms + protocol: pva + serve: true + Population: + pvname: Population + protocol: pva + serve: true + AveOccup: + pvname: AveOccup + protocol: pva + serve: true + Latitude: + pvname: Latitude + protocol: pva + serve: true + Longitude: + pvname: Longitude + protocol: pva + serve: true +output_variables: + MedHouseVal: + pvname: MedHouseVal + protocol: pva + serve: true diff --git a/examples/files/california_model_info.json b/examples/files/california_model_info.json new file mode 100644 index 0000000..062e63f --- /dev/null +++ b/examples/files/california_model_info.json @@ -0,0 +1,48 @@ +{ + "train_input_mins": [ + 0.4999000132083893, + 1.0, + 0.8461538553237915, + 0.375, + 3.0, + 0.692307710647583, + 32.65999984741211, + -124.3499984741211 + ], + "train_input_maxs": [ + 15.000100135803223, + 52.0, + 141.90908813476562, + 34.06666564941406, + 28566.0, + 599.7142944335938, + 41.95000076293945, + -114.30999755859375 + ], + "model_in_list": [ + "MedInc", + "HouseAge", + "AveRooms", + "AveBedrms", + "Population", + "AveOccup", + "Latitude", + "Longitude" + ], + "model_out_list": [ + "MedHouseVal" + ], + "loc_in": { + "MedInc": 0, + "HouseAge": 1, + "AveRooms": 2, + "AveBedrms": 3, + "Population": 4, + "AveOccup": 5, + "Latitude": 6, + "Longitude": 7 + }, + "loc_out": { + "MedHouseVal": 0 + } +} \ No newline at end of file diff --git a/examples/files/california_normalization.json b/examples/files/california_normalization.json new file mode 100644 index 0000000..801156e --- /dev/null +++ b/examples/files/california_normalization.json @@ -0,0 +1,28 @@ +{ + "x_mean": [ + 3.7857348454995345, + 29.282134699245518, + 5.4074908062777505, + 1.1071723692329019, + 1437.0687339932165, + 3.0354138620008504, + 35.283234587868925, + -119.11572989538202 + ], + "x_scale": [ + 1.8973481323832475, + 12.395369585636528, + 2.8019995847982195, + 0.5464532026823882, + 1141.2672447074444, + 5.462164977692798, + 2.026003694993028, + 1.8325257865272555 + ], + "y_mean": [ + 1.985587451669133 + ], + "y_scale": [ + 1.1218406322612422 + ] +} \ No newline at end of file diff --git a/examples/files/california_regression.pt b/examples/files/california_regression.pt new file mode 100644 index 0000000..c0814b5 Binary files /dev/null and b/examples/files/california_regression.pt differ diff --git a/examples/pytorch/california_client.py b/examples/pytorch/california_client.py new file mode 100644 index 0000000..0b9a6f4 --- /dev/null +++ b/examples/pytorch/california_client.py @@ -0,0 +1,89 @@ +from bokeh.io import curdoc +from bokeh.layouts import column, row +from bokeh.models import Div, Button + +from lume_epics.client.controller import Controller +from lume_model.utils import variables_from_yaml +from lume_epics.utils import config_from_yaml + +from lume_epics.client.widgets.tables import ValueTable +from lume_epics.client.widgets.controls import build_sliders +from lume_epics.client.controller import Controller + +# load the model and the variables from LUME model +with open("examples/files/california_config.yml", "r") as f: + input_variables, output_variables = variables_from_yaml(f) + +# load the EPICS pv definitions +with open("examples/files/california_epics_config.yml", "r") as f: + epics_config = config_from_yaml(f) + +# create controller from epics config +controller = Controller(epics_config) + +# prepare as list for rendering +# define the variables that have range to make as sliders +sliding_variables = [ + input_var + for input_var in input_variables.values() + if input_var.value_range[0] != input_var.value_range[1] +] +input_variables = list(input_variables.values()) +output_variables = list(output_variables.values()) + +# define the plots we want to see - sliders for all input values +# and tables summarising the current state of the inputs and +# output values +sliders = build_sliders(sliding_variables, controller) +input_value_table = ValueTable(input_variables, controller) +output_value_table = ValueTable(output_variables, controller) + + +title_div = Div( + text=f"California Housing Prediction: Last update {controller.last_update}", + style={ + "font-size": "150%", + "color": "#3881e8", + "text-align": "center", + "width": "100%", + }, +) + + +def update_div_text(): + global controller + title_div.text = ( + f"California Housing Prediction: Last update {controller.last_update}" + ) + + +def reset_slider_values(): + for slider in sliders: + slider.reset() + + +slider_reset_button = Button(label="Reset") +slider_reset_button.on_click(reset_slider_values) + +# render +curdoc().title = "California Housing Prediction" +curdoc().add_root( + column( + row(column(title_div, width=600)), + row( + column( + [slider_reset_button] + [slider.bokeh_slider for slider in sliders], + width=350, + ), + column(input_value_table.table, output_value_table.table, width=350), + ), + ), +) + +# add refresh callbacks to ensure that the values are updated +# curdoc().add_periodic_callback(image_plot.update, 1000) +for slider in sliders: + curdoc().add_periodic_callback(slider.update, 1000) +curdoc().add_periodic_callback(update_div_text, 1000) +curdoc().add_periodic_callback(input_value_table.update, 1000) +curdoc().add_periodic_callback(output_value_table.update, 1000) diff --git a/examples/pytorch/california_server.py b/examples/pytorch/california_server.py new file mode 100644 index 0000000..31e994b --- /dev/null +++ b/examples/pytorch/california_server.py @@ -0,0 +1,42 @@ +from lume_epics.epics_server import Server +from lume_model.utils import model_from_yaml +from lume_epics.utils import config_from_yaml +from pathlib import Path +import json +from botorch.models.transforms.input import AffineInputTransform +import torch +from pprint import pprint + +if __name__ == "__main__": + # load the model and the variables from LUME model + with open("examples/files/california_config.yml", "r") as f: + model_class, model_kwargs = model_from_yaml(f, load_model=False) + + # load the EPICS pv definitions + with open("examples/files/california_epics_config.yml", "r") as f: + epics_config = config_from_yaml(f) + + # load the transformers required for the model + with open("examples/files/california_normalization.json", "r") as f: + normalizations = json.load(f) + + input_transformer = AffineInputTransform( + len(normalizations["x_mean"]), + coefficient=torch.tensor(normalizations["x_scale"]), + offset=torch.tensor(normalizations["x_mean"]), + ) + output_transformer = AffineInputTransform( + len(normalizations["y_mean"]), + coefficient=torch.tensor(normalizations["y_scale"]), + offset=torch.tensor(normalizations["y_mean"]), + ) + + # update the model kwargs with the transformers + model_kwargs["input_transformers"] = [input_transformer] + model_kwargs["output_transformers"] = [output_transformer] + + # start the EPICS server + server = Server(model_class, epics_config, model_kwargs=model_kwargs) + + # monitor = False does not loop in main thread + server.start(monitor=True) diff --git a/lume_epics/client/widgets/controls.py b/lume_epics/client/widgets/controls.py index 984ed58..c069766 100644 --- a/lume_epics/client/widgets/controls.py +++ b/lume_epics/client/widgets/controls.py @@ -81,6 +81,9 @@ def update(self): """ self.bokeh_slider.value = self.controller.get_value(self.pvname) + + def reset(self): + self.bokeh_slider.value = self.variable.default def build_sliders( diff --git a/lume_epics/epics_pva_server.py b/lume_epics/epics_pva_server.py index 27083c6..58c3284 100644 --- a/lume_epics/epics_pva_server.py +++ b/lume_epics/epics_pva_server.py @@ -117,14 +117,16 @@ def update_pv(self, pvname: str, value: Union[np.ndarray, float]) -> None: varname = self._pvname_to_varname_map[pvname] model_variable = self._input_variables[varname] + # check for already cached variable + model_variable = self._cached_values.get(varname, model_variable) + if model_variable.variable_type == "image": model_variable.x_min = value.attrib["x_min"] model_variable.x_max = value.attrib["x_max"] model_variable.y_min = value.attrib["y_min"] model_variable.y_max = value.attrib["y_max"] - - # check for already cached variable - model_variable = self._cached_values.get(varname, model_variable) + else: + model_variable.value = value self._cached_values[varname] = model_variable @@ -178,7 +180,6 @@ def setup_server(self) -> None: ].default else: - if self._context is None: self._context = Context("pva") @@ -198,7 +199,6 @@ def setup_server(self) -> None: self._initialize_model() model_outputs = None while not self.shutdown_event.is_set() and model_outputs is None: - try: model_outputs = self._out_queue.get(timeout=0.1) except Empty: @@ -224,14 +224,11 @@ def setup_server(self) -> None: self._structures = {} self._structure_specs = {} for variable_name, config in self._epics_config.items(): - if config["serve"]: - fields = config.get("fields") pvname = config.get("pvname") if fields is not None: - spec = [] structure = {} @@ -255,7 +252,6 @@ def setup_server(self) -> None: spec.append((field, "v")) table_rep = () for col in variable.columns: - # here we assume double type in tables... table_rep += (col, "ad") @@ -320,7 +316,6 @@ def setup_server(self) -> None: elif variable.variable_type == "table": table_rep = () for col in variable.columns: - # here we assume double type in tables... table_rep += (col, "ad")