ivoryos 0.1.5__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of ivoryos might be problematic. Click here for more details.

Files changed (46) hide show
  1. ivoryos/__init__.py +94 -0
  2. ivoryos/config.py +46 -0
  3. ivoryos/routes/__init__.py +0 -0
  4. ivoryos/routes/auth/__init__.py +0 -0
  5. ivoryos/routes/auth/auth.py +65 -0
  6. ivoryos/routes/auth/templates/auth/login.html +25 -0
  7. ivoryos/routes/auth/templates/auth/signup.html +32 -0
  8. ivoryos/routes/control/__init__.py +0 -0
  9. ivoryos/routes/control/control.py +233 -0
  10. ivoryos/routes/control/templates/control/controllers.html +71 -0
  11. ivoryos/routes/control/templates/control/controllers_home.html +50 -0
  12. ivoryos/routes/control/templates/control/controllers_new.html +89 -0
  13. ivoryos/routes/database/__init__.py +0 -0
  14. ivoryos/routes/database/database.py +122 -0
  15. ivoryos/routes/database/templates/database/experiment_database.html +72 -0
  16. ivoryos/routes/design/__init__.py +0 -0
  17. ivoryos/routes/design/design.py +396 -0
  18. ivoryos/routes/design/templates/design/experiment_builder.html +413 -0
  19. ivoryos/routes/design/templates/design/experiment_run.html +325 -0
  20. ivoryos/routes/main/__init__.py +0 -0
  21. ivoryos/routes/main/main.py +25 -0
  22. ivoryos/routes/main/templates/main/help.html +144 -0
  23. ivoryos/routes/main/templates/main/home.html +68 -0
  24. ivoryos/static/favicon.ico +0 -0
  25. ivoryos/static/gui_annotation/Slide1.png +0 -0
  26. ivoryos/static/gui_annotation/Slide2.PNG +0 -0
  27. ivoryos/static/js/overlay.js +12 -0
  28. ivoryos/static/js/socket_handler.js +25 -0
  29. ivoryos/static/js/sortable_card.js +24 -0
  30. ivoryos/static/js/sortable_design.js +36 -0
  31. ivoryos/static/logo.png +0 -0
  32. ivoryos/static/style.css +202 -0
  33. ivoryos/templates/base.html +141 -0
  34. ivoryos/utils/__init__.py +0 -0
  35. ivoryos/utils/db_models.py +501 -0
  36. ivoryos/utils/form.py +316 -0
  37. ivoryos/utils/global_config.py +68 -0
  38. ivoryos/utils/llm_agent.py +183 -0
  39. ivoryos/utils/script_runner.py +158 -0
  40. ivoryos/utils/task_manager.py +80 -0
  41. ivoryos/utils/utils.py +337 -0
  42. ivoryos-0.1.5.dist-info/LICENSE +21 -0
  43. ivoryos-0.1.5.dist-info/METADATA +96 -0
  44. ivoryos-0.1.5.dist-info/RECORD +46 -0
  45. ivoryos-0.1.5.dist-info/WHEEL +5 -0
  46. ivoryos-0.1.5.dist-info/top_level.txt +1 -0
@@ -0,0 +1,158 @@
1
+ import os
2
+ import csv
3
+ import threading
4
+ import time
5
+ from datetime import datetime
6
+
7
+ from ivoryos.utils import utils
8
+ from ivoryos.utils.db_models import Script
9
+ from ivoryos.utils.global_config import GlobalConfig
10
+
11
+ global_config = GlobalConfig()
12
+ global deck
13
+ deck = None
14
+
15
+ class ScriptRunner:
16
+ def __init__(self, globals_dict=None):
17
+ if globals_dict is None:
18
+ globals_dict = globals()
19
+ self.globals_dict = globals_dict
20
+
21
+ self.stop_event = threading.Event()
22
+ self.is_running = False
23
+ self.lock = threading.Lock()
24
+
25
+ def reset_stop_event(self):
26
+ self.stop_event.clear()
27
+
28
+ def stop_execution(self):
29
+ self.stop_event.set()
30
+ # print("Stop pending tasks")
31
+
32
+ def run_script(self, script, repeat_count=1, run_name=None, logger=None, socketio=None, config=None, bo_args=None,
33
+ output_path=""):
34
+ global deck
35
+ if deck is None:
36
+ deck = global_config.deck
37
+ exec_string = script.compile()
38
+ exec(exec_string)
39
+ time.sleep(1)
40
+ with self.lock:
41
+ if self.is_running:
42
+ logger.info("System is busy. Please wait for it to finish or stop it before starting a new one.")
43
+ return None
44
+ self.is_running = True
45
+
46
+ self.reset_stop_event()
47
+
48
+ thread = threading.Thread(target=self._run_with_stop_check,
49
+ args=(script, repeat_count, run_name, logger, socketio, config, bo_args, output_path))
50
+ thread.start()
51
+ return thread
52
+
53
+ def _run_with_stop_check(self, script: Script, repeat_count, run_name, logger, socketio, config, bo_args,
54
+ output_path):
55
+ time.sleep(1)
56
+ try:
57
+ # Run "prep" section once
58
+ script_dict = script.script_dict
59
+ self._run_actions(script_dict.get("prep", []), section_name="prep", run_name=run_name, logger=logger)
60
+ output_list = []
61
+ _, arg_type = script.config("script")
62
+ _, return_list = script.config_return()
63
+ # Run "script" section multiple times
64
+ if repeat_count:
65
+ self._run_repeat_section(repeat_count, bo_args, output_list, return_list, run_name, logger, socketio)
66
+ elif config:
67
+ self._run_config_section(config, arg_type, output_list, return_list, run_name, logger, socketio)
68
+ # Run "cleanup" section once
69
+ self._run_actions(script_dict.get("cleanup", []), section_name="cleanup", run_name=run_name, logger=logger)
70
+ # Save results if necessary
71
+ if output_list:
72
+ self._save_results(run_name, arg_type, return_list, output_list, logger, output_path)
73
+ except Exception as e:
74
+ logger.error(f"Error during script execution: {e}")
75
+ finally:
76
+ with self.lock:
77
+ self.is_running = False # Reset the running flag when done
78
+
79
+ def _run_actions(self, actions, section_name="", run_name=None, logger=None):
80
+ logger.info(f'Executing {section_name} steps') if actions else logger.info(f'No {section_name} steps')
81
+ for action in actions:
82
+ if self.stop_event.is_set():
83
+ logger.info(f"Stopping execution during {section_name} section.")
84
+ break
85
+ logger.info(f'Executing {action.get("action", "")} action')
86
+ fname = f"{run_name}_{section_name}"
87
+ function = self.globals_dict[fname]
88
+ function()
89
+
90
+ def _run_config_section(self, config, arg_type, output_list, return_list, run_name, logger, socketio):
91
+ compiled = True
92
+ for i in config:
93
+ try:
94
+ i = utils.convert_config_type(i, arg_type)
95
+ except Exception as e:
96
+ logger.info(e)
97
+ compiled = False
98
+ break
99
+ if compiled:
100
+ for i, kwargs in enumerate(config):
101
+ kwargs = dict(kwargs)
102
+ if self.stop_event.is_set():
103
+ logger.info(f'Stopping execution during {run_name}: {i + 1}/{len(config)}')
104
+ break
105
+ logger.info(f'Executing {i + 1} of {len(config)} with kwargs = {kwargs}')
106
+ progress = (i + 1) * 100 / len(config)
107
+ socketio.emit('progress', {'progress': progress})
108
+ fname = f"{run_name}_script"
109
+ function = self.globals_dict[fname]
110
+ output = function(**kwargs)
111
+ if output:
112
+ kwargs.update(output)
113
+ output_list.append(kwargs)
114
+
115
+ def _run_repeat_section(self, repeat_count, bo_args, output_list, return_list, run_name, logger, socketio):
116
+ if bo_args:
117
+ logger.info('Initializing optimizer...')
118
+ ax_client = utils.ax_initiation(bo_args)
119
+ for i in range(int(repeat_count)):
120
+ if self.stop_event.is_set():
121
+ logger.info(f'Stopping execution during {run_name}: {i + 1}/{int(repeat_count)}')
122
+ break
123
+ logger.info(f'Executing {run_name} experiment: {i + 1}/{int(repeat_count)}')
124
+ progress = (i + 1) * 100 / int(repeat_count)
125
+ socketio.emit('progress', {'progress': progress})
126
+ if bo_args:
127
+ try:
128
+ parameters, trial_index = ax_client.get_next_trial()
129
+ logger.info(f'Output value: {parameters}')
130
+ fname = f"{run_name}_script"
131
+ function = self.globals_dict[fname]
132
+ output = function(**parameters)
133
+ # output = eval(f"{run_name}_script(**{parameters})")
134
+ ax_client.complete_trial(trial_index=trial_index, raw_data=output)
135
+ except Exception as e:
136
+ logger.info(f'Optimization error: {e}')
137
+ break
138
+ else:
139
+ fname = f"{run_name}_script"
140
+ function = self.globals_dict[fname]
141
+ output = function()
142
+
143
+ if output:
144
+ output_list.append(output)
145
+ logger.info(f'Output value: {output}')
146
+ return output_list
147
+
148
+ @staticmethod
149
+ def _save_results(run_name, arg_type, return_list, output_list, logger, output_path):
150
+ args = list(arg_type.keys()) if arg_type else []
151
+ args.extend(return_list)
152
+ filename = run_name + "_" + datetime.now().strftime("%Y-%m-%d %H-%M") + ".csv"
153
+ file_path = os.path.join(output_path, filename)
154
+ with open(file_path, "w", newline='') as file:
155
+ writer = csv.DictWriter(file, fieldnames=args)
156
+ writer.writeheader()
157
+ writer.writerows(output_list)
158
+ logger.info(f'Results saved to {file_path}')
@@ -0,0 +1,80 @@
1
+ import threading
2
+ import queue
3
+ import time
4
+
5
+
6
+ # A task manager class to manage the queue and tasks
7
+ class TaskManager:
8
+ def __init__(self):
9
+ self.task_queue = queue.Queue()
10
+ self.current_task = None
11
+ self.stop_event = threading.Event()
12
+
13
+ def add_task(self, func, **kwargs):
14
+ # Add the function and its kwargs to the task queue
15
+ self.task_queue.put((func, kwargs))
16
+
17
+ def run_tasks(self):
18
+ # Run the tasks from the queue
19
+ while not self.task_queue.empty():
20
+ func, kwargs = self.task_queue.get()
21
+ thread = threading.Thread(target=self.run_task, args=(func, kwargs))
22
+ self.current_task = thread
23
+ thread.start()
24
+ thread.join() # Wait for the task to finish
25
+
26
+ def run_task(self, func, kwargs):
27
+ # Run the task function with control to stop in the middle
28
+ self.stop_event.clear() # Reset the stop flag
29
+ func(**kwargs)
30
+ if self.stop_event.is_set():
31
+ print("Current task was stopped.")
32
+
33
+ def stop_current_task(self):
34
+ # Stop the current task by setting the stop flag
35
+ if self.current_task and self.current_task.is_alive():
36
+ print("Stopping current task...")
37
+ self.stop_event.set() # Signal to stop the current task
38
+ self.current_task.join() # Wait for the task to stop
39
+
40
+
41
+ # Wrapping tasks to allow stopping between them
42
+ def function_to_call(stop_event, **kwargs):
43
+ if stop_event.is_set():
44
+ return
45
+ task1(kwargs['arg1'])
46
+ if stop_event.is_set():
47
+ return
48
+ task2(kwargs['arg2'])
49
+
50
+
51
+ # Dummy task functions as provided
52
+ def task1(arg1):
53
+ for i in range(arg1):
54
+ print(f"Task 1 running: {i}")
55
+ time.sleep(1)
56
+
57
+
58
+ def task2(arg2):
59
+ for i in range(arg2):
60
+ print(f"Task 2 running: {i}")
61
+ time.sleep(1)
62
+
63
+
64
+ if __name__ == "__main__":
65
+ manager = TaskManager()
66
+
67
+ # Add tasks to the manager
68
+ manager.add_task(function_to_call, stop_event=manager.stop_event, arg1=3, arg2=5)
69
+ manager.add_task(function_to_call, stop_event=manager.stop_event, arg1=2, arg2=4)
70
+
71
+ # Run tasks in a separate thread
72
+ manager_thread = threading.Thread(target=manager.run_tasks)
73
+ manager_thread.start()
74
+
75
+ # Example: Stop the current workflow while task1 is running
76
+ time.sleep(2) # Let task1 run for a bit
77
+ manager.stop_current_task()
78
+
79
+ # Wait for all tasks to finish
80
+ manager_thread.join()
ivoryos/utils/utils.py ADDED
@@ -0,0 +1,337 @@
1
+ import importlib
2
+ import inspect
3
+ import logging
4
+ import os
5
+ import pickle
6
+ import subprocess
7
+ import sys
8
+ from typing import Optional, Dict, Tuple
9
+
10
+ from flask import session
11
+ from flask_socketio import SocketIO
12
+
13
+ from ivoryos.utils.db_models import Script
14
+
15
+
16
+ def get_script_file():
17
+ session_script = session.get("scripts")
18
+ if session_script:
19
+ s = Script()
20
+ s.__dict__.update(**session_script)
21
+ return s
22
+ else:
23
+ return Script(author=session.get('user'))
24
+
25
+
26
+ def post_script_file(script, is_dict=False):
27
+ if is_dict:
28
+ session['scripts'] = script
29
+ else:
30
+ session['scripts'] = script.as_dict()
31
+
32
+
33
+ def create_gui_dir(parent_path):
34
+ os.makedirs(parent_path, exist_ok=True)
35
+ for path in ["config_csv", "scripts", "results", "pseudo_deck"]:
36
+ os.makedirs(os.path.join(parent_path, path), exist_ok=True)
37
+
38
+
39
+ def save_to_history(filepath, history_path):
40
+ connections = []
41
+ try:
42
+ with open(history_path, 'r') as file:
43
+ lines = file.read()
44
+ connections = lines.split('\n')
45
+ except FileNotFoundError:
46
+ pass
47
+ if filepath not in connections:
48
+ with open(history_path, 'a') as file:
49
+ file.writelines(f"{filepath}\n")
50
+
51
+
52
+ def import_history(history_path):
53
+ connections = []
54
+ try:
55
+ with open(history_path, 'r') as file:
56
+ lines = file.read()
57
+ connections = lines.split('\n')
58
+ except FileNotFoundError:
59
+ pass
60
+ connections = [i for i in connections if not i == '']
61
+ return connections
62
+
63
+
64
+ def available_pseudo_deck(path):
65
+ import os
66
+ return os.listdir(path)
67
+
68
+
69
+ def parse_functions(class_object=None, debug=False):
70
+ functions = {}
71
+ under_score = "_"
72
+ if debug:
73
+ under_score = "__"
74
+ for function, method in inspect.getmembers(type(class_object), predicate=inspect.isfunction):
75
+ if not function.startswith(under_score) and not function.isupper():
76
+ try:
77
+ annotation = inspect.signature(method)
78
+ # if doc_string:
79
+ docstring = inspect.getdoc(method)
80
+ functions[function] = dict(signature=annotation, docstring=docstring)
81
+
82
+ # handle getter setters
83
+ # if callable(att):
84
+ # functions[function] = inspect.signature(att)
85
+ # else:
86
+ # att = getattr(class_object.__class__, function)
87
+ # if isinstance(att, property) and att.fset is not None:
88
+ # setter = att.fset.__annotations__
89
+ # setter.pop('return', None)
90
+ # if setter:
91
+ # functions[function] = setter
92
+ except Exception:
93
+ pass
94
+ return functions
95
+
96
+
97
+ def _get_type_from_parameters(arg, parameters):
98
+ arg_type = ''
99
+ if type(parameters) is inspect.Signature:
100
+ p = parameters.parameters
101
+ # print(p[arg].annotation)
102
+ if p[arg].annotation is not inspect._empty:
103
+ # print(p[arg].annotation)
104
+ if p[arg].annotation.__module__ == 'typing':
105
+ # print(p[arg].annotation.__args__)
106
+ arg_type = [i.__name__ for i in p[arg].annotation.__args__]
107
+ else:
108
+ arg_type = p[arg].annotation.__name__
109
+ # print(arg_type)
110
+ elif type(parameters) is dict:
111
+ if parameters[arg]:
112
+
113
+ if parameters[arg].__module__ == 'typing':
114
+ arg_type = [i.__name__ for i in parameters[arg].__args__]
115
+ else:
116
+ arg_type = parameters[arg].__name__
117
+ return arg_type
118
+
119
+
120
+ def find_variable_in_script(script: Script, args: Dict[str, str]) -> Optional[Tuple[Dict[str, str], Dict[str, str]]]:
121
+ # TODO: need to search for if the variable exists
122
+ added_variables: list[Dict[str, str]] = [action for action in script.currently_editing_script if
123
+ action["instrument"] == "variable"]
124
+
125
+ possible_variable_arguments = {}
126
+ possible_variable_types = {}
127
+
128
+ for arg_name, arg_val in args.items():
129
+ for added_variable in added_variables:
130
+ if added_variable["action"] == arg_val:
131
+ possible_variable_arguments[arg_name] = added_variable["action"]
132
+ possible_variable_types[arg_name] = "variable"
133
+
134
+ return possible_variable_arguments, possible_variable_types
135
+
136
+
137
+ def _convert_by_str(args, arg_types):
138
+ # print(arg_types)
139
+ if type(arg_types) is not list:
140
+ arg_types = [arg_types]
141
+ for i in arg_types:
142
+ if i == "any":
143
+ try:
144
+ args = eval(args)
145
+ except Exception:
146
+ pass
147
+ return args
148
+ try:
149
+ args = eval(f'{i}("{args}")')
150
+ return args
151
+ except Exception:
152
+ raise TypeError(f"Input type error: cannot convert '{args}' to {i}.")
153
+
154
+
155
+ def _convert_by_class(args, arg_types):
156
+ if arg_types.__module__ == 'builtins':
157
+ args = arg_types(args)
158
+ return args
159
+ elif arg_types.__module__ == "typing":
160
+ for i in arg_types.__args__: # for typing.Union
161
+ try:
162
+ args = i(args)
163
+ return args
164
+ except Exception:
165
+ pass
166
+ raise TypeError("Input type error.")
167
+ # else:
168
+ # args = globals()[args]
169
+ return args
170
+
171
+
172
+ def convert_config_type(args, arg_types, is_class: bool = False):
173
+ bool_dict = {"True": True, "False": False}
174
+ # print(args, arg_types)
175
+ # print(globals())
176
+ if args:
177
+ for arg in args:
178
+ if arg not in arg_types.keys():
179
+ raise ValueError("config file format not supported.")
180
+ if args[arg] == '' or args[arg] == "None":
181
+ args[arg] = None
182
+ elif args[arg] == "True" or args[arg] == "False":
183
+ args[arg] = bool_dict[args[arg]]
184
+ else:
185
+ arg_type = arg_types[arg]
186
+
187
+ if is_class:
188
+ # if arg_type.__module__ == 'builtins':
189
+ args[arg] = _convert_by_class(args[arg], arg_type)
190
+ else:
191
+ args[arg] = _convert_by_str(args[arg], arg_type)
192
+ return args
193
+
194
+
195
+ def import_module_by_filepath(filepath: str, name: str):
196
+ spec = importlib.util.spec_from_file_location(name, filepath)
197
+ module = importlib.util.module_from_spec(spec)
198
+ spec.loader.exec_module(module)
199
+ return module
200
+
201
+
202
+ class SocketIOHandler(logging.Handler):
203
+ def __init__(self, socketio: SocketIO):
204
+ super().__init__()
205
+ self.formatter = logging.Formatter('%(asctime)s - %(message)s', datefmt='%Y-%m-%d %H:%M:%S')
206
+ self.socketio = socketio
207
+
208
+ def emit(self, record):
209
+ message = self.format(record)
210
+ # session["last_log"] = message
211
+ self.socketio.emit('log', {'message': message})
212
+
213
+
214
+ def start_logger(socketio: SocketIO, logger_name: str, log_filename: str = None):
215
+ # logging.basicConfig( format='%(asctime)s - %(message)s')
216
+ formatter = logging.Formatter(fmt='%(asctime)s - %(message)s', datefmt='%Y-%m-%d %H:%M:%S')
217
+ logger = logging.getLogger(logger_name)
218
+ logger.setLevel(logging.INFO)
219
+ file_handler = logging.FileHandler(filename=log_filename, )
220
+ file_handler.setFormatter(formatter)
221
+ logger.addHandler(file_handler)
222
+ # console_logger = logging.StreamHandler() # stream to console
223
+ # logger.addHandler(console_logger)
224
+ socketio_handler = SocketIOHandler(socketio)
225
+ logger.addHandler(socketio_handler)
226
+ return logger
227
+
228
+
229
+ def ax_wrapper(data):
230
+ from ax.service.utils.instantiation import ObjectiveProperties
231
+ parameter = []
232
+ objectives = {}
233
+ # Iterate through the webui_data dictionary
234
+ for key, value in data.items():
235
+ # Check if the key corresponds to a parameter type
236
+ if "_type" in key:
237
+ param_name = key.split("_type")[0]
238
+ param_type = value
239
+ param_value = data[f"{param_name}_value"].split(",")
240
+ try:
241
+ values = [float(v) for v in param_value]
242
+ except Exception:
243
+ values = param_value
244
+ if param_type == "range":
245
+ parameter.append({"name": param_name, "type": param_type, "bounds": values})
246
+ if param_type == "choice":
247
+ parameter.append({"name": param_name, "type": param_type, "values": values})
248
+ if param_type == "fixed":
249
+ parameter.append({"name": param_name, "type": param_type, "value": values[0]})
250
+ elif key.endswith("_min"):
251
+ if not value == 'none':
252
+ obj_name = key.split("_min")[0]
253
+ is_min = True if value == "minimize" else False
254
+
255
+ threshold = None if f"{obj_name}_threshold" not in data else data[f"{obj_name}_threshold"]
256
+ properties = ObjectiveProperties(minimize=is_min, threshold=threshold)
257
+ objectives[obj_name] = properties
258
+ return parameter, objectives
259
+
260
+
261
+ def ax_initiation(data):
262
+ install_and_import("ax", "ax-platform")
263
+ parameter, objectives = ax_wrapper(data)
264
+ from ax.service.ax_client import AxClient
265
+ ax_client = AxClient()
266
+ ax_client.create_experiment(parameter, objectives)
267
+ return ax_client
268
+
269
+
270
+ def get_arg_type(args, parameters):
271
+ arg_types = {}
272
+ # print(args, parameters)
273
+ if args:
274
+ for arg in args:
275
+ arg_types[arg] = _get_type_from_parameters(arg, parameters)
276
+ return arg_types
277
+
278
+
279
+ def install_and_import(package, package_name=None):
280
+ try:
281
+ # Check if the package is already installed
282
+ importlib.import_module(package)
283
+ # print(f"{package} is already installed.")
284
+ except ImportError:
285
+ # If not installed, install it
286
+ # print(f"{package} is not installed. Installing now...")
287
+ subprocess.check_call([sys.executable, "-m", "pip", "install", package_name or package])
288
+ # print(f"{package} has been installed successfully.")
289
+
290
+
291
+ def process_data(data, config_type):
292
+ rows = {} # Dictionary to hold webui_data organized by rows
293
+
294
+ # Organize webui_data by rows
295
+ for key, value in data.items():
296
+ if value: # Only process non-empty values
297
+ # Extract the field name and row index
298
+ field_name, row_index = key.split('[')
299
+ row_index = int(row_index.rstrip(']'))
300
+
301
+ # If row not in rows, create a new dictionary for that row
302
+ if row_index not in rows:
303
+ rows[row_index] = {}
304
+
305
+ # Add or update the field value in the specific row's dictionary
306
+ rows[row_index][field_name] = value
307
+
308
+ # Filter out any empty rows and create a list of dictionaries
309
+ filtered_rows = [row for row in rows.values() if len(row) == len(config_type)]
310
+
311
+ return filtered_rows
312
+
313
+
314
+ def parse_deck(deck, save=False, output_path=''):
315
+ deck_variables = {f"deck.{name}": parse_functions(val) for name, val in vars(deck).items()
316
+ if not type(val).__module__ == 'builtins'
317
+ and not name[0].isupper()
318
+ and not name.startswith("_")}
319
+ if deck_variables and save:
320
+ # pseudo_deck = parse_dict
321
+ parse_dict = deck_variables.copy()
322
+ parse_dict["deck_name"] = os.path.splitext(os.path.basename(deck.__file__))[
323
+ 0] if deck.__name__ == "__main__" else deck.__name__
324
+ with open(os.path.join(output_path, f"{parse_dict['deck_name']}.pkl"), 'wb') as file:
325
+ pickle.dump(parse_dict, file)
326
+ return deck_variables
327
+
328
+
329
+ def load_deck(pkl_name):
330
+ if not pkl_name:
331
+ return None
332
+ try:
333
+ with open(pkl_name, 'rb') as f:
334
+ pseudo_deck = pickle.load(f)
335
+ return pseudo_deck
336
+ except FileNotFoundError:
337
+ return None
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2023-2024 Ivory Zhang | Hein Lab
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,96 @@
1
+ Metadata-Version: 2.1
2
+ Name: ivoryos
3
+ Version: 0.1.5
4
+ Summary: an open-source Python package enabling Self-Driving Labs (SDLs) interoperability
5
+ Home-page: https://gitlab.com/heingroup/ivoryos
6
+ Author: Ivory Zhang
7
+ Author-email: ivoryzhang@chem.ubc.ca
8
+ License: MIT
9
+ Description-Content-Type: text/markdown
10
+ License-File: LICENSE
11
+ Requires-Dist: bcrypt
12
+ Requires-Dist: Flask-Login
13
+ Requires-Dist: Flask-Session
14
+ Requires-Dist: Flask-SocketIO
15
+ Requires-Dist: Flask-SQLAlchemy
16
+ Requires-Dist: Flask-WTF
17
+ Requires-Dist: SQLAlchemy-Utils
18
+ Requires-Dist: python-dotenv
19
+
20
+ ![](https://gitlab.com/heingroup/ivoryos/raw/main/docs/ivoryos.png)
21
+ # ivoryOS: interoperable Web UI for self-driving laboratories (SDLs)
22
+ ivoryOS is a "plug and play" web UI extension for flexible SDLs, enabling interoperability between SDLs.
23
+ ## Description
24
+ Granting SDLs flexibility and modularity makes it almost impossible to design a UI, yet it's a necessity for allowing more people to interact with it (democratisation).
25
+ This web UI aims to ease up the control of any Python-based SDLs by displaying functions and parameters for initialized modules dynamically.
26
+ The modules can be hardware API, high-level functions, or experiment workflow.
27
+ With the least modification of the current workflow, user can design, manage and execute their experimental designs and monitor the execution process.
28
+ ## AI assistant
29
+ To streamline the experimental design on SDLs, we also integrate Large Language Models (LLMs) to interpret the inspected functions and generate code according to task descriptions.
30
+
31
+ ## Installation
32
+ ```
33
+ pip install ivoryos
34
+ ```
35
+
36
+ ## Usage
37
+ ### Quick start
38
+ In your SDL script, use `ivoryos(__name__)`. Example in [abstract_sdl.py](https://gitlab.com/heingroup/ivoryos/-/blob/main/example/dummy_ur/dummy_deck.py)
39
+
40
+ ```python
41
+ import ivoryos
42
+
43
+ ivoryos.run(__name__)
44
+ ```
45
+
46
+ ### Additional settings
47
+ #### Enable LLMs with [OpenAI API](https://github.com/openai/openai-python)
48
+ 1. Create a `.env` file for `OPENAI_API_KEY`
49
+ ```
50
+ OPENAI_API_KEY="Your API Key"
51
+ ```
52
+ 2. In your SDL script, define model, you can use any GPT models.
53
+
54
+ ```python
55
+ ivoryos.run(__name__, model="gpt-3.5-turbo")
56
+ ```
57
+
58
+ #### Enable local LLMs with [Ollama](https://ollama.com/)
59
+ 1. Download Ollama.
60
+ 2. pull models from Ollama
61
+ 3. In your SDL script, define LLM server and model, you can use any models available on Ollama.
62
+
63
+ ```python
64
+ ivoryos.run(__name__, llm_server="localhost", model="llama3.1")
65
+ ```
66
+
67
+ #### Add additional logger(s)
68
+ ```python
69
+ ivoryos.run(__name__, logger="logger name")
70
+ ```
71
+ or
72
+ ```python
73
+ ivoryos.run(__name__, logger=["logger 1", "logger 2"])
74
+ ```
75
+ #### Offline (design without hardware connection)
76
+ After one successful connection, a blueprint will be automatically saved and made accessible without hardware connection. In a new Python script in the same directory, use `ivoryos.run()` to start offline mode.
77
+
78
+ ```python
79
+ ivoryos.run()
80
+ ```
81
+ ## Deck snapshot example
82
+ ![](https://gitlab.com/heingroup/ivoryos/raw/main/docs/demo.gif)
83
+ ## Developing
84
+ This is a wip project. Here are some future actions.
85
+ 1. Support @setter decorator.
86
+ 2. Documentation: white paper wip
87
+ 3. Compatibility: compatability report to open-source lab hardware APIs will soon be added. As of now, due to the limitation of web form, the usability of APIs with object inputs (e.g. Opentron Python API) is very limited.
88
+
89
+
90
+ ## Authors and Acknowledgement
91
+ Ivory Zhang, Lucy Hao
92
+
93
+ Authors acknowledge all former and current Hein Lab members for their valuable suggestions.
94
+
95
+ ## License
96
+ [LICENSE](LICENSE)