deepboard 0.2.0__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 (69) hide show
  1. deepboard/__init__.py +1 -0
  2. deepboard/__version__.py +4 -0
  3. deepboard/gui/THEME.yml +28 -0
  4. deepboard/gui/__init__.py +0 -0
  5. deepboard/gui/assets/artefacts.css +108 -0
  6. deepboard/gui/assets/base.css +208 -0
  7. deepboard/gui/assets/base.js +77 -0
  8. deepboard/gui/assets/charts.css +188 -0
  9. deepboard/gui/assets/compare.css +90 -0
  10. deepboard/gui/assets/datagrid.css +120 -0
  11. deepboard/gui/assets/fileview.css +13 -0
  12. deepboard/gui/assets/right_panel.css +227 -0
  13. deepboard/gui/assets/theme.css +85 -0
  14. deepboard/gui/components/__init__.py +8 -0
  15. deepboard/gui/components/artefact_group.py +12 -0
  16. deepboard/gui/components/chart_type.py +22 -0
  17. deepboard/gui/components/legend.py +34 -0
  18. deepboard/gui/components/log_selector.py +22 -0
  19. deepboard/gui/components/modal.py +20 -0
  20. deepboard/gui/components/smoother.py +21 -0
  21. deepboard/gui/components/split_selector.py +21 -0
  22. deepboard/gui/components/stat_line.py +8 -0
  23. deepboard/gui/entry.py +21 -0
  24. deepboard/gui/main.py +93 -0
  25. deepboard/gui/pages/__init__.py +1 -0
  26. deepboard/gui/pages/compare_page/__init__.py +6 -0
  27. deepboard/gui/pages/compare_page/compare_page.py +22 -0
  28. deepboard/gui/pages/compare_page/components/__init__.py +4 -0
  29. deepboard/gui/pages/compare_page/components/card_list.py +19 -0
  30. deepboard/gui/pages/compare_page/components/chart.py +54 -0
  31. deepboard/gui/pages/compare_page/components/compare_setup.py +30 -0
  32. deepboard/gui/pages/compare_page/components/split_card.py +51 -0
  33. deepboard/gui/pages/compare_page/components/utils.py +20 -0
  34. deepboard/gui/pages/compare_page/routes.py +58 -0
  35. deepboard/gui/pages/main_page/__init__.py +4 -0
  36. deepboard/gui/pages/main_page/datagrid/__init__.py +5 -0
  37. deepboard/gui/pages/main_page/datagrid/compare_button.py +21 -0
  38. deepboard/gui/pages/main_page/datagrid/datagrid.py +67 -0
  39. deepboard/gui/pages/main_page/datagrid/handlers.py +54 -0
  40. deepboard/gui/pages/main_page/datagrid/header.py +43 -0
  41. deepboard/gui/pages/main_page/datagrid/routes.py +112 -0
  42. deepboard/gui/pages/main_page/datagrid/row.py +20 -0
  43. deepboard/gui/pages/main_page/datagrid/sortable_column_js.py +45 -0
  44. deepboard/gui/pages/main_page/datagrid/utils.py +9 -0
  45. deepboard/gui/pages/main_page/handlers.py +16 -0
  46. deepboard/gui/pages/main_page/main_page.py +21 -0
  47. deepboard/gui/pages/main_page/right_panel/__init__.py +12 -0
  48. deepboard/gui/pages/main_page/right_panel/config.py +57 -0
  49. deepboard/gui/pages/main_page/right_panel/fragments.py +133 -0
  50. deepboard/gui/pages/main_page/right_panel/hparams.py +25 -0
  51. deepboard/gui/pages/main_page/right_panel/images.py +358 -0
  52. deepboard/gui/pages/main_page/right_panel/run_info.py +86 -0
  53. deepboard/gui/pages/main_page/right_panel/scalars.py +251 -0
  54. deepboard/gui/pages/main_page/right_panel/template.py +151 -0
  55. deepboard/gui/pages/main_page/routes.py +25 -0
  56. deepboard/gui/pages/not_found.py +3 -0
  57. deepboard/gui/requirements.txt +5 -0
  58. deepboard/gui/utils.py +267 -0
  59. deepboard/resultTable/__init__.py +2 -0
  60. deepboard/resultTable/cursor.py +20 -0
  61. deepboard/resultTable/logwritter.py +667 -0
  62. deepboard/resultTable/resultTable.py +529 -0
  63. deepboard/resultTable/scalar.py +29 -0
  64. deepboard/resultTable/table_schema.py +135 -0
  65. deepboard/resultTable/utils.py +50 -0
  66. deepboard-0.2.0.dist-info/METADATA +164 -0
  67. deepboard-0.2.0.dist-info/RECORD +69 -0
  68. deepboard-0.2.0.dist-info/WHEEL +4 -0
  69. deepboard-0.2.0.dist-info/entry_points.txt +2 -0
@@ -0,0 +1,112 @@
1
+ from typing import *
2
+ from fasthtml.common import *
3
+ from .datagrid import DataGrid
4
+ from .compare_button import CompareButton
5
+ from ..main_page import MainPage
6
+
7
+ def build_datagrid_endpoints(rt):
8
+ rt("/hide")(hide_column)
9
+ rt("/show")(show_column)
10
+ rt("/rename_col_datagrid")(get_rename_column)
11
+ rt("/rename_col", methods=["POST"])(post_rename_column)
12
+ rt("/sort")(sort)
13
+ rt("/reorder_columns", methods=["POST"])(reorder_columns)
14
+ rt("/shift_click_row")(shift_click_row) # Endpoint is called in the javascript file
15
+ rt("/hide_run")(hide_run)
16
+ rt("/show_run")(show_run)
17
+ rt("/show_hidden")(show_hidden)
18
+ rt("/hide_hidden")(hide_hidden)
19
+ rt("/compare_action")(compare_action)
20
+
21
+ async def hide_column(session, col: str):
22
+ from __main__ import rTable
23
+ rTable.hide_column(col)
24
+
25
+ # Return the datagrid
26
+ return DataGrid(session)
27
+
28
+ async def show_column(session, col: str, after: str):
29
+ from __main__ import rTable
30
+ cols = rTable.result_columns
31
+ if after not in cols:
32
+ print(f"[WARNING]: Did not find column: {after}")
33
+ return DataGrid(session)
34
+ pos = cols[after][0] + 1
35
+ print(f"Show column: {col} after {after} position {pos}")
36
+ rTable.show_column(col, pos)
37
+
38
+ # Return the datagrid
39
+ return DataGrid(session)
40
+
41
+ async def get_rename_column(session, col: str):
42
+ from __main__ import rTable
43
+ if col not in rTable.result_columns:
44
+ print(f"[WARNING]: Did not find column: {col}")
45
+ return DataGrid(session)
46
+ return DataGrid(session, rename_col=col)
47
+
48
+ async def post_rename_column(session, col_id: str, new_name: str):
49
+ from __main__ import rTable
50
+ if col_id not in rTable.result_columns:
51
+ print(f"[WARNING]: Did not find column: {col_id}")
52
+ return DataGrid(session)
53
+ rTable.set_column_alias({col_id: new_name})
54
+
55
+ # Return the datagrid
56
+ return DataGrid(session)
57
+
58
+ async def sort(session, by: str, order: str):
59
+ if "datagrid" not in session:
60
+ session["datagrid"] = dict(
61
+ sort_by=None,
62
+ sort_order=None
63
+ )
64
+ session["datagrid"]["sort_by"] = by
65
+ session["datagrid"]["sort_order"] = order
66
+ return DataGrid(session)
67
+
68
+ async def reorder_columns(session, order: str):
69
+ from __main__ import rTable
70
+ order = order.split(",")
71
+ prep_order = {col_id: i + 1 for i, col_id in enumerate(order)}
72
+ rTable.set_column_order(prep_order)
73
+ return DataGrid(session)
74
+
75
+ async def shift_click_row(session, run_id: int):
76
+ if "datagrid" not in session:
77
+ session["datagrid"] = dict()
78
+
79
+ session["datagrid"]["multiselection"] = True
80
+ if "selected-rows" not in session["datagrid"]:
81
+ session["datagrid"]["selected-rows"] = []
82
+
83
+ if run_id in session["datagrid"]["selected-rows"]:
84
+ session["datagrid"]["selected-rows"].remove(run_id)
85
+ else:
86
+ session["datagrid"]["selected-rows"].append(run_id)
87
+
88
+ return DataGrid(session), CompareButton(session, swap=True)
89
+
90
+ async def hide_run(session, run_id: int):
91
+ from __main__ import rTable
92
+ rTable.hide_run(run_id)
93
+ return DataGrid(session)
94
+
95
+ async def show_run(session, run_id: int):
96
+ from __main__ import rTable
97
+ rTable.show_run(run_id)
98
+ return DataGrid(session)
99
+
100
+ async def show_hidden(session):
101
+ session["show_hidden"] = True
102
+ return DataGrid(session)
103
+
104
+ async def hide_hidden(session):
105
+ session["show_hidden"] = False
106
+ return DataGrid(session)
107
+
108
+ def compare_action(session, run_ids: str):
109
+ if "show_hidden" not in session:
110
+ session["show_hidden"] = False
111
+ session["datagrid"] = dict()
112
+ return MainPage(session, swap=True), HttpHeader("HX-Blank-Redirect", f"/compare?run_ids={run_ids}")
@@ -0,0 +1,20 @@
1
+ from typing import *
2
+ from fasthtml.common import *
3
+ from .utils import format_value
4
+ def Row(data, run_id, selected: bool, hidden: bool, max_decimals: int, fullscreen: bool):
5
+ cls = "table-row"
6
+ if selected:
7
+ cls += " table-row-selected"
8
+
9
+ if hidden:
10
+ cls += " table-row-hidden"
11
+
12
+ return Tr(
13
+ *[Td(format_value(value, decimals=max_decimals)) for value in data],
14
+ hx_get=f"/click_row?run_id={run_id}&fullscreen={fullscreen}", # HTMX will GET this URL
15
+ hx_trigger="click[!event.ctrlKey && !event.metaKey]",
16
+ hx_target="#experiment-table", # Target DOM element to update
17
+ hx_swap="innerHTML", # Optional: how to replace content
18
+ id=f"grid-row-{run_id}",
19
+ cls=cls,
20
+ )
@@ -0,0 +1,45 @@
1
+ from fasthtml.common import *
2
+
3
+ def SortableColumnsJs():
4
+ src = """
5
+ import { Sortable } from 'https://cdn.jsdelivr.net/npm/sortablejs/+esm';
6
+
7
+ document.addEventListener('DOMContentLoaded', function() {
8
+ initSortable();
9
+ });
10
+
11
+ function initSortable() {
12
+ const headerRow = document.getElementById('column-header-row');
13
+
14
+ if (!headerRow) return;
15
+
16
+ // Initialize SortableJS on the header row
17
+ new Sortable(headerRow, {
18
+ animation: 150,
19
+ ghostClass: 'sortable-ghost',
20
+ onEnd: function(evt) {
21
+ // Get the new column order
22
+ const headers = Array.from(headerRow.children);
23
+ const columnOrder = headers.map(header =>
24
+ header.getAttribute('data-col'));
25
+
26
+ // Send the new order to the server using htmx as a POST request
27
+ htmx.ajax('POST', '/reorder_columns', {
28
+ target: '#experiment-table',
29
+ swap: 'innerHTML',
30
+ values: {
31
+ order: columnOrder.join(',')
32
+ }
33
+ });
34
+ }
35
+ });
36
+ }
37
+
38
+ // Re-initialize Sortable after HTMX content swaps
39
+ document.body.addEventListener('htmx:afterSwap', function(evt) {
40
+ if (evt.detail.target.id === 'experiment-table') {
41
+ initSortable();
42
+ }
43
+ });
44
+ """
45
+ return Script(src, type='module')
@@ -0,0 +1,9 @@
1
+ from deepboard.gui.utils import smart_round
2
+ from datetime import datetime
3
+
4
+ def format_value(value, decimals: int = 4):
5
+ if isinstance(value, datetime):
6
+ return value.strftime('%Y-%m-%d %H:%M:%S')
7
+ if isinstance(value, float):
8
+ return smart_round(value, decimals)
9
+ return value
@@ -0,0 +1,16 @@
1
+ from typing import *
2
+ from fasthtml.common import *
3
+ from .datagrid import DataGrid, CompareButton
4
+ from .right_panel import RightPanel, reset_scalar_session
5
+ from .main_page import MainPage
6
+
7
+ # Choose a row in the datagrid
8
+ def click_row_handler(session, run_id: int, fullscreen: bool):
9
+ reset_scalar_session(session)
10
+ if "datagrid" not in session:
11
+ session["datagrid"] = dict()
12
+ session["datagrid"]["selected-rows"] = [run_id]
13
+ if fullscreen:
14
+ return MainPage(session, swap=True)
15
+ else:
16
+ return DataGrid(session), CompareButton(session, swap=True), RightPanel(session)
@@ -0,0 +1,21 @@
1
+ from typing import *
2
+ from fasthtml.common import *
3
+ from .datagrid import DataGrid, CompareButton
4
+ from .right_panel import RightPanel
5
+
6
+ def MainPage(session, swap: bool = False, fullscreen: bool = False):
7
+ return Div(
8
+ Div(
9
+ DataGrid(session, wrapincontainer=True, fullscreen=fullscreen),
10
+ CompareButton(session),
11
+ Div(hx_target="#container",
12
+ hx_swap="outerHTML",
13
+ hx_trigger="keyup[key=='f' || key=='F'] from:body",
14
+ hx_get=f"/fullscreen?full={'true' if not fullscreen else 'false'}",),
15
+ cls="table-container"
16
+ ),
17
+ RightPanel(session, closed = fullscreen),
18
+ cls='container',
19
+ id="container",
20
+ hx_swap_oob="true" if swap else None
21
+ )
@@ -0,0 +1,12 @@
1
+ from .template import RightPanel, fill_panel, reset_scalar_session
2
+ from .scalars import build_scalar_routes
3
+ from .run_info import build_info_routes
4
+ from .images import build_images_routes
5
+ from .fragments import build_fragment_routes
6
+
7
+ def build_right_panel_routes(rt):
8
+ rt("/fillpanel")(fill_panel)
9
+ build_images_routes(rt)
10
+ build_scalar_routes(rt)
11
+ build_info_routes(rt)
12
+ build_fragment_routes(rt)
@@ -0,0 +1,57 @@
1
+ from typing import *
2
+ from datetime import datetime, timedelta
3
+ from fasthtml.common import *
4
+ from markupsafe import Markup
5
+
6
+ def CopyToClipboard(text: str, cls):
7
+ return Div(
8
+ Span(text, cls='copy-text' + ' ' + cls),
9
+ Span(
10
+ I(cls=f'fas fa-copy copy-icon default-icon {cls}'),
11
+ I(cls=f'fas fa-check copy-icon check-icon {cls}'),
12
+ cls='copy-icon-container',
13
+ ),
14
+ onclick='copyToClipboard(this)',
15
+ cls='copy-container'
16
+ )
17
+
18
+ def ConfigView(runID: int):
19
+ from __main__ import rTable
20
+
21
+ # Config
22
+ cfg_text = rTable.load_config(runID)
23
+ cfg_parts = cfg_text.splitlines()
24
+ cfg = []
25
+ for part in cfg_parts:
26
+ cfg.append(P(Markup(part), cls="config-part"))
27
+
28
+ # Cli
29
+ row = rTable.fetch_experiment(runID)
30
+ command_line = row[5]
31
+ if row[4] == "":
32
+ lines = [P(Markup(""), cls="config-part")]
33
+ else:
34
+ cli = {keyvalue.split("=")[0]: "=".join(keyvalue.split("=")[1:]) for keyvalue in row[4].split(" ")}
35
+ lines = [P(Markup(f"- {key}: {value}"), cls="config-part") for key, value in cli.items()]
36
+ return Div(
37
+ Div(
38
+ H2("Configuration"),
39
+ Div(
40
+ *cfg,
41
+ cls="file-view",
42
+ )
43
+ ),
44
+ Div(
45
+ H2("Cli"),
46
+ Div(
47
+ *lines,
48
+ cls="file-view",
49
+ ),
50
+
51
+ Div(
52
+ CopyToClipboard(command_line, cls=""),
53
+ cls="file-view",
54
+ style="margin-top: 2em;"
55
+ )
56
+ )
57
+ )
@@ -0,0 +1,133 @@
1
+ from fasthtml.common import *
2
+ from starlette.responses import Response
3
+ from typing import *
4
+ from deepboard.gui.components import Modal, SplitSelector, StatLine, ArtefactGroup
5
+
6
+ def _get_fragment_groups(socket, type: Literal["RAW", "HTML"]):
7
+ if type == "RAW":
8
+ fragments = socket.read_text()
9
+ else:
10
+ fragments = socket.read_fragment()
11
+
12
+ index = list({(fragment["step"], fragment["epoch"], fragment["run_rep"], fragment["split"]) for fragment in fragments})
13
+
14
+ splits = list({elem[3] for elem in index})
15
+
16
+ # Package fragments
17
+ frag_groups = {}
18
+ for key in index:
19
+ cropped_key = key[:-1] # Remove split
20
+ if cropped_key not in frag_groups:
21
+ frag_groups[cropped_key] = {split: [] for split in splits}
22
+
23
+ for fragment in fragments:
24
+ key = fragment["step"], fragment["epoch"], fragment["run_rep"]
25
+ split = fragment["split"]
26
+ frag_groups[key][split].append(fragment["fragment"])
27
+
28
+ # Sort fragments groups by step, and run_rep
29
+ return dict(sorted(frag_groups.items(), key=lambda x: (x[0][0], x[0][2])))
30
+
31
+
32
+ def TextComponent(text: str):
33
+ return Div(
34
+ P(text, cls="fragment-text"),
35
+ cls="fragment-text-container"
36
+ )
37
+
38
+ def HTMLComponent(html_str: str):
39
+ # Return whatever is in html_str as a HTML component
40
+ return NotStr(html_str)
41
+
42
+ def FragmentCard(runID: int, step: int, epoch: Optional[int], run_rep: int, frag_type: Literal["RAW", "HTML"],
43
+ selected: Optional[str] = None):
44
+ from __main__ import rTable
45
+
46
+ socket = rTable.load_run(runID)
47
+ data = _get_fragment_groups(socket, type=frag_type)
48
+
49
+ if (step, epoch, run_rep) not in data:
50
+ avail_splits = []
51
+ fragments = []
52
+ else:
53
+ fragment_splits = data[(step, epoch, run_rep)]
54
+ print(fragment_splits)
55
+ avail_splits = list(fragment_splits.keys())
56
+ avail_splits.sort()
57
+ if selected is None:
58
+ selected = avail_splits[0]
59
+ fragments = fragment_splits[selected]
60
+
61
+ return Div(
62
+ Div(
63
+ SplitSelector(runID, avail_splits, selected=selected, step=step, epoch=epoch, run_rep=run_rep,
64
+ type=frag_type, path="/fragments/change_split"),
65
+ Div(
66
+ StatLine("Step", str(step)),
67
+ StatLine("Epoch", str(epoch) if epoch is not None else "N/A"),
68
+ StatLine("Run Repetition", str(run_rep)),
69
+ cls="artefact-stats-column"
70
+ ),
71
+ cls="artefact-card-header",
72
+ ),
73
+ ArtefactGroup(*[
74
+ TextComponent(frag_content) if frag_type == "RAW" else HTMLComponent(frag_content)
75
+ for frag_content in fragments
76
+ ]),
77
+ id=f"artefact-card-{step}-{epoch}-{run_rep}",
78
+ cls="artefact-card",
79
+ )
80
+
81
+ def FragmentTab(session, runID, type: Literal["RAW", "HTML"], swap: bool = False):
82
+ from __main__ import rTable
83
+ socket = rTable.load_run(runID)
84
+
85
+ fragment_groups = _get_fragment_groups(socket, type=type)
86
+ return Div(
87
+ *[
88
+ FragmentCard(runID, step, epoch, run_rep, frag_type=type)
89
+ for step, epoch, run_rep in fragment_groups.keys()
90
+ ],
91
+ style="display; flex; flex-direction: column; align-items: center; justify-content: center;",
92
+ id="fragment-tab",
93
+ hx_swap_oob="true" if swap else None,
94
+ )
95
+
96
+
97
+ def fragment_enable(runID, type: Literal["RAW", "HTML"]):
98
+ """
99
+ Check if some fragments/text are logged and available for the runID. If not, we consider disable it.
100
+ :param runID: The runID to check.
101
+ :return: True if scalars are available, False otherwise.
102
+ """
103
+ from __main__ import rTable
104
+ socket = rTable.load_run(runID)
105
+ if type == "RAW":
106
+ return len(socket.read_text()) > 0
107
+ else:
108
+ return len(socket.read_fragment()) > 0
109
+
110
+ # routes
111
+ def build_fragment_routes(rt):
112
+ rt("/fragments/change_split")(change_split)
113
+
114
+
115
+ def change_split(session, runID: int, step: int, epoch: Optional[int], run_rep: int, split_select: str, type: str):
116
+ """
117
+ Change the split for the fragment.
118
+ :param session: The session object.
119
+ :param step: The step of the fragment.
120
+ :param epoch: The epoch of the fragment.
121
+ :param run_rep: The run repetition of the fragments.
122
+ :param split: The split to change to.
123
+ :return: The updated fragment card HTML.
124
+ """
125
+ return FragmentCard(
126
+ runID,
127
+ step,
128
+ epoch,
129
+ run_rep,
130
+ frag_type=type,
131
+ selected=split_select,
132
+ )
133
+
@@ -0,0 +1,25 @@
1
+ from typing import *
2
+ from datetime import datetime, timedelta
3
+ from fasthtml.common import *
4
+ from markupsafe import Markup
5
+
6
+ def HParamsView(runID: int):
7
+ from __main__ import rTable
8
+ socket = rTable.load_run(runID)
9
+ hparams = socket.get_hparams()
10
+ return Div(
11
+ Table(
12
+ Tr(
13
+ Th('Parameter'),
14
+ Th('Value'),
15
+ ),
16
+ *[
17
+ Tr(
18
+ Td(key),
19
+ Td(value),
20
+ )
21
+ for key, value in hparams.items()
22
+ ],
23
+ cls="hparams-table",
24
+ )
25
+ )