cometx 3.6.7__tar.gz → 3.6.8__tar.gz
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.
- {cometx-3.6.7 → cometx-3.6.8}/PKG-INFO +1 -1
- {cometx-3.6.7 → cometx-3.6.8}/cometx/_version.py +1 -1
- {cometx-3.6.7 → cometx-3.6.8}/cometx/utils.py +71 -0
- cometx-3.6.8/cometx/views.py +877 -0
- {cometx-3.6.7 → cometx-3.6.8}/cometx.egg-info/PKG-INFO +1 -1
- {cometx-3.6.7 → cometx-3.6.8}/cometx.egg-info/SOURCES.txt +1 -0
- {cometx-3.6.7 → cometx-3.6.8}/LICENSE +0 -0
- {cometx-3.6.7 → cometx-3.6.8}/README.md +0 -0
- {cometx-3.6.7 → cometx-3.6.8}/cometx/__init__.py +0 -0
- {cometx-3.6.7 → cometx-3.6.8}/cometx/_typing.py +0 -0
- {cometx-3.6.7 → cometx-3.6.8}/cometx/_ui.py +0 -0
- {cometx-3.6.7 → cometx-3.6.8}/cometx/api.py +0 -0
- {cometx-3.6.7 → cometx-3.6.8}/cometx/audio.py +0 -0
- {cometx-3.6.7 → cometx-3.6.8}/cometx/cli/__init__.py +0 -0
- {cometx-3.6.7 → cometx-3.6.8}/cometx/cli/admin.py +0 -0
- {cometx-3.6.7 → cometx-3.6.8}/cometx/cli/admin_app.py +0 -0
- {cometx-3.6.7 → cometx-3.6.8}/cometx/cli/admin_gpu_app.py +0 -0
- {cometx-3.6.7 → cometx-3.6.8}/cometx/cli/admin_gpu_report.py +0 -0
- {cometx-3.6.7 → cometx-3.6.8}/cometx/cli/admin_growth_render.py +0 -0
- {cometx-3.6.7 → cometx-3.6.8}/cometx/cli/admin_growth_report.py +0 -0
- {cometx-3.6.7 → cometx-3.6.8}/cometx/cli/admin_optimizer_report.py +0 -0
- {cometx-3.6.7 → cometx-3.6.8}/cometx/cli/admin_usage_report.py +0 -0
- {cometx-3.6.7 → cometx-3.6.8}/cometx/cli/admin_utils.py +0 -0
- {cometx-3.6.7 → cometx-3.6.8}/cometx/cli/config.py +0 -0
- {cometx-3.6.7 → cometx-3.6.8}/cometx/cli/copy.py +0 -0
- {cometx-3.6.7 → cometx-3.6.8}/cometx/cli/copy_utils.py +0 -0
- {cometx-3.6.7 → cometx-3.6.8}/cometx/cli/count.py +0 -0
- {cometx-3.6.7 → cometx-3.6.8}/cometx/cli/delete_assets.py +0 -0
- {cometx-3.6.7 → cometx-3.6.8}/cometx/cli/download.py +0 -0
- {cometx-3.6.7 → cometx-3.6.8}/cometx/cli/list_command.py +0 -0
- {cometx-3.6.7 → cometx-3.6.8}/cometx/cli/log.py +0 -0
- {cometx-3.6.7 → cometx-3.6.8}/cometx/cli/migrate_users.py +0 -0
- {cometx-3.6.7 → cometx-3.6.8}/cometx/cli/rename_duplicates.py +0 -0
- {cometx-3.6.7 → cometx-3.6.8}/cometx/cli/reproduce.py +0 -0
- {cometx-3.6.7 → cometx-3.6.8}/cometx/cli/smoke_test.py +0 -0
- {cometx-3.6.7 → cometx-3.6.8}/cometx/cli/update.py +0 -0
- {cometx-3.6.7 → cometx-3.6.8}/cometx/cli/upload_optimizer_experiments.py +0 -0
- {cometx-3.6.7 → cometx-3.6.8}/cometx/cli/utils.py +0 -0
- {cometx-3.6.7 → cometx-3.6.8}/cometx/framework/__init__.py +0 -0
- {cometx-3.6.7 → cometx-3.6.8}/cometx/framework/comet/__init__.py +0 -0
- {cometx-3.6.7 → cometx-3.6.8}/cometx/framework/comet/download_manager.py +0 -0
- {cometx-3.6.7 → cometx-3.6.8}/cometx/framework/neptune.py +0 -0
- {cometx-3.6.7 → cometx-3.6.8}/cometx/framework/wandb.py +0 -0
- {cometx-3.6.7 → cometx-3.6.8}/cometx/generate_utils.py +0 -0
- {cometx-3.6.7 → cometx-3.6.8}/cometx/magics.py +0 -0
- {cometx-3.6.7 → cometx-3.6.8}/cometx/optimizer.py +0 -0
- {cometx-3.6.7 → cometx-3.6.8}/cometx/panel_utils.py +0 -0
- {cometx-3.6.7 → cometx-3.6.8}/cometx/tools/__init__.py +0 -0
- {cometx-3.6.7 → cometx-3.6.8}/cometx/tools/dataset.py +0 -0
- {cometx-3.6.7 → cometx-3.6.8}/cometx/tools/pointcloud.py +0 -0
- {cometx-3.6.7 → cometx-3.6.8}/cometx.egg-info/dependency_links.txt +0 -0
- {cometx-3.6.7 → cometx-3.6.8}/cometx.egg-info/entry_points.txt +0 -0
- {cometx-3.6.7 → cometx-3.6.8}/cometx.egg-info/requires.txt +0 -0
- {cometx-3.6.7 → cometx-3.6.8}/cometx.egg-info/top_level.txt +0 -0
- {cometx-3.6.7 → cometx-3.6.8}/pyproject.toml +0 -0
- {cometx-3.6.7 → cometx-3.6.8}/setup.cfg +0 -0
- {cometx-3.6.7 → cometx-3.6.8}/setup.py +0 -0
- {cometx-3.6.7 → cometx-3.6.8}/tests/testlib.py +0 -0
|
@@ -304,6 +304,77 @@ def get_unit_label_plural(unit):
|
|
|
304
304
|
return labels.get(unit, unit.capitalize() + "s")
|
|
305
305
|
|
|
306
306
|
|
|
307
|
+
def resolve_workspace(workspace=None, api=None):
|
|
308
|
+
"""
|
|
309
|
+
Return a workspace name, falling back to the account's default.
|
|
310
|
+
|
|
311
|
+
Args:
|
|
312
|
+
workspace: (str, optional) a workspace name; returned unchanged if given
|
|
313
|
+
api: (API, optional) an API instance to reuse; one is created if omitted
|
|
314
|
+
|
|
315
|
+
Returns: the given workspace, the configured default workspace, or -- if
|
|
316
|
+
neither is set and the account has exactly one workspace -- that one.
|
|
317
|
+
|
|
318
|
+
Raises:
|
|
319
|
+
ValueError: if no workspace was given, no default is configured, and the
|
|
320
|
+
account has several workspaces to choose between.
|
|
321
|
+
"""
|
|
322
|
+
if workspace:
|
|
323
|
+
return workspace
|
|
324
|
+
|
|
325
|
+
if api is None:
|
|
326
|
+
from .api import API
|
|
327
|
+
|
|
328
|
+
api = API()
|
|
329
|
+
|
|
330
|
+
configured = api.get_default_workspace()
|
|
331
|
+
if configured:
|
|
332
|
+
return configured
|
|
333
|
+
|
|
334
|
+
workspaces = api.get_workspaces()
|
|
335
|
+
if len(workspaces) == 1:
|
|
336
|
+
return workspaces[0]
|
|
337
|
+
|
|
338
|
+
raise ValueError(
|
|
339
|
+
"Unable to pick a workspace: several are available -- %s"
|
|
340
|
+
% ", ".join(sorted(workspaces))
|
|
341
|
+
)
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
def get_project_url(workspace, project_name, api=None):
|
|
345
|
+
"""
|
|
346
|
+
Return the URL of a project in the Comet UI.
|
|
347
|
+
|
|
348
|
+
Args:
|
|
349
|
+
workspace: (str) the workspace name
|
|
350
|
+
project_name: (str) the project name
|
|
351
|
+
api: (API, optional) an API instance to reuse; one is created if omitted
|
|
352
|
+
|
|
353
|
+
Returns: the project's URL, on whichever server the API is configured for.
|
|
354
|
+
"""
|
|
355
|
+
if api is None:
|
|
356
|
+
from .api import API
|
|
357
|
+
|
|
358
|
+
api = API()
|
|
359
|
+
|
|
360
|
+
return "%s/%s/%s" % (api.server_url.rstrip("/"), workspace, project_name)
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
def get_first_experiment(api, workspace, project_name):
|
|
364
|
+
"""
|
|
365
|
+
Return one APIExperiment from a project, for code that just needs an example.
|
|
366
|
+
|
|
367
|
+
Args:
|
|
368
|
+
api: (API) an API instance
|
|
369
|
+
workspace: (str) the workspace name
|
|
370
|
+
project_name: (str) the project name
|
|
371
|
+
|
|
372
|
+
Returns: the project's first APIExperiment, or None if the project is empty.
|
|
373
|
+
"""
|
|
374
|
+
experiments = api.get_experiments(workspace, project_name)
|
|
375
|
+
return experiments[0] if experiments else None
|
|
376
|
+
|
|
377
|
+
|
|
307
378
|
def remove_extra_slashes(path):
|
|
308
379
|
if path:
|
|
309
380
|
if path.startswith("/"):
|
|
@@ -0,0 +1,877 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
# ****************************************
|
|
3
|
+
# __
|
|
4
|
+
# _________ ____ ___ ___ / /__ __
|
|
5
|
+
# / ___/ __ \/ __ `__ \/ _ \/ __/ |/_/
|
|
6
|
+
# / /__/ /_/ / / / / / / __/ /__> <
|
|
7
|
+
# \___/\____/_/ /_/ /_/\___/\__/_/|_|
|
|
8
|
+
#
|
|
9
|
+
#
|
|
10
|
+
# Copyright (c) 2026 Cometx Development
|
|
11
|
+
# Team. All rights reserved.
|
|
12
|
+
# ****************************************
|
|
13
|
+
"""
|
|
14
|
+
cometx.views -- a builder library for Comet dashboard Views.
|
|
15
|
+
|
|
16
|
+
Views are the saved dashboards and per-experiment chart layouts in the Comet UI.
|
|
17
|
+
This module builds them programmatically: import the panel factories, compose
|
|
18
|
+
them into ``Section`` objects, and hand the result to ``build_view()`` (project
|
|
19
|
+
dashboards) or ``build_experiment_view()`` (per-experiment views).
|
|
20
|
+
|
|
21
|
+
from cometx.views import Section, build_view, line_panel, scalar_panel
|
|
22
|
+
|
|
23
|
+
view = build_view(
|
|
24
|
+
"My Dashboard",
|
|
25
|
+
sections=[
|
|
26
|
+
Section("At a glance", [scalar_panel("accuracy", aggregation="max")]),
|
|
27
|
+
Section("Curves", [line_panel(["loss", "val_loss"], smoothing=0.6)]),
|
|
28
|
+
],
|
|
29
|
+
)
|
|
30
|
+
API().create_view(workspace, project_name, view)
|
|
31
|
+
|
|
32
|
+
The Comet SDK exposes four functions for working with saved dashboard views:
|
|
33
|
+
|
|
34
|
+
API.get_views(workspace, project_name) -> list[View]
|
|
35
|
+
API.create_view(workspace, project_name, v) -> View (project-level)
|
|
36
|
+
APIExperiment.get_views() -> list[View]
|
|
37
|
+
APIExperiment.create_view(view) -> View (experiment-level)
|
|
38
|
+
|
|
39
|
+
A ``View`` is mostly a carrier for the dashboard's serialized layout state, and
|
|
40
|
+
the interesting content lives in ``view.v3`` -- a dict of *sections*, each
|
|
41
|
+
holding *panels* plus a grid *layout*. This module provides typed-ish factories
|
|
42
|
+
for every panel type the dashboard supports, plus ``build_view()`` to assemble
|
|
43
|
+
sections into a ``View`` that the Comet UI renders correctly.
|
|
44
|
+
|
|
45
|
+
Schema notes (derived from views saved by the Comet UI):
|
|
46
|
+
|
|
47
|
+
v3 = {
|
|
48
|
+
"layout": [], # legacy mirror of section[0]; safe to leave empty
|
|
49
|
+
"globalConfig": {...}, # dashboard-wide x-axis / smoothing / sampling
|
|
50
|
+
"sections": [
|
|
51
|
+
{"id": "aB3xY9", "title": "Metrics", "autogenerated": False,
|
|
52
|
+
"expanded": True, "panels": [ ... ], "layout": [ ... ]},
|
|
53
|
+
],
|
|
54
|
+
"autoGenerate": False, # True lets Comet append auto panels for new metrics
|
|
55
|
+
"isPersonalView": False,
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
chart_state ("dashboardChartState") is the pre-sections legacy format: the
|
|
59
|
+
same panel dicts bucketed by type. We emit it alongside v3 for compatibility
|
|
60
|
+
with older backends and with code that reads the legacy field.
|
|
61
|
+
|
|
62
|
+
query_state ("experimentQueryState") holds the filter/segment state.
|
|
63
|
+
table_state ("reactGridTableState") holds the experiment table config.
|
|
64
|
+
|
|
65
|
+
Experiment-level views (the chart-template scope, written by
|
|
66
|
+
``APIExperiment.create_view``) use a *different* envelope: the sections live in
|
|
67
|
+
``view.v2["panels"]`` and the display settings sit at the top of ``v2``, with
|
|
68
|
+
``v3`` unused. ``build_view`` targets the project scope and
|
|
69
|
+
``build_experiment_view`` the experiment scope; sections and panels are
|
|
70
|
+
identical between the two.
|
|
71
|
+
|
|
72
|
+
Nothing here calls the network; ``build_view`` returns a plain ``View`` that you
|
|
73
|
+
pass to ``API.create_view`` or ``APIExperiment.create_view``.
|
|
74
|
+
"""
|
|
75
|
+
|
|
76
|
+
import json
|
|
77
|
+
import random
|
|
78
|
+
import string
|
|
79
|
+
from typing import Any, Dict, List, Optional, Sequence, Union
|
|
80
|
+
|
|
81
|
+
from comet_ml.api_objects.view import View
|
|
82
|
+
|
|
83
|
+
__all__ = [
|
|
84
|
+
"new_id",
|
|
85
|
+
"Section",
|
|
86
|
+
"build_view",
|
|
87
|
+
"build_experiment_view",
|
|
88
|
+
"line_panel",
|
|
89
|
+
"bar_panel",
|
|
90
|
+
"scatter_panel",
|
|
91
|
+
"parallel_panel",
|
|
92
|
+
"scalar_panel",
|
|
93
|
+
"image_panel",
|
|
94
|
+
"data_panel",
|
|
95
|
+
"custom_panel",
|
|
96
|
+
"python_panel",
|
|
97
|
+
"legend_key",
|
|
98
|
+
"global_config",
|
|
99
|
+
"table_state",
|
|
100
|
+
"query_state",
|
|
101
|
+
"rule",
|
|
102
|
+
"any_of",
|
|
103
|
+
"all_of",
|
|
104
|
+
"describe_view",
|
|
105
|
+
]
|
|
106
|
+
|
|
107
|
+
# The dashboard grid is 6 units wide. A panel with w=2 is one third of a row.
|
|
108
|
+
GRID_WIDTH = 6
|
|
109
|
+
|
|
110
|
+
_ID_ALPHABET = string.ascii_letters + string.digits
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def new_id(length: int = 6) -> str:
|
|
114
|
+
"""Return a random id in the same style the UI generates ('F5ReNw')."""
|
|
115
|
+
return "".join(random.choice(_ID_ALPHABET) for _ in range(length))
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
# ---------------------------------------------------------------------------
|
|
119
|
+
# Legend keys
|
|
120
|
+
# ---------------------------------------------------------------------------
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def legend_key(
|
|
124
|
+
value: str = "Name", label: Optional[str] = None, source: str = "log_other"
|
|
125
|
+
) -> Dict[str, Any]:
|
|
126
|
+
"""One entry of a chart's ``selectedLegendKeys``.
|
|
127
|
+
|
|
128
|
+
Common combinations:
|
|
129
|
+
legend_key("Name") # experiment name
|
|
130
|
+
legend_key("experimentTags", "Tags", "tags") # tags
|
|
131
|
+
legend_key("learning_rate", source="params") # a hyperparameter
|
|
132
|
+
"""
|
|
133
|
+
return {"value": value, "label": label or value, "source": source}
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
DEFAULT_LEGEND = [legend_key("Name")]
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
# ---------------------------------------------------------------------------
|
|
140
|
+
# Panel factories. Each returns a v3 panel dict with a fresh chartId.
|
|
141
|
+
# ---------------------------------------------------------------------------
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def line_panel(
|
|
145
|
+
y: Union[str, Sequence[str]],
|
|
146
|
+
x: str = "step",
|
|
147
|
+
name: str = "",
|
|
148
|
+
smoothing: float = 0,
|
|
149
|
+
transform_y: Optional[str] = None,
|
|
150
|
+
outliers: str = "show",
|
|
151
|
+
sample_size: int = 100,
|
|
152
|
+
legend: Optional[List[Dict[str, Any]]] = None,
|
|
153
|
+
locked: bool = False,
|
|
154
|
+
) -> Dict[str, Any]:
|
|
155
|
+
"""A line chart of one or more metrics over an x-axis.
|
|
156
|
+
|
|
157
|
+
Args:
|
|
158
|
+
y: metric name, or a list of metric names to overlay in one chart.
|
|
159
|
+
x: "step", "duration", "epoch", "wall", or any logged metric name.
|
|
160
|
+
transform_y: None, "log", "ln", "sqrt" -- y-axis transform.
|
|
161
|
+
smoothing: 0..1 exponential smoothing factor.
|
|
162
|
+
outliers: "show" or "hide".
|
|
163
|
+
sample_size: max points fetched per curve.
|
|
164
|
+
locked: True pins this panel's config against dashboard-level changes.
|
|
165
|
+
"""
|
|
166
|
+
metric_names = [y] if isinstance(y, str) else list(y)
|
|
167
|
+
return {
|
|
168
|
+
"chartType": "line",
|
|
169
|
+
"chartId": new_id(),
|
|
170
|
+
"chartName": name,
|
|
171
|
+
"selectedXAxis": x,
|
|
172
|
+
"selectedYAxis": metric_names[0],
|
|
173
|
+
"selectedOutliers": outliers,
|
|
174
|
+
"selectedLegendKeys": legend or DEFAULT_LEGEND,
|
|
175
|
+
"transformX": None,
|
|
176
|
+
"transformY": transform_y,
|
|
177
|
+
"smoothingX": 0,
|
|
178
|
+
"smoothingY": smoothing,
|
|
179
|
+
"metricName": "",
|
|
180
|
+
"metricNames": metric_names,
|
|
181
|
+
"locked": locked,
|
|
182
|
+
"sampleSize": sample_size,
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def bar_panel(
|
|
187
|
+
metric: str,
|
|
188
|
+
name: str = "",
|
|
189
|
+
aggregation: str = "last",
|
|
190
|
+
orientation: str = "v",
|
|
191
|
+
plot_type: str = "BAR",
|
|
192
|
+
group_by_aggregation: Optional[str] = None,
|
|
193
|
+
legend: Optional[List[Dict[str, Any]]] = None,
|
|
194
|
+
) -> Dict[str, Any]:
|
|
195
|
+
"""A bar chart of one metric, one bar per experiment.
|
|
196
|
+
|
|
197
|
+
Args:
|
|
198
|
+
aggregation: how to reduce a metric's history to one number --
|
|
199
|
+
"last", "min", "max", "average", "sum".
|
|
200
|
+
orientation: "v" (vertical bars) or "h".
|
|
201
|
+
plot_type: "BAR", "BOX", or "VIOLIN".
|
|
202
|
+
group_by_aggregation: if set (e.g. "mean"), bars are grouped and
|
|
203
|
+
aggregated instead of one-per-experiment.
|
|
204
|
+
"""
|
|
205
|
+
return {
|
|
206
|
+
"chartType": "bar",
|
|
207
|
+
"chartId": new_id(),
|
|
208
|
+
"chartName": name,
|
|
209
|
+
"selectedXAxis": metric,
|
|
210
|
+
"metricName": metric,
|
|
211
|
+
"aggregationX": aggregation,
|
|
212
|
+
"orientation": orientation,
|
|
213
|
+
"plotType": plot_type,
|
|
214
|
+
"grouping": {
|
|
215
|
+
"enabled": group_by_aggregation is not None,
|
|
216
|
+
"plotType": plot_type,
|
|
217
|
+
"aggregation": group_by_aggregation or "mean",
|
|
218
|
+
},
|
|
219
|
+
"customRange": {"x": {"min": "", "max": ""}, "y": {"min": "", "max": ""}},
|
|
220
|
+
"selectedLegendKeys": legend or DEFAULT_LEGEND,
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
def scatter_panel(
|
|
225
|
+
x: str,
|
|
226
|
+
y: str,
|
|
227
|
+
z: Optional[str] = None,
|
|
228
|
+
name: str = "",
|
|
229
|
+
aggregation: str = "last",
|
|
230
|
+
metrics: Optional[List[str]] = None,
|
|
231
|
+
params: Optional[List[str]] = None,
|
|
232
|
+
outliers: str = "show",
|
|
233
|
+
sample_size: int = 100,
|
|
234
|
+
legend: Optional[List[Dict[str, Any]]] = None,
|
|
235
|
+
) -> Dict[str, Any]:
|
|
236
|
+
"""A 2D or 3D scatter plot; one point per experiment.
|
|
237
|
+
|
|
238
|
+
Axes may be metric names or hyperparameter names. Pass ``z`` for a 3D plot.
|
|
239
|
+
``metrics``/``params`` declare which of the axes are metrics vs params; when
|
|
240
|
+
omitted every axis is treated as a metric.
|
|
241
|
+
"""
|
|
242
|
+
axes = [a for a in (x, y, z) if a]
|
|
243
|
+
panel = {
|
|
244
|
+
"chartType": "scatter",
|
|
245
|
+
"chartId": new_id(),
|
|
246
|
+
"chartName": name,
|
|
247
|
+
"selectedXAxis": x,
|
|
248
|
+
"selectedYAxis": y,
|
|
249
|
+
"selectedOutliers": outliers,
|
|
250
|
+
"sampleSize": sample_size,
|
|
251
|
+
"selectedLegendKeys": legend or DEFAULT_LEGEND,
|
|
252
|
+
"aggregationX": aggregation,
|
|
253
|
+
"aggregationY": aggregation,
|
|
254
|
+
"metrics": metrics if metrics is not None else [],
|
|
255
|
+
"params": params or [],
|
|
256
|
+
"metricName": "",
|
|
257
|
+
"metricNames": axes if metrics is None else metrics,
|
|
258
|
+
"annotations": {
|
|
259
|
+
"MIN_Y": {"name": "MIN_Y", "label": "Min y-values", "checked": False},
|
|
260
|
+
"MAX_Y": {"name": "MAX_Y", "label": "Max y-values", "checked": False},
|
|
261
|
+
"AVG_Y": {"name": "AVG_Y", "label": "Avg y-values", "checked": False},
|
|
262
|
+
},
|
|
263
|
+
"customRange": {"x": {"min": "", "max": ""}, "y": {"min": "", "max": ""}},
|
|
264
|
+
"legendMode": "auto",
|
|
265
|
+
"locked": False,
|
|
266
|
+
"fromChart": False,
|
|
267
|
+
}
|
|
268
|
+
if z:
|
|
269
|
+
panel["selectedZAxis"] = z
|
|
270
|
+
panel["aggregationZ"] = aggregation
|
|
271
|
+
return panel
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
def parallel_panel(
|
|
275
|
+
params: Sequence[str],
|
|
276
|
+
metrics: Sequence[str] = (),
|
|
277
|
+
target: str = "",
|
|
278
|
+
name: str = "",
|
|
279
|
+
precision: str = ".3f",
|
|
280
|
+
) -> Dict[str, Any]:
|
|
281
|
+
"""A parallel-coordinates chart -- the standard hyperparameter sweep view.
|
|
282
|
+
|
|
283
|
+
Args:
|
|
284
|
+
params: hyperparameter names, drawn as axes left to right.
|
|
285
|
+
metrics: metric names, drawn as the right-most axes.
|
|
286
|
+
target: which axis colors the lines (usually a metric, e.g. "loss").
|
|
287
|
+
"""
|
|
288
|
+
return {
|
|
289
|
+
"chartType": "parallel",
|
|
290
|
+
"chartId": new_id(),
|
|
291
|
+
"chartName": name,
|
|
292
|
+
"params": list(params),
|
|
293
|
+
"metrics": list(metrics),
|
|
294
|
+
"selectedTargetVariable": target or (list(metrics)[0] if metrics else ""),
|
|
295
|
+
"selectedDecimalPrecision": precision,
|
|
296
|
+
"locked": False,
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
def scalar_panel(
|
|
301
|
+
metric: str,
|
|
302
|
+
aggregation: str = "last",
|
|
303
|
+
name: str = "",
|
|
304
|
+
precision: int = 3,
|
|
305
|
+
description: Optional[str] = None,
|
|
306
|
+
) -> Dict[str, Any]:
|
|
307
|
+
"""A single big-number tile.
|
|
308
|
+
|
|
309
|
+
Args:
|
|
310
|
+
aggregation: "last", "min", "max", "average", "sum".
|
|
311
|
+
description: subtitle. Defaults to Comet's generated wording.
|
|
312
|
+
"""
|
|
313
|
+
return {
|
|
314
|
+
"chartType": "scalar",
|
|
315
|
+
"chartId": new_id(),
|
|
316
|
+
"chartName": name,
|
|
317
|
+
"metricName": metric,
|
|
318
|
+
"paramName": "",
|
|
319
|
+
"aggregation": aggregation,
|
|
320
|
+
"precision": precision,
|
|
321
|
+
"description": description
|
|
322
|
+
or "The %s value of %s metric" % (aggregation, metric),
|
|
323
|
+
"isCustomDescription": description is not None,
|
|
324
|
+
"isNumber": True,
|
|
325
|
+
"locked": False,
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+
def image_panel(
|
|
330
|
+
images: Sequence[str] = (),
|
|
331
|
+
step: Optional[int] = None,
|
|
332
|
+
name: str = "",
|
|
333
|
+
experiments_count: int = 25,
|
|
334
|
+
) -> Dict[str, Any]:
|
|
335
|
+
"""An image-grid panel showing logged images across experiments.
|
|
336
|
+
|
|
337
|
+
Args:
|
|
338
|
+
images: logged image file names to display. Empty means "all".
|
|
339
|
+
step: pin the grid to one step; None follows the dashboard.
|
|
340
|
+
"""
|
|
341
|
+
return {
|
|
342
|
+
"chartType": "image",
|
|
343
|
+
"chartId": new_id(),
|
|
344
|
+
"chartName": name,
|
|
345
|
+
"images": list(images),
|
|
346
|
+
"step": step,
|
|
347
|
+
"experimentsCount": experiments_count,
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
def data_panel(
|
|
352
|
+
file_name: str,
|
|
353
|
+
name: str = "",
|
|
354
|
+
axis: int = 0,
|
|
355
|
+
join_type: str = "outer",
|
|
356
|
+
experiments_count: int = 25,
|
|
357
|
+
) -> Dict[str, Any]:
|
|
358
|
+
"""A table panel rendering a logged tabular asset (e.g. a .csv).
|
|
359
|
+
|
|
360
|
+
Args:
|
|
361
|
+
file_name: the logged asset's file name, as passed to log_table().
|
|
362
|
+
axis: 0 concatenates rows across experiments, 1 concatenates columns.
|
|
363
|
+
join_type: "outer" or "inner", when combining across experiments.
|
|
364
|
+
"""
|
|
365
|
+
return {
|
|
366
|
+
"chartType": "data",
|
|
367
|
+
"chartId": new_id(),
|
|
368
|
+
"chartName": name,
|
|
369
|
+
"fileName": file_name,
|
|
370
|
+
"index": None,
|
|
371
|
+
"hasData": True,
|
|
372
|
+
"experimentsCount": experiments_count,
|
|
373
|
+
"axis": axis,
|
|
374
|
+
"joinType": join_type,
|
|
375
|
+
"columnFilters": None,
|
|
376
|
+
"columnState": None,
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
|
|
380
|
+
def custom_panel(
|
|
381
|
+
instance_id: str,
|
|
382
|
+
instance_name: str = "",
|
|
383
|
+
name: str = "",
|
|
384
|
+
default_config: str = "",
|
|
385
|
+
) -> Dict[str, Any]:
|
|
386
|
+
"""A JavaScript custom panel from the Panel gallery.
|
|
387
|
+
|
|
388
|
+
``instance_id``/``instance_name`` identify an existing panel instance and
|
|
389
|
+
cannot be invented -- copy them off a panel already on a dashboard (scan
|
|
390
|
+
existing views with ``get_views()``) or from the panel's URL in the UI.
|
|
391
|
+
"""
|
|
392
|
+
panel = {
|
|
393
|
+
"chartType": "custom",
|
|
394
|
+
"chartId": new_id(),
|
|
395
|
+
"instanceId": instance_id,
|
|
396
|
+
"instanceName": instance_name,
|
|
397
|
+
}
|
|
398
|
+
if name:
|
|
399
|
+
panel["chartName"] = name
|
|
400
|
+
if default_config:
|
|
401
|
+
panel["defaultConfig"] = default_config
|
|
402
|
+
return panel
|
|
403
|
+
|
|
404
|
+
|
|
405
|
+
def python_panel(
|
|
406
|
+
template_id: str,
|
|
407
|
+
revision_id: str = "",
|
|
408
|
+
name: str = "",
|
|
409
|
+
code_version: str = "",
|
|
410
|
+
) -> Dict[str, Any]:
|
|
411
|
+
"""A Python panel (a saved Python panel template rendered server-side).
|
|
412
|
+
|
|
413
|
+
Like ``custom_panel``, the ids refer to an existing saved panel template.
|
|
414
|
+
"""
|
|
415
|
+
return {
|
|
416
|
+
"chartType": "python",
|
|
417
|
+
"chartId": new_id(),
|
|
418
|
+
"chartName": name,
|
|
419
|
+
"templateId": template_id,
|
|
420
|
+
"revisionId": revision_id,
|
|
421
|
+
"codeVersion": code_version,
|
|
422
|
+
"code": None,
|
|
423
|
+
"locked": False,
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
|
|
427
|
+
# Every legacy chart_state bucket, so older readers see a well-formed dict.
|
|
428
|
+
_LEGACY_BUCKETS = [
|
|
429
|
+
"line",
|
|
430
|
+
"scatter",
|
|
431
|
+
"bar",
|
|
432
|
+
"parallel",
|
|
433
|
+
"scalar",
|
|
434
|
+
"image",
|
|
435
|
+
"data",
|
|
436
|
+
"custom",
|
|
437
|
+
"video",
|
|
438
|
+
"pcd",
|
|
439
|
+
"curves",
|
|
440
|
+
"python",
|
|
441
|
+
"autoGeneratedMetrics",
|
|
442
|
+
"autoGeneratedCurve",
|
|
443
|
+
"autoGeneratedImage",
|
|
444
|
+
"autoGeneratedDataframe",
|
|
445
|
+
]
|
|
446
|
+
|
|
447
|
+
|
|
448
|
+
# ---------------------------------------------------------------------------
|
|
449
|
+
# Sections
|
|
450
|
+
# ---------------------------------------------------------------------------
|
|
451
|
+
|
|
452
|
+
|
|
453
|
+
class Section:
|
|
454
|
+
"""A titled, collapsible group of panels on a dashboard.
|
|
455
|
+
|
|
456
|
+
Args:
|
|
457
|
+
title: the section header shown in the UI.
|
|
458
|
+
panels: panel dicts from the factories above.
|
|
459
|
+
columns: panels per row (1, 2, 3, or 6). Panels flow left to right.
|
|
460
|
+
expanded: whether the section starts open.
|
|
461
|
+
height: row height in grid units (1 is the UI default, max 3).
|
|
462
|
+
"""
|
|
463
|
+
|
|
464
|
+
def __init__(
|
|
465
|
+
self,
|
|
466
|
+
title: str,
|
|
467
|
+
panels: Sequence[Dict[str, Any]] = (),
|
|
468
|
+
columns: int = 3,
|
|
469
|
+
expanded: bool = True,
|
|
470
|
+
height: int = 1,
|
|
471
|
+
):
|
|
472
|
+
if GRID_WIDTH % columns:
|
|
473
|
+
raise ValueError("columns must divide %d evenly" % GRID_WIDTH)
|
|
474
|
+
self.title = title
|
|
475
|
+
self.panels = list(panels)
|
|
476
|
+
self.columns = columns
|
|
477
|
+
self.expanded = expanded
|
|
478
|
+
self.height = height
|
|
479
|
+
self.id = new_id()
|
|
480
|
+
|
|
481
|
+
def add(self, *panels: Dict[str, Any]) -> "Section":
|
|
482
|
+
"""Append panels; returns self so calls can be chained."""
|
|
483
|
+
self.panels.extend(panels)
|
|
484
|
+
return self
|
|
485
|
+
|
|
486
|
+
def layout(self) -> List[Dict[str, Any]]:
|
|
487
|
+
"""Compute the react-grid-layout entries for this section's panels."""
|
|
488
|
+
width = GRID_WIDTH // self.columns
|
|
489
|
+
entries = []
|
|
490
|
+
for index, panel in enumerate(self.panels):
|
|
491
|
+
entries.append(
|
|
492
|
+
{
|
|
493
|
+
"i": panel["chartId"],
|
|
494
|
+
"x": (index % self.columns) * width,
|
|
495
|
+
"y": (index // self.columns) * self.height,
|
|
496
|
+
"w": width,
|
|
497
|
+
"h": self.height,
|
|
498
|
+
"minW": 2,
|
|
499
|
+
"maxH": 3,
|
|
500
|
+
"moved": False,
|
|
501
|
+
"static": False,
|
|
502
|
+
}
|
|
503
|
+
)
|
|
504
|
+
return entries
|
|
505
|
+
|
|
506
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
507
|
+
return {
|
|
508
|
+
"id": self.id,
|
|
509
|
+
"title": self.title,
|
|
510
|
+
"autogenerated": False,
|
|
511
|
+
"expanded": self.expanded,
|
|
512
|
+
"panels": self.panels,
|
|
513
|
+
"layout": self.layout(),
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
|
|
517
|
+
# ---------------------------------------------------------------------------
|
|
518
|
+
# Dashboard-level state
|
|
519
|
+
# ---------------------------------------------------------------------------
|
|
520
|
+
|
|
521
|
+
|
|
522
|
+
def global_config(
|
|
523
|
+
x_axis: str = "step",
|
|
524
|
+
smoothing: float = 0,
|
|
525
|
+
transform_y: Optional[str] = None,
|
|
526
|
+
outliers: str = "show",
|
|
527
|
+
sample_size: int = 100,
|
|
528
|
+
) -> Dict[str, Any]:
|
|
529
|
+
"""Dashboard-wide defaults applied to every non-``locked`` panel."""
|
|
530
|
+
return {
|
|
531
|
+
"selectedXAxis": x_axis,
|
|
532
|
+
"transformY": transform_y,
|
|
533
|
+
"smoothingY": smoothing,
|
|
534
|
+
"selectedOutliers": outliers,
|
|
535
|
+
"sampleSize": sample_size,
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
|
|
539
|
+
def table_state(
|
|
540
|
+
columns: Sequence[str] = (),
|
|
541
|
+
sort_by: Optional[str] = None,
|
|
542
|
+
descending: bool = True,
|
|
543
|
+
page_size: int = 25,
|
|
544
|
+
decimals: int = 6,
|
|
545
|
+
visible: bool = True,
|
|
546
|
+
column_width: int = 150,
|
|
547
|
+
) -> str:
|
|
548
|
+
"""Serialize the experiment-table configuration (``reactGridTableState``).
|
|
549
|
+
|
|
550
|
+
Args:
|
|
551
|
+
columns: column ids, in display order. Metric and parameter names work
|
|
552
|
+
directly; UI columns include "Name", "duration", "experimentTags",
|
|
553
|
+
"end_server_timestamp", "user_name".
|
|
554
|
+
sort_by: column to sort on.
|
|
555
|
+
"""
|
|
556
|
+
fixed = ["pinned", "singleExperimentBtn", "isVisibleOnDashboard"]
|
|
557
|
+
ordered = fixed + [c for c in columns if c not in fixed]
|
|
558
|
+
state = {
|
|
559
|
+
"columnGrouping": [],
|
|
560
|
+
"columnOrders": ordered,
|
|
561
|
+
"columnSorting": (
|
|
562
|
+
[{"columnName": sort_by, "direction": "desc" if descending else "asc"}]
|
|
563
|
+
if sort_by
|
|
564
|
+
else []
|
|
565
|
+
),
|
|
566
|
+
"columnWidths": [
|
|
567
|
+
{"columnName": c, "width": column_width} for c in ordered if c not in fixed
|
|
568
|
+
],
|
|
569
|
+
"decimalsPrecision": decimals,
|
|
570
|
+
"expandedGroups": [],
|
|
571
|
+
"isVisible": visible,
|
|
572
|
+
"pageSize": page_size,
|
|
573
|
+
"selection": [],
|
|
574
|
+
"hiddenExperiments": [],
|
|
575
|
+
}
|
|
576
|
+
return json.dumps(state)
|
|
577
|
+
|
|
578
|
+
|
|
579
|
+
def rule(
|
|
580
|
+
field: str,
|
|
581
|
+
operator: str,
|
|
582
|
+
value: Any,
|
|
583
|
+
source: str = "metrics",
|
|
584
|
+
type_: Optional[str] = None,
|
|
585
|
+
) -> Dict[str, Any]:
|
|
586
|
+
"""One filter condition.
|
|
587
|
+
|
|
588
|
+
Args:
|
|
589
|
+
field: metric / parameter / column name.
|
|
590
|
+
operator: "greater", "less", "equal", "not_equal", "contains",
|
|
591
|
+
"not_contains", "is_not_empty", "begins_with".
|
|
592
|
+
source: "metrics", "params", "log_other", "tags", "metadata".
|
|
593
|
+
type_: value type -- inferred from ``value`` when omitted
|
|
594
|
+
("double", "string", "boolean").
|
|
595
|
+
"""
|
|
596
|
+
if type_ is None:
|
|
597
|
+
if isinstance(value, bool):
|
|
598
|
+
type_ = "boolean"
|
|
599
|
+
elif isinstance(value, (int, float)):
|
|
600
|
+
type_ = "double"
|
|
601
|
+
else:
|
|
602
|
+
type_ = "string"
|
|
603
|
+
return {
|
|
604
|
+
"type": type_,
|
|
605
|
+
"field": field,
|
|
606
|
+
"source": source,
|
|
607
|
+
"value": value,
|
|
608
|
+
"operator": operator,
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
|
|
612
|
+
def all_of(*rules: Dict[str, Any]) -> Dict[str, Any]:
|
|
613
|
+
"""AND-group of rules; use as one branch inside ``query_state``."""
|
|
614
|
+
return {"condition": "AND", "rules": list(rules)}
|
|
615
|
+
|
|
616
|
+
|
|
617
|
+
def any_of(*groups: Dict[str, Any]) -> Dict[str, Any]:
|
|
618
|
+
"""OR of AND-groups -- the shape the filter builder produces."""
|
|
619
|
+
return {"condition": "OR", "rules": list(groups) or [all_of()]}
|
|
620
|
+
|
|
621
|
+
|
|
622
|
+
def query_state(
|
|
623
|
+
rules_tree: Optional[Dict[str, Any]] = None, segment_id: str = ""
|
|
624
|
+
) -> str:
|
|
625
|
+
"""Serialize the filter state (``experimentQueryState``).
|
|
626
|
+
|
|
627
|
+
Example:
|
|
628
|
+
query_state(any_of(all_of(rule("accuracy", "greater", 0.9))))
|
|
629
|
+
"""
|
|
630
|
+
return json.dumps(
|
|
631
|
+
{
|
|
632
|
+
"activeSegment": {"segment_id": segment_id},
|
|
633
|
+
"rulesTree": rules_tree or any_of(),
|
|
634
|
+
"filters": [],
|
|
635
|
+
}
|
|
636
|
+
)
|
|
637
|
+
|
|
638
|
+
|
|
639
|
+
# ---------------------------------------------------------------------------
|
|
640
|
+
# Assembly
|
|
641
|
+
# ---------------------------------------------------------------------------
|
|
642
|
+
|
|
643
|
+
|
|
644
|
+
def build_view(
|
|
645
|
+
name: str,
|
|
646
|
+
sections: Sequence[Section],
|
|
647
|
+
filters: Optional[str] = None,
|
|
648
|
+
table: Optional[str] = None,
|
|
649
|
+
config: Optional[Dict[str, Any]] = None,
|
|
650
|
+
auto_generate: bool = False,
|
|
651
|
+
personal: bool = False,
|
|
652
|
+
) -> View:
|
|
653
|
+
"""Assemble sections into a ``View`` ready for ``create_view``.
|
|
654
|
+
|
|
655
|
+
Args:
|
|
656
|
+
name: the view's display name.
|
|
657
|
+
sections: ``Section`` objects, top to bottom.
|
|
658
|
+
filters: output of ``query_state()``, or None for no filtering.
|
|
659
|
+
table: output of ``table_state()``, or None to keep the UI default.
|
|
660
|
+
config: output of ``global_config()``.
|
|
661
|
+
auto_generate: True lets Comet append auto-generated panels for
|
|
662
|
+
newly logged metrics underneath your sections.
|
|
663
|
+
personal: mark the view as personal (visible only to you).
|
|
664
|
+
|
|
665
|
+
Returns:
|
|
666
|
+
A ``View``. Pass it to ``API.create_view(workspace, project, view)`` or
|
|
667
|
+
``APIExperiment.create_view(view)``.
|
|
668
|
+
"""
|
|
669
|
+
section_dicts = [s.to_dict() for s in sections]
|
|
670
|
+
all_panels = [p for s in section_dicts for p in s["panels"]]
|
|
671
|
+
|
|
672
|
+
v3 = {
|
|
673
|
+
"layout": [], # legacy top-level mirror; sections carry the real layout
|
|
674
|
+
"globalConfig": config or global_config(),
|
|
675
|
+
"sections": section_dicts,
|
|
676
|
+
"autoGenerate": auto_generate,
|
|
677
|
+
"isPersonalView": personal,
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
charts: Dict[str, Any] = {bucket: [] for bucket in _LEGACY_BUCKETS}
|
|
681
|
+
for panel in all_panels:
|
|
682
|
+
charts.setdefault(panel["chartType"], []).append(panel)
|
|
683
|
+
charts["numberOfCharts"] = len(all_panels)
|
|
684
|
+
|
|
685
|
+
return View(
|
|
686
|
+
name=name,
|
|
687
|
+
chart_state=json.dumps({"charts": charts, "layout": None}),
|
|
688
|
+
query_state=filters if filters is not None else query_state(),
|
|
689
|
+
table_state=table,
|
|
690
|
+
v2={
|
|
691
|
+
"toggles": {
|
|
692
|
+
"DASHBOARD_TOGGLE": {"toggleId": "DASHBOARD_TOGGLE", "isToggled": True},
|
|
693
|
+
"EXPERIMENT_TABLE_TOGGLE": {
|
|
694
|
+
"toggleId": "EXPERIMENT_TABLE_TOGGLE",
|
|
695
|
+
"isToggled": True,
|
|
696
|
+
},
|
|
697
|
+
}
|
|
698
|
+
},
|
|
699
|
+
v3=v3,
|
|
700
|
+
)
|
|
701
|
+
|
|
702
|
+
|
|
703
|
+
def build_experiment_view(
|
|
704
|
+
name: str,
|
|
705
|
+
sections: Sequence[Section],
|
|
706
|
+
x_axis: str = "step",
|
|
707
|
+
smoothing: float = 0,
|
|
708
|
+
transform_y: Optional[str] = None,
|
|
709
|
+
sample_size: int = 100,
|
|
710
|
+
auto_generate: bool = False,
|
|
711
|
+
auto_refresh: bool = True,
|
|
712
|
+
media_sort_by: str = "name",
|
|
713
|
+
media_order: str = "desc",
|
|
714
|
+
group_media: bool = False,
|
|
715
|
+
) -> View:
|
|
716
|
+
"""Assemble sections into a ``View`` for ``APIExperiment.create_view``.
|
|
717
|
+
|
|
718
|
+
Experiment-scoped views are stored in the chart-template tables, which use a
|
|
719
|
+
different envelope than project dashboards: everything lives under ``v2``
|
|
720
|
+
(sections inside ``v2["panels"]``), and the per-tab display settings for
|
|
721
|
+
images / text / audio / histograms / confusion matrices sit alongside them.
|
|
722
|
+
|
|
723
|
+
Args:
|
|
724
|
+
name: the view's display name.
|
|
725
|
+
sections: ``Section`` objects -- the same ones ``build_view`` takes.
|
|
726
|
+
x_axis: "step", "duration", "wall", or "epoch".
|
|
727
|
+
smoothing: 0..1 curve smoothing applied to every chart.
|
|
728
|
+
transform_y: None, "log", "ln", "sqrt".
|
|
729
|
+
sample_size: max points fetched per curve.
|
|
730
|
+
auto_generate: let Comet append auto panels for new metrics.
|
|
731
|
+
auto_refresh: refresh the view while the run is live.
|
|
732
|
+
media_sort_by / media_order / group_media: sorting of the image, audio
|
|
733
|
+
and text tabs.
|
|
734
|
+
|
|
735
|
+
Returns:
|
|
736
|
+
A ``View`` to pass to ``APIExperiment.create_view(view)``. Do not set
|
|
737
|
+
``experiment_key`` -- ``create_view`` stamps it on for you.
|
|
738
|
+
"""
|
|
739
|
+
media = {"groupBy": group_media, "orderBy": media_order, "sortBy": media_sort_by}
|
|
740
|
+
v2 = {
|
|
741
|
+
"template_name": name,
|
|
742
|
+
"x_axis": x_axis,
|
|
743
|
+
"smoothing": smoothing,
|
|
744
|
+
"transformY": transform_y,
|
|
745
|
+
"sampleSize": sample_size,
|
|
746
|
+
"entire_row_charts": False,
|
|
747
|
+
"charts": [], # legacy per-experiment chart list; sections supersede it
|
|
748
|
+
"outliers": {"percentile": 0, "isVisible": True},
|
|
749
|
+
"panels": {
|
|
750
|
+
"version": 5,
|
|
751
|
+
"sections": [s.to_dict() for s in sections],
|
|
752
|
+
"panels": [],
|
|
753
|
+
"layout": [],
|
|
754
|
+
"compareXAxis": x_axis,
|
|
755
|
+
"autoGenerate": auto_generate,
|
|
756
|
+
"isAutoRefreshEnabled": auto_refresh,
|
|
757
|
+
},
|
|
758
|
+
"autoGeneratedDraft": False,
|
|
759
|
+
"deleteAutogenerated": False,
|
|
760
|
+
"projectName": "",
|
|
761
|
+
"project_id": "",
|
|
762
|
+
"metricColors": [],
|
|
763
|
+
"metricColorsV2": {
|
|
764
|
+
"singleExperiment": {
|
|
765
|
+
"isSingleExperimentColor": False,
|
|
766
|
+
"colors": [],
|
|
767
|
+
"singleExperimentColor": False,
|
|
768
|
+
}
|
|
769
|
+
},
|
|
770
|
+
"lastUpdate": 0,
|
|
771
|
+
"layout": [],
|
|
772
|
+
"images": dict(media),
|
|
773
|
+
"audio": dict(media),
|
|
774
|
+
"text": {
|
|
775
|
+
"groupBy": group_media,
|
|
776
|
+
"orderBy": media_order,
|
|
777
|
+
"sortBy": "step",
|
|
778
|
+
"fullHeightToggle": False,
|
|
779
|
+
"step": -1,
|
|
780
|
+
},
|
|
781
|
+
"confusionMatrix": {
|
|
782
|
+
"assetsIds": [],
|
|
783
|
+
"compareAssetsIds": [],
|
|
784
|
+
"cellValue": "Counts",
|
|
785
|
+
"colorDistribution": "Equal",
|
|
786
|
+
},
|
|
787
|
+
"histogram": {
|
|
788
|
+
"groupBy": group_media,
|
|
789
|
+
"ignoreOutliers": 0.1,
|
|
790
|
+
"maxSteps": 50,
|
|
791
|
+
"orderBy": media_order,
|
|
792
|
+
"sortBy": "name",
|
|
793
|
+
},
|
|
794
|
+
"metrics": {"decimalsPrecision": None},
|
|
795
|
+
"hyperParams": {"decimalsPrecision": None},
|
|
796
|
+
"otherParams": {"decimalsPrecision": None},
|
|
797
|
+
}
|
|
798
|
+
return View(name=name, v2=v2)
|
|
799
|
+
|
|
800
|
+
|
|
801
|
+
# ---------------------------------------------------------------------------
|
|
802
|
+
# Inspection
|
|
803
|
+
# ---------------------------------------------------------------------------
|
|
804
|
+
|
|
805
|
+
|
|
806
|
+
def describe_view(view: View, indent: str = "") -> str:
|
|
807
|
+
"""Render a human-readable outline of a View's sections and panels."""
|
|
808
|
+
lines = ["%s%s" % (indent, view.name)]
|
|
809
|
+
meta = []
|
|
810
|
+
if view.view_source:
|
|
811
|
+
meta.append(view.view_source)
|
|
812
|
+
if view.template_id:
|
|
813
|
+
meta.append("id=%s" % view.template_id)
|
|
814
|
+
if view.experiment_key:
|
|
815
|
+
meta.append("experiment=%s" % view.experiment_key[:8])
|
|
816
|
+
if view.project_default:
|
|
817
|
+
meta.append("project default")
|
|
818
|
+
if meta:
|
|
819
|
+
lines.append("%s [%s]" % (indent, ", ".join(meta)))
|
|
820
|
+
|
|
821
|
+
sections = None
|
|
822
|
+
if isinstance(view.v3, dict):
|
|
823
|
+
sections = view.v3.get("sections")
|
|
824
|
+
if not sections and isinstance(view.v2, dict):
|
|
825
|
+
# Experiment-scoped view: sections live under v2["panels"].
|
|
826
|
+
sections = (view.v2.get("panels") or {}).get("sections")
|
|
827
|
+
if sections:
|
|
828
|
+
for section in sections:
|
|
829
|
+
flag = " (auto)" if section.get("autogenerated") else ""
|
|
830
|
+
lines.append("%s - section %r%s" % (indent, section.get("title"), flag))
|
|
831
|
+
for panel in section.get("panels", []):
|
|
832
|
+
lines.append("%s * %s" % (indent, _panel_label(panel)))
|
|
833
|
+
else:
|
|
834
|
+
# Older view: read the legacy bucketed chart_state.
|
|
835
|
+
try:
|
|
836
|
+
charts = json.loads(view.chart_state or "{}").get("charts", {})
|
|
837
|
+
except ValueError:
|
|
838
|
+
charts = {}
|
|
839
|
+
panels = [p for v in charts.values() if isinstance(v, list) for p in v]
|
|
840
|
+
if panels:
|
|
841
|
+
lines.append("%s - (legacy chart_state, no sections)" % indent)
|
|
842
|
+
for panel in panels:
|
|
843
|
+
lines.append("%s * %s" % (indent, _panel_label(panel)))
|
|
844
|
+
else:
|
|
845
|
+
lines.append("%s - (no panels)" % indent)
|
|
846
|
+
|
|
847
|
+
try:
|
|
848
|
+
tree = json.loads(view.query_state or "{}").get("rulesTree", {})
|
|
849
|
+
except ValueError:
|
|
850
|
+
tree = {}
|
|
851
|
+
conditions = [r for group in tree.get("rules", []) for r in group.get("rules", [])]
|
|
852
|
+
for condition in conditions:
|
|
853
|
+
lines.append(
|
|
854
|
+
"%s filter: %s %s %r"
|
|
855
|
+
% (
|
|
856
|
+
indent,
|
|
857
|
+
condition.get("field"),
|
|
858
|
+
condition.get("operator"),
|
|
859
|
+
condition.get("value"),
|
|
860
|
+
)
|
|
861
|
+
)
|
|
862
|
+
return "\n".join(lines)
|
|
863
|
+
|
|
864
|
+
|
|
865
|
+
def _panel_label(panel: Dict[str, Any]) -> str:
|
|
866
|
+
kind = panel.get("chartType", "?")
|
|
867
|
+
name = panel.get("chartName") or panel.get("instanceName") or ""
|
|
868
|
+
detail = (
|
|
869
|
+
panel.get("metricNames")
|
|
870
|
+
or panel.get("metricName")
|
|
871
|
+
or panel.get("fileName")
|
|
872
|
+
or panel.get("images")
|
|
873
|
+
or panel.get("params")
|
|
874
|
+
or ""
|
|
875
|
+
)
|
|
876
|
+
label = "%-9s %s" % (kind, name)
|
|
877
|
+
return "%s %s" % (label.rstrip(), detail) if detail else label.rstrip()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|