ivoryos 1.2.5__py3-none-any.whl → 1.4.4__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.
Files changed (75) hide show
  1. docs/source/conf.py +84 -0
  2. ivoryos/__init__.py +16 -246
  3. ivoryos/app.py +154 -0
  4. ivoryos/optimizer/ax_optimizer.py +55 -28
  5. ivoryos/optimizer/base_optimizer.py +20 -1
  6. ivoryos/optimizer/baybe_optimizer.py +27 -17
  7. ivoryos/optimizer/nimo_optimizer.py +173 -0
  8. ivoryos/optimizer/registry.py +3 -1
  9. ivoryos/routes/auth/auth.py +35 -8
  10. ivoryos/routes/auth/templates/change_password.html +32 -0
  11. ivoryos/routes/control/control.py +58 -28
  12. ivoryos/routes/control/control_file.py +12 -15
  13. ivoryos/routes/control/control_new_device.py +21 -11
  14. ivoryos/routes/control/templates/controllers.html +27 -0
  15. ivoryos/routes/control/utils.py +2 -0
  16. ivoryos/routes/data/data.py +110 -44
  17. ivoryos/routes/data/templates/components/step_card.html +78 -13
  18. ivoryos/routes/data/templates/workflow_view.html +343 -113
  19. ivoryos/routes/design/design.py +59 -10
  20. ivoryos/routes/design/design_file.py +3 -3
  21. ivoryos/routes/design/design_step.py +43 -17
  22. ivoryos/routes/design/templates/components/action_form.html +2 -2
  23. ivoryos/routes/design/templates/components/canvas_main.html +6 -1
  24. ivoryos/routes/design/templates/components/edit_action_form.html +18 -3
  25. ivoryos/routes/design/templates/components/info_modal.html +318 -0
  26. ivoryos/routes/design/templates/components/instruments_panel.html +23 -1
  27. ivoryos/routes/design/templates/components/python_code_overlay.html +27 -10
  28. ivoryos/routes/design/templates/experiment_builder.html +3 -0
  29. ivoryos/routes/execute/execute.py +82 -22
  30. ivoryos/routes/execute/templates/components/logging_panel.html +50 -25
  31. ivoryos/routes/execute/templates/components/run_tabs.html +45 -2
  32. ivoryos/routes/execute/templates/components/tab_bayesian.html +447 -325
  33. ivoryos/routes/execute/templates/components/tab_configuration.html +303 -18
  34. ivoryos/routes/execute/templates/components/tab_repeat.html +6 -2
  35. ivoryos/routes/execute/templates/experiment_run.html +0 -264
  36. ivoryos/routes/library/library.py +9 -11
  37. ivoryos/routes/main/main.py +30 -2
  38. ivoryos/server.py +180 -0
  39. ivoryos/socket_handlers.py +1 -1
  40. ivoryos/static/ivoryos_logo.png +0 -0
  41. ivoryos/static/js/action_handlers.js +259 -88
  42. ivoryos/static/js/socket_handler.js +40 -5
  43. ivoryos/static/js/sortable_design.js +29 -11
  44. ivoryos/templates/base.html +61 -2
  45. ivoryos/utils/bo_campaign.py +18 -17
  46. ivoryos/utils/client_proxy.py +267 -36
  47. ivoryos/utils/db_models.py +286 -60
  48. ivoryos/utils/decorators.py +34 -0
  49. ivoryos/utils/form.py +52 -19
  50. ivoryos/utils/global_config.py +21 -0
  51. ivoryos/utils/nest_script.py +314 -0
  52. ivoryos/utils/py_to_json.py +80 -10
  53. ivoryos/utils/script_runner.py +573 -189
  54. ivoryos/utils/task_runner.py +69 -22
  55. ivoryos/utils/utils.py +48 -5
  56. ivoryos/version.py +1 -1
  57. {ivoryos-1.2.5.dist-info → ivoryos-1.4.4.dist-info}/METADATA +109 -47
  58. ivoryos-1.4.4.dist-info/RECORD +119 -0
  59. ivoryos-1.4.4.dist-info/top_level.txt +3 -0
  60. tests/__init__.py +0 -0
  61. tests/conftest.py +133 -0
  62. tests/integration/__init__.py +0 -0
  63. tests/integration/test_route_auth.py +80 -0
  64. tests/integration/test_route_control.py +94 -0
  65. tests/integration/test_route_database.py +61 -0
  66. tests/integration/test_route_design.py +36 -0
  67. tests/integration/test_route_main.py +35 -0
  68. tests/integration/test_sockets.py +26 -0
  69. tests/unit/test_type_conversion.py +42 -0
  70. tests/unit/test_util.py +3 -0
  71. ivoryos/routes/api/api.py +0 -56
  72. ivoryos-1.2.5.dist-info/RECORD +0 -100
  73. ivoryos-1.2.5.dist-info/top_level.txt +0 -1
  74. {ivoryos-1.2.5.dist-info → ivoryos-1.4.4.dist-info}/WHEEL +0 -0
  75. {ivoryos-1.2.5.dist-info → ivoryos-1.4.4.dist-info}/licenses/LICENSE +0 -0
@@ -1,7 +1,10 @@
1
+ import inspect
2
+ import asyncio
1
3
  import threading
2
4
  import time
3
5
  from datetime import datetime
4
6
 
7
+ from ivoryos.utils.decorators import BUILDING_BLOCKS
5
8
  from ivoryos.utils.db_models import db, SingleStep
6
9
  from ivoryos.utils.global_config import GlobalConfig
7
10
 
@@ -18,8 +21,7 @@ class TaskRunner:
18
21
  self.globals_dict = globals_dict
19
22
  self.lock = global_config.runner_lock
20
23
 
21
-
22
- def run_single_step(self, component, method, kwargs, wait=True, current_app=None):
24
+ async def run_single_step(self, component, method, kwargs, wait=True, current_app=None):
23
25
  global deck
24
26
  if deck is None:
25
27
  deck = global_config.deck
@@ -28,18 +30,18 @@ class TaskRunner:
28
30
  if not self.lock.acquire(blocking=False):
29
31
  current_status = global_config.runner_status
30
32
  current_status["status"] = "busy"
33
+ current_status["output"] = "busy"
31
34
  return current_status
32
35
 
33
-
34
36
  if wait:
35
- output = self._run_single_step(component, method, kwargs, current_app)
37
+ output = await self._run_single_step(component, method, kwargs, current_app)
36
38
  else:
37
- print("running with thread")
38
- thread = threading.Thread(
39
- target=self._run_single_step, args=(component, method, kwargs, current_app)
40
- )
41
- thread.start()
42
- time.sleep(0.1)
39
+ # Create background task properly
40
+ async def background_runner():
41
+ await self._run_single_step(component, method, kwargs, current_app)
42
+
43
+ asyncio.create_task(background_runner())
44
+ await asyncio.sleep(0.1) # Change time.sleep to await asyncio.sleep
43
45
  output = {"status": "task started", "task_id": global_config.runner_status.get("id")}
44
46
 
45
47
  return output
@@ -48,37 +50,82 @@ class TaskRunner:
48
50
  if component.startswith("deck."):
49
51
  component = component.split(".")[1]
50
52
  instrument = getattr(deck, component)
53
+ function_executable = getattr(instrument, method)
54
+ elif component.startswith("blocks."):
55
+ component = component.split(".")[1]
56
+ function_executable = BUILDING_BLOCKS[component][method]["func"]
51
57
  else:
52
58
  temp_connections = global_config.defined_variables
53
59
  instrument = temp_connections.get(component)
54
- function_executable = getattr(instrument, method)
60
+ function_executable = getattr(instrument, method)
55
61
  return function_executable
56
62
 
57
- def _run_single_step(self, component, method, kwargs, current_app=None):
63
+ async def _run_single_step(self, component, method, kwargs, current_app=None):
58
64
  try:
59
65
  function_executable = self._get_executable(component, deck, method)
60
- method_name = f"{function_executable.__self__.__class__.__name__}.{function_executable.__name__}"
66
+ method_name = f"{component}.{method}"
61
67
  except Exception as e:
62
68
  self.lock.release()
63
- return {"status": "error", "msg": e.__str__()}
69
+ return {"status": "error", "msg": str(e)}
64
70
 
65
- # with self.lock:
71
+ # Flask context is NOT async → just use normal "with"
66
72
  with current_app.app_context():
67
- step = SingleStep(method_name=method_name, kwargs=kwargs, run_error=False, start_time=datetime.now())
73
+ step = SingleStep(
74
+ method_name=method_name,
75
+ kwargs=kwargs,
76
+ run_error=None,
77
+ start_time=datetime.now()
78
+ )
68
79
  db.session.add(step)
69
- db.session.commit()
70
- global_config.runner_status = {"id":step.id, "type": "task"}
80
+ db.session.flush()
81
+ global_config.runner_status = {"id": step.id, "type": "task"}
82
+
71
83
  try:
72
- output = function_executable(**kwargs)
84
+ kwargs = self._convert_kwargs_type(kwargs, function_executable)
85
+
86
+ if inspect.iscoroutinefunction(function_executable):
87
+ output = await function_executable(**kwargs)
88
+ else:
89
+ output = function_executable(**kwargs)
90
+
73
91
  step.output = output
74
92
  step.end_time = datetime.now()
75
93
  success = True
76
94
  except Exception as e:
77
- step.run_error = e.__str__()
95
+ step.run_error = str(e)
78
96
  step.end_time = datetime.now()
79
97
  success = False
80
- output = e.__str__()
98
+ output = str(e)
81
99
  finally:
82
100
  db.session.commit()
83
101
  self.lock.release()
84
- return dict(success=success, output=output)
102
+
103
+ return dict(success=success, output=output)
104
+
105
+ @staticmethod
106
+ def _convert_kwargs_type(kwargs, function_executable):
107
+ def convert_guess(str_value):
108
+ str_value = str_value.strip()
109
+ if str_value.isdigit() or (str_value.startswith('-') and str_value[1:].isdigit()):
110
+ return int(str_value)
111
+ try:
112
+ return float(str_value)
113
+ except ValueError:
114
+ return str_value
115
+
116
+ sig = inspect.signature(function_executable)
117
+ converted = {}
118
+
119
+ for name, value in kwargs.items():
120
+ if name in sig.parameters:
121
+ param = sig.parameters[name]
122
+ if param.annotation != inspect.Parameter.empty:
123
+ # convert using type hint
124
+ try:
125
+ converted[name] = param.annotation(value)
126
+ except Exception:
127
+ converted[name] = value
128
+ else:
129
+ # no type hint → guess
130
+ converted[name] = convert_guess(value)
131
+ return converted
ivoryos/utils/utils.py CHANGED
@@ -1,6 +1,7 @@
1
1
  import ast
2
2
  import importlib
3
3
  import inspect
4
+ import json
4
5
  import logging
5
6
  import os
6
7
  import pickle
@@ -15,7 +16,7 @@ from flask_login import current_user
15
16
  from flask_socketio import SocketIO
16
17
 
17
18
  from ivoryos.utils.db_models import Script
18
-
19
+ from ivoryos.utils.decorators import BUILDING_BLOCKS
19
20
 
20
21
  def get_script_file():
21
22
  """Get script from Flask session and returns the script"""
@@ -105,7 +106,8 @@ def _inspect_class(class_object=None, debug=False):
105
106
  try:
106
107
  annotation = inspect.signature(method)
107
108
  docstring = inspect.getdoc(method)
108
- functions[function] = dict(signature=annotation, docstring=docstring)
109
+ coroutine = inspect.iscoroutinefunction(method)
110
+ functions[function] = dict(signature=annotation, docstring=docstring, coroutine=coroutine,)
109
111
 
110
112
  except Exception:
111
113
  pass
@@ -141,6 +143,7 @@ def _get_type_from_parameters(arg, parameters):
141
143
  def _convert_by_str(args, arg_types):
142
144
  """
143
145
  Converts a value to type through eval(f'{type}("{args}")')
146
+ v1.3.4 TODO try str lastly, otherwise it's always converted to str
144
147
  """
145
148
  if type(arg_types) is not list:
146
149
  arg_types = [arg_types]
@@ -151,6 +154,7 @@ def _convert_by_str(args, arg_types):
151
154
  return args
152
155
  except Exception:
153
156
  raise TypeError(f"Input type error: cannot convert '{args}' to {arg_type}.")
157
+ return args
154
158
 
155
159
 
156
160
  def _convert_by_class(args, arg_types):
@@ -348,8 +352,8 @@ def create_deck_snapshot(deck, save: bool = False, output_path: str = '', exclud
348
352
  for name, class_type in items.items():
349
353
  print(f" {name}: {class_type}")
350
354
 
351
- print_section("✅ INCLUDED", deck_summary["included"])
352
- print_section("❌ FAILED", deck_summary["failed"])
355
+ print_section("✅ INCLUDED MODULES", deck_summary["included"])
356
+ print_section("❌ FAILED MODULES", deck_summary["failed"])
353
357
  print("\n")
354
358
 
355
359
  print_deck_snapshot(deck_summary)
@@ -364,6 +368,28 @@ def create_deck_snapshot(deck, save: bool = False, output_path: str = '', exclud
364
368
  return deck_snapshot
365
369
 
366
370
 
371
+ def create_block_snapshot(save: bool = False, output_path: str = ''):
372
+ block_snapshot = {}
373
+ included = {}
374
+ failed = {}
375
+ for category, data in BUILDING_BLOCKS.items():
376
+ key = f"blocks.{category}"
377
+ block_snapshot[key] = {}
378
+
379
+ for func_name, meta in data.items():
380
+ func = meta["func"]
381
+ block_snapshot[key][func_name] = {
382
+ "signature": meta["signature"],
383
+ "docstring": meta["docstring"],
384
+ "coroutine": meta["coroutine"],
385
+ "path": f"{func.__module__}.{func.__qualname__}"
386
+ }
387
+ if block_snapshot:
388
+ print(f"\n=== ✅ BUILDING_BLOCKS ({len(block_snapshot)}) ===")
389
+ for category, blocks in block_snapshot.items():
390
+ print(f" {category}: ", ",".join(blocks.keys()))
391
+ return block_snapshot
392
+
367
393
  def load_deck(pkl_name: str):
368
394
  """
369
395
  Loads a pickled deck snapshot from disk on offline mode
@@ -420,4 +446,21 @@ def get_local_ip():
420
446
  ip = '127.0.0.1'
421
447
  finally:
422
448
  s.close()
423
- return ip
449
+ return ip
450
+
451
+
452
+ def safe_dump(obj):
453
+ try:
454
+ json.dumps(obj)
455
+ return obj
456
+ except (TypeError, OverflowError):
457
+ return repr(obj) # store readable representation
458
+
459
+
460
+ def create_module_snapshot(module):
461
+ classes = inspect.getmembers(module, inspect.isclass)
462
+ api_variables = {}
463
+ for i in classes:
464
+ # globals()[i[0]] = i[1]
465
+ api_variables[i[0]] = i[1]
466
+ return api_variables
ivoryos/version.py CHANGED
@@ -1 +1 @@
1
- __version__ = "1.2.5"
1
+ __version__ = "1.4.4"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ivoryos
3
- Version: 1.2.5
3
+ Version: 1.4.4
4
4
  Summary: an open-source Python package enabling Self-Driving Labs (SDLs) interoperability
5
5
  Author-email: Ivory Zhang <ivoryzhang@chem.ubc.ca>
6
6
  License: MIT
@@ -9,6 +9,7 @@ Requires-Python: >=3.7
9
9
  Description-Content-Type: text/markdown
10
10
  License-File: LICENSE
11
11
  Requires-Dist: bcrypt
12
+ Requires-Dist: Flask[async]
12
13
  Requires-Dist: Flask-Login
13
14
  Requires-Dist: Flask-Session
14
15
  Requires-Dist: Flask-SocketIO
@@ -16,10 +17,24 @@ Requires-Dist: Flask-SQLAlchemy
16
17
  Requires-Dist: Flask-WTF
17
18
  Requires-Dist: SQLAlchemy-Utils
18
19
  Requires-Dist: python-dotenv
20
+ Requires-Dist: pandas
19
21
  Requires-Dist: astor; python_version < "3.9"
20
- Provides-Extra: optimizer
21
- Requires-Dist: ax-platform; extra == "optimizer"
22
- Requires-Dist: baybe; extra == "optimizer"
22
+ Provides-Extra: optimizer-ax
23
+ Requires-Dist: ax-platform; extra == "optimizer-ax"
24
+ Provides-Extra: optimizer-baybe
25
+ Requires-Dist: baybe; extra == "optimizer-baybe"
26
+ Provides-Extra: optimizer-nimo
27
+ Requires-Dist: nimo; extra == "optimizer-nimo"
28
+ Provides-Extra: optimizers
29
+ Requires-Dist: ax-platform>=1.1.2; extra == "optimizers"
30
+ Requires-Dist: baybe>=0.14.0; extra == "optimizers"
31
+ Requires-Dist: nimo; extra == "optimizers"
32
+ Provides-Extra: doc
33
+ Requires-Dist: sphinx; extra == "doc"
34
+ Requires-Dist: sphinx-rtd-theme; extra == "doc"
35
+ Requires-Dist: sphinxcontrib-httpdomain; extra == "doc"
36
+ Provides-Extra: dev
37
+ Requires-Dist: pytest; extra == "dev"
23
38
  Dynamic: license-file
24
39
 
25
40
  [![Documentation Status](https://readthedocs.org/projects/ivoryos/badge/?version=latest)](https://ivoryos.readthedocs.io/en/latest/?badge=latest)
@@ -28,33 +43,36 @@ Dynamic: license-file
28
43
  [![YouTube](https://img.shields.io/badge/YouTube-tutorial-red?logo=youtube)](https://youtu.be/dFfJv9I2-1g)
29
44
  [![YouTube](https://img.shields.io/badge/YouTube-demo-red?logo=youtube)](https://youtu.be/flr5ydiE96s)
30
45
  [![Published](https://img.shields.io/badge/Nature_Comm.-paper-blue)](https://www.nature.com/articles/s41467-025-60514-w)
31
- [![Discord](https://img.shields.io/discord/1313641159356059770?label=Discord&logo=discord&color=5865F2)](https://discord.gg/AX5P9EdGVX)
32
46
 
33
- ![](https://gitlab.com/heingroup/ivoryos/raw/main/docs/source/_static/ivoryos.png)
34
- # ivoryOS: interoperable Web UI for self-driving laboratories (SDLs)
35
- A **plug-and-play** web interface for flexible SDLs
47
+ [//]: # ([![Discord]&#40;https://img.shields.io/discord/1313641159356059770?label=Discord&logo=discord&color=5865F2&#41;]&#40;https://discord.gg/AX5P9EdGVX&#41;)
48
+
49
+ ![ivoryos_logo.png](https://gitlab.com/heingroup/ivoryos/raw/main/docs/source/_static/ivoryos_logo.png)
50
+
51
+ # [IvoryOS](https://ivoryos.ai): interoperable orchestrator for self-driving laboratories (SDLs)
52
+
53
+ A **plug-and-play** web interface for flexible, modular SDLs —
54
+ you focus on developing protocols, IvoryOS handles the rest.
55
+
56
+ ![code_launch_design.png](https://gitlab.com/heingroup/ivoryos/raw/main/docs/source/_static/code_launch_design.png)
36
57
 
37
58
  ---
38
59
 
39
60
  ## Table of Contents
40
- - [Description](#description)
61
+ - [What IvoryOS does](#what-ivoryos-does)
41
62
  - [System requirements](#system-requirements)
42
63
  - [Installation](#installation)
43
- - [Quick Start](#quick-start)
44
64
  - [Features](#features)
45
65
  - [Demo](#demo)
46
66
  - [Roadmap](#roadmap)
67
+ - [Contributing](#contributing)
47
68
  - [Acknowledgements](#acknowledgements)
48
69
 
49
70
  ---
50
- ## Description
51
- Building UIs for SDLs is challenging because flexibility and modularity make them unpredictable yet accessibility is essential for **democratisation** of AI-driven scientific discovery.
52
-
53
- **IvoryOS** bridges the gap by:
54
- - Dynamically inspecting initialized Python modules (hardware APIs, high-level functions, or workflows)
55
- - Automatically displaying functions and parameters in a web UI
56
- - Allowing users to **design**, **manage**, and **execute** experimental workflows with minimal changes to existing scripts
57
- - Providing natural language support for workflow design and execution, check [IvoryOS MCP](https://gitlab.com/heingroup/ivoryos-suite/ivoryos-mcp) for more details.
71
+ ## What IvoryOS Does
72
+ - Turns Python modules into UIs by dynamically inspecting your hardware APIs, functions, and workflows.
73
+ - Standardizes optimization inputs/outputs, making any optimizer plug-and-play.
74
+ - Provides a visual workflow builder for designing and running experiments.
75
+ - Adds natural-language control for creating and executing workflows, see [IvoryOS MCP](https://gitlab.com/heingroup/ivoryos-suite/ivoryos-mcp) for more details.
58
76
 
59
77
  ----
60
78
  ## System Requirements
@@ -76,10 +94,13 @@ Building UIs for SDLs is challenging because flexibility and modularity make the
76
94
  - SQLAlchemy-Utils~=0.41
77
95
  - Flask-WTF~=1.2
78
96
  - python-dotenv==1.0.1
97
+ - pandas
79
98
 
80
99
  **Optional:**
81
- - ax-platform (≥1.0, Python≥3.10)
82
- - baybe
100
+ - ax-platform==1.1.2
101
+ - baybe==0.14.0
102
+ - nimo
103
+ - slack-sdk
83
104
  </details>
84
105
 
85
106
  ---
@@ -90,22 +111,31 @@ From PyPI:
90
111
  ```bash
91
112
  pip install ivoryos
92
113
  ```
93
- From source:
94
- ```bash
95
- git clone https://gitlab.com/heingroup/ivoryos.git
96
- cd ivoryos
97
- pip install -e .
98
- ```
114
+
115
+ [//]: # (From source:)
116
+
117
+ [//]: # (```bash)
118
+
119
+ [//]: # (git clone https://gitlab.com/heingroup/ivoryos.git)
120
+
121
+ [//]: # (cd ivoryos)
122
+
123
+ [//]: # (pip install -e .)
124
+
125
+ [//]: # (```)
99
126
 
100
127
 
101
128
  ## Quick start
102
- In your SDL script,
129
+ In your script, where you initialize or import your robot:
103
130
  ```python
131
+ my_robot = Robot()
132
+
104
133
  import ivoryos
105
134
 
106
135
  ivoryos.run(__name__)
107
136
  ```
108
- Login: Create an account (local DB, bcrypt password)
137
+ Then run the script and visit `http://localhost:8000` in your browser.
138
+ Use `admin` for both username and password, and start building workflows!
109
139
 
110
140
  ----
111
141
  ## Features
@@ -127,42 +157,72 @@ Add single or multiple loggers:
127
157
  ivoryos.run(__name__, logger="logger name")
128
158
  ivoryos.run(__name__, logger=["logger 1", "logger 2"])
129
159
  ```
160
+ ### Human-in-the-loop
161
+ Use `pause` in flow control to pause the workflow and send a notification with custom message handler(s).
162
+ When run into `pause`, it will pause, send a message, and wait for human's response. Example of a Slack bot:
163
+ ```python
164
+
165
+ def slack_bot(msg: str = "Hi"):
166
+ """
167
+ a function that can be used as a notification handler function("msg")
168
+ :param msg: message to send
169
+ """
170
+ from slack_sdk import WebClient
171
+
172
+ slack_token = "your slack token"
173
+ client = WebClient(token=slack_token)
174
+
175
+ my_user_id = "your user id" # replace with your actual Slack user ID
176
+
177
+ client.chat_postMessage(channel=my_user_id, text=msg)
178
+
179
+ import ivoryos
180
+ ivoryos.run(__name__, notification_handler=slack_bot)
181
+ ```
182
+
130
183
  ### Directory Structure
131
184
 
132
- Created automatically on first run:
185
+ Created automatically in the same working directory on the first run:
186
+ <details>
187
+ <summary>click to see the data folder structure</summary>
188
+
133
189
  - **`ivoryos_data/`**:
134
- - **`ivoryos_data/config_csv/`**: Batch configuration `csv`
135
- - **`ivoryos_data/pseudo_deck/`**: Offline deck `.pkl`
136
- - **`ivoryos_data/results/`**: Execution results
137
- - **`ivoryos_data/scripts/`**: Compiled workflows Python scripts
138
- - **`default.log`**: Application logs
139
- - **`ivoryos.db`**: Local database
190
+ - **`config_csv/`**: Batch configuration `csv`
191
+ - **`pseudo_deck/`**: Offline deck `.pkl`
192
+ - **`results/`**: Execution results
193
+ - **`scripts/`**: Compiled workflows Python scripts
194
+ - **`default.log`**: Application logs
195
+ - **`ivoryos.db`**: Local database
196
+ </details>
197
+
140
198
  ---
141
- ## Demo
142
- In the [abstract_sdl.py](https://gitlab.com/heingroup/ivoryos/-/blob/main/example/abstract_sdl_example/abstract_sdl.py)
143
- ```Python
144
- ivoryos.run(__name__)
145
- ```
146
199
 
147
- * Running on all addresses (0.0.0.0)
148
- * Running on http://127.0.0.1:8000
149
- * Running on http://0.0.0.0:8000
200
+ ## Demo
201
+ Online demo at [demo.ivoryos.ai](https://demo.ivoryos.ai).
202
+ Local version in [abstract_sdl.py](https://gitlab.com/heingroup/ivoryos/-/blob/main/community/examples/abstract_sdl_example/abstract_sdl.py)
150
203
 
151
204
  ---
152
205
 
153
206
  ## Roadmap
154
207
 
155
- - [x] Allow plugin pages ✅
156
- - [x] pause, resume, abort current and pending workflows ✅
157
208
  - [ ] dropdown input
158
209
  - [ ] snapshot version control
159
- - [ ] optimizer-agnostic
160
210
  - [ ] check batch-config file compatibility
161
211
 
162
212
  ---
163
213
 
214
+ ## Contributing
215
+
216
+ We welcome all contributions — from core improvements to new drivers, plugins, and real-world use cases.
217
+ See `CONTRIBUTING.md` for details and let us know you're interested: https://forms.gle/fPSvw5LEGrweUQUH8
218
+
219
+ ---
220
+
164
221
  ## Citing
165
222
 
223
+ <details>
224
+ <summary>Click to see citations</summary>
225
+
166
226
  If you find this project useful, please consider citing the following manuscript:
167
227
 
168
228
  > Zhang, W., Hao, L., Lai, V. et al. [IvoryOS: an interoperable web interface for orchestrating Python-based self-driving laboratories.](https://www.nature.com/articles/s41467-025-60514-w) Nat Commun 16, 5182 (2025).
@@ -196,6 +256,8 @@ For an additional perspective related to the development of the tool, please see
196
256
  url = {https://communities.springernature.com/posts/behind-ivoryos-empowering-scientists-to-harness-self-driving-labs-for-accelerated-discovery}
197
257
  }
198
258
  ```
259
+ </details>
260
+
199
261
  ---
200
262
  ## Acknowledgements
201
- Authors acknowledge Telescope Innovations Corp., Hein Lab members for their valuable suggestions and contributions.
263
+ Authors acknowledge Telescope Innovations Corp., UBC Hein Lab, and Acceleration Consortium members for their valuable suggestions and contributions.
@@ -0,0 +1,119 @@
1
+ docs/source/conf.py,sha256=hETfkDMTdj-NYgSI_671gVOVd7iwqU3tEeXnQe2xEWs,2004
2
+ ivoryos/__init__.py,sha256=gEvBO2y5TRq06Itjjej3iAcq73UsihqKPWcb2HykPwM,463
3
+ ivoryos/app.py,sha256=TG-RctBKj8VVOeXciYZ2s_ehP01PACwvLRfl2f3vF6w,5561
4
+ ivoryos/config.py,sha256=y3RxNjiIola9tK7jg-mHM8EzLMwiLwOzoisXkDvj0gA,2174
5
+ ivoryos/server.py,sha256=9zw3c4IGj3DCtjRzEdEG3N4uxAtRZA1fowQGW1d_y94,7208
6
+ ivoryos/socket_handlers.py,sha256=KSh8TxbLFTcmaa-lEb5rKJvOYjKgnGTtaKZST8Wh3b8,1326
7
+ ivoryos/version.py,sha256=6stz_YFBRP_lguydVylsTgSmj3VTb6WFq8Sp45WYQyY,22
8
+ ivoryos/optimizer/ax_optimizer.py,sha256=43w4ollMfWn1DPB4jJ15pc13XxIktWjlxHn9NX339go,8566
9
+ ivoryos/optimizer/base_optimizer.py,sha256=m_wXan_xPueUWNQi3-561Pe_060tsKuUcCTOOu0qu8s,2652
10
+ ivoryos/optimizer/baybe_optimizer.py,sha256=grXmePNuTQsCgRDhJGsAWQuSvvt0hqjhvzpyrZKYjD0,8502
11
+ ivoryos/optimizer/nimo_optimizer.py,sha256=ZevLJUhJKpwatyiOSmPEz3JAUZTdZNKEc_J7z1fAvVQ,7303
12
+ ivoryos/optimizer/registry.py,sha256=dLMo5cszcwa06hfBxdINQIGpkHtRe5-J3J7t76Jq6X0,306
13
+ ivoryos/routes/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
14
+ ivoryos/routes/auth/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
15
+ ivoryos/routes/auth/auth.py,sha256=AkYnyI4u5ySshpwPK8Z0KVEbR9X1BGdrqDu_IDea-AM,4348
16
+ ivoryos/routes/auth/templates/change_password.html,sha256=GfnytIPyhiLgnc2FzX-aXad1IV2I0nE3A-BQ4KVdaA0,1517
17
+ ivoryos/routes/auth/templates/login.html,sha256=WSRrKbdM_oobqSXFRTo-j9UlOgp6sYzS9tm7TqqPULI,1207
18
+ ivoryos/routes/auth/templates/signup.html,sha256=b5LTXtpfTSkSS7X8u1ldwQbbgEFTk6UNMAediA5BwBY,1465
19
+ ivoryos/routes/control/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
20
+ ivoryos/routes/control/control.py,sha256=0g23PoPPoTeMD7Szm3UAW10sEMLpSD-iTfASAZE4iIE,6442
21
+ ivoryos/routes/control/control_file.py,sha256=3fQ9R8EcdqKs_hABn2EqRAB1xC2DHAT_q_pwsMIDDQI,864
22
+ ivoryos/routes/control/control_new_device.py,sha256=oHbNUjTyv9yh-4FNTxkJqunaZbyH0RiiLoqIjLYUTEQ,5725
23
+ ivoryos/routes/control/utils.py,sha256=XlhhqAtOj7n3XfHPDxJ8TvCV2K2I2IixB0CBkl1QeQc,1242
24
+ ivoryos/routes/control/templates/controllers.html,sha256=5hF3zcx5Rpy0Zaoq-5YGrR_TvPD9MGIa30fI4smEii0,9702
25
+ ivoryos/routes/control/templates/controllers_new.html,sha256=eVeLABT39DWOIYrwWClw7sAD3lCoAGCznygPgFbQoRc,5945
26
+ ivoryos/routes/data/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
27
+ ivoryos/routes/data/data.py,sha256=MR7Vu7HB9dWjVr3cPwJbfIvTWs1Rqh2uXOPWIEFivzs,6399
28
+ ivoryos/routes/data/templates/workflow_database.html,sha256=ofvHcovpwmJXo1SFiSrL8I9kLU_3U1UxsJUUrQ2CJUU,4878
29
+ ivoryos/routes/data/templates/workflow_view.html,sha256=NljmhBVBUYfoOkuUWpVYUYsQkBFPbKlmzYFmFY7KHvY,13011
30
+ ivoryos/routes/data/templates/components/step_card.html,sha256=fxTg-1_Vw8b8QzAUXsPMfFgTmcHo8esAdLTUvK1cKVI,3726
31
+ ivoryos/routes/design/__init__.py,sha256=zS3HXKaw0ALL5n6t_W1rUz5Uj5_tTQ-Y1VMXyzewvR0,113
32
+ ivoryos/routes/design/design.py,sha256=n7768F60Nx04ST7yZSoKiNsoRFCwKjFsZvdzveoFLq0,20042
33
+ ivoryos/routes/design/design_file.py,sha256=MVIc5uGSaGxZhs86hfPjX2n0iy1OcXeLq7b9Ucdg4VQ,2115
34
+ ivoryos/routes/design/design_step.py,sha256=W2mFKMOkbSDBk4Gx9wRYcwjeru2mj-ub1l_K4hPRuZY,6031
35
+ ivoryos/routes/design/templates/experiment_builder.html,sha256=B5NHnOfTbC8xIfmhIQZlQn1_eTd1Ld6c_q5xoMed_Hs,1747
36
+ ivoryos/routes/design/templates/components/action_form.html,sha256=kXJOrJLbFsMHHWVSuMQHpt1xFrUMnwgzTG8e6Qfn0Cg,3042
37
+ ivoryos/routes/design/templates/components/actions_panel.html,sha256=jHTR58saTUIZInBdC-vLc1ZTbStLiULeWbupjB4hQzo,977
38
+ ivoryos/routes/design/templates/components/autofill_toggle.html,sha256=CRVQUHoQT7sOSO5-Vax54ImHdT4G_mEgqR5OQkeUwK8,617
39
+ ivoryos/routes/design/templates/components/canvas.html,sha256=bKLCJaG1B36Yy9Vsnz4P5qiX4BPdfaGe9JeQQzu9rsI,268
40
+ ivoryos/routes/design/templates/components/canvas_footer.html,sha256=5VRRacMZbzx0hUej0NPP-PmXM_AtUqduHzDS7a60cQY,435
41
+ ivoryos/routes/design/templates/components/canvas_header.html,sha256=7iIzLDGHX7MnmBbf98nWtLDprbeIgoNV4dJUO1zE4Tc,3598
42
+ ivoryos/routes/design/templates/components/canvas_main.html,sha256=nLEtp3U2YtfJwob1kR8ua8-UVdu9hwc6z1L5UMNVz8c,1524
43
+ ivoryos/routes/design/templates/components/deck_selector.html,sha256=ryTRpljYezo0AzGLCJu_qOMokjjnft3GIxddmNGtBA0,657
44
+ ivoryos/routes/design/templates/components/edit_action_form.html,sha256=1xL0wueewp_Hdua9DzYJBry09Qcchh4XgGmt4qPCwnc,2782
45
+ ivoryos/routes/design/templates/components/info_modal.html,sha256=ifetOzL124a7NZFsJhjmm04fTaKyibHJ32I0ek0zPoA,19156
46
+ ivoryos/routes/design/templates/components/instruments_panel.html,sha256=tRKd-wOqKjaMJCLuGgRmHtxIgSjklhBkuX8arm5aTCU,4268
47
+ ivoryos/routes/design/templates/components/modals.html,sha256=6Dl8I8oD4ln7kK8C5e92pFVVH5KDte-vVTL0U_6NSTg,306
48
+ ivoryos/routes/design/templates/components/python_code_overlay.html,sha256=Iyk-wOzk1x2997kqGt3uJONh_rpi5sCUs7VCRDg8Q9U,2150
49
+ ivoryos/routes/design/templates/components/sidebar.html,sha256=A6dRo53zIB6QJVrRLJcBZHUNJ3qpYPnR3kWxM8gTkjw,501
50
+ ivoryos/routes/design/templates/components/text_to_code_panel.html,sha256=d-omdXk-PXAR5AyWPr4Rc4pqsebZOiTiMrnz3pPCnUY,1197
51
+ ivoryos/routes/design/templates/components/modals/drop_modal.html,sha256=LPxcycSiBjdQbajYOegjMQEi7ValcaczGoWmW8Sz3Ms,779
52
+ ivoryos/routes/design/templates/components/modals/json_modal.html,sha256=R-SeEdhtuDVbwOWYYH_hCdpul7y4ybCWoNwVIO5j49s,1122
53
+ ivoryos/routes/design/templates/components/modals/new_script_modal.html,sha256=pxZdWWDgI52VsTFzz6pIM9m_dTwR6jARcvCYQ6fV3Lc,937
54
+ ivoryos/routes/design/templates/components/modals/rename_modal.html,sha256=40rLNF9JprdXekB3mv_S3OdqVuQYOe-BZSCgOnIkxJQ,1202
55
+ ivoryos/routes/design/templates/components/modals/saveas_modal.html,sha256=N5PEqUuK3qxDFbtDKFnzHwhLarQLPHiX-XQAdQPL1AU,1555
56
+ ivoryos/routes/execute/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
57
+ ivoryos/routes/execute/execute.py,sha256=hf64lnhiFxjAXy7b5XsFbORKAlkFTQEtqbfrhdYplXk,14603
58
+ ivoryos/routes/execute/execute_file.py,sha256=TelWYV295p4ZPhkUDJSVxfYROfVaKodEmDPTS2plQHI,2816
59
+ ivoryos/routes/execute/templates/experiment_run.html,sha256=fvHRrG5ii1v5zPHL9N_ljDGHUo3t3amiproVPtnOFVE,944
60
+ ivoryos/routes/execute/templates/components/error_modal.html,sha256=5Dmp9V0Ys6781Y-pKn_mc4N9J46c8EwIkjkHX22xCsw,1025
61
+ ivoryos/routes/execute/templates/components/logging_panel.html,sha256=-elcawnE4CaumyivzxaHW3S5xSv5CgZtXt0OHljlits,2554
62
+ ivoryos/routes/execute/templates/components/progress_panel.html,sha256=-nX76aFLxSOiYgI1xMjznC9rDYF-Vb92TmfjXYpBtps,1323
63
+ ivoryos/routes/execute/templates/components/run_panel.html,sha256=CmK-LYJ4K6RonHn6l9eJkqRw0XQizThOugxiXZonSDs,373
64
+ ivoryos/routes/execute/templates/components/run_tabs.html,sha256=HZUBQxJIigp9DgMvcxZOoR_pqz4ZW9kpxhbCjW6_dRg,2724
65
+ ivoryos/routes/execute/templates/components/tab_bayesian.html,sha256=oxtXWb5rUo88yTyyB9dZNxfzi0YanKy8NS9SBuiZKbI,24864
66
+ ivoryos/routes/execute/templates/components/tab_configuration.html,sha256=uZQfi8QLIGbCvgQR2M-fuFAjW4id9ANlYgBUbTJfaZw,14878
67
+ ivoryos/routes/execute/templates/components/tab_repeat.html,sha256=s8Q9Vuztf_h0vy97CBjHwgMdbaLQwall6StVdbL6FY8,989
68
+ ivoryos/routes/library/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
69
+ ivoryos/routes/library/library.py,sha256=QKeP34COssXDipn3d9yEn7pVPYbPb-eFg_X0C8JTvVQ,5387
70
+ ivoryos/routes/library/templates/library.html,sha256=kMaQphkoGQdxIRcQmVcEIn8eghuv2AAClHpo7jGDVv8,4021
71
+ ivoryos/routes/main/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
72
+ ivoryos/routes/main/main.py,sha256=3hM85DB0edtjTh0YYVqwfWe0-gL2dX7F6HooTrCRvvs,1856
73
+ ivoryos/routes/main/templates/help.html,sha256=IOktMEsOPk0SCiMBXZ4mpffClERAyX8W82fel71M3M0,9370
74
+ ivoryos/routes/main/templates/home.html,sha256=BDvwkVthxniQ157H6E2hgYHT1Vv1GVBwu6dQejtzwoo,4633
75
+ ivoryos/static/favicon.ico,sha256=RhlrPtfITOkzC9BjP1UB1V5L9Oyp6NwNtWeMcGOnpyc,15406
76
+ ivoryos/static/ivoryos_logo.png,sha256=I-1POqhLdPveruxsFbKhKUKAXspHfyxvowpCRFxEzvc,11656
77
+ ivoryos/static/logo.webp,sha256=lXgfQR-4mHTH83k7VV9iB54-oC2ipe6uZvbwdOnLETc,14974
78
+ ivoryos/static/style.css,sha256=zQVx35A5g6JMJ-K84-6fSKtzXGjp_p5ZVG6KLHPM2IE,4021
79
+ ivoryos/static/gui_annotation/Slide1.png,sha256=Lm4gdOkUF5HIUFaB94tl6koQVkzpitKj43GXV_XYMMc,121727
80
+ ivoryos/static/gui_annotation/Slide2.PNG,sha256=z3wQ9oVgg4JTWVLQGKK_KhtepRHUYP1e05XUWGT2A0I,118761
81
+ ivoryos/static/js/action_handlers.js,sha256=VC_kHFk0KKE7Q20PNRSIyxUDgrl_-54Tr2w55JvlhCU,11512
82
+ ivoryos/static/js/db_delete.js,sha256=l67fqUaN_FVDaL7v91Hd7LyRbxnqXx9nyjF34-7aewY,561
83
+ ivoryos/static/js/overlay.js,sha256=dPxop19es0E0ZUSY3d_4exIk7CJuQEnlW5uTt5fZfzI,483
84
+ ivoryos/static/js/script_metadata.js,sha256=m8VYZ8OGT2oTx1kXMXq60bKQI9WCbJNkzcFDzLvRuGc,1188
85
+ ivoryos/static/js/socket_handler.js,sha256=5MGLj-nNA3053eBcko4MZAB5zq2mkgr1g__c_rr3cE8,6849
86
+ ivoryos/static/js/sortable_card.js,sha256=ifmlGe3yy0U_KzMphV4ClRhK2DLOvkELYMlq1vECuac,807
87
+ ivoryos/static/js/sortable_design.js,sha256=ZezDxKtFxqmqSLfFy-HJ61LvQSXL36imYud6TIREs3U,5503
88
+ ivoryos/static/js/ui_state.js,sha256=XYsOcfGlduqLlqHySvPrRrR50CiAsml51duqneigsRY,3368
89
+ ivoryos/templates/base.html,sha256=uJ5lcYUCImiOasyP3g1-dQ-9Fdkff0Re6uUEGud2ySo,12011
90
+ ivoryos/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
91
+ ivoryos/utils/bo_campaign.py,sha256=Z4WXJwbf1Hhg1dtlvhWHncBiq9Cuaf0BiqR60bWy36U,10038
92
+ ivoryos/utils/client_proxy.py,sha256=74G3HAuq50iEHkSvlMZFmQaukm613FbRgOdzO_T3dMg,10191
93
+ ivoryos/utils/db_models.py,sha256=3cO5EcekYMDQQl3LujXR_8i0Lrgvs34BX34KUtafqwY,37994
94
+ ivoryos/utils/decorators.py,sha256=pcD8WijFjNfkNvr-wmJSWTHn_OqCOCpAO-7w_qg9_xM,1051
95
+ ivoryos/utils/form.py,sha256=MNIvmx4RhDukFACwyrXS-88tBeMKeNdxVba_KzOab_Y,23469
96
+ ivoryos/utils/global_config.py,sha256=leYoEXvAS0AH4xQpYsqu4HI9CJ9-wiLM-pIh_bEG4Ak,3087
97
+ ivoryos/utils/llm_agent.py,sha256=-lVCkjPlpLues9sNTmaT7bT4sdhWvV2DiojNwzB2Lcw,6422
98
+ ivoryos/utils/nest_script.py,sha256=kc4V0cBca5TviqQuquMFBF8Q5IjqKqj2R6g-_N6gMak,10645
99
+ ivoryos/utils/py_to_json.py,sha256=ZtejHgwdEAUCVVMYeVNR8G7ceLINue294q6WpiJ6jn0,9734
100
+ ivoryos/utils/script_runner.py,sha256=jjgloXAbcZprZmjeS-IXnw-KCIFQWKjPYqsv_PrN9Qs,32249
101
+ ivoryos/utils/serilize.py,sha256=lkBhkz8r2bLmz2_xOb0c4ptSSOqjIu6krj5YYK4Nvj8,6784
102
+ ivoryos/utils/task_runner.py,sha256=AnY0DwS5ytU5f7OfCFlNJ72yAonA3tla4UhC7MOM3mw,4818
103
+ ivoryos/utils/utils.py,sha256=n0tQKIsEIVqrWEjN8vJHmobDcZOPmvyrHxoBAdaQr4g,15414
104
+ ivoryos-1.4.4.dist-info/licenses/LICENSE,sha256=p2c8S8i-8YqMpZCJnadLz1-ofxnRMILzz6NCMIypRag,1084
105
+ tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
106
+ tests/conftest.py,sha256=u2sQ6U-Lghyl7et1Oz6J2E5VZ47VINKcjRM_2leAE2s,3627
107
+ tests/integration/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
108
+ tests/integration/test_route_auth.py,sha256=l3ZDqr0oiCWS3yYSXGK5yMP6qI2t7Sv5I9zoYTkiyQU,2754
109
+ tests/integration/test_route_control.py,sha256=YYIll84bTUEKiAxFiFSz6LF3fTldPNfCtHs0IR3mSdM,3935
110
+ tests/integration/test_route_database.py,sha256=mS026W_hEuCTMpSkdRWvM-f4MYykK_6nRDJ4K5a7QA0,2342
111
+ tests/integration/test_route_design.py,sha256=PJAvGRiCY6B53Pu1v5vPAVHHsuaqRmRKk2eesSNshLU,1157
112
+ tests/integration/test_route_main.py,sha256=bmuf8Y_9CRWhiLLf4up11ltEd5YCdsLx6I-o26VGDEw,1228
113
+ tests/integration/test_sockets.py,sha256=4ZyFyExm7a-DYzVqpzEONpWeb1a0IT68wyFaQu0rY_Y,925
114
+ tests/unit/test_type_conversion.py,sha256=zJjlBZPF4U0TJDECdFgYHPf-7lIEoQ3uy015RIRgTTA,2346
115
+ tests/unit/test_util.py,sha256=XSrZ3V2gKYhr1qEniWU9RY2Rm7PnO06ZCM05GaElncI,76
116
+ ivoryos-1.4.4.dist-info/METADATA,sha256=NEmN9NwX6lx6QAbX4JQciWSJfRNrmoFfP5qdjA6QAPY,9135
117
+ ivoryos-1.4.4.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
118
+ ivoryos-1.4.4.dist-info/top_level.txt,sha256=ZxZvj1N-GvvGOSN8pBy9SU9Ohf3ehzJfmGDh-M-0YuI,19
119
+ ivoryos-1.4.4.dist-info/RECORD,,
@@ -0,0 +1,3 @@
1
+ docs
2
+ ivoryos
3
+ tests
tests/__init__.py ADDED
File without changes